strobemonkey-god 0.7.13

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 (115) hide show
  1. data/History.txt +289 -0
  2. data/Manifest.txt +114 -0
  3. data/README.txt +59 -0
  4. data/Rakefile +35 -0
  5. data/bin/god +128 -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/behavior.rb +52 -0
  14. data/lib/god/behaviors/clean_pid_file.rb +21 -0
  15. data/lib/god/behaviors/clean_unix_socket.rb +21 -0
  16. data/lib/god/behaviors/notify_when_flapping.rb +51 -0
  17. data/lib/god/cli/command.rb +229 -0
  18. data/lib/god/cli/run.rb +176 -0
  19. data/lib/god/cli/version.rb +23 -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 +27 -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 +106 -0
  36. data/lib/god/contacts/campfire.rb +82 -0
  37. data/lib/god/contacts/email.rb +95 -0
  38. data/lib/god/contacts/jabber.rb +65 -0
  39. data/lib/god/contacts/twitter.rb +39 -0
  40. data/lib/god/contacts/webhook.rb +47 -0
  41. data/lib/god/dependency_graph.rb +41 -0
  42. data/lib/god/diagnostics.rb +37 -0
  43. data/lib/god/driver.rb +206 -0
  44. data/lib/god/errors.rb +24 -0
  45. data/lib/god/event_handler.rb +111 -0
  46. data/lib/god/event_handlers/dummy_handler.rb +13 -0
  47. data/lib/god/event_handlers/kqueue_handler.rb +17 -0
  48. data/lib/god/event_handlers/netlink_handler.rb +13 -0
  49. data/lib/god/logger.rb +120 -0
  50. data/lib/god/metric.rb +59 -0
  51. data/lib/god/process.rb +341 -0
  52. data/lib/god/registry.rb +32 -0
  53. data/lib/god/simple_logger.rb +53 -0
  54. data/lib/god/socket.rb +96 -0
  55. data/lib/god/sugar.rb +47 -0
  56. data/lib/god/system/portable_poller.rb +42 -0
  57. data/lib/god/system/process.rb +42 -0
  58. data/lib/god/system/slash_proc_poller.rb +92 -0
  59. data/lib/god/task.rb +491 -0
  60. data/lib/god/timeline.rb +25 -0
  61. data/lib/god/trigger.rb +43 -0
  62. data/lib/god/watch.rb +183 -0
  63. data/lib/god.rb +667 -0
  64. data/test/configs/child_events/child_events.god +44 -0
  65. data/test/configs/child_events/simple_server.rb +3 -0
  66. data/test/configs/child_polls/child_polls.god +37 -0
  67. data/test/configs/child_polls/simple_server.rb +12 -0
  68. data/test/configs/complex/complex.god +59 -0
  69. data/test/configs/complex/simple_server.rb +3 -0
  70. data/test/configs/contact/contact.god +84 -0
  71. data/test/configs/contact/simple_server.rb +3 -0
  72. data/test/configs/daemon_events/daemon_events.god +37 -0
  73. data/test/configs/daemon_events/simple_server.rb +8 -0
  74. data/test/configs/daemon_events/simple_server_stop.rb +11 -0
  75. data/test/configs/daemon_polls/daemon_polls.god +17 -0
  76. data/test/configs/daemon_polls/simple_server.rb +6 -0
  77. data/test/configs/degrading_lambda/degrading_lambda.god +31 -0
  78. data/test/configs/degrading_lambda/tcp_server.rb +15 -0
  79. data/test/configs/matias/matias.god +50 -0
  80. data/test/configs/real.rb +59 -0
  81. data/test/configs/running_load/running_load.god +16 -0
  82. data/test/configs/stress/simple_server.rb +3 -0
  83. data/test/configs/stress/stress.god +15 -0
  84. data/test/configs/task/logs/.placeholder +0 -0
  85. data/test/configs/task/task.god +26 -0
  86. data/test/configs/test.rb +61 -0
  87. data/test/helper.rb +151 -0
  88. data/test/suite.rb +6 -0
  89. data/test/test_behavior.rb +21 -0
  90. data/test/test_campfire.rb +41 -0
  91. data/test/test_condition.rb +50 -0
  92. data/test/test_conditions_disk_usage.rb +56 -0
  93. data/test/test_conditions_http_response_code.rb +109 -0
  94. data/test/test_conditions_process_running.rb +44 -0
  95. data/test/test_conditions_tries.rb +67 -0
  96. data/test/test_contact.rb +109 -0
  97. data/test/test_dependency_graph.rb +62 -0
  98. data/test/test_driver.rb +11 -0
  99. data/test/test_email.rb +45 -0
  100. data/test/test_event_handler.rb +80 -0
  101. data/test/test_god.rb +598 -0
  102. data/test/test_handlers_kqueue_handler.rb +16 -0
  103. data/test/test_logger.rb +63 -0
  104. data/test/test_metric.rb +72 -0
  105. data/test/test_process.rb +246 -0
  106. data/test/test_registry.rb +15 -0
  107. data/test/test_socket.rb +42 -0
  108. data/test/test_sugar.rb +42 -0
  109. data/test/test_system_portable_poller.rb +17 -0
  110. data/test/test_system_process.rb +30 -0
  111. data/test/test_task.rb +262 -0
  112. data/test/test_timeline.rb +37 -0
  113. data/test/test_trigger.rb +59 -0
  114. data/test/test_watch.rb +279 -0
  115. metadata +193 -0
