Class: Pipemaster::Configurator

Inherits:
Struct
  • Object
show all
Defined in:
lib/pipemaster/configurator.rb

Overview

Implements a simple DSL for configuring a Pipemaster server.

Constant Summary collapse

DEFAULTS =

Default settings for Pipemaster

{
  :timeout => 60,
  :logger => Logger.new($stderr),
  :after_fork => lambda { |server, worker|
      server.logger.info("spawned pid=#{$$}")
    },
  :before_fork => lambda { |server, worker|
      server.logger.info("spawning...")
    },
  :before_exec => lambda { |server|
      server.logger.info("forked child re-executing...")
    },
  :pid => nil,
  :background => {},
  :commands => { }
}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(defaults = {}) ⇒ Configurator

:nodoc:



28
29
30
31
32
33
34
35
36
37
38
# File 'lib/pipemaster/configurator.rb', line 28

def initialize(defaults = {}) #:nodoc:
  self.set = Hash.new(:unset)
  use_defaults = defaults.delete(:use_defaults)
  self.config_file = defaults.delete(:config_file)
  set.merge!(DEFAULTS) if use_defaults
  defaults.each { |key, value| self.send(key, value) }
  Hash === set[:listener_opts] or
      set[:listener_opts] = Hash.new { |hash,key| hash[key] = {} }
  Array === set[:listeners] or set[:listeners] = []
  reload
end

Instance Attribute Details

#config_fileObject

Returns the value of attribute config_file

Returns:

  • (Object)

    the current value of config_file



8
9
10
# File 'lib/pipemaster/configurator.rb', line 8

def config_file
  @config_file
end

#setObject

Returns the value of attribute set

Returns:

  • (Object)

    the current value of set



8
9
10
# File 'lib/pipemaster/configurator.rb', line 8

def set
  @set
end

Instance Method Details

#[](key) ⇒ Object

:nodoc:



62
63
64
# File 'lib/pipemaster/configurator.rb', line 62

def [](key) # :nodoc:
  set[key]
end

#after_fork(*args, &block) ⇒ Object

Sets after_fork hook to a given block. This block will be called by the worker after forking.



128
129
130
# File 'lib/pipemaster/configurator.rb', line 128

def after_fork(*args, &block)
  set_hook(:after_fork, block_given? ? block : args[0])
end

#background(name_or_hash, a_proc = nil, &block) ⇒ Object

Background process: the block is executed in a child process. The master will tell the child process to stop/terminate using appropriate signals, restart the process after a successful upgrade. Block accepts two arguments: server and worker.



152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/pipemaster/configurator.rb', line 152

def background(name_or_hash, a_proc = nil, &block)
  set[:background] = {} if set[:background] == :unset
  if Hash === name_or_hash
    name_or_hash.each_pair do |name, a_proc|
      background name, a_proc
    end
  else
    name = name_or_hash.to_sym
    a_proc ||= block
    arity = a_proc.arity
    (arity == 0 || arity < 0) or raise ArgumentError, "background #{name}#{a_proc.inspect} has invalid arity: #{arity} (need 0)"
    set[:background][name] = a_proc
  end
end

#before_exec(*args, &block) ⇒ Object

Sets the before_exec hook to a given Proc object. This Proc object will be called by the master process right before exec()-ing the new binary. This is useful for freeing certain OS resources that you do NOT wish to share with the reexeced child process. There is no corresponding after_exec hook (for obvious reasons).



144
145
146
# File 'lib/pipemaster/configurator.rb', line 144

def before_exec(*args, &block)
  set_hook(:before_exec, block_given? ? block : args[0], 1)
end

#before_fork(*args, &block) ⇒ Object

Sets before_fork hook to a given block. This block will be called by the worker before forking.



134
135
136
# File 'lib/pipemaster/configurator.rb', line 134

def before_fork(*args, &block)
  set_hook(:before_fork, block_given? ? block : args[0])
end

#command(name, a_proc = nil, &block) ⇒ Object

Defines a command.



84
85
86
# File 'lib/pipemaster/configurator.rb', line 84

def command(name, a_proc = nil, &block)
  set[:commands][name.to_sym] = a_proc || block
end

#commands(hash) ⇒ Object

Set commands from a hash (name=>proc).



