firenxis-god 0.11.0

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