Class: JerbilService::Supervisor

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

Overview

Supervisor (Supervisory Client) - a wrapper class for managing a Jerbil Service

Can be used to create a daemon script for a service. First gather options from the command line using optparse and then call Supervisor.new. Within the block call the methods below in response to the options on the command line. When the block closes the service will be started and will wait for calls.

All of this is already done for you by /usr/sbin/jserviced. See Services Readme for more details.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(klass) {|service| ... } ⇒ Supervisor

create and run a service daemon for the given class, which should be a JerbilService class.

The service called through this supervisor needs to comply with Jerbil naming conventions. This requires the service to be defined within a module with the name of the service, and having a class called Service within it. See Service Readme for more details.

The method yields a block. Within the block, use the methods below to set parameters and the service will then be launched when the block returns:

JerbilService::Supervisor.new(MyService, opts) do |service|
  service.no_daemon
  service.quiet
end

The supervisor will start the service when the block exits.

Parameters:

  • klass (Module)

    is the service module (see above)

Yields:

  • (service)

    instance of supervisor to config



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/jerbil/jerbil_service/sclient.rb', line 59

def initialize(klass, &block)

  @daemonize = true
  @config_file = nil
  @verbose = false
  @quiet = false
  @no_syslog = false
  @output = $stderr
  @logger = nil
  @set_log_daemon = false # flag to log the daemon itself
  @jerbil_env = nil
  @klass = klass
  @name = klass.to_s.downcase
  @name_symbol = @name.to_sym

  # only called without a block internally to effect the stop
  # see below
  if block_given? then
    block.call(self)
  else
    return self # created for stop
  end
  
  # only gets here for a start
  self.start_service

end

Class Method Details

.stop(klass) {|service| ... } ⇒ Object

this class method if called to create an instance of the supervisor to stop a Jerbil service. Use the methods above in the block to set parameters, although some of them have no meaning in this context.

Parameters:

  • klass (Module)

    is the service module of the service to stop

Yields:

  • (service)

    instance of supervisor to config



152
153
154
155
156
157
158
159
160
161
162
# File 'lib/jerbil/jerbil_service/sclient.rb', line 152

def self.stop(klass, &block)

  # create an instance of this class without starting the service
  # (no block)
  sclient = self.new(klass)

  block.call(sclient)

  sclient.stop_service

end

Instance Method Details

#config_file=(cfile) ⇒ Object

set the config file for the service. Each service expects an options hash, which the supervisor will obtain from either this file, or the default, which is usually: /etc/jermine/<service>.rb



114
115
116
# File 'lib/jerbil/jerbil_service/sclient.rb', line 114

def config_file=(cfile)
  @config_file = cfile
end

#jerbil_env=(env) ⇒ Object

Deprecated.
  • jerbil_env is a standard config parameter now, although still only

intended for test purposes.

override the default jerbil config file, used only for testing new versions of jerbil



141
142
143
# File 'lib/jerbil/jerbil_service/sclient.rb', line 141

def jerbil_env=(env)
  @jerbil_env = env
end

#log_daemonObject

create a log file for the daemon task, the output of which is otherwise lost. This uses jellog and will write the log to a file in the log_dir (see Config) named after the service with _sd added. By default this would be /var/log/jermine/<service>_sd.log

This logger takes over from the output file set in #output, but only when the supervisor daemonises.



132
133
134
# File 'lib/jerbil/jerbil_service/sclient.rb', line 132

def log_daemon
  @set_log_daemon = true
end

#no_daemonObject

start the service without daemonising



88
89
90
# File 'lib/jerbil/jerbil_service/sclient.rb', line 88

def no_daemon
  @daemonize = false
end

#no_syslogObject

ensure logging does not log to syslog see Jellog::Logger#disable_syslog for details



108
109
110
# File 'lib/jerbil/jerbil_service/sclient.rb', line 108

def no_syslog
  @no_syslog = true
end

#output=(ofile) ⇒ Object

set the supervisor output to a file

Parameters:

  • ofile (IO)

    is a file object to output to



120
121
122
# File 'lib/jerbil/jerbil_service/sclient.rb', line 120

def output=(ofile)
  @output = ofile unless @quiet
end

#quietObject

output absolutely nothing



100
101
102
103
104
# File 'lib/jerbil/jerbil_service/sclient.rb', line 100

def quiet
  @quiet = true
  @verbose = false
  @output = File.open('/dev/null', 'w')
end

#start_serviceObject

this method is called by the initialize method to start a Jerbil Service messages are logged through @output, which is stderr by default or /dev/null if quiet was enabled, or a Jellog logger.



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
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/jerbil/jerbil_service/sclient.rb', line 170