79
80
81
# File 'lib/pipemaster/configurator.rb', line 79

def commands(hash)
  set[:commands] = hash
end

#commit!(server, options = {}) ⇒ Object

:nodoc:



53
54
55
56
57
58
59
60
# File 'lib/pipemaster/configurator.rb', line 53

def commit!(server, options = {}) #:nodoc:
  skip = options[:skip] || []
  set.each do |key, value|
    value == :unset and next
    skip.include?(key) and next
    server.__send__("#{key}=", value)
  end
end

#expand_addr(address) ⇒ Object

expands “unix:path/to/foo” to a socket relative to the current path expands pathnames of sockets if relative to “~” or “~username” expands “*:port and ”:port“ to ”0.0.0.0:port“



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/pipemaster/configurator.rb', line 307

def expand_addr(address) #:nodoc
  return "0.0.0.0:#{address}" if Integer === address
  return address unless String === address

  case address
  when %r{\Aunix:(.*)\z}
    File.expand_path($1)
  when %r{\A~}
    File.expand_path(address)
  when %r{\A(?:\*:)?(\d+)\z}
    "0.0.0.0:#$1"
  when %r{\A(.*):(\d+)\z}
    # canonicalize the name
    packed = Socket.pack_sockaddr_in($2.to_i, $1)
    Socket.unpack_sockaddr_in(packed).reverse!.join(':')
  else
    address
  end
end

#listen(address, opt = {}) ⇒ Object

Adds an address to the existing listener set.

The following options may be specified (but are generally not needed):

:backlog: this is the backlog of the listen() syscall.

Some operating systems allow negative values here to specify the maximum allowable value. In most cases, this number is only recommendation and there are other OS-specific tunables and variables that can affect this number. See the listen(2) syscall documentation of your OS for the exact semantics of this.

If you are running pipemaster on multiple machines, lowering this number can help your load balancer detect when a machine is overloaded and give requests to a different machine.

Default: 1024

:rcvbuf, :sndbuf: maximum receive and send buffer sizes of sockets

These correspond to the SO_RCVBUF and SO_SNDBUF settings which can be set via the setsockopt(2) syscall. Some kernels (e.g. Linux 2.4+) have intelligent auto-tuning mechanisms and there is no need (and it is sometimes detrimental) to specify them.

See the socket API documentation of your operating system to determine the exact semantics of these settings and other operating system-specific knobs where they can be specified.

Defaults: operating system defaults

:tcp_nodelay: disables Nagle’s algorithm on TCP sockets

This has no effect on UNIX sockets.

Default: operating system defaults (usually Nagle’s algorithm enabled)

:tcp_nopush: enables TCP_CORK in Linux or TCP_NOPUSH in FreeBSD

This will prevent partial TCP frames from being sent out. Enabling tcp_nopush is generally not needed or recommended as controlling tcp_nodelay already provides sufficient latency reduction whereas Pipemaster does not know when the best times are for flushing corked sockets.

This has no effect on UNIX sockets.

:tries: times to retry binding a socket if it is already in use

A negative number indicates we will retry indefinitely, this is useful for migrations and upgrades when individual workers are binding to different ports.

Default: 5

:delay: seconds to wait between successive tries

Default: 0.5 seconds

:umask: sets the file mode creation mask for UNIX sockets

Typically UNIX domain sockets are created with more liberal file permissions than the rest of the application. By default, we create UNIX domain sockets to be readable and writable by all local users to give them the same accessibility as locally-bound TCP listeners.

This has no effect on TCP listeners.

Default: 0 (world read/writable)



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/pipemaster/configurator.rb', line 261

def listen(address, opt = {})
  address = expand_addr(address)
  if String === address
    [ :umask, :backlog, :sndbuf, :rcvbuf, :tries ].each do |key|
      value = opt[key] or next
      Integer === value or
        raise ArgumentError, "not an integer: #{key}=#{value.inspect}"
    end
    [ :tcp_nodelay, :tcp_nopush ].each do |key|
      (value = opt[key]).nil? and next
      TrueClass === value || FalseClass === value or
        raise ArgumentError, "not boolean: #{key}=#{value.inspect}"
    end
    unless (value = opt[:delay]).nil?
      Numeric === value or
        raise ArgumentError, "not numeric: delay=#{value.inspect}"
    end
    set[:listener_opts][address].merge!(opt)
  end

  set[:listeners] << address
