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,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,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,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
data/lib/god/driver.rb ADDED
@@ -0,0 +1,108 @@
1
+ module God
2
+
3
+ class DriverEvent
4
+ attr_accessor :condition, :at
5
+
6
+ # Instantiate a new TimerEvent that will be triggered after the specified delay
7
+ # +condition+ is the Condition
8
+ # +delay+ is the number of seconds from now at which to trigger
9
+ #
10
+ # Returns TimerEvent
11
+ def initialize(condition, delay)
12
+ self.condition = condition
13
+ self.at = Time.now + delay
14
+ end
15
+
16
+ def due?
17
+ Time.now >= self.at
18
+ end
19
+ end # DriverEvent
20
+
21
+ class Driver
22
+ attr_reader :thread
23
+
24
+ INTERVAL = 0.25
25
+
26
+ # Instantiate a new Driver and start the scheduler loop to handle events
27
+ # +task+ is the Task this Driver belongs to
28
+ #
29
+ # Returns Driver
30
+ def initialize(task)
31
+ @task = task
32
+ @events = []
33
+ @ops = Queue.new
34
+
35
+ @thread = Thread.new do
36
+ loop do
37
+ begin
38
+ if !@ops.empty?
39
+ self.handle_op
40
+ elsif !@events.empty?
41
+ self.handle_event
42
+ else
43
+ sleep INTERVAL
44
+ end
45
+ rescue Exception => e
46
+ message = format("Unhandled exception in driver loop - (%s): %s\n%s",
47
+ e.class, e.message, e.backtrace.join("\n"))
48
+ applog(nil, :fatal, message)
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ # Handle the next queued operation that was issued asynchronously
55
+ #
56
+ # Returns nothing
57
+ def handle_op
58
+ command = @ops.pop
59
+ @task.send(command[0], *command[1])
60
+ end
61
+
62
+ # Handle the next event (poll condition) that is due
63
+ #
64
+ # Returns nothing
65
+ def handle_event
66
+ if @events.first.due?
67
+ event = @events.shift
68
+ @task.handle_poll(event.condition)
69
+ end
70
+
71
+ # don't sleep if there is a pending event and it is due
72
+ unless @events.first && @events.first.due?
73
+ sleep INTERVAL
74
+ end
75
+ end
76
+
77
+ # Clear all events for this Driver
78
+ #
79
+ # Returns nothing
80
+ def clear_events
81
+ @events.clear
82
+ end
83
+
84
+ # Queue an asynchronous message
85
+ # +name+ is the Symbol name of the operation
86
+ # +args+ is an optional Array of arguments
87
+ #
88
+ # Returns nothing
89
+ def message(name, args = [])
90
+ @ops.push([name, args])
91
+ end
92
+
93
+ # Create and schedule a new DriverEvent
94
+ # +condition+ is the Condition
95
+ # +delay+ is the number of seconds to delay (default: interval defined in condition)
96
+ #
97
+ # Returns nothing
98
+ def schedule(condition, delay = condition.interval)
99
+ applog(nil, :debug, "driver schedule #{condition} in #{delay} seconds")
100
+
101
+ @events.concat([DriverEvent.new(condition, delay)])
102
+
103
+ # sort events
104
+ @events.sort! { |x, y| x.at <=> y.at }
105
+ end
106
+ end # Driver
107
+
108
+ end # God