godhead 0.0.1
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.
- data/.document +5 -0
- data/.gitignore +43 -0
- data/LICENSE +20 -0
- data/README-god.textile +54 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/lib/godhead/extensions/hash.rb +48 -0
- data/lib/godhead/extensions.rb +1 -0
- data/lib/godhead/god_recipe.rb +223 -0
- data/lib/godhead/mixins/runs_as_service.rb +19 -0
- data/lib/godhead/mixins.rb +3 -0
- data/lib/godhead/notification/gmail.rb +0 -0
- data/lib/godhead/notification.rb +45 -0
- data/lib/godhead/process_groups.rb +32 -0
- data/lib/godhead/recipes/beanstalkd_recipe.rb +36 -0
- data/lib/godhead/recipes/generic_worker_recipe.rb +40 -0
- data/lib/godhead/recipes/memcached_recipe.rb +21 -0
- data/lib/godhead/recipes/nginx_recipe.rb +69 -0
- data/lib/godhead/recipes/starling_recipe.rb +19 -0
- data/lib/godhead/recipes/thin_recipe.rb +36 -0
- data/lib/godhead/recipes/tyrant_recipe.rb +64 -0
- data/lib/godhead/recipes.rb +9 -0
- data/lib/godhead.rb +5 -0
- data/sample.god +36 -0
- data/spec/godhead_spec.rb +7 -0
- data/spec/spec_helper.rb +9 -0
- metadata +111 -0
data/.document
ADDED
data/.gitignore
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
\#*
|
2
|
+
.\#*
|
3
|
+
*~
|
4
|
+
.DS_Store
|
5
|
+
Icon?
|
6
|
+
REVISION
|
7
|
+
TAGS*
|
8
|
+
nohup.out
|
9
|
+
.bzr
|
10
|
+
.hg
|
11
|
+
.svn
|
12
|
+
|
13
|
+
a.out
|
14
|
+
*.o
|
15
|
+
*.pyc
|
16
|
+
*.so
|
17
|
+
*.stackdump
|
18
|
+
*.sw?
|
19
|
+
*.tmproj
|
20
|
+
*_flymake.*
|
21
|
+
.project
|
22
|
+
.pydevproject
|
23
|
+
.settings
|
24
|
+
.tasks-cache
|
25
|
+
.yardoc
|
26
|
+
|
27
|
+
/**/*DONTVERSION*
|
28
|
+
/**/*private*
|
29
|
+
/**/cache/*
|
30
|
+
/**/log/*
|
31
|
+
/**/tmp/*
|
32
|
+
/coverage
|
33
|
+
/doc/*
|
34
|
+
/pkg/*
|
35
|
+
/rdoc/*
|
36
|
+
|
37
|
+
/db/*.sqlite3
|
38
|
+
/db/sphinx
|
39
|
+
/config/*.sphinx.conf
|
40
|
+
/config/database.yml
|
41
|
+
/config/sphinx.yml
|
42
|
+
/public/stylesheets/compiled/*
|
43
|
+
/vendor/src/**/*
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Philip (flip) Kromer
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README-god.textile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
Keep running:
|
2
|
+
|
3
|
+
* Worker queue (beanstalkd)
|
4
|
+
* Backing store (ttserver)
|
5
|
+
* Request queue-fed Scrapers (list of scripts)
|
6
|
+
* Feed/Periodic scrapers
|
7
|
+
* Constant scrapers
|
8
|
+
|
9
|
+
* http://god.rubyforge.org/
|
10
|
+
* http://railscasts.com/episodes/130-monitoring-with-god
|
11
|
+
|
12
|
+
sudo gem install god
|
13
|
+
god -c config/mailit.god
|
14
|
+
god status
|
15
|
+
god terminate
|
16
|
+
god log mailit-workling
|
17
|
+
kill `cat log/workling.pid`
|
18
|
+
|
19
|
+
* http://nubyonrails.com/articles/about-this-blog-beanstalk-messaging-queue
|
20
|
+
** The "god.conf":http://pastie.textmate.org/private/ovgxu2ihoicli2ktrwtbew is
|
21
|
+
taken from there.
|
22
|
+
|
23
|
+
h2. Beanstalkd
|
24
|
+
|
25
|
+
*Usage*:
|
26
|
+
|
27
|
+
beanstalkd --help
|
28
|
+
Use: beanstalkd [OPTIONS]
|
29
|
+
|
30
|
+
Options:
|
31
|
+
-d detach
|
32
|
+
-l ADDR listen on address (default is 0.0.0.0)
|
33
|
+
-p PORT listen on port (default is 11300)
|
34
|
+
-u USER become user and group
|
35
|
+
-z SIZE set the maximum job size in bytes (default is 65535)
|
36
|
+
-v show version information
|
37
|
+
-h show this help
|
38
|
+
|
39
|
+
|
40
|
+
h2. Tokyo Tyrant
|
41
|
+
|
42
|
+
*Usage*:
|
43
|
+
|
44
|
+
ttserver --help
|
45
|
+
ttserver: the server of Tokyo Tyrant
|
46
|
+
|
47
|
+
usage:
|
48
|
+
ttserver [-host name] [-port num] [-thnum num] [-tout num] [-dmn]
|
49
|
+
[-pid path] [-kl] [-log path] [-ld|-le] [-ulog path]
|
50
|
+
[-ulim num] [-uas] [-sid num]
|
51
|
+
[-mhost name] [-mport num] [-rts path] [-rcc] [-skel name]
|
52
|
+
[-ext path] [-extpc name period] [-mask expr] [-unmask expr] [dbname]
|
53
|
+
|
54
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "godhead"
|
8
|
+
gem.summary = %Q{God recipes that separate configuration for processes, site policy and notifications; comes with many examples}
|
9
|
+
gem.description = %Q{Configure God monitored processes according to their concerns the servers (path and so forth), site policy (number, ports, etc), and notification (email groups, mailserver, etc).}
|
10
|
+
gem.email = "flip@infochimps.org"
|
11
|
+
gem.homepage = "http://github.com/mrflip/godhead"
|
12
|
+
gem.authors = ["Philip (flip) Kromer"]
|
13
|
+
gem.add_development_dependency "rspec"
|
14
|
+
gem.add_development_dependency "yard"
|
15
|
+
gem.add_dependency 'extlib'
|
16
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
17
|
+
end
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'spec/rake/spectask'
|
23
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
24
|
+
spec.libs << 'lib' << 'spec'
|
25
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
26
|
+
end
|
27
|
+
|
28
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
29
|
+
spec.libs << 'lib' << 'spec'
|
30
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
31
|
+
spec.rcov = true
|
32
|
+
end
|
33
|
+
|
34
|
+
task :spec => :check_dependencies
|
35
|
+
|
36
|
+
task :default => :spec
|
37
|
+
|
38
|
+
begin
|
39
|
+
require 'yard'
|
40
|
+
YARD::Rake::YardocTask.new
|
41
|
+
rescue LoadError
|
42
|
+
task :yardoc do
|
43
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
44
|
+
end
|
45
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class Hash
|
2
|
+
# remove all key-value pairs where the value is nil
|
3
|
+
def compact
|
4
|
+
reject{|key,val| val.nil? }
|
5
|
+
end
|
6
|
+
# Replace the hash with its compacted self
|
7
|
+
def compact!
|
8
|
+
replace(compact)
|
9
|
+
end
|
10
|
+
|
11
|
+
# lambda for recursive merges
|
12
|
+
Hash::DEEP_MERGER = proc do |key,v1,v2|
|
13
|
+
if (v1.respond_to?(:merge) && v2.respond_to?(:merge))
|
14
|
+
v1.merge(v2.compact, &Hash::DEEP_MERGER)
|
15
|
+
else
|
16
|
+
(v2.nil? ? v1 : v2)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# Merge hashes recursively.
|
22
|
+
# Nothing special happens to array values
|
23
|
+
#
|
24
|
+
# x = { :subhash => { 1 => :val_from_x, 222 => :only_in_x, 333 => :only_in_x }, :scalar => :scalar_from_x}
|
25
|
+
# y = { :subhash => { 1 => :val_from_y, 999 => :only_in_y }, :scalar => :scalar_from_y }
|
26
|
+
# x.deep_merge y
|
27
|
+
# => {:subhash=>{1=>:val_from_y, 222=>:only_in_x, 333=>:only_in_x, 999=>:only_in_y}, :scalar=>:scalar_from_y}
|
28
|
+
# y.deep_merge x
|
29
|
+
# => {:subhash=>{1=>:val_from_x, 222=>:only_in_x, 333=>:only_in_x, 999=>:only_in_y}, :scalar=>:scalar_from_x}
|
30
|
+
#
|
31
|
+
# Nil values always lose.
|
32
|
+
#
|
33
|
+
# x = {:subhash=>{:nil_in_x=>nil, 1=>:val1,}, :nil_in_x=>nil}
|
34
|
+
# y = {:subhash=>{:nil_in_x=>5}, :nil_in_x=>5}
|
35
|
+
# y.deep_merge x
|
36
|
+
# => {:subhash=>{1=>:val1, :nil_in_x=>5}, :nil_in_x=>5}
|
37
|
+
# x.deep_merge y
|
38
|
+
# => {:subhash=>{1=>:val1, :nil_in_x=>5}, :nil_in_x=>5}
|
39
|
+
#
|
40
|
+
def deep_merge hsh2
|
41
|
+
merge hsh2, &Hash::DEEP_MERGER
|
42
|
+
end
|
43
|
+
|
44
|
+
# Merge hashes recursively -- see #deep_merge
|
45
|
+
def deep_merge! hsh2
|
46
|
+
merge! hsh2, &Hash::DEEP_MERGER
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'godhead/extensions/hash'
|
@@ -0,0 +1,223 @@
|
|
1
|
+
module Godhead
|
2
|
+
class GodRecipe
|
3
|
+
DEFAULT_OPTIONS = {
|
4
|
+
:monitor_group => nil,
|
5
|
+
:uid => nil,
|
6
|
+
:gid => nil,
|
7
|
+
:start_notify => nil,
|
8
|
+
:restart_notify => nil,
|
9
|
+
:flapping_notify => nil,
|
10
|
+
:process_log_dir => '/var/log/god',
|
11
|
+
:pid_dir => "/var/run/god",
|
12
|
+
:start_grace_time => 10.seconds,
|
13
|
+
:restart_grace_time => nil, # will be start_grace_time+2 if left nil
|
14
|
+
:default_interval => 5.minutes,
|
15
|
+
:start_interval => 5.minutes,
|
16
|
+
:mem_usage_interval => 10.minutes,
|
17
|
+
:cpu_usage_interval => 10.minutes,
|
18
|
+
}
|
19
|
+
#
|
20
|
+
# Hash mapping recipe_name to default options
|
21
|
+
#
|
22
|
+
cattr_accessor :global_options
|
23
|
+
self.global_options = {}
|
24
|
+
#
|
25
|
+
# options for this instance
|
26
|
+
#
|
27
|
+
attr_accessor :options
|
28
|
+
|
29
|
+
#
|
30
|
+
# pass in options to override the class default_options
|
31
|
+
#
|
32
|
+
def initialize _options={}
|
33
|
+
self.options = self.class.default_options.deep_merge _options
|
34
|
+
end
|
35
|
+
|
36
|
+
def do_setup! options={}
|
37
|
+
self.options.merge! options
|
38
|
+
mkdirs!
|
39
|
+
God.watch do |watcher|
|
40
|
+
setup_watcher watcher
|
41
|
+
setup_start watcher
|
42
|
+
setup_restart watcher
|
43
|
+
setup_lifecycle watcher
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.create options={}
|
48
|
+
recipe = self.new options
|
49
|
+
recipe.do_setup!
|
50
|
+
recipe
|
51
|
+
end
|
52
|
+
|
53
|
+
# ===========================================================================
|
54
|
+
#
|
55
|
+
# Watcher setup
|
56
|
+
#
|
57
|
+
|
58
|
+
#
|
59
|
+
# Setup common to most watchers
|
60
|
+
#
|
61
|
+
def setup_watcher watcher
|
62
|
+
watcher.name = self.handle
|
63
|
+
watcher.group = monitor_group
|
64
|
+
watcher.start = start_command
|
65
|
+
watcher.stop = stop_command if stop_command
|
66
|
+
watcher.restart = restart_command if restart_command
|
67
|
+
watcher.pid_file = pid_file if pid_file
|
68
|
+
watcher.uid = options[:uid] if options[:uid]
|
69
|
+
watcher.gid = options[:gid] if options[:gid]
|
70
|
+
watcher.interval = options[:default_interval]
|
71
|
+
watcher.start_grace = options[:start_grace_time]
|
72
|
+
watcher.restart_grace = options[:restart_grace_time] || (options[:start_grace_time] + 2.seconds)
|
73
|
+
watcher.behavior(:clean_pid_file)
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# Starts process
|
78
|
+
#
|
79
|
+
def setup_start watcher
|
80
|
+
watcher.start_if do |start|
|
81
|
+
start.condition(:process_running) do |c|
|
82
|
+
c.interval = options[:start_interval]
|
83
|
+
c.running = false
|
84
|
+
c.notify = options[:start_notify] if options[:start_notify]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
#
|
90
|
+
def setup_restart watcher
|
91
|
+
watcher.restart_if do |restart|
|
92
|
+
restart.condition(:memory_usage) do |c|
|
93
|
+
c.interval = options[:mem_usage_interval] if options[:mem_usage_interval]
|
94
|
+
c.above = options[:max_mem_usage] || 150.megabytes
|
95
|
+
c.times = [3, 5] # 3 out of 5 intervals
|
96
|
+
c.notify = options[:restart_notify] if options[:restart_notify]
|
97
|
+
end
|
98
|
+
restart.condition(:cpu_usage) do |c|
|
99
|
+
c.interval = options[:cpu_usage_interval] if options[:cpu_usage_interval]
|
100
|
+
c.above = options[:max_cpu_usage] || 50.percent
|
101
|
+
c.times = 5
|
102
|
+
c.notify = options[:restart_notify] if options[:restart_notify]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Define lifecycle
|
108
|
+
def setup_lifecycle watcher
|
109
|
+
watcher.lifecycle do |on|
|
110
|
+
on.condition(:flapping) do |c|
|
111
|
+
c.to_state = [:start, :restart]
|
112
|
+
c.times = 10
|
113
|
+
c.within = 15.minute
|
114
|
+
c.transition = :unmonitored
|
115
|
+
c.retry_in = 60.minutes
|
116
|
+
c.retry_times = 5
|
117
|
+
c.retry_within = 12.hours
|
118
|
+
c.notify = options[:flapping_notify] if options[:flapping_notify]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# ===========================================================================
|
124
|
+
#
|
125
|
+
# Configuration
|
126
|
+
#
|
127
|
+
|
128
|
+
#
|
129
|
+
# string holding name for this process type, eg 'mongrel' or 'starling'
|
130
|
+
#
|
131
|
+
# Unless told otherwise, uses the class_name -- MongrelRecipe
|
132
|
+
#
|
133
|
+
def self.recipe_name
|
134
|
+
@recipe_name ||= recipe_name_from_class_name
|
135
|
+
end
|
136
|
+
# calls back to self.class.recipe_name
|
137
|
+
def recipe_name() self.class.recipe_name ; end
|
138
|
+
|
139
|
+
def self.recipe_name_from_class_name
|
140
|
+
self.to_s.underscore.demodulize.
|
141
|
+
gsub(%r{.*\/+}, ''). # remove module portion
|
142
|
+
gsub(%r{_recipe}, '') # remove _recipe part
|
143
|
+
end
|
144
|
+
|
145
|
+
# unique label for this process
|
146
|
+
def handle
|
147
|
+
[recipe_name, options[:port]].compact.map(&:to_s).join('_')
|
148
|
+
end
|
149
|
+
|
150
|
+
#
|
151
|
+
# Starts with the portion of the global_options dealing with this class then
|
152
|
+
# walks upwards through the inheritance tree, accumulating default options.
|
153
|
+
#
|
154
|
+
# Subclasses should override with something like
|
155
|
+
#
|
156
|
+
# def self.default_options
|
157
|
+
# super.deep_merge(ThisClass::DEFAULT_OPTIONS)
|
158
|
+
# end
|
159
|
+
#
|
160
|
+
def self.default_options
|
161
|
+
GodRecipe::DEFAULT_OPTIONS.deep_merge(global_options[recipe_name] || {})
|
162
|
+
end
|
163
|
+
|
164
|
+
#
|
165
|
+
# load a series of YAML-format options files
|
166
|
+
#
|
167
|
+
# Later files win out over earlier files
|
168
|
+
#
|
169
|
+
def self.options_from_files *options_filenames
|
170
|
+
options = {}
|
171
|
+
options_filenames.each do |options_filename|
|
172
|
+
options.deep_merge! YAML.load_file(options_filename)
|
173
|
+
end
|
174
|
+
options
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
# ===========================================================================
|
179
|
+
#
|
180
|
+
# helpers
|
181
|
+
#
|
182
|
+
|
183
|
+
# by default, groups by process -- you may want to instead group by project
|
184
|
+
def monitor_group
|
185
|
+
options[:monitor_group].to_s || recipe_name.pluralize
|
186
|
+
end
|
187
|
+
|
188
|
+
# by default, uses :pid_dir/:recipe_name_:port.pid
|
189
|
+
def pid_file
|
190
|
+
options[:pid_file] || File.join(options[:pid_dir], "#{recipe_name}_#{options[:port]}.pid")
|
191
|
+
end
|
192
|
+
|
193
|
+
# command to start the daemon
|
194
|
+
def start_command
|
195
|
+
options[:start_command]
|
196
|
+
end
|
197
|
+
# command to stop the daemon
|
198
|
+
# return nil to have god daemonize the process
|
199
|
+
def stop_command
|
200
|
+
options[:stop_command]
|
201
|
+
end
|
202
|
+
# command to restart
|
203
|
+
# if stop_command is nil, it lets god daemonize the process
|
204
|
+
# otherwise, by default it runs stop_command, pauses for 1 second, then runs start_command
|
205
|
+
def restart_command
|
206
|
+
return unless stop_command
|
207
|
+
[stop_command, "sleep 1", start_command].join(" && ")
|
208
|
+
end
|
209
|
+
|
210
|
+
# Default log filename
|
211
|
+
def process_log_file
|
212
|
+
File.join(options[:process_log_dir], handle+".log")
|
213
|
+
end
|
214
|
+
|
215
|
+
# create any directories required by the process
|
216
|
+
def mkdirs!
|
217
|
+
require 'fileutils'
|
218
|
+
FileUtils.mkdir_p File.dirname(process_log_file)
|
219
|
+
FileUtils.mkdir_p File.dirname(options[:pid_file]) unless options[:pid_file].blank?
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Godhead
|
2
|
+
# Uses the init.d "service" command for running
|
3
|
+
module RunsAsService
|
4
|
+
# "service #{recipe_name} start"
|
5
|
+
def start_command
|
6
|
+
"service #{recipe_name} start"
|
7
|
+
end
|
8
|
+
|
9
|
+
# "service #{recipe_name} stop"
|
10
|
+
def stop_command
|
11
|
+
"service #{recipe_name} stop"
|
12
|
+
end
|
13
|
+
|
14
|
+
# "service #{recipe_name} restart"
|
15
|
+
def restart_command
|
16
|
+
"service #{recipe_name} restart"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
File without changes
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module God
|
2
|
+
def self.setup_email options
|
3
|
+
God::Contacts::Email.message_settings = {
|
4
|
+
:from => options[:username], }
|
5
|
+
God.contact(:email) do |c|
|
6
|
+
c.name = options[:to_name]
|
7
|
+
c.email = options[:to]
|
8
|
+
c.group = options[:group] || 'default'
|
9
|
+
end
|
10
|
+
if options[:address] then delivery_by_smtp(options)
|
11
|
+
else delivery_by_gmail(options) end
|
12
|
+
end
|
13
|
+
|
14
|
+
#
|
15
|
+
# GMail
|
16
|
+
#
|
17
|
+
# http://millarian.com/programming/ruby-on-rails/monitoring-thin-using-god-with-google-apps-notifications/
|
18
|
+
def self.delivery_by_gmail options
|
19
|
+
require 'tlsmail'
|
20
|
+
Net::SMTP.enable_tls(OpenSSL::SSL::VERIFY_NONE)
|
21
|
+
God::Contacts::Email.server_settings = {
|
22
|
+
:address => 'smtp.gmail.com',
|
23
|
+
:tls => 'true',
|
24
|
+
:port => 587,
|
25
|
+
:domain => options[:email_domain],
|
26
|
+
:user_name => options[:username],
|
27
|
+
:password => options[:password],
|
28
|
+
:authentication => :plain
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# SMTP email
|
34
|
+
#
|
35
|
+
def self.delivery_by_smtp options
|
36
|
+
God::Contacts::Email.server_settings = {
|
37
|
+
:address => options[:address],
|
38
|
+
:port => 25,
|
39
|
+
:domain => options[:email_domain],
|
40
|
+
:user_name => options[:username],
|
41
|
+
:password => options[:password],
|
42
|
+
:authentication => :plain,
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module God
|
2
|
+
#
|
3
|
+
# Given a base port number and an associative array
|
4
|
+
# [ [GodProcessSubclass, { :options => 'for factory methods', ... }],
|
5
|
+
# ..., }
|
6
|
+
# this creates each given service with incrementing port numbers.
|
7
|
+
#
|
8
|
+
# For example,
|
9
|
+
#
|
10
|
+
# God.service_group 12300, [
|
11
|
+
# [BeanstalkdGod, { :max_mem_usage => 2.gigabytes, }],
|
12
|
+
# [TyrantGod, { :db_dirname => EDAMAME_DB_DIR, :db_name => 'queue_jobs.tch' }],
|
13
|
+
# [TyrantGod, { :db_dirname => EDAMAME_DB_DIR, :db_name => 'fetched_urls.tch' }],
|
14
|
+
# [ThinGod, { :thin_config_yml => '/slice/www/edamame_monitor/current/config.yml' }],
|
15
|
+
# ]
|
16
|
+
#
|
17
|
+
# will create an edamame pair of beanstalkd queue on 123000 and tyrant DB on
|
18
|
+
# 12301, an app-specific DB on 12302, and a lightweight monitoring web app on
|
19
|
+
# 12303.
|
20
|
+
#
|
21
|
+
# It's up to you to choose the ports to not overlap with other groups, etc.
|
22
|
+
#
|
23
|
+
# If an explicit port is given, that port is used with no regard to the rest
|
24
|
+
# of the group, and its number is skipped.
|
25
|
+
#
|
26
|
+
def self.process_group base_port, services
|
27
|
+
services.each do |klass, options|
|
28
|
+
klass.create({ :port => base_port }.deep_merge(options))
|
29
|
+
base_port += 1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Godhead
|
2
|
+
class BeanstalkdRecipe < GodRecipe
|
3
|
+
DEFAULT_OPTIONS = {
|
4
|
+
:listen_on => '0.0.0.0',
|
5
|
+
:port => 11300,
|
6
|
+
:max_job_size => '65535',
|
7
|
+
:max_cpu_usage => 20.percent,
|
8
|
+
:max_mem_usage => 500.megabytes,
|
9
|
+
:runner_path => '/usr/local/bin/beanstalkd',
|
10
|
+
}
|
11
|
+
def self.default_options() super.deep_merge(Godhead::BeanstalkdRecipe::DEFAULT_OPTIONS) ; end
|
12
|
+
|
13
|
+
def start_command
|
14
|
+
[
|
15
|
+
options[:runner_path],
|
16
|
+
"-l #{options[:listen_on]}",
|
17
|
+
"-p #{options[:port]}",
|
18
|
+
(options[:user] ? "-u #{options[:user]}" : nil),
|
19
|
+
"-z #{options[:max_job_size]}",
|
20
|
+
].flatten.compact.join(" ")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# $ beanstalkd -h
|
26
|
+
# Use: beanstalkd [OPTIONS]
|
27
|
+
#
|
28
|
+
# Options:
|
29
|
+
# -d detach
|
30
|
+
# -l ADDR listen on address (default is 0.0.0.0)
|
31
|
+
# -p PORT listen on port (default is 11300)
|
32
|
+
# -u USER become user and group
|
33
|
+
# -z SIZE set the maximum job size in bytes (default is 65535)
|
34
|
+
# -v show version information
|
35
|
+
# -h show this help
|
36
|
+
#
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Godhead
|
2
|
+
#
|
3
|
+
# Starling monitoring recipe
|
4
|
+
#
|
5
|
+
class GenericWorkerRecipe < GodRecipe
|
6
|
+
DEFAULT_OPTIONS = {
|
7
|
+
:max_cpu_usage => 20.percent,
|
8
|
+
:max_mem_usage => 50.megabytes,
|
9
|
+
#
|
10
|
+
:user => nil,
|
11
|
+
:runner_path => nil,
|
12
|
+
}
|
13
|
+
def self.default_options() super.deep_merge(DEFAULT_OPTIONS) ; end
|
14
|
+
|
15
|
+
def initialize _options={}
|
16
|
+
super _options
|
17
|
+
raise "need a runner path" unless options[:runner_path]
|
18
|
+
p options
|
19
|
+
end
|
20
|
+
|
21
|
+
# name the recipe after the worker script
|
22
|
+
def recipe_name
|
23
|
+
File.basename(options[:runner_path]).gsub(/\..*/, '')
|
24
|
+
end
|
25
|
+
|
26
|
+
# don't try to invent a pid_file -- by default god will find and make one
|
27
|
+
def pid_file
|
28
|
+
options[:pid_file]
|
29
|
+
end
|
30
|
+
|
31
|
+
def start_command
|
32
|
+
[
|
33
|
+
"sudo",
|
34
|
+
(options[:user] ? "-u #{options[:user]}" : nil),
|
35
|
+
options[:runner_path]
|
36
|
+
].flatten.compact.join(" ")
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Godhead
|
2
|
+
#
|
3
|
+
# Nginx monitoring recipe
|
4
|
+
#
|
5
|
+
class MemcachedRecipe < GodRecipe
|
6
|
+
DEFAULT_OPTIONS = {
|
7
|
+
:max_cpu_usage => 20.percent,
|
8
|
+
:max_mem_usage => 500.megabytes,
|
9
|
+
#
|
10
|
+
:pid_file => "/var/run/god/memcached.pid",
|
11
|
+
:port => 45000,
|
12
|
+
}
|
13
|
+
def self.default_options() super.deep_merge(DEFAULT_OPTIONS) ; end
|
14
|
+
include Godhead::RunsAsService
|
15
|
+
|
16
|
+
def start_command
|
17
|
+
"memcached -u #{options[:user]} -p #{options[:port]} -d -P #{options[:pid_file]}"
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Godhead
|
2
|
+
#
|
3
|
+
# Nginx monitoring recipe
|
4
|
+
#
|
5
|
+
class GenericNginxRecipe < GodRecipe
|
6
|
+
DEFAULT_OPTIONS = {
|
7
|
+
:max_cpu_usage => 20.percent,
|
8
|
+
:max_mem_usage => 500.megabytes,
|
9
|
+
:pid_file => "/var/run/nginx/nginx.pid",
|
10
|
+
}
|
11
|
+
def self.default_options() super.deep_merge(DEFAULT_OPTIONS) ; end
|
12
|
+
|
13
|
+
def self.recipe_name
|
14
|
+
'nginx'
|
15
|
+
end
|
16
|
+
|
17
|
+
def mkdirs!
|
18
|
+
FileUtils.mkdir_p File.dirname(options[:pid_file])
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Recipe for nginx process
|
24
|
+
#
|
25
|
+
# uses 'service nginx start' and so forth for task management
|
26
|
+
class NginxRecipe < GenericNginxRecipe
|
27
|
+
include Godhead::RunsAsService
|
28
|
+
end
|
29
|
+
|
30
|
+
# Recipe for nginx process, use on OSX
|
31
|
+
#
|
32
|
+
# calls out to the runner directly, as defined by :runner_path and
|
33
|
+
#:runner_conf option
|
34
|
+
class NginxRunnerRecipe < GenericNginxRecipe
|
35
|
+
DEFAULT_OPTIONS = {
|
36
|
+
# runner options
|
37
|
+
:runner_path => '/usr/local/nginx/sbin/nginx',
|
38
|
+
:runner_conf => nil,
|
39
|
+
}
|
40
|
+
def self.default_options() super.deep_merge(DEFAULT_OPTIONS) ; end
|
41
|
+
|
42
|
+
# send signal to a master process: stop, quit, reopen, reload
|
43
|
+
def tell_runner action=nil
|
44
|
+
[
|
45
|
+
options[:runner_path],
|
46
|
+
(options[:runner_conf] ? "-c #{options[:runner_conf]}" : nil),
|
47
|
+
(!action.blank? ? "-s #{action}" : nil),
|
48
|
+
].flatten.compact.join(" ")
|
49
|
+
end
|
50
|
+
|
51
|
+
def start_command() tell_runner '' ; end
|
52
|
+
def stop_command() tell_runner 'stop' ; end
|
53
|
+
def restart_command() tell_runner 'restart' ; end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# nginx version: nginx/0.7.61
|
59
|
+
# Usage: nginx [-?hvVt] [-s signal] [-c filename] [-p prefix] [-g directives]
|
60
|
+
#
|
61
|
+
# Options:
|
62
|
+
# -?,-h : this help
|
63
|
+
# -v : show version and exit
|
64
|
+
# -V : show version and configure options then exit
|
65
|
+
# -t : test configuration and exit
|
66
|
+
# -s signal : send signal to a master process: stop, quit, reopen, reload
|
67
|
+
# -p prefix : set prefix path (default: /usr/local/nginx/)
|
68
|
+
# -c filename : set configuration file (default: /slice/etc/nginx/nginx.conf)
|
69
|
+
# -g directives : set global directives out of configuration file
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Godhead
|
2
|
+
#
|
3
|
+
# Starling monitoring recipe
|
4
|
+
#
|
5
|
+
class StarlingRecipe < GodRecipe
|
6
|
+
DEFAULT_OPTIONS = {
|
7
|
+
:max_cpu_usage => 20.percent,
|
8
|
+
:max_mem_usage => 50.megabytes,
|
9
|
+
#
|
10
|
+
:port => 22122,
|
11
|
+
}
|
12
|
+
def self.default_options() super.deep_merge(DEFAULT_OPTIONS) ; end
|
13
|
+
|
14
|
+
def start_command
|
15
|
+
"starling -d -p #{options[:port]} -d -P #{pid_file}"
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Godhead
|
2
|
+
class ThinRecipe < GodRecipe
|
3
|
+
DEFAULT_OPTIONS = {
|
4
|
+
:port => 3000,
|
5
|
+
:runner_path => '/usr/bin/thin', # path to thin. Override this in the site config file.
|
6
|
+
:runner_conf => nil,
|
7
|
+
:pid_file => nil,
|
8
|
+
}
|
9
|
+
def self.default_options() super.deep_merge(DEFAULT_OPTIONS) ; end
|
10
|
+
|
11
|
+
# w.start = "thin -s 3 -C #{YUPFRONT_CONFIG} start"
|
12
|
+
def tell_thin action
|
13
|
+
[
|
14
|
+
options[:runner_path],
|
15
|
+
"--config=#{options[:runner_conf]}",
|
16
|
+
"--rackup=#{options[:rackup_file]}",
|
17
|
+
"--port=#{ options[:port]}",
|
18
|
+
"--pid=#{pid_file}",
|
19
|
+
action
|
20
|
+
].flatten.compact.join(" ")
|
21
|
+
end
|
22
|
+
|
23
|
+
def start_command
|
24
|
+
tell_thin :start
|
25
|
+
end
|
26
|
+
|
27
|
+
def restart_command
|
28
|
+
tell_thin :restart
|
29
|
+
end
|
30
|
+
|
31
|
+
def stop_command
|
32
|
+
tell_thin :stop
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Godhead
|
2
|
+
class TyrantRecipe < GodRecipe
|
3
|
+
DEFAULT_OPTIONS = {
|
4
|
+
:max_cpu_usage => 50.percent,
|
5
|
+
:max_mem_usage => 150.megabytes,
|
6
|
+
# runner-specific
|
7
|
+
:listen_on => '0.0.0.0',
|
8
|
+
:port => 1978,
|
9
|
+
:db_dirname => '/tmp',
|
10
|
+
:runner_path => '/usr/local/bin/ttserver',
|
11
|
+
}
|
12
|
+
def self.default_options()
|
13
|
+
super.deep_merge(Godhead::TyrantRecipe::DEFAULT_OPTIONS)
|
14
|
+
end
|
15
|
+
|
16
|
+
def dbname
|
17
|
+
basename = options[:db_name] || (handle+'.tct')
|
18
|
+
File.join(options[:db_dirname], basename)
|
19
|
+
end
|
20
|
+
|
21
|
+
# create any directories required by the process
|
22
|
+
def mkdirs!
|
23
|
+
FileUtils.mkdir_p File.dirname(dbname)
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
def start_command
|
28
|
+
[
|
29
|
+
options[:runner_path],
|
30
|
+
"-host #{options[:listen_on]}",
|
31
|
+
"-port #{options[:port]}",
|
32
|
+
"-log #{process_log_file}",
|
33
|
+
dbname
|
34
|
+
].flatten.compact.join(" ")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# -host name : specify the host name or the address of the server. By default, every network address is bound.
|
41
|
+
# -port num : specify the port number. By default, it is 1978.
|
42
|
+
#
|
43
|
+
# -thnum num : specify the number of worker threads. By default, it is 8.
|
44
|
+
# -tout num : specify the timeout of each session in seconds. By default, no timeout is specified.
|
45
|
+
#
|
46
|
+
# -log path : output log messages into the file.
|
47
|
+
# -ld : log debug messages also.
|
48
|
+
# -le : log error messages only.
|
49
|
+
# -ulog path : specify the update log directory.
|
50
|
+
# -ulim num : specify the limit size of each update log file.
|
51
|
+
# -uas : use asynchronous I/O for the update log.
|
52
|
+
#
|
53
|
+
# -sid num : specify the server ID.
|
54
|
+
# -mhost name : specify the host name of the replication master server.
|
55
|
+
# -mport num : specify the port number of the replication master server.
|
56
|
+
# -rts path : specify the replication time stamp file.
|
57
|
+
# -rcc : check consistency of replication.
|
58
|
+
#
|
59
|
+
# -skel name : specify the name of the skeleton database library.
|
60
|
+
# -ext path : specify the script language extension file.
|
61
|
+
# -extpc name period : specify the function name and the calling period of a periodic command.
|
62
|
+
# -mask expr : specify the names of forbidden commands.
|
63
|
+
# -unmask expr : specify the names of allowed commands.
|
64
|
+
#
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Godhead
|
2
|
+
autoload :BeanstalkdRecipe, 'godhead/recipes/beanstalkd_recipe.rb'
|
3
|
+
autoload :GenericWorkerRecipe, 'godhead/recipes/generic_worker_recipe.rb'
|
4
|
+
autoload :NginxRecipe, 'godhead/recipes/nginx_recipe.rb'
|
5
|
+
autoload :ThinRecipe, 'godhead/recipes/thin_recipe.rb'
|
6
|
+
autoload :TyrantRecipe, 'godhead/recipes/tyrant_recipe.rb'
|
7
|
+
autoload :MemcachedRecipe, 'godhead/recipes/memcached_recipe.rb'
|
8
|
+
autoload :StarlingRecipe, 'godhead/recipes/starling_recipe.rb'
|
9
|
+
end
|
data/lib/godhead.rb
ADDED
data/sample.god
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'god'
|
4
|
+
require 'active_support'
|
5
|
+
$: << File.dirname(__FILE__)+'/lib'
|
6
|
+
require 'godhead'
|
7
|
+
|
8
|
+
YUPFRONT_ROOT = "/slice/www/apps/yuploader_static/yupfront"
|
9
|
+
|
10
|
+
# ===========================================================================
|
11
|
+
#
|
12
|
+
# Yupshot god monitoring: the backend processing chain for yuploader
|
13
|
+
#
|
14
|
+
group_options = { :monitor_group => :yupshot, }
|
15
|
+
|
16
|
+
Godhead::MemcachedRecipe.create group_options.merge({ :user => 'www-data', :pid_file => '/tmp/mc.pid'})
|
17
|
+
Godhead::StarlingRecipe.create group_options
|
18
|
+
Godhead::GenericWorkerRecipe.create group_options.merge({
|
19
|
+
:runner_path => "/slice/www/apps/yuploader_static/yupshot/bin/yupshot_worker_daemon" })
|
20
|
+
|
21
|
+
# ===========================================================================
|
22
|
+
#
|
23
|
+
# Yupfront god monitoring: the frontend processes for yuploader
|
24
|
+
#
|
25
|
+
group_options = { :monitor_group => :yupfront}
|
26
|
+
|
27
|
+
Godhead::NginxRecipe.create group_options.merge({ })
|
28
|
+
# replace with this one on OSX
|
29
|
+
# Godhead::NginxRunnerRecipe.create group_options.merge({ })
|
30
|
+
|
31
|
+
(5000..5003).each do |port|
|
32
|
+
Godhead::ThinRecipe.create(group_options.merge({
|
33
|
+
:port => port,
|
34
|
+
:rackup_file => File.join(YUPFRONT_ROOT, 'config.ru'),
|
35
|
+
:runner_conf => File.join(YUPFRONT_ROOT, 'production.yml') }))
|
36
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: godhead
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Philip (flip) Kromer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-12-07 00:00:00 +00:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: yard
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: extlib
|
37
|
+
type: :runtime
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
version:
|
45
|
+
description: Configure God monitored processes according to their concerns the servers (path and so forth), site policy (number, ports, etc), and notification (email groups, mailserver, etc).
|
46
|
+
email: flip@infochimps.org
|
47
|
+
executables: []
|
48
|
+
|
49
|
+
extensions: []
|
50
|
+
|
51
|
+
extra_rdoc_files:
|
52
|
+
- LICENSE
|
53
|
+
- README-god.textile
|
54
|
+
files:
|
55
|
+
- .document
|
56
|
+
- .gitignore
|
57
|
+
- LICENSE
|
58
|
+
- README-god.textile
|
59
|
+
- Rakefile
|
60
|
+
- VERSION
|
61
|
+
- lib/godhead.rb
|
62
|
+
- lib/godhead/extensions.rb
|
63
|
+
- lib/godhead/extensions/hash.rb
|
64
|
+
- lib/godhead/god_recipe.rb
|
65
|
+
- lib/godhead/mixins.rb
|
66
|
+
- lib/godhead/mixins/runs_as_service.rb
|
67
|
+
- lib/godhead/notification.rb
|
68
|
+
- lib/godhead/notification/gmail.rb
|
69
|
+
- lib/godhead/process_groups.rb
|
70
|
+
- lib/godhead/recipes.rb
|
71
|
+
- lib/godhead/recipes/beanstalkd_recipe.rb
|
72
|
+
- lib/godhead/recipes/generic_worker_recipe.rb
|
73
|
+
- lib/godhead/recipes/memcached_recipe.rb
|
74
|
+
- lib/godhead/recipes/nginx_recipe.rb
|
75
|
+
- lib/godhead/recipes/starling_recipe.rb
|
76
|
+
- lib/godhead/recipes/thin_recipe.rb
|
77
|
+
- lib/godhead/recipes/tyrant_recipe.rb
|
78
|
+
- sample.god
|
79
|
+
- spec/godhead_spec.rb
|
80
|
+
- spec/spec_helper.rb
|
81
|
+
has_rdoc: true
|
82
|
+
homepage: http://github.com/mrflip/godhead
|
83
|
+
licenses: []
|
84
|
+
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options:
|
87
|
+
- --charset=UTF-8
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: "0"
|
95
|
+
version:
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: "0"
|
101
|
+
version:
|
102
|
+
requirements: []
|
103
|
+
|
104
|
+
rubyforge_project:
|
105
|
+
rubygems_version: 1.3.5
|
106
|
+
signing_key:
|
107
|
+
specification_version: 3
|
108
|
+
summary: God recipes that separate configuration for processes, site policy and notifications; comes with many examples
|
109
|
+
test_files:
|
110
|
+
- spec/godhead_spec.rb
|
111
|
+
- spec/spec_helper.rb
|