Class: Sunshine::Daemon

Inherits:
Object
  • Object
show all
Defined in:
lib/sunshine/daemon.rb

Overview

An abstract class to wrap simple daemon software setup and start/stop.

Child classes are expected to at least provide a start and stop bash script by either overloading the start_cmd and stop_cmd methods, or by setting may also be specified if restart requires more functionality than simply calling start_cmd && stop_cmd.

Direct Known Subclasses

ARSendmail, DelayedJob, Server

Constant Summary collapse

START_FAILED_CODE =
10
STOP_FAILED_CODE =
11
RESTART_FAILED_CODE =
12
STATUS_DOWN_CODE =
13

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app, options = {}) ⇒ Daemon

Daemon objects need only an App object to be instantiated but many options are available for customization:

:bin

bin_path - Set the daemon app bin path (e.g. usr/local/nginx)

defaults to svr_name.

:processes

prcss_num - Number of processes daemon should run;

defaults to 1.

:config_file

name - Remote file name the daemon should load;

defaults to svr_name.conf

:config_path

path - Remote path daemon configs will be uploaded to;

defaults to app.current_path/daemons/svr_name

:config_template

path - Glob path to tempates to render and upload;

defaults to sunshine_path/templates/svr_name/*

:log_path

path - Path to where the log files should be output;

defaults to app.log_path.

:pid

pid_path - Set the pid; default: app.shared_path/pids/svr_name.pid

defaults to app.shared_path/pids/svr_name.pid.

:sudo

bool|str - Define if sudo should be used to run the daemon,

and/or with what user.

:timeout

int - Timeout to use for daemon config, defaults to 0.

The Daemon constructor also supports any App#find options to narrow the server apps to use. Note: subclasses such as Server already have a default :role that can be overridden.



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/sunshine/daemon.rb', line 89

def initialize app, options={}
  @options = options
  @app     = app

  @name        = options[:name] || self.class.short_name
  @pid         = options[:pid]  || "#{@app.shared_path}/pids/#{@name}.pid"
  @bin         = options[:bin]  || self.class.short_name
  @sudo        = options[:sudo]
  @timeout     = options[:timeout]   || 0
  @dep_name    = options[:dep_name]  || self.class.short_name
  @processes   = options[:processes] || 1
  @sigkill     = 'QUIT'

  @config_template = options[:config_template] ||
    "#{Sunshine::ROOT}/templates/#{self.class.short_name}/*"

  @config_path     = options[:config_path] ||
    "#{@app.current_path}/daemons/#{@name}"

  @config_file = options[:config_file] || "#{self.class.short_name}.conf"

  log_path  = options[:log_path] || @app.log_path
  @log_files = {
    :stderr => "#{log_path}/#{@name}_stderr.log",
    :stdout => "#{log_path}/#{@name}_stdout.log"
  }

  @start_cmd = @stop_cmd = @restart_cmd = @status_cmd = nil

  @setup_successful = nil

  register_after_user_script
end

Instance Attribute Details

#appObject (readonly)

Returns the value of attribute app.



47
48
49
# File 'lib/sunshine/daemon.rb', line 47

def app
  @app
end

#binObject

Returns the value of attribute bin.



49
50
51
# File 'lib/sunshine/daemon.rb', line 49

def bin
  @bin
end

#config_fileObject

Returns the value of attribute config_file.



51
52
53
# File 'lib/sunshine/daemon.rb', line 51

def config_file
  @config_file
end

#config_pathObject

Returns the value of attribute config_path.



51
52
53
# File 'lib/sunshine/daemon.rb', line 51

def config_path
  @config_path
end

#config_templateObject

Returns the value of attribute config_template.



51
52
53
# File 'lib/sunshine/daemon.rb', line 51

def config_template
  @config_template
end

#nameObject (readonly)

Returns the value of attribute name.



47
48
49
# File 'lib/sunshine/daemon.rb', line 47

def name
  @name
end

#pidObject

Returns the value of attribute pid.



49
50
51
# File 'lib/sunshine/daemon.rb', line 49

def pid
  @pid
end

#processesObject

Returns the value of attribute processes.



49
50
51
# File 'lib/sunshine/daemon.rb', line 49

def processes
  @processes
end

#restart_cmdObject

Gets the command that restarts the daemon. Should be overridden by child classes if different from start_cmd && stop_cmd.



311
312
313
# File 'lib/sunshine/daemon.rb', line 311

def restart_cmd
  @restart_cmd
end

#server_appsObject

Returns the value of attribute server_apps.



49
50
51
# File 'lib/sunshine/daemon.rb', line 49

def server_apps
  @server_apps
end

#start_cmdObject

Gets the command that starts the daemon. Should be overridden by child classes.

Raises:



273
274
275
276
# File 'lib/sunshine/daemon.rb', line 273

def start_cmd
  return @start_cmd if @start_cmd
  raise DaemonError, "start_cmd undefined for #{@name}"
end

#status_cmdObject

Status command wrapped with an exit_on_failure handler.



332
333
334
# File 'lib/sunshine/daemon.rb', line 332

def status_cmd
  @status_cmd || "test -f #{@pid} && kill -0 $(cat #{@pid})"
end

#stop_cmdObject

Default daemon stop command.



291
292
293
294
# File 'lib/sunshine/daemon.rb', line 291

def stop_cmd
  "test -f #{@pid} && kill -#{@sigkill} $(cat #{@pid}) && sleep 1 && "+
    "rm -f #{@pid}"
end

#sudoObject

Returns the value of attribute sudo.



49
50
51
# File 'lib/sunshine/daemon.rb', line 49

def sudo
  @sudo
end

#timeoutObject

Returns the value of attribute timeout.



49
50
51
# File 'lib/sunshine/daemon.rb', line 49

def timeout
  @timeout
end

Class Method Details

.binder_methodsObject

Returns an array of method names to assign to the binder for template rendering.



23
24
25
# File 'lib/sunshine/daemon.rb', line 23

def self.binder_methods
  [:app, :name, :bin, :pid, :processes, :config_path, :log_file, :timeout]
end

.short_nameObject

Returns the short, snake-case version of the class:

Sunshine::Daemon.short_name
#=> "daemon"


33
34
35
# File 'lib/sunshine/daemon.rb', line 33

def self.short_name
  @short_name ||= self.underscore self.to_s.split("::").last
end

.underscore(str) ⇒ Object

Turn camelcase into underscore. Used for Daemon#name.



41
42
43
44
# File 'lib/sunshine/daemon.rb', line 41

def self.underscore str
  str.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
   gsub(/([a-z\d])([A-Z])/,'\1_\2').downcase
end

Instance Method Details

#_restart_cmdObject

Restart command wrapped with an exit_on_failure handler.



319
320
321
322
323
324
325
326
# File 'lib/sunshine/daemon.rb', line 319

def _restart_cmd
  if restart_cmd
    exit_on_failure restart_cmd, RESTART_FAILED_CODE,
      "Could not restart #{@name} for #{@app.name}"
  else
    "(#{_stop_cmd}) && (#{_start_cmd});"
  end
end

#_start_cmdObject

Start command wrapped with an exit_on_failure handler.



282
283
284
285
# File 'lib/sunshine/daemon.rb', line 282

def _start_cmd
  exit_on_failure start_cmd, START_FAILED_CODE,
    "Could not start #{@name} for #{@app.name}"
end

#_status_cmdObject

Get the command to check if the daemon is running.



340
341
342
343
# File 'lib/sunshine/daemon.rb', line 340

def _status_cmd
  exit_on_failure status_cmd, STATUS_DOWN_CODE,
    "#{@app.name} #{@name} is not running"
end

#_stop_cmdObject

Stop command wrapped with an exit_on_failure handler.



300
301
302
303
# File 'lib/sunshine/daemon.rb', line 300

def _stop_cmd
  exit_on_failure stop_cmd, STOP_FAILED_CODE,
    "Could not kill #{@name} pid for #{@app.name}"
end

#chown_log_files(shell) ⇒ Object

Make sure log files are owned by the daemon’s user.



447
448
449
450
451
452
453
454
455
456
457
458
459
460
# File 'lib/sunshine/daemon.rb', line 447

def chown_log_files shell
  files = @log_files.values.join(" ")

  sudo = pick_sudo(shell)
  user = case sudo
         when true then 'root'
         when String then sudo
         else
           nil
         end

  return unless user
  shell.call "chown -f #{user} #{files}", :sudo => true rescue nil
end

#config_binding(shell) ⇒ Object

Create and setup a binding for a given shell.



403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/sunshine/daemon.rb', line 403

def config_binding shell
  binder = Binder.new self
  binder.forward(*self.class.binder_methods)

  binder.set :shell, shell

  binder_sudo = pick_sudo(shell)
  binder.set :sudo, binder_sudo

  binder.set :expand_path do |path|
    shell.expand_path path
  end

  binder
end

#config_file_pathObject

Get the file path to the daemon’s config file.



368
369
370
# File 'lib/sunshine/daemon.rb', line 368

def config_file_path
  "#{@config_path}/#{@config_file}"
end

#config_template_filesObject

Get the array of local config template files needed by the daemon.



395
396
397
# File 'lib/sunshine/daemon.rb', line 395

def config_template_files
  @config_template_files ||= Dir[@config_template].select{|f| File.file?(f)}
end

#configure_remote_dirs(shell) ⇒ Object

Make sure all the remote directories needed by the daemon exist.



432
433
434
435
436
437
438
439
440
441
# File 'lib/sunshine/daemon.rb', line 432

def configure_remote_dirs shell
  dirs = @log_files.values.map{|f| File.dirname(f)}

  dirs << File.dirname(@pid)
  dirs << @config_path
  dirs.delete_if{|d| d == "."}
  dirs = dirs.join(" ")

  shell.call "mkdir -p #{dirs}"
end

#each_server_app(&block) ⇒ Object

Do something with each server app used by the daemon.



127
128
129
# File 'lib/sunshine/daemon.rb', line 127

def each_server_app(&block)
  @app.each(@options, &block)
end

#exit_on_failure(cmd, exitcode = 1, message = nil) ⇒ Object

Wrap a command with a fail-specific exitcode and message.



264
265
266
# File 'lib/sunshine/daemon.rb', line 264

def exit_on_failure cmd, exitcode=1, message=nil
  "(#{cmd}) || (echo '#{message}' && exit #{exitcode});"
end

#has_setup?(force = false) ⇒ Boolean

Check if setup was run successfully.

Returns:

  • (Boolean)


170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/sunshine/daemon.rb', line 170

def has_setup? force=false
  return @setup_successful unless @setup_successful.nil? || force

  each_server_app do |server_app|

    unless server_app.shell.file? config_file_path
      return @setup_successful = false
    end
  end

  @setup_successful = true
end

#log_file(key) ⇒ Object

Get the path of a log file:

daemon.log_file(:stderr)
#=> "/all_logs/stderr.log"


360
361
362
# File 'lib/sunshine/daemon.rb', line 360

def log_file key
  @log_files[key]
end

#log_files(hash) ⇒ Object

Append or override daemon log file paths:

daemon.log_files :stderr => "/all_logs/stderr.log"


350
351
352
# File 'lib/sunshine/daemon.rb', line 350

def log_files hash
  @log_files.merge!(hash)
end

#pick_sudo(shell) ⇒ Object

Pick which sudo to use between the daemon sudo and shell sudo. (Useful when running servers on ports < 1024)



424
425
426
# File 'lib/sunshine/daemon.rb', line 424

def pick_sudo shell
  self.sudo.nil? ? shell.sudo : self.sudo
end

#register_after_user_scriptObject

Setup what should be run after the user block on App#deploy.



466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
# File 'lib/sunshine/daemon.rb', line 466

def register_after_user_script
  @app.after_user_script do |app|
    next unless has_setup?

    each_server_app do |sa|
      sudo = pick_sudo sa.shell

      %w{start stop restart status}.each do |script|
        script_file = "#{@config_path}/#{script}"

        cmd = send "_#{script}_cmd"

        sa.shell.make_file script_file, cmd,
          :flags => '--chmod=ugo=rwx'


        cmd = sa.shell.sudo_cmd "#{@app.root_path}/env #{script_file}", sudo

        sa.scripts[script.to_sym] << [*cmd].join(" ")
      end
    end
  end
end

#restartObject

Restarts the daemon using the restart_cmd attribute if provided. If restart_cmd is not provided, calls stop and start.



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/sunshine/daemon.rb', line 243

def restart
  self.setup unless has_setup?

  Sunshine.logger.info @name, "Restarting #{@name} daemon" do
    each_server_app do |server_app|
      begin
        server_app.shell.call _restart_cmd,
          :sudo => pick_sudo(server_app.shell)

        yield(server_app) if block_given?
      rescue => e
        raise DaemonError.new(e, "Could not restart #{@name}")
      end
    end
  end
end

#setupObject

Setup the daemon, parse and upload config templates. If a dependency with the daemon name exists in Sunshine.dependencies, setup will attempt to install the dependency before uploading configs. If a block is given it will be passed each server_app and binder object which will be used for the building erb config templates. See the ConfigBinding class for more information.



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/sunshine/daemon.rb', line 140

def setup
  Sunshine.logger.info @name, "Setting up #{@name} daemon" do

    each_server_app do |server_app|

      # Build erb binding
      binder = config_binding server_app.shell

      configure_remote_dirs server_app.shell
      chown_log_files server_app.shell

      yield(server_app, binder) if block_given?

      server_app.install_deps @dep_name if
        Sunshine.dependencies.exist?(@dep_name)

      upload_config_files(server_app.shell, binder.get_binding)
    end
  end

  @setup_successful = true

rescue => e
  raise DaemonError.new(e, "Could not setup #{@name}")
end

#startObject

Start the daemon app after running setup.



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/sunshine/daemon.rb', line 187

def start
  self.setup unless has_setup?
  Sunshine.logger.info @name, "Starting #{@name} daemon" do

    each_server_app do |server_app|
      begin
        server_app.shell.call _start_cmd,
          :sudo => pick_sudo(server_app.shell)

        yield(server_app) if block_given?
      rescue => e
        raise DaemonError.new(e, "Could not start #{@name}")
      end
    end
  end
end

#statusObject

Check if the daemon is running on all servers



208
209
210
211
212
213
214
215
216
# File 'lib/sunshine/daemon.rb', line 208

def status
  each_server_app do |server_app|
    server_app.shell.call _status_cmd, :sudo => pick_sudo(server_app.shell)
  end
  true

rescue CmdError => e
  false
end

#stopObject

Stop the daemon app.



222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/sunshine/daemon.rb', line 222

def stop
  Sunshine.logger.info @name, "Stopping #{@name} daemon" do

    each_server_app do |server_app|
      begin
        server_app.shell.call _stop_cmd,
          :sudo => pick_sudo(server_app.shell)

        yield(server_app) if block_given?
      rescue => e
        raise DaemonError.new(e, "Could not stop #{@name}")
      end
    end
  end
end

#upload_config_files(shell, setup_binding = binding) ⇒ Object

Upload config files and run them through erb with the provided binding if necessary.



377
378
379
380
381
382
383
384
385
386
387
388
389
# File 'lib/sunshine/daemon.rb', line 377

def upload_config_files shell, setup_binding=binding
  config_template_files.each do |config_file|

    if File.extname(config_file) == ".erb"
      filename = File.basename(config_file[0..-5])
      parsed_config = @app.build_erb(config_file, setup_binding)
      shell.make_file "#{@config_path}/#{filename}", parsed_config
    else
      filename = File.basename(config_file)
      shell.upload config_file, "#{@config_path}/#{filename}"
    end
  end
end