gohanlonllc-god 0.7.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. data/History.txt +261 -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 +97 -0
  37. data/lib/god/contacts/smtp_tls.rb +90 -0
  38. data/lib/god/dependency_graph.rb +41 -0
  39. data/lib/god/diagnostics.rb +37 -0
  40. data/lib/god/driver.rb +206 -0
  41. data/lib/god/errors.rb +24 -0
  42. data/lib/god/event_handler.rb +111 -0
  43. data/lib/god/event_handlers/dummy_handler.rb +13 -0
  44. data/lib/god/event_handlers/kqueue_handler.rb +17 -0
  45. data/lib/god/event_handlers/netlink_handler.rb +13 -0
  46. data/lib/god/logger.rb +120 -0
  47. data/lib/god/metric.rb +59 -0
  48. data/lib/god/process.rb +327 -0
  49. data/lib/god/registry.rb +32 -0
  50. data/lib/god/simple_logger.rb +53 -0
  51. data/lib/god/socket.rb +96 -0
  52. data/lib/god/sugar.rb +47 -0
  53. data/lib/god/system/portable_poller.rb +42 -0
  54. data/lib/god/system/process.rb +42 -0
  55. data/lib/god/system/slash_proc_poller.rb +82 -0
  56. data/lib/god/task.rb +487 -0
  57. data/lib/god/timeline.rb +25 -0
  58. data/lib/god/trigger.rb +43 -0
  59. data/lib/god/watch.rb +183 -0
  60. data/test/configs/child_events/child_events.god +44 -0
  61. data/test/configs/child_events/simple_server.rb +3 -0
  62. data/test/configs/child_polls/child_polls.god +37 -0
  63. data/test/configs/child_polls/simple_server.rb +12 -0
  64. data/test/configs/complex/complex.god +59 -0
  65. data/test/configs/complex/simple_server.rb +3 -0
  66. data/test/configs/contact/contact.god +74 -0
  67. data/test/configs/contact/simple_server.rb +3 -0
  68. data/test/configs/daemon_events/daemon_events.god +37 -0
  69. data/test/configs/daemon_events/simple_server.rb +8 -0
  70. data/test/configs/daemon_events/simple_server_stop.rb +11 -0
  71. data/test/configs/daemon_polls/daemon_polls.god +17 -0
  72. data/test/configs/daemon_polls/simple_server.rb +6 -0
  73. data/test/configs/degrading_lambda/degrading_lambda.god +31 -0
  74. data/test/configs/degrading_lambda/tcp_server.rb +15 -0
  75. data/test/configs/matias/matias.god +50 -0
  76. data/test/configs/real.rb +59 -0
  77. data/test/configs/running_load/running_load.god +16 -0
  78. data/test/configs/stress/simple_server.rb +3 -0
  79. data/test/configs/stress/stress.god +15 -0
  80. data/test/configs/task/logs/.placeholder +0 -0
  81. data/test/configs/task/task.god +26 -0
  82. data/test/configs/test.rb +61 -0
  83. data/test/helper.rb +151 -0
  84. data/test/suite.rb +6 -0
  85. data/test/test_behavior.rb +21 -0
  86. data/test/test_condition.rb +50 -0
  87. data/test/test_conditions_disk_usage.rb +56 -0
  88. data/test/test_conditions_http_response_code.rb +109 -0
  89. data/test/test_conditions_process_running.rb +44 -0
  90. data/test/test_conditions_tries.rb +67 -0
  91. data/test/test_contact.rb +109 -0
  92. data/test/test_dependency_graph.rb +62 -0
  93. data/test/test_driver.rb +11 -0
  94. data/test/test_event_handler.rb +80 -0
  95. data/test/test_god.rb +598 -0
  96. data/test/test_handlers_kqueue_handler.rb +16 -0
  97. data/test/test_logger.rb +63 -0
  98. data/test/test_metric.rb +72 -0
  99. data/test/test_process.rb +246 -0
  100. data/test/test_registry.rb +15 -0
  101. data/test/test_socket.rb +42 -0
  102. data/test/test_sugar.rb +42 -0
  103. data/test/test_system_portable_poller.rb +17 -0
  104. data/test/test_system_process.rb +30 -0
  105. data/test/test_task.rb +262 -0
  106. data/test/test_timeline.rb +37 -0
  107. data/test/test_trigger.rb +59 -0
  108. data/test/test_watch.rb +279 -0
  109. metadata +187 -0