end

#listeners(addresses) ⇒ Object

This is for internal API use only, do not use it in your Pipemaster config file. Use listen instead.



170
171
172
173
174
# File 'lib/pipemaster/configurator.rb', line 170

def listeners(addresses) # :nodoc:
  Array === addresses or addresses = Array(addresses)
  addresses.map! { |addr| expand_addr(addr) }
  set[:listeners] = addresses
end

#logger(new) ⇒ Object

Sets object to the new Logger-like object. The new logger-like object must respond to the following methods:

+debug+, +info+, +warn+, +error+, +fatal+, +close+


69
70
71
72
73
74
75
76
# File 'lib/pipemaster/configurator.rb', line 69

def logger(new)
  %w(debug info warn error fatal close).each do |m|
    new.respond_to?(m) and next
    raise ArgumentError, "logger=#{new} does not respond to method=#{m}"
  end

  set[:logger] = new
end

#pid(path) ⇒ Object

Sets the path for the PID file of the Pipemaster master process



285
286
287
# File 'lib/pipemaster/configurator.rb', line 285

def pid(path)
  set_path(:pid, path)
end

#reloadObject

:nodoc:



40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/pipemaster/configurator.rb', line 40

def reload #:nodoc:
  instance_eval(File.read(config_file), config_file) if config_file

  # working_directory binds immediately (easier error checking that way),
  # now ensure any paths we changed are correctly set.
  [ :pid, :stderr_path, :stdout_path ].each do |var|
    String === (path = set[var]) or next
    path = File.expand_path(path)
    test(?w, path) || test(?w, File.dirname(path)) or \
          raise ArgumentError, "directory for #{var}=#{path} not writable"
  end
end

#setup(*args, &block) ⇒ Object

Setup block runs on startup. Put all the heavy stuff here (e.g. loading libraries, initializing state from database).



122
123
124
# File 'lib/pipemaster/configurator.rb', line 122

def setup(*args, &block)
  set_hook(:setup, block_given? ? block : args[0], 0)
end

#stderr_path(path) ⇒ Object

Allow redirecting $stderr to a given path. Unlike doing this from the shell, this allows the Pipemaster process to know the path its writing to and rotate the file if it is used for logging. The file will be opened with the File::APPEND flag and writes synchronized to the kernel (but not necessarily to disk) so multiple processes can safely append to it.



295
296
297
# File 'lib/pipemaster/configurator.rb', line 295

def stderr_path(path)
  set_path(:stderr_path, path)
end

#stdout_path(path) ⇒ Object

Same as stderr_path, except for $stdout



300
301
302
# File 'lib/pipemaster/configurator.rb', line 300

def stdout_path(path)
  set_path(:stdout_path, path)
end

#timeout(seconds) ⇒ Object

Does nothing interesting for now.



177
178
179
# File 'lib/pipemaster/configurator.rb', line 177

def timeout(seconds)
  # Not implemented yet.
end

#user(user, group = nil) ⇒ Object

Change user/group ownership of the master process.



90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/pipemaster/configurator.rb', line 90

def user(user, group = nil)
  # we do not protect the caller, checking Process.euid == 0 is
  # insufficient because modern systems have fine-grained
  # capabilities.  Let the caller handle any and all errors.
  uid = Etc.getpwnam(user).uid
  gid = Etc.getgrnam(group).gid if group
  Pipemaster::Util.chown_logs(uid, gid)
  if gid && Process.egid != gid
    Process.initgroups(user, gid)
    Process::GID.change_privilege(gid)
  end
  Process.euid != uid and Process::UID.change_privilege(uid)
end

#working_directory(path) ⇒ Object

Sets the working directory for Pipemaster. Defaults to the location of the Pipefile. This may be a symlink.



106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/pipemaster/configurator.rb', line 106

def working_directory(path)
  # just let chdir raise errors
  path = File.expand_path(path)
  if config_file &&
     config_file[0] != ?/ &&
     ! test(?r, "#{path}/#{config_file}")
    raise ArgumentError,
          "pipefile=#{config_file} would not be accessible in" \
          " working_directory=#{path}"
  end
  Dir.chdir(path)
  Server::START_CTX[:cwd] = ENV["PWD"] = path
end