svdir 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +29 -0
- data/LICENSE +19 -0
- data/README +67 -0
- data/Rakefile +66 -0
- data/example/svctl +117 -0
- data/lib/sys/sv/statusbytes.rb +53 -0
- data/lib/sys/sv/svdir.rb +224 -0
- data/lib/sys/sv/util.rb +36 -0
- data/test/fixtures/PrefabSvDir.rb +22 -0
- data/test/fixtures/TempSvDir.rb +62 -0
- data/test/services/corrupt-normd/down +0 -0
- data/test/services/corrupt-normd/supervise/ok +0 -0
- data/test/services/corrupt-normd/supervise/status +0 -0
- data/test/services/corrupt-normu/supervise/ok +0 -0
- data/test/services/corrupt-normu/supervise/status +0 -0
- data/test/services/corrupt-zero-normd/down +0 -0
- data/test/services/corrupt-zero-normd/supervise/ok +0 -0
- data/test/services/corrupt-zero-normd/supervise/status +0 -0
- data/test/services/corrupt-zero-normu/supervise/ok +0 -0
- data/test/services/corrupt-zero-normu/supervise/status +0 -0
- data/test/services/down-0-normd-nowant/down +0 -0
- data/test/services/down-0-normd-nowant/supervise/ok +0 -0
- data/test/services/down-0-normd-nowant/supervise/status +0 -0
- data/test/services/down-0-normu-nowant/supervise/ok +0 -0
- data/test/services/down-0-normu-nowant/supervise/status +0 -0
- data/test/services/up-12526-normu-wantd/supervise/ok +0 -0
- data/test/services/up-12526-normu-wantd/supervise/status +0 -0
- data/test/services/up-12581-normd-wantd-paused/down +0 -0
- data/test/services/up-12581-normd-wantd-paused/supervise/ok +0 -0
- data/test/services/up-12581-normd-wantd-paused/supervise/status +0 -0
- data/test/services/up-12581-normu-wantd-paused/supervise/ok +0 -0
- data/test/services/up-12581-normu-wantd-paused/supervise/status +0 -0
- data/test/services/up-12816-normd-nowant/down +0 -0
- data/test/services/up-12816-normd-nowant/supervise/ok +0 -0
- data/test/services/up-12816-normd-nowant/supervise/status +0 -0
- data/test/services/up-12816-normu-nowant/supervise/ok +0 -0
- data/test/services/up-12816-normu-nowant/supervise/status +0 -0
- data/test/services/up-12868-normd-wantu-paused/down +0 -0
- data/test/services/up-12868-normd-wantu-paused/supervise/ok +0 -0
- data/test/services/up-12868-normd-wantu-paused/supervise/status +0 -0
- data/test/services/up-12868-normd-wantu/down +0 -0
- data/test/services/up-12868-normd-wantu/supervise/ok +0 -0
- data/test/services/up-12868-normd-wantu/supervise/status +0 -0
- data/test/services/up-12868-normu-wantu-paused/supervise/ok +0 -0
- data/test/services/up-12868-normu-wantu-paused/supervise/status +0 -0
- data/test/services/up-12868-normu-wantu/supervise/ok +0 -0
- data/test/services/up-12868-normu-wantu/supervise/status +0 -0
- data/test/services/up-16464-normd-wantd/down +0 -0
- data/test/services/up-16464-normd-wantd/supervise/ok +0 -0
- data/test/services/up-16464-normd-wantd/supervise/status +0 -0
- data/test/testbase.rb +30 -0
- data/test/ts_corrupt.rb +133 -0
- data/test/ts_nosupervisor.rb +81 -0
- data/test/ts_signal.rb +128 -0
- data/test/ts_svstat.rb +548 -0
- metadata +117 -0
data/CHANGELOG
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
2011-02-28 mjp version 0.2
|
2
|
+
[doc] Documentation touch-up, move from Qualys SVN to github.
|
3
|
+
|
4
|
+
2011-02-17 mjp
|
5
|
+
[api] Remove #epoch() method, #pid() now returns nil if service is not
|
6
|
+
running (formerly returned zero).
|
7
|
+
[int] Remove Forwardable delegation, refactor SvDir and StatusBytes
|
8
|
+
accordingly. A skosh more efficient, but also puts method documentation
|
9
|
+
in the class that needs it.
|
10
|
+
[tst] Update tests for API changes, delegation refactoring and corrupt
|
11
|
+
status file handling.
|
12
|
+
[doc] update LICENSE, copyright notice, README
|
13
|
+
[pkg] remove Echoe dependency, generate gem with vanilla rake tasks
|
14
|
+
(Rakefile)
|
15
|
+
|
16
|
+
2008-10-13 mjp
|
17
|
+
[api] make that ENXIO/EWHATEVER on non-running supervisor. Raise
|
18
|
+
EPROTO specifically on truncated 'status' file.
|
19
|
+
[tst] Fixtures::TempSvDir makes bona fide FIFOs, correctly makes
|
20
|
+
incremented tempdirs. ts_control renamed ts_signal, also test non-
|
21
|
+
running supervisor (ts_nosupervisor).
|
22
|
+
[doc] note Errno:: conventions, use "supervisor" consistently.
|
23
|
+
|
24
|
+
2008-10-10 mjp
|
25
|
+
[bug] qualify 'ok' FIFO path (cpforbes).
|
26
|
+
[api] raise EPROTO for non-running supervisor.
|
27
|
+
|
28
|
+
2008-10-06 mjp
|
29
|
+
Initial check-in.
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2011 Qualys, Inc.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
Copyright (c) 2010, 2011 Qualys, Inc.
|
2
|
+
|
3
|
+
Copyright (c) 2008 - 2010 Nemean Networks, LLC.
|
4
|
+
|
5
|
+
= Overview
|
6
|
+
|
7
|
+
+svdir+ is a Ruby interface to the "service directory" style of robust daemon
|
8
|
+
process supervision introducted in Dan Bernstein's +daemontools+ software and
|
9
|
+
compatibly extended in Gerit Pape's +runit+ package.
|
10
|
+
|
11
|
+
It exposes a programmatic interface to reliably starting, stopping, signalling
|
12
|
+
and interrogating services all implemented directly -- no need to shell out to
|
13
|
+
separate utilities. See the documentation for Sys::Sv::SvDir for complete
|
14
|
+
information.
|
15
|
+
|
16
|
+
More information on +daemontools+ is available at
|
17
|
+
http://cr.yp.to/daemontools.html. More information on +runit+ is available at
|
18
|
+
http://smarden.org/runit/.
|
19
|
+
|
20
|
+
== Example
|
21
|
+
|
22
|
+
The <tt>example/</tt> subdirectory in the source distribution contains a
|
23
|
+
demonstration program which a system adminstrator might use to control daemons.
|
24
|
+
|
25
|
+
Typical programmatic use of this software might look like this:
|
26
|
+
|
27
|
+
#! /usr/bin/env ruby
|
28
|
+
|
29
|
+
require 'sys/sv/svdir'
|
30
|
+
include Sys::Sv
|
31
|
+
|
32
|
+
# shut down all daemons running under /service
|
33
|
+
Dir["/service"].each do |svpath|
|
34
|
+
s = SvDir.new(svpath)
|
35
|
+
|
36
|
+
# force the .../log to shut down, ignoring services without loggers
|
37
|
+
s.log.signal(:exit) rescue nil
|
38
|
+
|
39
|
+
pid = s.pid
|
40
|
+
if pid != 0
|
41
|
+
s.signal(:exit)
|
42
|
+
puts "Told #{s.path} (pid #{pid}) to exit"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
= Installation
|
47
|
+
|
48
|
+
Look for the gem on http://rubygems.org.
|
49
|
+
|
50
|
+
`rake package` will build a .gem under pkg/, and `rake rdoc` will
|
51
|
+
generate module documentation.
|
52
|
+
|
53
|
+
`rake test` and `rake rcov` will give a good idea of where the code is.
|
54
|
+
|
55
|
+
= To Do
|
56
|
+
|
57
|
+
* Further testing
|
58
|
+
- log()
|
59
|
+
- TAI64 testing
|
60
|
+
|
61
|
+
* Possible extensions
|
62
|
+
- <tt>SvDir.new(d) &block</tt> - persist StatusBytes object for block?
|
63
|
+
- <tt>normally_down!</tt> and <tt>normally_up!</tt>
|
64
|
+
|
65
|
+
= Author
|
66
|
+
|
67
|
+
Mike Pomraning ("mpomraning" at "qualys" dot "com")
|
data/Rakefile
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'rake/testtask'
|
5
|
+
|
6
|
+
FILES = FileList['Rakefile', 'CHANGELOG', 'LICENSE', 'README',
|
7
|
+
'lib/**/*.rb', 'test/**/*', 'example/*'].to_a
|
8
|
+
TESTS = FileList['test/ts_*.rb']
|
9
|
+
|
10
|
+
spec = Gem::Specification.new do |s|
|
11
|
+
s.platform = Gem::Platform::RUBY
|
12
|
+
s.author = "Mike Pomraning"
|
13
|
+
s.summary = "An interface to service directories ala supervise/runsv"
|
14
|
+
s.name = 'svdir'
|
15
|
+
s.version = '0.2'
|
16
|
+
s.require_path = 'lib'
|
17
|
+
s.description = <<eodesc
|
18
|
+
The svdir package controls service directories, a scheme for reliably
|
19
|
+
controlling daemon processes as implemented in Dan Bernstein's daemontools
|
20
|
+
software ("supervise") or Gerit Pape's runit software ("runsv").
|
21
|
+
eodesc
|
22
|
+
s.files = FILES
|
23
|
+
s.test_files = TESTS
|
24
|
+
s.has_rdoc = true
|
25
|
+
end
|
26
|
+
|
27
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
28
|
+
pkg.package_dir = 'pkg'
|
29
|
+
pkg.need_tar = true
|
30
|
+
end
|
31
|
+
|
32
|
+
desc "Generate rdoc"
|
33
|
+
Rake::RDocTask.new("rdoc") do |rdoc|
|
34
|
+
rdoc.rdoc_dir = 'doc/rdoc'
|
35
|
+
rdoc.title = "svdir"
|
36
|
+
# Show source inline with line numbers
|
37
|
+
rdoc.options << "--inline-source" << "--line-numbers"
|
38
|
+
# Make the readme file the start page for the generated html
|
39
|
+
rdoc.options << '--main' << 'README'
|
40
|
+
rdoc.rdoc_files.include('lib/**/*.rb',
|
41
|
+
'CHANGELOG',
|
42
|
+
'README',
|
43
|
+
'LICENSE')
|
44
|
+
end
|
45
|
+
|
46
|
+
desc "Run included tests"
|
47
|
+
Rake::TestTask.new do |t|
|
48
|
+
t.libs << "test"
|
49
|
+
t.test_files = TESTS
|
50
|
+
t.libs << "lib"
|
51
|
+
end
|
52
|
+
|
53
|
+
desc "Run test coverage (rcov)"
|
54
|
+
begin
|
55
|
+
require 'rcov/rcovtask'
|
56
|
+
Rcov::RcovTask.new do |t|
|
57
|
+
t.test_files = TESTS
|
58
|
+
t.libs << "test" << "lib"
|
59
|
+
t.rcov_opts << '--exclude /gems/,/Library/,/usr/,spec,lib/tasks'
|
60
|
+
end
|
61
|
+
rescue LoadError
|
62
|
+
task :rcov do
|
63
|
+
puts "Error - you seem to be missing 'rcov'"
|
64
|
+
exit 1
|
65
|
+
end
|
66
|
+
end
|
data/example/svctl
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'sys/sv/svdir'
|
4
|
+
include Sys::Sv
|
5
|
+
|
6
|
+
USAGE = <<'__eousage'
|
7
|
+
Usage: svctl [cmd] service_dir [service_dir ...]
|
8
|
+
svctl [-h|--help]
|
9
|
+
|
10
|
+
If 'cmd' is 'status', print a summary of the current state of each
|
11
|
+
given service directory and its subordinate log directory, if any.
|
12
|
+
|
13
|
+
Issue 'cmd' to the given service directories in turn. Behavior
|
14
|
+
varies with the command:
|
15
|
+
|
16
|
+
status ...... summarize status of service and log service, if any
|
17
|
+
up .......... start and, as needed, restart the service
|
18
|
+
down ........ TERM+CONT a running service, do not restart
|
19
|
+
exit ........ "down" a service, TERM its log service, then exit
|
20
|
+
once ........ start but do not restart the service
|
21
|
+
|
22
|
+
Other values of 'cmd' instruct the supervisory process to send a
|
23
|
+
UNIX signal to its service, if running:
|
24
|
+
|
25
|
+
pause, STOP, continue, CONT, hangup, HUP, alarm, ALRM, interrupt, INT,
|
26
|
+
terminate, TERM, kill, KILL, user1, USR1, user2, USR2
|
27
|
+
|
28
|
+
Note that the 'user'/'USR' commands are extensions to the original
|
29
|
+
process supervision implementation, and not supported by all supervisors.
|
30
|
+
__eousage
|
31
|
+
|
32
|
+
def usage_exit(errmsg = nil)
|
33
|
+
outio, rc = [$stdout, 0]
|
34
|
+
|
35
|
+
if errmsg
|
36
|
+
outio = $stderr
|
37
|
+
rc = 1
|
38
|
+
outio.puts errmsg
|
39
|
+
end
|
40
|
+
|
41
|
+
outio.puts USAGE
|
42
|
+
|
43
|
+
exit(rc)
|
44
|
+
end
|
45
|
+
|
46
|
+
def formatted_status(sv, name = nil)
|
47
|
+
# sv(1)-inspired status
|
48
|
+
#
|
49
|
+
# For each SvDir argument, show:
|
50
|
+
# current state: running, paused ("running" but SIGSTOP'd), or down
|
51
|
+
# service name: supplied by caller
|
52
|
+
# elapsed time: time since begun running or since stopped
|
53
|
+
# pid: if applicable
|
54
|
+
# typical state: normally up/down? (only if different than current)
|
55
|
+
# want up/down?: if applicable
|
56
|
+
#
|
57
|
+
name = File.basename(sv.path) unless name
|
58
|
+
|
59
|
+
runstate, elapsed, pid_str, unusual_state = [nil, nil, nil, nil]
|
60
|
+
|
61
|
+
if sv.up?
|
62
|
+
runstate = sv.paused? ? "paused" : "run"
|
63
|
+
elapsed = sv.uptime
|
64
|
+
pid_str = "(pid #{sv.pid})"
|
65
|
+
unusual_state = "normally down" if sv.normally_down? # Hmm!
|
66
|
+
else
|
67
|
+
runstate = "down"
|
68
|
+
elapsed = sv.downtime
|
69
|
+
unusual_state = "normally up" if sv.normally_up? # Hmm!
|
70
|
+
end
|
71
|
+
|
72
|
+
elapsed = elapsed.round.to_s + 's'
|
73
|
+
want_state = sv.want_up? ? "want up" :
|
74
|
+
sv.want_down? ? "want down" :
|
75
|
+
""
|
76
|
+
|
77
|
+
["#{runstate}: #{name}:", pid_str, elapsed, unusual_state].compact.join(" ")
|
78
|
+
rescue SystemCallError => e
|
79
|
+
"err: #{name}: No supervisor detected (#{e})"
|
80
|
+
end
|
81
|
+
|
82
|
+
def do_signal(sig, *dirs)
|
83
|
+
dirs.each do |dir|
|
84
|
+
begin
|
85
|
+
SvDir.new(dir).signal(sig)
|
86
|
+
rescue SystemCallError => e
|
87
|
+
$stderr.puts "error signalling #{dir}: #{e.message}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def main
|
93
|
+
# A quick demonstration of Sys::Sv::SvDir ...
|
94
|
+
cmd = ARGV.shift
|
95
|
+
|
96
|
+
case cmd
|
97
|
+
when '--help', '-h', nil
|
98
|
+
usage_exit
|
99
|
+
when 'status'
|
100
|
+
ARGV.each do |dir|
|
101
|
+
sv = SvDir.new(dir)
|
102
|
+
sv_stat = formatted_status(sv, dir)
|
103
|
+
if log = sv.log
|
104
|
+
sv_stat += '; ' + formatted_status(log, 'log')
|
105
|
+
end
|
106
|
+
puts sv_stat
|
107
|
+
end
|
108
|
+
else
|
109
|
+
begin
|
110
|
+
do_signal(cmd, *ARGV)
|
111
|
+
rescue Exception => e
|
112
|
+
usage_exit("error: #{e.message}")
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
main if __FILE__ == $0
|
@@ -0,0 +1,53 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Mike Pomraning
|
3
|
+
# Copyright:: Copyright (c) 2011 Qualys, Inc.
|
4
|
+
# License:: MIT (see the file LICENSE)
|
5
|
+
#
|
6
|
+
|
7
|
+
module Sys # :nodoc:
|
8
|
+
module Sv # :nodoc:
|
9
|
+
|
10
|
+
# The StatusBytes class interprets state files maintained by a SvDir's
|
11
|
+
# _monitor_ process. It should normally not be instantiated directly.
|
12
|
+
class StatusBytes # :nodoc:
|
13
|
+
BUFLEN = 18 # TODO - grok runit extended info (20 bytes)
|
14
|
+
TAI_EPOCH = 4611686018427387914 # time_t 0 on the TAI scale
|
15
|
+
|
16
|
+
attr_reader :pid, :pauseflag, :wantflag
|
17
|
+
|
18
|
+
def initialize(bytes) # :nodoc:
|
19
|
+
if bytes.size < BUFLEN
|
20
|
+
raise ::Errno::EPROTO.new("corrupt status buffer")
|
21
|
+
end
|
22
|
+
|
23
|
+
@bytes = bytes
|
24
|
+
@pid, @pauseflag, @wantflag = @bytes.unpack('x12 V c a')
|
25
|
+
@epoch = nil # computed if needed
|
26
|
+
end
|
27
|
+
|
28
|
+
# Number of seconds since service was most recently started or
|
29
|
+
# stopped.
|
30
|
+
def elapsed
|
31
|
+
::Time.now.to_f - epoch()
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns the number of seconds since the UNIX epoch since the
|
35
|
+
# service was most recently started or stopped.
|
36
|
+
def epoch
|
37
|
+
return @epoch if @epoch
|
38
|
+
|
39
|
+
# assemble UNIX-scale seconds from TAI64N label
|
40
|
+
hi32, lo32, nano = @bytes.unpack('N N N')
|
41
|
+
@epoch = (hi32 << 32) + lo32 - TAI_EPOCH
|
42
|
+
if @epoch <= 0
|
43
|
+
@epoch = 0.0
|
44
|
+
else
|
45
|
+
@epoch += nano/10e9
|
46
|
+
end
|
47
|
+
|
48
|
+
@epoch
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end #-- module Sv
|
53
|
+
end #-- module Sys
|
data/lib/sys/sv/svdir.rb
ADDED
@@ -0,0 +1,224 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Author:: Mike Pomraning
|
4
|
+
# Copyright:: Copyright (c) 2011 Qualys, Inc.
|
5
|
+
# License:: MIT (see the file LICENSE)
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'sys/sv/statusbytes' # class StatusBytes
|
9
|
+
require 'sys/sv/util' # Util::open_write(), open_read()
|
10
|
+
|
11
|
+
module Sys # :nodoc
|
12
|
+
module Sv # :nodoc:
|
13
|
+
|
14
|
+
# The SvDir class encapsulates service directories, a scheme for
|
15
|
+
# reliably controlling daemon processes (services) introduced in Dan
|
16
|
+
# Bernstein's +daemontools+ software.
|
17
|
+
#
|
18
|
+
# Each service is monitored by a _supervisor_, which is responsible
|
19
|
+
# for starting, stopping, restarting and generally controlling the
|
20
|
+
# service. Examples of such supervisors include +supervise+ from
|
21
|
+
# the +daemontools+ package and +runsv+ from Gerit Pape's compatible
|
22
|
+
# +runit+ software.
|
23
|
+
#
|
24
|
+
# Most SvDir methods will raise <tt>Errno::</tt> exceptions (each
|
25
|
+
# a subclass of +SystemCallException+) if the underlying filesystem
|
26
|
+
# representation of the service directory is not as expected. For
|
27
|
+
# example, +EACCESS+ or +ENOENT+ may be raised if the _supervisor_'s
|
28
|
+
# status directory or state files are missing or unreadable. Additionally,
|
29
|
+
# +ENXIO+ is raised if the _supervisor_ itself isn't running.
|
30
|
+
#
|
31
|
+
|
32
|
+
class SvDir
|
33
|
+
|
34
|
+
VERSION = '0.2'
|
35
|
+
|
36
|
+
attr_reader :path
|
37
|
+
|
38
|
+
# ...SvDir::Commands = { :alarm => 'a', :ALRM => 'a', :exit => 'x' ... }
|
39
|
+
begin
|
40
|
+
h = {}
|
41
|
+
{
|
42
|
+
[:up ] => 'u',
|
43
|
+
[:down ] => 'd',
|
44
|
+
[:once ] => 'o',
|
45
|
+
[:pause, :STOP ] => 'p',
|
46
|
+
[:continue, :CONT ] => 'c',
|
47
|
+
[:hangup, :HUP ] => 'h',
|
48
|
+
[:alarm, :ALRM ] => 'a',
|
49
|
+
[:interrupt, :INT ] => 'i',
|
50
|
+
[:terminate, :TERM ] => 't',
|
51
|
+
[:kill, :KILL ] => 'k',
|
52
|
+
[:exit ] => 'x',
|
53
|
+
[:user1, :USR1 ] => '1', # runit and some patches to daemontools
|
54
|
+
[:user2, :USR2 ] => '2',
|
55
|
+
}.each do | cmds, byte |
|
56
|
+
cmds.each { |c| h[c] = byte }
|
57
|
+
end
|
58
|
+
const_set(:Commands, h)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Create a new SvDir corresponding to the service directory +path+.
|
62
|
+
def initialize(path)
|
63
|
+
@path = path
|
64
|
+
end
|
65
|
+
|
66
|
+
# Send a signal to the service via its _supervisor_.
|
67
|
+
#
|
68
|
+
# [<tt>:up</tt>] start the service if not running, restarting as necessary.
|
69
|
+
# [<tt>:down</tt>] stop the service, issuing a TERM followed by a CONT. Do not restart it if it stops.
|
70
|
+
# [<tt>:once</tt>] start the service if not running, but do not restart it if it stops.
|
71
|
+
# [<tt>:pause</tt> or <tt>:STOP</tt>] issue a STOP signal. See also #paused?.
|
72
|
+
# [<tt>:continue</tt> or <tt>:CONT</tt>] issue a CONT signal. See also #paused?.
|
73
|
+
# [<tt>:hangup</tt> or <tt>:HUP</tt>] issue a HUP signal.
|
74
|
+
# [<tt>:alarm</tt> or <tt>:ALRM</tt>] issue an ALRM signal.
|
75
|
+
# [<tt>:interrupt</tt> or <tt>INT</tt>] issue an INT signal.
|
76
|
+
# [<tt>:terminate</tt> or <tt>TERM</tt>] issue a TERM signal.
|
77
|
+
# [<tt>:kill</tt> or <tt>KILL</tt>] issue a KILL signal.
|
78
|
+
# [<tt>:exit</tt>] tell the _supervisor_ to exit as soon as the service stops.
|
79
|
+
# [<tt>:user1</tt> or <tt>:USR1</tt>] issue a USR1 signal. <i>Not supported by all supervisors</i>
|
80
|
+
# [<tt>:user2</tt> or <tt>:USR2</tt>] issue a USR2 signal. <i>Not supported by all supervisors</i>
|
81
|
+
def signal(cmd)
|
82
|
+
unless byte = Commands[cmd.to_sym]
|
83
|
+
raise ArgumentError.new("unsupported SvDir signal `#{cmd}'")
|
84
|
+
end
|
85
|
+
Util::open_write(svfn('control')) do |f|
|
86
|
+
f.syswrite(byte)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Return a SvDir object representing this service's attendant +log+
|
91
|
+
# service, otherwise +nil+.
|
92
|
+
#
|
93
|
+
# Typical SvDir daemons contain a "nested" SvDir responsible for
|
94
|
+
# logging the stdout of the base service. E.g.:
|
95
|
+
#
|
96
|
+
# /path/to/services/webserver # <--- base service
|
97
|
+
# /path/to/services/webserver/log # <--- logger
|
98
|
+
#
|
99
|
+
# The +log+ service is supervised and controllable just like the base
|
100
|
+
# service. (Also, the pipe connecting the base service's stdout to
|
101
|
+
# the +log+ service's stdin is maintained by a common parent, so that
|
102
|
+
# restarting either service won't lose data in the pipe.)
|
103
|
+
#
|
104
|
+
# Example:
|
105
|
+
#
|
106
|
+
# my_serv = Sys::Sv::SvDir.new("path/to/my_serv")
|
107
|
+
# my_serv.signal(:down)
|
108
|
+
# if logger = my_serv.log
|
109
|
+
# logger.signal(:down)
|
110
|
+
# end
|
111
|
+
def log
|
112
|
+
fn = File.join(@path, 'log')
|
113
|
+
return self.class.new(fn) if File.exists? fn
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns +true+ if the service directory's _supervisor_ is running.
|
117
|
+
#
|
118
|
+
# To determine whether the service itself is running, see #up?, #down?
|
119
|
+
# and #paused?
|
120
|
+
def svok?
|
121
|
+
Util::open_write(svfn('ok')) { true }
|
122
|
+
rescue Errno::ENXIO, Errno::ENOENT
|
123
|
+
false # No pipe reader, or no pipe!
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns +true+ if the service is typically running, i.e., if the
|
127
|
+
# service directory lacks a <tt>./down</tt> file.
|
128
|
+
#
|
129
|
+
# Note that this method functions whether or not the service is
|
130
|
+
# running, and whether or not a supervisor is running.
|
131
|
+
#
|
132
|
+
# See also the #want_up? method documentation.
|
133
|
+
def normally_up?
|
134
|
+
! normally_down?
|
135
|
+
end
|
136
|
+
|
137
|
+
# Returns +true+ if a _supervisor_ will not start the service without
|
138
|
+
# explicit instruction to do so.
|
139
|
+
#
|
140
|
+
# Note that this method functions whether or not the service is
|
141
|
+
# running, and whether or not a supervisor is running.
|
142
|
+
#
|
143
|
+
# See also the #want_down? method documentation.
|
144
|
+
def normally_down?
|
145
|
+
File.exists? File.join(@path, 'down')
|
146
|
+
end
|
147
|
+
|
148
|
+
# Returns the number of seconds the service has been down,
|
149
|
+
# as a float, or +nil+ if the service is in fact running.
|
150
|
+
def downtime
|
151
|
+
return elapsed() if down?
|
152
|
+
end
|
153
|
+
|
154
|
+
# Returns the number of seconds the service has been running,
|
155
|
+
# as a float, or +nil+ if the service is not running.
|
156
|
+
def uptime
|
157
|
+
return elapsed() if up?
|
158
|
+
end
|
159
|
+
|
160
|
+
# Return the pid of the service running under this SvDir, or
|
161
|
+
# +nil+ if no service is running.
|
162
|
+
def pid
|
163
|
+
p = statusbytes.pid
|
164
|
+
return p == 0 ? nil : p
|
165
|
+
end
|
166
|
+
|
167
|
+
# +true+ if the service is not running.
|
168
|
+
def down?
|
169
|
+
pid.nil?
|
170
|
+
end
|
171
|
+
|
172
|
+
# +true+ if the service is running, even if paused.
|
173
|
+
def up?
|
174
|
+
!down?
|
175
|
+
end
|
176
|
+
|
177
|
+
# Returns +true+ if the service is paused, that is, has received
|
178
|
+
# a SIGSTOP.
|
179
|
+
def paused?
|
180
|
+
statusbytes.pauseflag != 0
|
181
|
+
end
|
182
|
+
|
183
|
+
# Returns +true+ if the service's supervisor has been instructed to
|
184
|
+
# bring the service down.
|
185
|
+
def want_down?
|
186
|
+
statusbytes.wantflag == 'd'
|
187
|
+
end
|
188
|
+
|
189
|
+
# Returns +true+ if the service's supervisor has been instructed to
|
190
|
+
# bring the service up.
|
191
|
+
def want_up?
|
192
|
+
statusbytes.wantflag == 'u'
|
193
|
+
end
|
194
|
+
|
195
|
+
private
|
196
|
+
|
197
|
+
def elapsed
|
198
|
+
statusbytes.elapsed
|
199
|
+
end
|
200
|
+
|
201
|
+
def svfn(basename)
|
202
|
+
File.join(@path, 'supervise', basename)
|
203
|
+
end
|
204
|
+
|
205
|
+
# Instantiate our one-time, delegate helper class
|
206
|
+
def statusbytes
|
207
|
+
# We _want_ to pass Errno back to caller, which is why we don't
|
208
|
+
# simply call #svok?() here.
|
209
|
+
Util::open_write(svfn('ok')) {true}
|
210
|
+
|
211
|
+
buf = Util::open_read(svfn('status')) do |f|
|
212
|
+
begin
|
213
|
+
f.sysread( StatusBytes::BUFLEN )
|
214
|
+
rescue ::EOFError
|
215
|
+
""
|
216
|
+
end
|
217
|
+
end
|
218
|
+
StatusBytes.new(buf)
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
222
|
+
|
223
|
+
end #-- module Sv
|
224
|
+
end #-- module Sys
|