mojombo-god 0.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. data/History.txt +255 -0
  2. data/Manifest.txt +107 -0
  3. data/README.txt +59 -0
  4. data/Rakefile +35 -0
  5. data/bin/god +127 -0
  6. data/examples/events.god +84 -0
  7. data/examples/gravatar.god +54 -0
  8. data/examples/single.god +66 -0
  9. data/ext/god/extconf.rb +55 -0
  10. data/ext/god/kqueue_handler.c +123 -0
  11. data/ext/god/netlink_handler.c +167 -0
  12. data/init/god +42 -0
  13. data/lib/god.rb +644 -0
  14. data/lib/god/behavior.rb +52 -0
  15. data/lib/god/behaviors/clean_pid_file.rb +21 -0
  16. data/lib/god/behaviors/clean_unix_socket.rb +21 -0
  17. data/lib/god/behaviors/notify_when_flapping.rb +51 -0
  18. data/lib/god/cli/command.rb +206 -0
  19. data/lib/god/cli/run.rb +177 -0
  20. data/lib/god/cli/version.rb +23 -0
  21. data/lib/god/condition.rb +96 -0
  22. data/lib/god/conditions/always.rb +23 -0
  23. data/lib/god/conditions/complex.rb +86 -0
  24. data/lib/god/conditions/cpu_usage.rb +80 -0
  25. data/lib/god/conditions/degrading_lambda.rb +52 -0
  26. data/lib/god/conditions/disk_usage.rb +27 -0
  27. data/lib/god/conditions/flapping.rb +128 -0
  28. data/lib/god/conditions/http_response_code.rb +168 -0
  29. data/lib/god/conditions/lambda.rb +25 -0
  30. data/lib/god/conditions/memory_usage.rb +82 -0
  31. data/lib/god/conditions/process_exits.rb +72 -0
  32. data/lib/god/conditions/process_running.rb +74 -0
  33. data/lib/god/conditions/tries.rb +44 -0
  34. data/lib/god/configurable.rb +57 -0
  35. data/lib/god/contact.rb +106 -0
  36. data/lib/god/contacts/email.rb +95 -0
  37. data/lib/god/dependency_graph.rb +41 -0
  38. data/lib/god/diagnostics.rb +37 -0
  39. data/lib/god/driver.rb +108 -0
  40. data/lib/god/errors.rb +24 -0
  41. data/lib/god/event_handler.rb +111 -0
  42. data/lib/god/event_handlers/dummy_handler.rb +13 -0
  43. data/lib/god/event_handlers/kqueue_handler.rb +17 -0
  44. data/lib/god/event_handlers/netlink_handler.rb +13 -0
  45. data/lib/god/logger.rb +120 -0
  46. data/lib/god/metric.rb +59 -0
  47. data/lib/god/process.rb +325 -0
  48. data/lib/god/registry.rb +32 -0
  49. data/lib/god/simple_logger.rb +53 -0
  50. data/lib/god/socket.rb +96 -0
  51. data/lib/god/sugar.rb +47 -0
  52. data/lib/god/system/portable_poller.rb +42 -0
  53. data/lib/god/system/process.rb +42 -0
  54. data/lib/god/system/slash_proc_poller.rb +82 -0
  55. data/lib/god/task.rb +487 -0
  56. data/lib/god/timeline.rb +25 -0
  57. data/lib/god/trigger.rb +43 -0
  58. data/lib/god/watch.rb +183 -0
  59. data/test/configs/child_events/child_events.god +44 -0
  60. data/test/configs/child_events/simple_server.rb +3 -0
  61. data/test/configs/child_polls/child_polls.god +37 -0
  62. data/test/configs/child_polls/simple_server.rb +12 -0
  63. data/test/configs/complex/complex.god +59 -0
  64. data/test/configs/complex/simple_server.rb +3 -0
  65. data/test/configs/contact/contact.god +74 -0
  66. data/test/configs/contact/simple_server.rb +3 -0
  67. data/test/configs/daemon_events/daemon_events.god +37 -0
  68. data/test/configs/daemon_events/simple_server.rb +8 -0
  69. data/test/configs/daemon_events/simple_server_stop.rb +11 -0
  70. data/test/configs/daemon_polls/daemon_polls.god +17 -0
  71. data/test/configs/daemon_polls/simple_server.rb +6 -0
  72. data/test/configs/degrading_lambda/degrading_lambda.god +31 -0
  73. data/test/configs/degrading_lambda/tcp_server.rb +15 -0
  74. data/test/configs/matias/matias.god +50 -0
  75. data/test/configs/real.rb +59 -0
  76. data/test/configs/running_load/running_load.god +16 -0
  77. data/test/configs/stress/simple_server.rb +3 -0
  78. data/test/configs/stress/stress.god +15 -0
  79. data/test/configs/task/logs/.placeholder +0 -0
  80. data/test/configs/task/task.god +26 -0
  81. data/test/configs/test.rb +61 -0
  82. data/test/helper.rb +151 -0
  83. data/test/suite.rb +6 -0
  84. data/test/test_behavior.rb +21 -0
  85. data/test/test_condition.rb +50 -0
  86. data/test/test_conditions_disk_usage.rb +56 -0
  87. data/test/test_conditions_http_response_code.rb +109 -0
  88. data/test/test_conditions_process_running.rb +44 -0
  89. data/test/test_conditions_tries.rb +67 -0
  90. data/test/test_contact.rb +109 -0
  91. data/test/test_dependency_graph.rb +62 -0
  92. data/test/test_driver.rb +11 -0
  93. data/test/test_event_handler.rb +80 -0
  94. data/test/test_god.rb +598 -0
  95. data/test/test_handlers_kqueue_handler.rb +16 -0
  96. data/test/test_logger.rb +63 -0
  97. data/test/test_metric.rb +72 -0
  98. data/test/test_process.rb +246 -0
  99. data/test/test_registry.rb +15 -0
  100. data/test/test_socket.rb +42 -0
  101. data/test/test_sugar.rb +42 -0
  102. data/test/test_system_portable_poller.rb +17 -0
  103. data/test/test_system_process.rb +30 -0
  104. data/test/test_task.rb +262 -0
  105. data/test/test_timeline.rb +37 -0
  106. data/test/test_trigger.rb +59 -0
  107. data/test/test_watch.rb +279 -0
  108. metadata +186 -0
