Class: JerbilService::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/jerbil/jerbil_service/client.rb

Overview

JerbilService::Client provides a wrapper around a Base type service that hides all interaction with Jerbil itself, enabling client interfaces with a service to be constructed quickly and painlessly

To use, call the Client.find_services method, which yields a block for each matching service registered with the Jerbil Server. The service’s methods can then be called on the yielded object, which are then transparently passed back to the service itself.

It should always be remembered that these method calls will be mediated through Ruby’s DRb mechanisms and therefore access to objects across this interface may not be the same as accessing them directly. See Services Readme for more details

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(modl, options, &block) ⇒ Client

Returns a new instance of Client.



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/jerbil/jerbil_service/client.rb', line 152

def initialize(modl, options, &block)

  @klass = modl::Service
  name = modl.to_s
  name_symbol = name.downcase.to_sym

  quiet = options[:quiet]
  unless quiet
    @output = options[:output] || $stderr
  else
    @output = File.open('/dev/null', 'w')
  end
  
  @welcome = options[:welcome]

  @output.puts "Welcome to #{name} (#{modl.ident})" if @welcome

  unless quiet
    @output.puts "Options:"
    options.each {|key, val| @output.puts("  #{key}:#{val}") }
  end

  env = options[:environment]

  service_opts = {:name=>name_symbol, :env=>env}
  service_opts[:host] = Socket.gethostname if options[:local]

  begin
    # find jerbil
    @jerbil_server = Jerbil::Servers.get_local_server(options[:jerbil_env])

    # now connect to it
    jerbil = @jerbil_server.connect

    # and find all of the services
    @services = []
    @services = jerbil.find(service_opts)

    if @services.length == 0 then
      @output.puts "No services for #{name}[:#{env}] were found"
      raise Jerbil::ServiceNotFound, "No services for #{name}[:#{env}] were found"
    end

    block.call(self)

    @output.puts "Stopping the service" if @welcome
    #output.close unless @output == $stderr

    return nil # give the caller nothing back

  rescue Jerbil::MissingServer
    @output.puts("Cannot find a local Jerbil server")
    raise
  rescue Jerbil::JerbilConfigError => err
    @output.puts("Error in Jerbil Config File: #{err.message}")
    raise
  rescue Jerbil::JerbilServiceError =>jerr
    @output.puts("Error with Jerbil Service: #{jerr.message}")
    raise
  rescue Jerbil::ServerConnectError
    @output.puts("Error connecting to Jerbil Server")
    raise
  rescue DRb::DRbConnError =>derr
    @output.puts("Error setting up DRb Server: #{derr.message}")
    raise Jerbil::ServerConnectError
  end

end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(symb, *parameters) ⇒ Object



274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'lib/jerbil/jerbil_service/client.rb', line 274

def method_missing(symb, *parameters)

  # stop anyone from calling the stop method
  raise Jerbil::UnauthorizedMethod if symb == :stop_callback

  # make sure this is a valid method of the receiving instance
  # This is needed cos sending an unknown method over DRb with $SAFE = 1
  # raises a Security Error. Could catch this, but it might happen for
  # different reasons so best not to.
  
  # need to fix symbols problem
  if String.instance_methods.first.instance_of?(String) then
    # < 1.9 Ruby
    method_id = symb.to_s
    #@output.puts "Setting method id to a string: #{method_id}"
  else
    method_id = symb
    #@output.puts "Ensuring method id is a symbol: #{method_id}"
  end
  
  unless @klass.instance_methods.include?(method_id)
    @output.puts "Failed to find method id:"
    @output.puts "#{@klass.instance_methods.inspect}"
    raise NoMethodError, "Failed to find method: #{method_id}"
  end

  retries = 0
  begin

    @session.send(symb, *parameters)

  rescue DRb::DRbConnError
    # service did not respond nicely
    @output.puts "Failed to connected to the service - checking its OK" 
    jerbil = @jerbil_server.connect
    if jerbil.service_missing?(@service) then
      # something nasty has occurred
      @output.puts "Jerbil confirms the service is missing"
      raise Jerbil::ServiceConnectError, "Service is missing"
    else
      # seems jerbil thinks its ok, so try again
      retries += 1
      @output.puts "Jerbil thinks the service is OK, retrying"
      retry unless retries > 2
      @output.puts "Retried too many times, giving up"
      raise Jerbil::ServiceConnectError, "Service is not responding"
    end
  end
end

Class Method Details

.connect(modl, options = {}, &block) ⇒ Object

Deprecated.

Use find_services instead

backwards compatible method to connect to first service using environment defined in config_file which is read in from options or the default location (see get_config)

Parameters:

  • what (Symbol)

    being one of :first, :all or :local

  • modl (Object)
    • constant for the service module

  • options (Hash) (defaults to: {})
    • hash of the following