def start_service

  @output.puts "Welcome to #{@klass.to_s} (#{@klass.ident})"
  
  # get the config options for this service
  config = @klass.get_config(@config_file)
                   
  # create a hash for logger options
  log_opts = {}
  
  # to test a new jerbil server, this needs to be set to the
  # jerbil server's environment. Only needed for test purposes
  if @jerbil_env then
    config[:jerbil_env] = @jerbil_env
  end

  # create a Jellog logging object if requested
  # if @set_log_daemon then
  #   Jellog::Logger.disable_syslog if @no_syslog
  #   log_opts = Jellog::Logger.get_options(config)
  #   log_opts[:log_level] = :debug if @verbose
  #   @logger = Jellog::Logger.new("#{@klass.to_s.downcase}_sd", log_opts)
  #   @output.puts "Logging output to #{@logger.logfilename}"
  #   @output.flush
  #   @output = @logger
  # end

  # log the configuration options if requested
  if @verbose then
    @output.puts "Obtained configuration options"
    config.each_pair do |key, value|
      @output.puts "  :#{key} => #{value}"
    end
  end

  @output.puts "Running Service in environment: #{config[:environment]}"
  
  # create a private key for the service
  pkey = Jerbil::Support.create_private_key(@name_symbol, config[:environment], config[:key_dir])

  @output.puts "Created a private key in: #{config[:key_dir]}" if @verbose

  # the service will be daemonized so need to set up daemon parameters
  if @daemonize then
    @output.puts "About to demonize this service"
    # cleanly close everything
    #@output.close
    
    dopts = {:backtrace=>true,
      :app_name=>@klass.to_s.downcase + "_daemon",
      :log_dir=>config[:log_dir],
      :log_output=>true,
      :dir_mode=>:normal,
      :dir=>config[:log_dir]}
    Daemons.daemonize(dopts)
    
    # all those open files are closed?
    # so open the logger again
    if @set_log_daemon then
      log_opts = Jellog::Config.intersection(config)
      log_opts[:log_level] = :debug if @verbose
      @output = Jellog::Logger.new("#{@klass.to_s.downcase}_sd", log_opts)
    else
      # no logger, so write any messages to /dev/null
      @output = File.open('/dev/null', 'w')
    end
    
  else
    @output.puts "Service is running in the foreground"
  end

  if @no_syslog then
    @output.puts "Disabling messages to syslog"
    Jellog::Logger.disable_syslog
  else
    @output.puts "Sending messages to syslog"
  end

  # now create the pid file
  Jerbil::Support.write_pid_file(@name_symbol, config[:environment], config[:pid_dir])

  @output.puts "Created a pid file for process: #{Process.pid}"

  @service = @klass::Service.new(pkey, config)

  @output.puts "Registered Service with Jerbil"

  @service.wait(pkey)

  @output.puts "Service has stopped"
  
rescue => err
  puts "Error while starting service: #{err.class.to_s}, #{err.message}"
  puts err.backtrace.join("\n") if @verbose
end

#stop_serviceObject



267
268
269
270
271
272
273
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
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
# File 'lib/jerbil/jerbil_service/sclient.rb', line 267

def stop_service

  config = @klass.get_config(@config_file)
  pid = 0
  
  # to test a new jerbil server, this needs to be set to the
  # jerbil server's environment. Only needed for test purposes
  if @jerbil_env then
    config[:jerbil_env] = @jerbil_env
  end

  if @verbose then
    @output.puts "Obtained configuration options"
    config.each_pair do |key, value|
      @output.puts "  :#{key} => #{value}"
    end
  end

  @output.puts "Stopping Service #{@klass.to_s} in environment: #{config[:environment]}"
  pkey = Jerbil::Support.get_key_and_delete_file(@name_symbol, config[:environment], config[:key_dir])
  pid = Jerbil::Support.get_pid_and_delete_file(@name_symbol, config[:environment], config[:pid_dir])

  service_opts = {:name=>@name_symbol, :env=>config[:environment]}
  service_opts[:host] = Socket.gethostname

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

    # now connect to it
    jerbil = jerbil_server.connect

    # and get service
    my_service = jerbil.get(service_opts)

    if my_service.nil? then
      @output.puts "Cannot find service through Jerbil"
    end

    session = my_service.connect

    # now to do the stopping
    begin
      session.stop_callback(pkey)
      @output.puts "Stopped service successfully"
    rescue DRb::DRbConnError
      @ouput.puts "Service stopped, but not gracefully"
      return nil
    end

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

rescue Jeckyl::JeckylError => jerr
  @output.puts "Error in Configuration file: #{jerr.message}"
  # there is no pid, so just exit
rescue => err
  @output.puts "Error while stopping service: #{err.message}"
  @output.puts err.backtrace if @verbose
  # it went wrong, so fall back on pid killing
  if pid > 0 then
    @output.puts "Killing the process: #{pid}"
    Process.kill("SIGKILL", pid)
  else
    @output.puts "No pid available - nothing to kill"
  end
end

#verboseObject

output extra information about starting up the service. Ignored if quiet has been set Note this controls messages from the Supervisor about the startup process and is unrelated to the service’s logger



95
96
97
# File 'lib/jerbil/jerbil_service/sclient.rb', line 95

def verbose
  @verbose = true unless @quiet
end