@@ -0,0 +1,52 @@
1
+ module God
2
+
3
+ class Behavior
4
+ include Configurable
5
+
6
+ attr_accessor :watch
7
+
8
+ # Generate a Behavior of the given kind. The proper class is found by camel casing the
9
+ # kind (which is given as an underscored symbol).
10
+ # +kind+ is the underscored symbol representing the class (e.g. foo_bar for God::Behaviors::FooBar)
11
+ def self.generate(kind, watch)
12
+ sym = kind.to_s.capitalize.gsub(/_(.)/){$1.upcase}.intern
13
+ b = God::Behaviors.const_get(sym).new
14
+ b.watch = watch
15
+ b
16
+ rescue NameError
17
+ raise NoSuchBehaviorError.new("No Behavior found with the class name God::Behaviors::#{sym}")
18
+ end
19
+
20
+ def valid?
21
+ true
22
+ end
23
+
24
+ #######
25
+
26
+ def before_start
27
+ end
28
+
29
+ def after_start
30
+ end
31
+
32
+ def before_restart
33
+ end
34
+
35
+ def after_restart
36
+ end
37
+
38
+ def before_stop
39
+ end
40
+
41
+ def after_stop
42
+ end
43
+
44
+ # Construct the friendly name of this Behavior, looks like:
45
+ #
46
+ # Behavior FooBar on Watch 'baz'
47
+ def friendly_name
48
+ "Behavior " + super + " on Watch '#{self.watch.name}'"
49
+ end
50
+ end
51
+
52
+ end
@@ -0,0 +1,21 @@
1
+ module God
2
+ module Behaviors
3
+
4
+ class CleanPidFile < Behavior
5
+ def valid?
6
+ valid = true
7
+ valid &= complain("Attribute 'pid_file' must be specified", self) if self.watch.pid_file.nil?
8
+ valid
9
+ end
10
+
11
+ def before_start
12
+ File.delete(self.watch.pid_file)
13
+
14
+ "deleted pid file"
15
+ rescue
16
+ "no pid file to delete"
17
+ end
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module God
2
+ module Behaviors
3
+
4
+ class CleanUnixSocket < Behavior
5
+ def valid?
6
+ valid = true
7
+ valid &= complain("Attribute 'unix_socket' must be specified", self) if self.watch.unix_socket.nil?
8
+ valid
9
+ end
10
+
11
+ def before_start
12
+ File.delete(self.watch.unix_socket)
13
+
14
+ "deleted unix socket"
15
+ rescue
16
+ "no unix socket to delete"
17
+ end
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,51 @@
1
+ module God
2
+ module Behaviors
3
+
4
+ class NotifyWhenFlapping < Behavior
5
+ attr_accessor :failures # number of failures
6
+ attr_accessor :seconds # number of seconds
7
+ attr_accessor :notifier # class to notify with
8
+
9
+ def initialize
10
+ super
11
+ @startup_times = []
12
+ end
13
+
14
+ def valid?
15
+ valid = true
16
+ valid &= complain("Attribute 'failures' must be specified", self) unless self.failures
17
+ valid &= complain("Attribute 'seconds' must be specified", self) unless self.seconds
18
+ valid &= complain("Attribute 'notifier' must be specified", self) unless self.notifier
19
+
20
+ # Must take one arg or variable args
21
+ unless self.notifier.respond_to?(:notify) and [1,-1].include?(self.notifier.method(:notify).arity)
22
+ valid &= complain("The 'notifier' must have a method 'notify' which takes 1 or variable args", self)
23
+ end
24
+
25
+ valid
26
+ end
27
+
28
+ def before_start
29
+ now = Time.now.to_i
30
+ @startup_times << now
31
+ check_for_flapping(now)
32
+ end
33
+
34
+ def before_restart
35
+ now = Time.now.to_i
36
+ @startup_times << now
37
+ check_for_flapping(now)
38
+ end
39
+
40
+ private
41
+
42
+ def check_for_flapping(now)
43
+ @startup_times.select! {|time| time >= now - self.seconds }
44
+ if @startup_times.length >= self.failures
45
+ self.notifier.notify("#{self.watch.name} has called start/restart #{@startup_times.length} times in #{self.seconds} seconds")
46
+ end
47
+ end
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,206 @@
1
+ module God
2
+ module CLI
3
+
4
+ class Command
5
+ def initialize(command, options, args)
6
+ @command = command
7
+ @options = options
8
+ @args = args
9
+
10
+ dispatch
11
+ end
12
+
13
+ def setup
14
+ # connect to drb unix socket
15
+ DRb.start_service("druby://127.0.0.1:0")
16
+ @server = DRbObject.new(nil, God::Socket.socket(@options[:port]))
17
+
18
+ # ping server to ensure that it is responsive
19
+ begin
20
+ @server.ping
21
+ rescue DRb::DRbConnError
22
+ puts "The server is not available (or you do not have permissions to access it)"
23
+ abort
24
+ end
25
+ end
26
+
27
+ def dispatch
28
+ if %w{load status log quit terminate}.include?(@command)
29
+ setup
30
+ send("#{@command}_command")
31
+ elsif %w{start stop restart monitor unmonitor remove}.include?(@command)
32
+ setup
33
+ lifecycle_command
34
+ elsif @command == 'check'
35
+ check_command
36
+ else
37
+ puts "Command '#{@command}' is not valid. Run 'god --help' for usage"
38
+ abort
39
+ end
40
+ end
41
+
42
+ def load_command
43
+ file = @args[1]
44
+
45
+ puts "Sending '#{@command}' command"
46
+ puts
47
+
48
+ unless File.exist?(file)
49
+ abort "File not found: #{file}"
50
+ end
51
+
52
+ names, errors = *@server.running_load(File.read(file), File.expand_path(file))
53
+
54
+ # output response
55
+ unless names.empty?
56
+ puts 'The following tasks were affected:'
57
+ names.each do |w|
58
+ puts ' ' + w
59
+ end
60
+ end
61
+
62
+ unless errors.empty?
63
+ puts errors
64
+ exit(1)
65
+ end
66
+ end
67
+
68
+ def status_command
69
+ watches = {}
70
+ @server.status.each do |name, status|
71
+ g = status[:group] || ''
72
+ unless watches.has_key?(g)
73
+ watches[g] = {}
74
+ end
75
+ watches[g][name] = status
76
+ end
77
+ watches.keys.sort.each do |group|
78
+ puts "#{group}:" unless group.empty?
79
+ watches[group].keys.sort.each do |name|
80
+ state = watches[group][name][:state]
81
+ print " " unless group.empty?
82
+ puts "#{name}: #{state}"
83
+ end
84
+ end
85
+ end
86
+
87
+ def log_command
88
+ begin
89
+ Signal.trap('INT') { exit }
90
+ name = @args[1]
91
+
92
+ unless name
93
+ puts "You must specify a Task or Group name"
94
+ exit!
95
+ end
96
+
97
+ t = Time.at(0)
98
+ loop do
99
+ print @server.running_log(name, t)
100
+ t = Time.now
101
+ sleep 1
102
+ end
103
+ rescue God::NoSuchWatchError
104
+ puts "No such watch"
105
+ rescue DRb::DRbConnError
106
+ puts "The server went away"
107
+ end
108
+ end
109
+
110
+ def quit_command
111
+ begin
112
+ @server.terminate
113
+ abort 'Could not stop god'
114
+ rescue DRb::DRbConnError
115
+ puts 'Stopped god'
116
+ end
117
+ end
118
+
119
+ def terminate_command
120
+ t = Thread.new { loop { STDOUT.print('.'); STDOUT.flush; sleep(1) } }
121
+ if @server.stop_all
122
+ t.kill; STDOUT.puts
123
+ puts 'Stopped all watches'
124
+ else
125
+ t.kill; STDOUT.puts
126
+ puts 'Could not stop all watches within 10 seconds'
127
+ end
128
+
129
+ begin
130
+ @server.terminate
131
+ abort 'Could not stop god'
132
+ rescue DRb::DRbConnError
133
+ puts 'Stopped god'
134
+ end
135
+ end
136
+
137
+ def check_command
138
+ Thread.new do
139
+ begin
140
+ event_system = God::EventHandler.event_system
141
+ puts "using event system: #{event_system}"
142
+
143
+ if God::EventHandler.loaded?
144
+ puts "starting event handler"
145
+ God::EventHandler.start
146
+ else
147
+ puts "[fail] event system did not load"
148
+ exit(1)
149
+ end
150
+
151
+ puts 'forking off new process'
152
+
153
+ pid = fork do
154
+ loop { sleep(1) }
155
+ end
156
+
157
+ puts "forked process with pid = #{pid}"
158
+
159
+ God::EventHandler.register(pid, :proc_exit) do
160
+ puts "[ok] process exit event received"
161
+ exit!(0)
162
+ end
163
+
164
+ sleep(1)
165
+
166
+ puts "killing process"
167
+
168
+ ::Process.kill('KILL', pid)
169
+ rescue => e
170
+ puts e.message
171
+ puts e.backtrace.join("\n")
172
+ end
173
+ end
174
+
175
+ sleep(2)
176
+
177
+ puts "[fail] never received process exit event"
178
+ exit(1)
179
+ end
180
+
181
+ def lifecycle_command
182
+ # get the name of the watch/group
183
+ name = @args[1]
184
+
185
+ puts "Sending '#{@command}' command"
186
+
187
+ t = Thread.new { loop { sleep(1); STDOUT.print('.'); STDOUT.flush; sleep(1) } }
188
+
189
+ # send @command
190
+ watches = @server.control(name, @command)
191
+
192
+ # output response
193
+ t.kill; STDOUT.puts
194
+ unless watches.empty?
195
+ puts 'The following watches were affected:'
196
+ watches.each do |w|
197
+ puts ' ' + w
198
+ end
199
+ else
200
+ puts 'No matching task or group'
201
+ end
202
+ end
203
+ end # Command
204
+
205
+ end
206
+ end
@@ -0,0 +1,177 @@
1
+ module God
2
+ module CLI
3
+
4
+ class Run
5
+ def initialize(options)
6
+ @options = options
7
+
8
+ dispatch
9
+ end
10
+
11
+ def dispatch
12
+ # have at_exit start god
13
+ $run = true
14
+
15
+ # run
16
+ if @options[:daemonize]
17
+ run_daemonized
18
+ else
19
+ run_in_front
20
+ end
21
+ end
22
+
23
+ def attach
24
+ process = System::Process.new(@options[:attach])
25
+ Thread.new do
26
+ loop do
27
+ unless process.exists?
28
+ applog(nil, :info, "Going down because attached process #{@options[:attach]} exited")
29
+ exit!
30
+ end
31
+ sleep 5
32
+ end
33
+ end
34
+ end
35
+
36
+ def default_run
37
+ # start attached pid watcher if necessary
38
+ if @options[:attach]
39
+ self.attach
40
+ end
41
+
42
+ if @options[:port]
43
+ God.port = @options[:port]
44
+ end
45
+
46
+ if @options[:events]
47
+ God::EventHandler.load
48
+ end
49
+
50
+ # set log level, defaults to WARN
51
+ if @options[:log_level]
52
+ God.log_level = @options[:log_level]
53
+ else
54
+ God.log_level = @options[:daemonize] ? :warn : :info
55
+ end
56
+
57
+ if @options[:config]
58
+ unless File.exist?(@options[:config])
59
+ abort "File not found: #{@options[:config]}"
60
+ end
61
+
62
+ # start the event handler
63
+ God::EventHandler.start if God::EventHandler.loaded?
64
+
65
+ load_config @options[:config]
66
+ end
67
+ end
68
+
69
+ def run_in_front
70
+ require 'god'
71
+
72
+ if @options[:bleakhouse]
73
+ BleakHouseDiagnostic.install
74
+ end
75
+
76
+ default_run
77
+
78
+ log_file = God.log_file
79
+ log_file = File.expand_path(@options[:log]) if @options[:log]
80
+ if log_file
81
+ puts "Sending output to log file: #{log_file}"
82
+
83
+ # reset file descriptors
84
+ STDIN.reopen "/dev/null"
85
+ STDOUT.reopen(log_file, "a")
86
+ STDERR.reopen STDOUT
87
+ STDOUT.sync = true
88
+ end
89
+ end
90
+
91
+ def run_daemonized
92
+ # trap and ignore SIGHUP
93
+ Signal.trap('HUP') {}
94
+
95
+ pid = fork do
96
+ begin
97
+ require 'god'
98
+
99
+ log_file = @options[:log] || God.log_file || "/dev/null"
100
+
101
+ # reset file descriptors
102
+ STDIN.reopen "/dev/null"
103
+ STDOUT.reopen(log_file, "a")
104
+ STDERR.reopen STDOUT
105
+ STDOUT.sync = true
106
+
107
+ # set pid if requested
108
+ if @options[:pid] # and as deamon
109
+ God.pid = @options[:pid]
110
+ end
111
+
112
+ unless @options[:syslog]
113
+ Logger.syslog = false
114
+ end
115
+
116
+ default_run
117
+
118
+ unless God::EventHandler.loaded?
119
+ puts
120
+ puts "***********************************************************************"
121
+ puts "*"
122
+ puts "* Event conditions are not available for your installation of god."
123
+ puts "* You may still use and write custom conditions using the poll system"
124
+ puts "*"
125
+ puts "***********************************************************************"
126
+ puts
127
+ end
128
+
129
+ rescue => e
130
+ puts e.message
131
+ puts e.backtrace.join("\n")
132
+ abort "There was a fatal system error while starting god (see above)"
133
+ end
134
+ end
135
+
136
+ if @options[:pid]
137
+ File.open(@options[:pid], 'w') { |f| f.write pid }
138
+ end
139
+
140
+ ::Process.detach pid
141
+
142
+ exit
143
+ end
144
+
145
+ def load_config(config)
146
+ if File.directory? config
147
+ files_loaded = false
148
+ Dir[File.expand_path('**/*.god', config)].each do |god_file|
149
+ files_loaded ||= load_god_file(File.expand_path(god_file))
150
+ end
151
+ unless files_loaded
152
+ abort "No files could be loaded"
153
+ end
154
+ else
155
+ unless load_god_file(File.expand_path(config))
156
+ abort "File could not be loaded"
157
+ end
158
+ end
159
+ end
160
+
161
+ def load_god_file(god_file)
162
+ load File.expand_path(god_file)
163
+ rescue Exception => e
164
+ if e.instance_of?(SystemExit)
165
+ raise
166
+ else
167
+ puts "There was an error in #{god_file}"
168
+ puts "\t" + e.message
169
+ puts "\t" + e.backtrace.join("\n\t")
170
+ return false
171
+ end
172
+ end
173
+
174
+ end # Run
175
+
176
+ end
177
+ end