Options Hash (options):

  • :quiet (Boolean)

    suppress messages on stdout (default: false)

  • :local (Boolean)

    find only the local service (default: false)

  • :output (IO)

    a file object or similar (not filename) to be used for output - defaults to $stderr. Is overridden with /dev/null if quiet.

  • :welcome (Boolean)

    output additional messages to stdout suitable for standalone operation

  • :environment (Symbol)

    being that which the service is operating in (:dev, :test, :prod)



138
139
140
141
142
143
144
145
146
147
148
# File 'lib/jerbil/jerbil_service/client.rb', line 138

def self.connect(modl, options={}, &block) # :yields: client

  config_file = options[:config_file]
  config = modl.get_config(config_file)

  options[:environment] ||= config[:environment]
  options[:jerbil_env] ||= config[:jerbil_env]

  self.find_services(:first, modl, options, &block)

end

.find_services(what, modl, options = {}) {|service| ... } ⇒ Object

Connect to a one or more instances of a service using Jerbil

The method will search the Jerbil Server for services of the given module, as defined by the what parameter:

  • :first, yields the block once with the first service regardless of how many services there are

and with no guarantee about which services this might be
  • :local, yields the service running on the same processor as the client

  • :all, yields the block for each service.

Within the block, the user can call any of the service’s methods, together with the instance methods of the client itself.

For example, to find a service running on a particular host:

JerbilService::Client.find_services(:all, MyService, opts) do |service|
  if service.host == 'a_server.network.com' then
    # found the services I am looking for, now do something
    ...
  end
end

To find a service running in a particular environment, you need to set the above option:

opts = {:environment => :dev}
JerbilService::Client.find_services(:all, MyService, opts) do ...

If you want to use a config file to access this information, you can use get_config and extract the env information:

config = JerbilService::Client.get_config(MyService)
opts[:environment] = config[:environment]
...

The connection to the service is closed when the block exits.

Parameters:

  • what (Symbol)

    being one of :first, :all or :local

  • modl (Object)
    • constant for the service module

  • options (Hash) (defaults to: {})
    • hash of the following

Options Hash (options):

  • :quiet (Boolean)

    suppress messages on stdout (default: false)

  • :local (Boolean)

    find only the local service (default: false)

  • :output (IO)

    a file object or similar (not filename) to be used for output - defaults to $stderr. Is overridden with /dev/null if quiet.

  • :welcome (Boolean)

    output additional messages to stdout suitable for standalone operation

  • :environment (Symbol)

    being that which the service is operating in (:dev, :test, :prod)

Yields:

  • (service)

    a client instance for each matching service registered with Jerbil



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/jerbil/jerbil_service/client.rb', line 105

def self.find_services(what, modl, options={}, &block)

  case what
  when :first
    select = :first
  when :all
    select = :all
  when :local
    select = :first
    options[:local] = true
  else
    raise ArgumentError, "Find Services invalid search key: #{what}"
  end

  unless options[:environment]
    # set the default environment if not already set
    options[:environment] = :prod
  end

  new(modl, options) do |client|
    client.connect(select, &block)
  end
end

.get_config(modl, config_file = nil) ⇒ Object

return the server’s config options for the given file, or the default if none given

This method uses [Jeckyl]() to obtain the config parameters. The module name is expected to follow the guidelines for a JerbilService: create a module with the service’s name (echoed in the gem name etc) and then a class called Service. The parameter passed in is the module name as a constant

config = JerbilService::Client.get_config(MyService)

Parameters:

  • modl (Object)

    must be the constant name of the service’s module.

  • config_file (String) (defaults to: nil)

    is an optional path to a Jeckyl config file



56
57
58
# File 'lib/jerbil/jerbil_service/client.rb', line 56

def self.get_config(modl, config_file=nil)
  modl.get_config(config_file)
end

Instance Method Details

#connect(index = :all, &block) ⇒ Object



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/jerbil/jerbil_service/client.rb', line 224

def connect(index=:all, &block)

  if index == :first then
    @session = @services[0].connect
    @service = @services[0]
    block.call(self)

    @session = nil
  else
    @services.each do |service|
      @service = service
      @session = service.connect
      block.call(self)
    end
  end

end

#hostString

return the name of the host on which the service is running

Returns:

  • (String)

    FQDN of the host on which the service is running



259
260
261
# File 'lib/jerbil/jerbil_service/client.rb', line 259

def host
  return @service.host
end

#service_keyString

return the service key for the given service, which can be used by the caller where a method requires. See Services Readme for details about keys.

Returns:

  • (String)

    key to use to connect to service methods requiring it



268
269
270
# File 'lib/jerbil/jerbil_service/client.rb', line 268

def service_key
  return @service.key
end

#verifyBoolean

verify that the service is working

This is really a no-op in that if the client has entered the block successfully, then the service that is passed to the block will be working and therefore this call will always return true. Consider it a placebo statement for a client interface that only wants to check the service is running!

Returns:

  • (Boolean)

    indicating if the services if running



251
252
253
# File 'lib/jerbil/jerbil_service/client.rb', line 251

def verify
  @session != nil
end