@@ -0,0 +1,167 @@
1
+ #ifdef __linux__ /* only build on linux */
2
+
3
+ #include <ruby.h>
4
+ #include <sys/types.h>
5
+ #include <unistd.h>
6
+ #include <sys/socket.h>
7
+ #include <linux/netlink.h>
8
+ #include <linux/connector.h>
9
+ #include <linux/cn_proc.h>
10
+ #include <errno.h>
11
+
12
+ static VALUE mGod;
13
+ static VALUE cNetlinkHandler;
14
+ static VALUE cEventHandler;
15
+
16
+ static ID proc_exit;
17
+ static ID proc_fork;
18
+ static ID m_call;
19
+ static ID m_watching_pid;
20
+
21
+ static int nl_sock; /* socket for netlink connection */
22
+
23
+
24
+ VALUE
25
+ nlh_handle_events()
26
+ {
27
+ char buff[CONNECTOR_MAX_MSG_SIZE];
28
+ struct nlmsghdr *hdr;
29
+ struct proc_event *event;
30
+
31
+ VALUE extra_data;
32
+
33
+ fd_set fds;
34
+
35
+ FD_ZERO(&fds);
36
+ FD_SET(nl_sock, &fds);
37
+
38
+ if (0 > rb_thread_select(nl_sock + 1, &fds, NULL, NULL, NULL)) {
39
+ rb_raise(rb_eStandardError, strerror(errno));
40
+ }
41
+
42
+ /* If there were no events detected, return */
43
+ if (! FD_ISSET(nl_sock, &fds)) {
44
+ return INT2FIX(0);
45
+ }
46
+
47
+ /* if there are events, make calls */
48
+ if (-1 == recv(nl_sock, buff, sizeof(buff), 0)) {
49
+ rb_raise(rb_eStandardError, strerror(errno));
50
+ }
51
+
52
+ hdr = (struct nlmsghdr *)buff;
53
+
54
+ if (NLMSG_ERROR == hdr->nlmsg_type) {
55
+ rb_raise(rb_eStandardError, strerror(errno));
56
+ } else if (NLMSG_DONE == hdr->nlmsg_type) {
57
+
58
+ event = (struct proc_event *)((struct cn_msg *)NLMSG_DATA(hdr))->data;
59
+
60
+ switch(event->what) {
61
+ case PROC_EVENT_EXIT:
62
+ if (Qnil == rb_funcall(cEventHandler, m_watching_pid, 1, INT2FIX(event->event_data.exit.process_pid))) {
63
+ return INT2FIX(0);
64
+ }
65
+
66
+ extra_data = rb_hash_new();
67
+ rb_hash_aset(extra_data, ID2SYM(rb_intern("pid")), INT2FIX(event->event_data.exit.process_pid));
68
+ rb_hash_aset(extra_data, ID2SYM(rb_intern("exit_code")), INT2FIX(event->event_data.exit.exit_code));
69
+ rb_hash_aset(extra_data, ID2SYM(rb_intern("exit_signal")), INT2FIX(event->event_data.exit.exit_signal));
70
+ rb_hash_aset(extra_data, ID2SYM(rb_intern("thread_group_id")), INT2FIX(event->event_data.exit.process_tgid));
71
+
72
+ rb_funcall(cEventHandler, m_call, 3, INT2FIX(event->event_data.exit.process_pid), ID2SYM(proc_exit), extra_data);
73
+ return INT2FIX(1);
74
+
75
+ case PROC_EVENT_FORK:
76
+ if (Qnil == rb_funcall(cEventHandler, m_watching_pid, 1, INT2FIX(event->event_data.fork.parent_pid))) {
77
+ return INT2FIX(0);
78
+ }
79
+
80
+ extra_data = rb_hash_new();
81
+ rb_hash_aset(extra_data, rb_intern("parent_pid"), INT2FIX(event->event_data.fork.parent_pid));
82
+ rb_hash_aset(extra_data, rb_intern("parent_thread_group_id"), INT2FIX(event->event_data.fork.parent_tgid));
83
+ rb_hash_aset(extra_data, rb_intern("child_pid"), INT2FIX(event->event_data.fork.child_pid));
84
+ rb_hash_aset(extra_data, rb_intern("child_thread_group_id"), INT2FIX(event->event_data.fork.child_tgid));
85
+
86
+ rb_funcall(cEventHandler, m_call, 3, INT2FIX(event->event_data.fork.parent_pid), ID2SYM(proc_fork), extra_data);
87
+ return INT2FIX(1);
88
+
89
+ case PROC_EVENT_NONE:
90
+ case PROC_EVENT_EXEC:
91
+ case PROC_EVENT_UID:
92
+ case PROC_EVENT_GID:
93
+ break;
94
+ }
95
+ }
96
+
97
+ return Qnil;
98
+ }
99
+
100
+
101
+ #define NL_MESSAGE_SIZE (sizeof(struct nlmsghdr) + sizeof(struct cn_msg) + \
102
+ sizeof(int))
103
+
104
+ void
105
+ connect_to_netlink()
106
+ {
107
+ struct sockaddr_nl sa_nl; /* netlink interface info */
108
+ char buff[NL_MESSAGE_SIZE];
109
+ struct nlmsghdr *hdr; /* for telling netlink what we want */
110
+ struct cn_msg *msg; /* the actual connector message */
111
+
112
+ /* connect to netlink socket */
113
+ nl_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
114
+
115
+ if (-1 == nl_sock) {
116
+ rb_raise(rb_eStandardError, strerror(errno));
117
+ }
118
+
119
+ bzero(&sa_nl, sizeof(sa_nl));
120
+ sa_nl.nl_family = AF_NETLINK;
121
+ sa_nl.nl_groups = CN_IDX_PROC;
122
+ sa_nl.nl_pid = getpid();
123
+
124
+ if (-1 == bind(nl_sock, (struct sockaddr *)&sa_nl, sizeof(sa_nl))) {
125
+ rb_raise(rb_eStandardError, strerror(errno));
126
+ }
127
+
128
+ /* Fill header */
129
+ hdr = (struct nlmsghdr *)buff;
130
+ hdr->nlmsg_len = NL_MESSAGE_SIZE;
131
+ hdr->nlmsg_type = NLMSG_DONE;
132
+ hdr->nlmsg_flags = 0;
133
+ hdr->nlmsg_seq = 0;
134
+ hdr->nlmsg_pid = getpid();
135
+
136
+ /* Fill message */
137
+ msg = (struct cn_msg *)NLMSG_DATA(hdr);
138
+ msg->id.idx = CN_IDX_PROC; /* Connecting to process information */
139
+ msg->id.val = CN_VAL_PROC;
140
+ msg->seq = 0;
141
+ msg->ack = 0;
142
+ msg->flags = 0;
143
+ msg->len = sizeof(int);
144
+ *(int*)msg->data = PROC_CN_MCAST_LISTEN;
145
+
146
+ if (-1 == send(nl_sock, hdr, hdr->nlmsg_len, 0)) {
147
+ rb_raise(rb_eStandardError, strerror(errno));
148
+ }
149
+ }
150
+
151
+ void
152
+ Init_netlink_handler_ext()
153
+ {
154
+ proc_exit = rb_intern("proc_exit");
155
+ proc_fork = rb_intern("proc_fork");
156
+ m_call = rb_intern("call");
157
+ m_watching_pid = rb_intern("watching_pid?");
158
+
159
+ mGod = rb_const_get(rb_cObject, rb_intern("God"));
160
+ cEventHandler = rb_const_get(mGod, rb_intern("EventHandler"));
161
+ cNetlinkHandler = rb_define_class_under(mGod, "NetlinkHandler", rb_cObject);
162
+ rb_define_singleton_method(cNetlinkHandler, "handle_events", nlh_handle_events, 0);
163
+
164
+ connect_to_netlink();
165
+ }
166
+
167
+ #endif
data/init/god ADDED
@@ -0,0 +1,42 @@
1
+ #!/bin/bash
2
+ #
3
+ # god Startup script for god (http://god.rubyforge.org)
4
+ #
5
+ # chkconfig: - 85 15
6
+ # description: God is an easy to configure, easy to extend monitoring \
7
+ # framework written in Ruby.
8
+ #
9
+
10
+ CONF_DIR=/etc/god
11
+
12
+ RETVAL=0
13
+
14
+ # Go no further if config directory is missing.
15
+ [ -d "$CONF_DIR" ] || exit 0
16
+
17
+ case "$1" in
18
+ start)
19
+ # Create pid directory
20
+ ruby /usr/bin/god -c $CONF_DIR/master.conf
21
+ RETVAL=$?
22
+ ;;
23
+ stop)
24
+ ruby /usr/bin/god terminate
25
+ RETVAL=$?
26
+ ;;
27
+ restart)
28
+ ruby /usr/bin/god terminate
29
+ ruby /usr/bin/god -c $CONF_DIR/master.conf
30
+ RETVAL=$?
31
+ ;;
32
+ status)
33
+ ruby /usr/bin/god status
34
+ RETVAL=$?
35
+ ;;
36
+ *)
37
+ echo "Usage: god {start|stop|restart|status}"
38
+ exit 1
39
+ ;;
40
+ esac
41
+
42
+ exit $RETVAL
data/lib/god.rb ADDED
@@ -0,0 +1,644 @@
1
+ $:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed
2
+
3
+ # rubygems
4
+ require 'rubygems'
5
+
6
+ # core
7
+ require 'stringio'
8
+ require 'fileutils'
9
+
10
+ begin
11
+ require 'fastthread'
12
+ rescue LoadError
13
+ ensure
14
+ require 'thread'
15
+ end
16
+
17
+ # stdlib
18
+
19
+ # internal requires
20
+ require 'god/errors'
21
+ require 'god/simple_logger'
22
+ require 'god/logger'
23
+
24
+ require 'god/system/process'
25
+ require 'god/system/portable_poller'
26
+ require 'god/system/slash_proc_poller'
27
+
28
+ require 'god/dependency_graph'
29
+ require 'god/timeline'
30
+ require 'god/configurable'
31
+
32
+ require 'god/task'
33
+
34
+ require 'god/behavior'
35
+ require 'god/behaviors/clean_pid_file'
36
+ require 'god/behaviors/clean_unix_socket'
37
+ require 'god/behaviors/notify_when_flapping'
38
+
39
+ require 'god/condition'
40
+ require 'god/conditions/process_running'
41
+ require 'god/conditions/process_exits'
42
+ require 'god/conditions/tries'
43
+ require 'god/conditions/memory_usage'
44
+ require 'god/conditions/cpu_usage'
45
+ require 'god/conditions/always'
46
+ require 'god/conditions/lambda'
47
+ require 'god/conditions/degrading_lambda'
48
+ require 'god/conditions/flapping'
49
+ require 'god/conditions/http_response_code'
50
+ require 'god/conditions/disk_usage'
51
+ require 'god/conditions/complex'
52
+
53
+ require 'god/contact'
54
+ require 'god/contacts/email'
55
+ begin
56
+ require 'god/contacts/jabber'
57
+ rescue LoadError
58
+ end
59
+
60
+ require 'god/socket'
61
+ require 'god/driver'
62
+
63
+ require 'god/metric'
64
+ require 'god/watch'
65
+
66
+ require 'god/trigger'
67
+ require 'god/event_handler'
68
+ require 'god/registry'
69
+ require 'god/process'
70
+
71
+ require 'god/sugar'
72
+
73
+ require 'god/cli/version'
74
+ require 'god/cli/command'
75
+
76
+ require 'god/diagnostics'
77
+
78
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. ext god])
79
+
80
+ # App wide logging system
81
+ LOG = God::Logger.new
82
+
83
+ def applog(watch, level, text)
84
+ LOG.log(watch, level, text)
85
+ end
86
+
87
+ # The $run global determines whether god should be started when the
88
+ # program would normally end. This should be set to true if when god
89
+ # should be started (e.g. `god -c <config file>`) and false otherwise
90
+ # (e.g. `god status`)
91
+ $run ||= nil
92
+
93
+ GOD_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
94
+
95
+ # Return the binding of god's root level
96
+ def root_binding
97
+ binding
98
+ end
99
+
100
+ module Kernel
101
+ alias_method :abort_orig, :abort
102
+
103
+ def abort(text = nil)
104
+ $run = false
105
+ applog(nil, :error, text) if text
106
+ exit(1)
107
+ end
108
+
109
+ alias_method :exit_orig, :exit
110
+
111
+ def exit(code = 0)
112
+ $run = false
113
+ exit_orig(code)
114
+ end
115
+ end
116
+
117
+ class Module
118
+ def safe_attr_accessor(*args)
119
+ args.each do |arg|
120
+ define_method((arg.to_s + "=").intern) do |other|
121
+ if !self.running && self.inited
122
+ abort "God.#{arg} must be set before any Tasks are defined"
123
+ end
124
+
125
+ if self.running && self.inited
126
+ applog(nil, :warn, "God.#{arg} can't be set while god is running")
127
+ return
128
+ end
129
+
130
+ instance_variable_set(('@' + arg.to_s).intern, other)
131
+ end
132
+
133
+ define_method(arg) do
134
+ instance_variable_get(('@' + arg.to_s).intern)
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ module God
141
+ VERSION = '0.7.9'
142
+
143
+ LOG_BUFFER_SIZE_DEFAULT = 100
144
+ PID_FILE_DIRECTORY_DEFAULTS = ['/var/run/god', '~/.god/pids']
145
+ DRB_PORT_DEFAULT = 17165
146
+ DRB_ALLOW_DEFAULT = ['127.0.0.1']
147
+ LOG_LEVEL_DEFAULT = :info
148
+
149
+ class << self
150
+ # user configurable
151
+ safe_attr_accessor :pid,
152
+ :host,
153
+ :port,
154
+ :allow,
155
+ :log_buffer_size,
156
+ :pid_file_directory,
157
+ :log_file,
158
+ :log_level,
159
+ :use_events
160
+
161
+ # internal
162
+ attr_accessor :inited,
163
+ :running,
164
+ :pending_watches,
165
+ :pending_watch_states,
166
+ :server,
167
+ :watches,
168
+ :groups,
169
+ :contacts,
170
+ :contact_groups,
171
+ :main
172
+ end
173
+
174
+ # initialize class instance variables
175
+ self.pid = nil
176
+ self.host = nil
177
+ self.port = nil
178
+ self.allow = nil
179
+ self.log_buffer_size = nil
180
+ self.pid_file_directory = nil
181
+ self.log_level = nil
182
+
183
+ # Initialize internal data.
184
+ #
185
+ # Returns nothing
186
+ def self.internal_init
187
+ # only do this once
188
+ return if self.inited
189
+
190
+ # variable init
191
+ self.watches = {}
192
+ self.groups = {}
193
+ self.pending_watches = []
194
+ self.pending_watch_states = {}
195
+ self.contacts = {}
196
+ self.contact_groups = {}
197
+
198
+ # set defaults
199
+ self.log_buffer_size ||= LOG_BUFFER_SIZE_DEFAULT
200
+ self.port ||= DRB_PORT_DEFAULT
201
+ self.allow ||= DRB_ALLOW_DEFAULT
202
+ self.log_level ||= LOG_LEVEL_DEFAULT
203
+
204
+ # additional setup
205
+ self.setup
206
+
207
+ # log level
208
+ log_level_map = {:debug => Logger::DEBUG,
209
+ :info => Logger::INFO,
210
+ :warn => Logger::WARN,
211
+ :error => Logger::ERROR,
212
+ :fatal => Logger::FATAL}
213
+ LOG.level = log_level_map[self.log_level]
214
+
215
+ # init has been executed
216
+ self.inited = true
217
+
218
+ # not yet running
219
+ self.running = false
220
+ end
221
+
222
+ # Instantiate a new, empty Watch object and pass it to the mandatory
223
+ # block. The attributes of the watch will be set by the configuration
224
+ # file.
225
+ #
226
+ # Aborts on duplicate watch name
227
+ # invalid watch
228
+ # conflicting group name
229
+ #
230
+ # Returns nothing
231
+ def self.watch(&block)
232
+ self.task(Watch, &block)
233
+ end
234
+
235
+ # Instantiate a new, empty Task object and yield it to the mandatory
236
+ # block. The attributes of the task will be set by the configuration
237
+ # file.
238
+ #
239
+ # Aborts on duplicate task name
240
+ # invalid task
241
+ # conflicting group name
242
+ #
243
+ # Returns nothing
244
+ def self.task(klass = Task)
245
+ self.internal_init
246
+
247
+ t = klass.new
248
+ yield(t)
249
+
250
+ # do the post-configuration
251
+ t.prepare
252
+
253
+ # if running, completely remove the watch (if necessary) to
254
+ # prepare for the reload
255
+ existing_watch = self.watches[t.name]
256
+ if self.running && existing_watch
257
+ self.pending_watch_states[existing_watch.name] = existing_watch.state
258
+ self.unwatch(existing_watch)
259
+ end
260
+
261
+ # ensure the new watch has a unique name
262
+ if self.watches[t.name] || self.groups[t.name]
263
+ abort "Task name '#{t.name}' already used for a Task or Group"
264
+ end
265
+
266
+ # ensure watch is internally valid
267
+ t.valid? || abort("Task '#{t.name}' is not valid (see above)")
268
+
269
+ # add to list of watches
270
+ self.watches[t.name] = t
271
+
272
+ # add to pending watches
273
+ self.pending_watches << t
274
+
275
+ # add to group if specified
276
+ if t.group
277
+ # ensure group name hasn't been used for a watch already
278
+ if self.watches[t.group]
279
+ abort "Group name '#{t.group}' already used for a Task"
280
+ end
281
+
282
+ self.groups[t.group] ||= []
283
+ self.groups[t.group] << t
284
+ end
285
+
286
+ # register watch
287
+ t.register!
288
+
289
+ # log
290
+ if self.running && existing_watch
291
+ applog(t, :info, "#{t.name} Reloaded config")
292
+ elsif self.running
293
+ applog(t, :info, "#{t.name} Loaded config")
294
+ end
295
+ end
296
+
297
+ # Unmonitor and remove the given watch from god.
298
+ # +watch+ is the Watch to remove
299
+ #
300
+ # Returns nothing
301
+ def self.unwatch(watch)
302
+ # unmonitor
303
+ watch.unmonitor unless watch.state == :unmonitored
304
+
305
+ # unregister
306
+ watch.unregister!
307
+
308
+ # remove from watches
309
+ self.watches.delete(watch.name)
310
+
311
+ # remove from groups
312
+ if watch.group
313
+ self.groups[watch.group].delete(watch)
314
+ end
315
+
316
+ applog(watch, :info, "#{watch.name} unwatched")
317
+ end
318
+
319
+ # Instantiate a new Contact of the given kind and send it to the block.
320
+ # Then prepare, validate, and record the Contact.
321
+ # +kind+ is the contact class specifier
322
+ #
323
+ # Aborts on invalid kind
324
+ # duplicate contact name
325
+ # invalid contact
326
+ # conflicting group name
327
+ #
328
+ # Returns nothing
329
+ def self.contact(kind)
330
+ self.internal_init
331
+
332
+ # create the contact
333
+ begin
334
+ c = Contact.generate(kind)
335
+ rescue NoSuchContactError => e
336
+ abort e.message
337
+ end
338
+
339
+ # send to block so config can set attributes
340
+ yield(c) if block_given?
341
+
342
+ # call prepare on the contact
343
+ c.prepare
344
+
345
+ # remove existing contacts of same name
346
+ existing_contact = self.contacts[c.name]
347
+ if self.running && existing_contact
348
+ self.uncontact(existing_contact)
349
+ end
350
+
351
+ # warn and noop if the contact has been defined before
352
+ if self.contacts[c.name] || self.contact_groups[c.name]
353
+ applog(nil, :warn, "Contact name '#{c.name}' already used for a Contact or Contact Group")
354
+ return
355
+ end
356
+
357
+ # abort if the Contact is invalid, the Contact will have printed
358
+ # out its own error messages by now
359
+ unless Contact.valid?(c) && c.valid?
360
+ abort "Exiting on invalid contact"
361
+ end
362
+
363
+ # add to list of contacts
364
+ self.contacts[c.name] = c
365
+
366
+ # add to contact group if specified
367
+ if c.group
368
+ # ensure group name hasn't been used for a contact already
369
+ if self.contacts[c.group]
370
+ abort "Contact Group name '#{c.group}' already used for a Contact"
371
+ end
372
+
373
+ self.contact_groups[c.group] ||= []
374
+ self.contact_groups[c.group] << c
375
+ end
376
+ end
377
+
378
+ # Remove the given contact from god.
379
+ # +contact+ is the Contact to remove
380
+ #
381
+ # Returns nothing
382
+ def self.uncontact(contact)
383
+ self.contacts.delete(contact.name)
384
+ if contact.group
385
+ self.contact_groups[contact.group].delete(contact)
386
+ end
387
+ end
388
+
389
+ # Control the lifecycle of the given task(s).
390
+ # +name+ is the name of a task/group (String)
391
+ # +command+ is the command to run (String)
392
+ # one of: "start"
393
+ # "monitor"
394
+ # "restart"
395
+ # "stop"
396
+ # "unmonitor"
397
+ # "remove"
398
+ #
399
+ # Returns String[]:task_names
400
+ def self.control(name, command)
401
+ # get the list of items
402
+ items = Array(self.watches[name] || self.groups[name]).dup
403
+
404
+ jobs = []
405
+
406
+ # do the command
407
+ case command
408
+ when "start", "monitor"
409
+ items.each { |w| jobs << Thread.new { w.monitor if w.state != :up } }
410
+ when "restart"
411
+ items.each { |w| jobs << Thread.new { w.move(:restart) } }
412
+ when "stop"
413
+ items.each { |w| jobs << Thread.new { w.unmonitor.action(:stop) if w.state != :unmonitored } }
414
+ when "unmonitor"
415
+ items.each { |w| jobs << Thread.new { w.unmonitor if w.state != :unmonitored } }
416
+ when "remove"
417
+ items.each { |w| self.unwatch(w) }
418
+ else
419
+ raise InvalidCommandError.new
420
+ end
421
+
422
+ jobs.each { |j| j.join }
423
+
424
+ items.map { |x| x.name }
425
+ end
426
+
427
+ # Unmonitor and stop all tasks.
428
+ #
429
+ # Returns true on success
430
+ # false if all tasks could not be stopped within 10 seconds
431
+ def self.stop_all
432
+ self.watches.sort.each do |name, w|
433
+ Thread.new do
434
+ w.unmonitor if w.state != :unmonitored
435
+ w.action(:stop) if w.alive?
436
+ end
437
+ end
438
+
439
+ 10.times do
440
+ return true unless self.watches.map { |name, w| w.alive? }.any?
441
+ sleep 1
442
+ end
443
+
444
+ return false
445
+ end
446
+
447
+ # Force the termination of god.
448
+ # * Clean up pid file if one exists
449
+ # * Stop DRb service
450
+ # * Hard exit using exit!
451
+ #
452
+ # Never returns because the process will no longer exist!
453
+ def self.terminate
454
+ FileUtils.rm_f(self.pid) if self.pid
455
+ self.server.stop if self.server
456
+ exit!(0)
457
+ end
458
+
459
+ # Gather the status of each task.
460
+ #
461
+ # Examples
462
+ # God.status
463
+ # # => { 'mongrel' => :up, 'nginx' => :up }
464
+ #
465
+ # Returns { String:task_name => Symbol:status, ... }
466
+ def self.status
467
+ info = {}
468
+ self.watches.map do |name, w|
469
+ info[name] = {:state => w.state, :group => w.group}
470
+ end
471
+ info
472
+ end
473
+
474
+ # Log lines for the given task since the specified time.
475
+ # +watch_name+ is the name of the task (may be abbreviated)
476
+ # +since+ is the Time since which to report log lines
477
+ #
478
+ # Raises God::NoSuchWatchError if no tasks matched
479
+ #
480
+ # Returns String:joined_log_lines
481
+ def self.running_log(watch_name, since)
482
+ matches = pattern_match(watch_name, self.watches.keys)
483
+
484
+ unless matches.first
485
+ raise NoSuchWatchError.new
486
+ end
487
+
488
+ LOG.watch_log_since(matches.first, since)
489
+ end
490
+
491
+ # Load a config file into a running god instance. Rescues any exceptions
492
+ # that the config may raise and reports these back to the caller.
493
+ # +code+ is a String containing the config file
494
+ # +filename+ is the filename of the config file
495
+ #
496
+ # Returns [String[]:task_names, String:errors]
497
+ def self.running_load(code, filename)
498
+ errors = ""
499
+ watches = []
500
+
501
+ begin
502
+ LOG.start_capture
503
+
504
+ Gem.clear_paths
505
+ eval(code, root_binding, filename)
506
+ self.pending_watches.each do |w|
507
+ if previous_state = self.pending_watch_states[w.name]
508
+ w.monitor unless previous_state == :unmonitored
509
+ else
510
+ w.monitor if w.autostart?
511
+ end
512
+ end
513
+ watches = self.pending_watches.dup
514
+ self.pending_watches.clear
515
+ self.pending_watch_states.clear
516
+ rescue Exception => e
517
+ # don't ever let running_load take down god
518
+ errors << LOG.finish_capture
519
+
520
+ unless e.instance_of?(SystemExit)
521
+ errors << e.message << "\n"
522
+ errors << e.backtrace.join("\n")
523
+ end
524
+ end
525
+
526
+ names = watches.map { |x| x.name }
527
+ [names, errors]
528
+ end
529
+
530
+ # Load the given file(s) according to the given glob.
531
+ # +glob+ is the glob-enabled path to load
532
+ #
533
+ # Returns nothing
534
+ def self.load(glob)
535
+ Dir[glob].each do |f|
536
+ Kernel.load f
537
+ end
538
+ end
539
+
540
+ def self.setup
541
+ if self.pid_file_directory
542
+ # pid file dir was specified, ensure it is created and writable
543
+ unless File.exist?(self.pid_file_directory)
544
+ begin
545
+ FileUtils.mkdir_p(self.pid_file_directory)
546
+ rescue Errno::EACCES => e
547
+ abort "Failed to create pid file directory: #{e.message}"
548
+ end
549
+ end
550
+
551
+ unless File.writable?(self.pid_file_directory)
552
+ abort "The pid file directory (#{self.pid_file_directory}) is not writable by #{Etc.getlogin}"
553
+ end
554
+ else
555
+ # no pid file dir specified, try defaults
556
+ PID_FILE_DIRECTORY_DEFAULTS.each do |idir|
557
+ dir = File.expand_path(idir)
558
+ begin
559
+ FileUtils.mkdir_p(dir)
560
+ if File.writable?(dir)
561
+ self.pid_file_directory = dir
562
+ break
563
+ end
564
+ rescue Errno::EACCES => e
565
+ end
566
+ end
567
+
568
+ unless self.pid_file_directory
569
+ dirs = PID_FILE_DIRECTORY_DEFAULTS.map { |x| File.expand_path(x) }
570
+ abort "No pid file directory exists, could be created, or is writable at any of #{dirs.join(', ')}"
571
+ end
572
+ end
573
+
574
+ applog(nil, :info, "Using pid file directory: #{self.pid_file_directory}")
575
+ end
576
+
577
+ # Initialize and startup the machinery that makes god work.
578
+ #
579
+ # Returns nothing
580
+ def self.start
581
+ self.internal_init
582
+
583
+ # instantiate server
584
+ self.server = Socket.new(self.port)
585
+
586
+ # start monitoring any watches set to autostart
587
+ self.watches.values.each { |w| w.monitor if w.autostart? }
588
+
589
+ # clear pending watches
590
+ self.pending_watches.clear
591
+
592
+ # mark as running
593
+ self.running = true
594
+
595
+ # don't exit
596
+ self.main =
597
+ Thread.new do
598
+ loop do
599
+ sleep 60
600
+ end
601
+ end
602
+
603
+ self.main.join
604
+ end
605
+
606
+ # To be called on program exit to start god
607
+ #
608
+ # Returns nothing
609
+ def self.at_exit
610
+ self.start
611
+ end
612
+
613
+ # private
614
+
615
+ # Match a shortened pattern against a list of String candidates.
616
+ # The pattern is expanded into a regular expression by
617
+ # inserting .* between each character.
618
+ # +pattern+ is the String containing the abbreviation
619
+ # +list+ is the Array of Strings to match against
620
+ #
621
+ # Examples
622
+ #
623
+ # list = %w{ foo bar bars }
624
+ # pattern = 'br'
625
+ # God.pattern_match(list, pattern)
626
+ # # => ['bar', 'bars']
627
+ #
628
+ # Returns String[]:matched_elements
629
+ def self.pattern_match(pattern, list)
630
+ regex = pattern.split('').join('.*')
631
+
632
+ list.select do |item|
633
+ item =~ Regexp.new(regex)
634
+ end
635
+ end
636
+ end
637
+
638
+ # Runs immediately before the program exits. If $run is true,
639
+ # start god, if $run is false, exit normally.
640
+ #
641
+ # Returns nothing
642
+ at_exit do
643
+ God.at_exit if $run
644
+ end