@@ -0,0 +1,74 @@
1
+ module God
2
+ module Conditions
3
+
4
+ # Condition Symbol :process_running
5
+ # Type: Poll
6
+ #
7
+ # Trigger when a process is running or not running depending on attributes.
8
+ #
9
+ # Paramaters
10
+ # Required
11
+ # +pid_file+ is the pid file of the process in question. Automatically
12
+ # populated for Watches.
13
+ # +running" specifies whether you want to trigger if the process is
14
+ # running (true) or whether it is not running (false)
15
+ #
16
+ # Examples
17
+ #
18
+ # Trigger if process IS NOT running (from a Watch):
19
+ #
20
+ # on.condition(:process_running) do |c|
21
+ # c.running = false
22
+ # end
23
+ #
24
+ # Trigger if process IS running (from a Watch):
25
+ #
26
+ # on.condition(:process_running) do |c|
27
+ # c.running = true
28
+ # end
29
+ #
30
+ # Non-Watch Tasks must specify a PID file:
31
+ #
32
+ # on.condition(:process_running) do |c|
33
+ # c.running = false
34
+ # c.pid_file = "/var/run/mongrel.3000.pid"
35
+ # end
36
+ class ProcessRunning < PollCondition
37
+ attr_accessor :running, :pid_file
38
+
39
+ def pid
40
+ self.pid_file ? File.read(self.pid_file).strip.to_i : self.watch.pid
41
+ end
42
+
43
+ def valid?
44
+ valid = true
45
+ valid &= complain("Attribute 'pid_file' must be specified", self) if self.pid_file.nil? && self.watch.pid_file.nil?
46
+ valid &= complain("Attribute 'running' must be specified", self) if self.running.nil?
47
+ valid
48
+ end
49
+
50
+ def test
51
+ self.info = []
52
+
53
+ pid = self.pid
54
+ active = pid && System::Process.new(pid).exists?
55
+
56
+ if (self.running && active)
57
+ self.info.concat(["process is running"])
58
+ true
59
+ elsif (!self.running && !active)
60
+ self.info.concat(["process is not running"])
61
+ true
62
+ else
63
+ if self.running
64
+ self.info.concat(["process is not running"])
65
+ else
66
+ self.info.concat(["process is running"])
67
+ end
68
+ false
69
+ end
70
+ end
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,44 @@
1
+ module God
2
+ module Conditions
3
+
4
+ class Tries < PollCondition
5
+ attr_accessor :times, :within
6
+
7
+ def prepare
8
+ @timeline = Timeline.new(self.times)
9
+ end
10
+
11
+ def reset
12
+ @timeline.clear
13
+ end
14
+
15
+ def valid?
16
+ valid = true
17
+ valid &= complain("Attribute 'times' must be specified", self) if self.times.nil?
18
+ valid
19
+ end
20
+
21
+ def test
22
+ @timeline << Time.now
23
+
24
+ concensus = (@timeline.size == self.times)
25
+ duration = self.within.nil? || (@timeline.last - @timeline.first) < self.within
26
+
27
+ if within
28
+ history = "[#{@timeline.size}/#{self.times} within #{(@timeline.last - @timeline.first).to_i}s]"
29
+ else
30
+ history = "[#{@timeline.size}/#{self.times}]"
31
+ end
32
+
33
+ if concensus && duration
34
+ self.info = "tries exceeded #{history}"
35
+ return true
36
+ else
37
+ self.info = "tries within bounds #{history}"
38
+ return false
39
+ end
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,57 @@
1
+ module God
2
+
3
+ module Configurable
4
+ # Override this method in your Configurable (optional)
5
+ #
6
+ # Called once after the Configurable has been sent to the block and attributes have been
7
+ # set. Do any post-processing on attributes here
8
+ def prepare
9
+
10
+ end
11
+
12
+ def reset
13
+
14
+ end
15
+
16
+ # Override this method in your Configurable (optional)
17
+ #
18
+ # Called once during evaluation of the config file. Return true if valid, false otherwise
19
+ #
20
+ # A convenience method 'complain' is available that will print out a message and return false,
21
+ # making it easy to report multiple validation errors:
22
+ #
23
+ # def valid?
24
+ # valid = true
25
+ # valid &= complain("You must specify the 'pid_file' attribute for :memory_usage") if self.pid_file.nil?
26
+ # valid &= complain("You must specify the 'above' attribute for :memory_usage") if self.above.nil?
27
+ # valid
28
+ # end
29
+ def valid?
30
+ true
31
+ end
32
+
33
+ def base_name
34
+ x = 1 # fix for MRI's local scope optimization bug DO NOT REMOVE!
35
+ self.class.name.split('::').last
36
+ end
37
+
38
+ def friendly_name
39
+ base_name
40
+ end
41
+
42
+ def self.complain(text, c = nil)
43
+ watch = c.watch rescue nil
44
+ msg = ""
45
+ msg += "#{watch.name}: " if watch
46
+ msg += text
47
+ msg += " for #{c.friendly_name}" if c
48
+ applog(watch, :error, msg)
49
+ false
50
+ end
51
+
52
+ def complain(text, c = nil)
53
+ Configurable.complain(text, c)
54
+ end
55
+ end
56
+
57
+ end
@@ -0,0 +1,106 @@
1
+ module God
2
+
3
+ class Contact
4
+ include Configurable
5
+
6
+ attr_accessor :name, :group, :info
7
+
8
+ def self.generate(kind)
9
+ sym = kind.to_s.capitalize.gsub(/_(.)/){$1.upcase}.intern
10
+ c = God::Contacts.const_get(sym).new
11
+
12
+ unless c.kind_of?(Contact)
13
+ abort "Contact '#{c.class.name}' must subclass God::Contact"
14
+ end
15
+
16
+ c
17
+ rescue NameError
18
+ raise NoSuchContactError.new("No Contact found with the class name God::Contacts::#{sym}")
19
+ end
20
+
21
+ def self.valid?(contact)
22
+ valid = true
23
+ valid &= Configurable.complain("Attribute 'name' must be specified", contact) if contact.name.nil?
24
+ valid
25
+ end
26
+
27
+ # Normalize the given notify specification into canonical form.
28
+ # +spec+ is the notify spec as a String, Array of Strings, or Hash
29
+ #
30
+ # Canonical form looks like:
31
+ # {:contacts => ['fred', 'john'], :priority => '1', :category => 'awesome'}
32
+ # Where :contacts will be present and point to an Array of Strings. Both
33
+ # :priority and :category may not be present but if they are, they will each
34
+ # contain a single String.
35
+ #
36
+ # Returns normalized notify spec
37
+ # Raises ArgumentError on invalid spec (message contains details)
38
+ def self.normalize(spec)
39
+ case spec
40
+ when String
41
+ {:contacts => Array(spec)}
42
+ when Array
43
+ unless spec.select { |x| !x.instance_of?(String) }.empty?
44
+ raise ArgumentError.new("contains non-String elements")
45
+ end
46
+ {:contacts => spec}
47
+ when Hash
48
+ copy = spec.dup
49
+
50
+ # check :contacts
51
+ if contacts = copy.delete(:contacts)
52
+ case contacts
53
+ when String
54
+ # valid
55
+ when Array
56
+ unless contacts.select { |x| !x.instance_of?(String) }.empty?
57
+ raise ArgumentError.new("has a :contacts key containing non-String elements")
58
+ end
59
+ # valid
60
+ else
61
+ raise ArgumentError.new("must have a :contacts key pointing to a String or Array of Strings")
62
+ end
63
+ else
64
+ raise ArgumentError.new("must have a :contacts key")
65
+ end
66
+
67
+ # remove priority and category
68
+ copy.delete(:priority)
69
+ copy.delete(:category)
70
+
71
+ # check for invalid keys
72
+ unless copy.empty?
73
+ raise ArgumentError.new("contains extra elements: #{copy.inspect}")
74
+ end
75
+
76
+ # normalize
77
+ spec[:contacts] &&= Array(spec[:contacts])
78
+ spec[:priority] &&= spec[:priority].to_s
79
+ spec[:category] &&= spec[:category].to_s
80
+
81
+ spec
82
+ else
83
+ raise ArgumentError.new("must be a String (contact name), Array (of contact names), or Hash (contact specification)")
84
+ end
85
+ end
86
+
87
+ # Abstract
88
+ # Send the message to the external source
89
+ # +message+ is the message body returned from the condition
90
+ # +time+ is the Time at which the notification was made
91
+ # +priority+ is the arbitrary priority String
92
+ # +category+ is the arbitrary category String
93
+ # +host+ is the hostname of the server
94
+ def notify(message, time, priority, category, host)
95
+ raise AbstractMethodNotOverriddenError.new("Contact#notify must be overridden in subclasses")
96
+ end
97
+
98
+ # Construct the friendly name of this Contact, looks like:
99
+ #
100
+ # Contact FooBar
101
+ def friendly_name
102
+ super + " Contact '#{self.name}'"
103
+ end
104
+ end
105
+
106
+ end
@@ -0,0 +1,82 @@
1
+ # notify campfire using tinder http://tinder.rubyforge.org
2
+ #
3
+ # Example: set up a new campfire notifier
4
+ #
5
+ # Credentials
6
+ #
7
+ # God::Contacts::Campfire.server_settings = {
8
+ # :subdomain => "yoursubdomain",
9
+ # :user_name => "youruser",
10
+ # :room => "yourroom",
11
+ # :password => "yourpassword"
12
+ # }
13
+ #
14
+ # Register a new notifier
15
+ #
16
+ # God.contact(:campfire) do |c|
17
+ # c.name = 'campfire'
18
+ # end
19
+ #
20
+ # Define a transition for the process running event
21
+ #
22
+ # w.transition(:up, :start) do |on|
23
+ # on.condition(:process_running) do |c|
24
+ # c.running = true
25
+ # c.notify = 'campfire'
26
+ # end
27
+ # end
28
+
29
+ require 'tinder'
30
+
31
+ module God
32
+ module Contacts
33
+
34
+ class Campfire < Contact
35
+ class << self
36
+ attr_accessor :server_settings, :format
37
+ end
38
+
39
+ self.server_settings = {:subdomain => '',
40
+ :user_name => '',
41
+ :password => '',
42
+ :room => ''}
43
+
44
+ self.format = lambda do |message, host|
45
+ <<-EOF
46
+ #{host} - #{message}
47
+ EOF
48
+ end
49
+
50
+ def initialize
51
+ @room = nil
52
+ end
53
+
54
+ def notify(message, time, priority, category, host)
55
+ begin
56
+ body = Campfire.format.call(message,host)
57
+
58
+ room.speak body
59
+
60
+ self.info = "notified campfire: #{Campfire.server_settings[:subdomain]}"
61
+ rescue => e
62
+ applog(nil, :info, "failed to notify campfire: #{e.message}")
63
+ applog(nil, :debug, e.backtrace.join("\n"))
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def room
70
+ unless @room
71
+ applog(nil,:debug, "initializing campfire connection using credentials: #{Campfire.server_settings.inspect}")
72
+
73
+ campfire = Tinder::Campfire.new Campfire.server_settings[:subdomain]
74
+ campfire.login Campfire.server_settings[:user_name], Campfire.server_settings[:password]
75
+ @room = campfire.find_room_by_name(Campfire.server_settings[:room])
76
+ end
77
+ @room
78
+ end
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,95 @@
1
+ require 'time'
2
+ require 'net/smtp'
3
+
4
+ module God
5
+ module Contacts
6
+
7
+ class Email < Contact
8
+ class << self
9
+ attr_accessor :message_settings, :delivery_method, :server_settings, :sendmail_settings, :format
10
+ end
11
+
12
+ self.message_settings = {:from => 'god@example.com'}
13
+
14
+ self.delivery_method = :smtp # or :sendmail
15
+
16
+ self.server_settings = {:address => 'localhost',
17
+ :port => 25}
18
+ # :domain
19
+ # :user_name
20
+ # :password
21
+ # :authentication
22
+
23
+ self.sendmail_settings = {:location => '/usr/sbin/sendmail',
24
+ :arguments => '-i -t'
25
+ }
26
+
27
+ self.format = lambda do |name, email, message, time, priority, category, host|
28
+ <<-EOF
29
+ From: god <#{self.message_settings[:from]}>
30
+ To: #{name} <#{email}>
31
+ Subject: [god] #{message}
32
+ Date: #{Time.now.httpdate}
33
+ Message-Id: <unique.message.id.string@example.com>
34
+
35
+ Message: #{message}
36
+ Host: #{host}
37
+ Priority: #{priority}
38
+ Category: #{category}
39
+ EOF
40
+ end
41
+
42
+ attr_accessor :email
43
+
44
+ def valid?
45
+ valid = true
46
+ valid &= complain("Attribute 'email' must be specified", self) if self.email.nil?
47
+ valid
48
+ end
49
+
50
+ def notify(message, time, priority, category, host)
51
+ begin
52
+ body = Email.format.call(self.name, self.email, message, time, priority, category, host)
53
+
54
+ case Email.delivery_method
55
+ when :smtp
56
+ notify_smtp(body)
57
+ when :sendmail
58
+ notify_sendmail(body)
59
+ else
60
+ raise "unknown delivery method: #{Email.delivery_method}"
61
+ end
62
+
63
+ self.info = "sent email to #{self.email}"
64
+ rescue => e
65
+ applog(nil, :info, "failed to send email to #{self.email}: #{e.message}")
66
+ applog(nil, :debug, e.backtrace.join("\n"))
67
+ end
68
+ end
69
+
70
+ # private
71
+
72
+ def notify_smtp(mail)
73
+ args = [Email.server_settings[:address], Email.server_settings[:port]]
74
+ if Email.server_settings[:authentication]
75
+ args << Email.server_settings[:domain]
76
+ args << Email.server_settings[:user_name]
77
+ args << Email.server_settings[:password]
78
+ args << Email.server_settings[:authentication]
79
+ end
80
+
81
+ Net::SMTP.start(*args) do |smtp|
82
+ smtp.send_message mail, Email.message_settings[:from], self.email
83
+ end
84
+ end
85
+
86
+ def notify_sendmail(mail)
87
+ IO.popen("#{Email.sendmail_settings[:location]} #{Email.sendmail_settings[:arguments]}","w+") do |sm|
88
+ sm.print(mail.gsub(/\r/, ''))
89
+ sm.flush
90
+ end
91
+ end
92
+ end
93
+
94
+ end
95
+ end
@@ -0,0 +1,65 @@
1
+ # To add Jabber notifications you must have xmpp4r gem installed.
2
+ # Configure your watches like this:
3
+ #
4
+ # God::Contacts::Jabber.settings = { :jabber_id => 'sender@example.com',
5
+ # :password => 'secret' }
6
+ # God.contact(:jabber) do |c|
7
+ # c.name = 'Tester'
8
+ # c.jabber_id = 'receiver@example.com'
9
+ # c.group = 'developers'
10
+ # end
11
+
12
+ module XMPP4R
13
+ require 'rubygems'
14
+ require 'xmpp4r'
15
+ include Jabber
16
+ end
17
+
18
+ module God
19
+ module Contacts
20
+ class Jabber < Contact
21
+ class << self
22
+ attr_accessor :settings, :format
23
+ end
24
+
25
+ self.format = lambda do |message, priority, category, host|
26
+ text = "Message: #{message}\n"
27
+ text += "Host: #{host}\n" if host
28
+ text += "Priority: #{priority}\n" if priority
29
+ text += "Category: #{category}\n" if category
30
+ return text
31
+ end
32
+
33
+ attr_accessor :jabber_id
34
+
35
+ def valid?
36
+ valid = true
37
+ end
38
+
39
+ def notify(message, time, priority, category, host)
40
+ begin
41
+ jabber_id = XMPP4R::JID::new "#{Jabber.settings[:jabber_id]}/God"
42
+ jabber_client = XMPP4R::Client::new jabber_id
43
+ jabber_client.connect
44
+ jabber_client.auth Jabber.settings[:password]
45
+
46
+ body = Jabber.format.call message, priority, category, host
47
+
48
+ message = XMPP4R::Message::new self.jabber_id, body
49
+ message.set_type :normal
50
+ message.set_id '1'
51
+ message.set_subject 'God'
52
+ jabber_client.send message
53
+
54
+ self.info = "sent jabber message to #{self.jabber_id}"
55
+ rescue => e
56
+ puts e.message
57
+ puts e.backtrace.join("\n")
58
+
59
+ self.info = "failed to send jabber message to #{self.jabber_id}: #{e.message}"
60
+ end
61
+ end
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,39 @@
1
+ # For Twitter updates you need the 'twitter' gem
2
+ # (gem install twitter)
3
+ #
4
+ # Configure your watches like this:
5
+ #
6
+ # God::Contacts::Twitter.settings = { :username => 'sender@example.com',
7
+ # :password => 'secret' }
8
+ # God.contact(:twitter) do |c|
9
+ # c.name = 'Tester'
10
+ # c.group = 'developers'
11
+ # end
12
+
13
+ require 'rubygems'
14
+ require 'twitter'
15
+
16
+ module God
17
+ module Contacts
18
+ class Twitter < Contact
19
+ class << self
20
+ attr_accessor :settings
21
+ end
22
+
23
+ def valid?
24
+ valid = true
25
+ end
26
+
27
+ def notify(message, time, priority, category, host)
28
+ begin
29
+ ::Twitter::Base.new(Twitter.settings[:username],
30
+ Twitter.settings[:password]).update(message)
31
+
32
+ self.info = "sent twitter update as #{Twitter.settings[:username]}"
33
+ rescue => e
34
+ self.info = "failed to send twitter update from #{self.twitter_id}: #{e.message}"
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,47 @@
1
+ # Configure your watches like this:
2
+ #
3
+ # God.contact(:webhook) do |c|
4
+ # c.name = 'Tester'
5
+ # c.hook_url = 'http://hook/url'
6
+ # end
7
+
8
+ require 'net/http'
9
+ require 'uri'
10
+
11
+ module God
12
+ module Contacts
13
+
14
+ class Webhook < Contact
15
+
16
+ attr_accessor :hook_url
17
+
18
+ def valid?
19
+ valid = true
20
+ end
21
+
22
+ def notify(message, time, priority, category, host)
23
+ begin
24
+ data = {
25
+ :message => message,
26
+ :time => time,
27
+ :priority => priority,
28
+ :category => category,
29
+ :host => host
30
+ }
31
+
32
+ uri = URI.parse(self.hook_url)
33
+ Net::HTTP.post_form(uri, data)
34
+
35
+ self.info = "sent webhook to #{self.hook_url}"
36
+ rescue => e
37
+ puts e.message
38
+ puts e.backtrace.join("\n")
39
+
40
+ self.info = "failed to send webhook to #{self.hook_url}: #{e.message}"
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,41 @@
1
+ module God
2
+ class DependencyGraph
3
+ attr_accessor :nodes
4
+
5
+ def initialize
6
+ self.nodes = {}
7
+ end
8
+
9
+ def add(a, b)
10
+ node_a = self.nodes[a] || Node.new(a)
11
+ node_b = self.nodes[b] || Node.new(b)
12
+
13
+ node_a.add(node_b)
14
+
15
+ self.nodes[a] ||= node_a
16
+ self.nodes[b] ||= node_b
17
+ end
18
+ end
19
+ end
20
+
21
+ module God
22
+ class DependencyGraph
23
+ class Node
24
+ attr_accessor :name
25
+ attr_accessor :dependencies
26
+
27
+ def initialize(name)
28
+ self.name = name
29
+ self.dependencies = []
30
+ end
31
+
32
+ def add(node)
33
+ self.dependencies << node unless self.dependencies.include?(node)
34
+ end
35
+
36
+ def has_node?(node)
37
+ (self == node) || self.dependencies.any { |x| x.has_node?(node) }
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,37 @@
1
+ def start_dike
2
+ require 'dike'
3
+ Thread.new do
4
+ Dike.logfactory File.join(File.dirname(__FILE__), *%w[.. .. logs])
5
+ loop do
6
+ Dike.finger
7
+ sleep(1)
8
+ end
9
+ end
10
+ end
11
+
12
+ class BleakHouseDiagnostic
13
+ LOG_FILE = File.join(File.dirname(__FILE__), *%w[.. .. logs bleak.log])
14
+
15
+ class << self
16
+ attr_accessor :logger
17
+ end
18
+
19
+ def self.install
20
+ require 'bleak_house'
21
+ self.logger = BleakHouse::Logger.new
22
+ File.delete(LOG_FILE) rescue nil
23
+ end
24
+
25
+ def self.snapshot(name)
26
+ self.logger.snapshot(LOG_FILE, name, false) if self.logger
27
+ end
28
+
29
+ def self.spin(delay = 1)
30
+ Thread.new do
31
+ loop do
32
+ self.snapshot
33
+ sleep(delay)
34
+ end
35
+ end
36
+ end
37
+ end