kato 0.1.0
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/History.txt +4 -0
- data/License.txt +20 -0
- data/Manifest.txt +27 -0
- data/README.txt +55 -0
- data/Rakefile +4 -0
- data/config/hoe.rb +70 -0
- data/config/requirements.rb +17 -0
- data/lib/kato.rb +3 -0
- data/lib/kato/pool_manager.rb +253 -0
- data/lib/kato/pool_supervisor.rb +22 -0
- data/lib/kato/version.rb +9 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +74 -0
- data/setup.rb +1585 -0
- data/spec/kato_spec.rb +11 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +7 -0
- data/tasks/deployment.rake +34 -0
- data/tasks/environment.rake +7 -0
- data/tasks/rspec.rake +21 -0
- data/tasks/website.rake +17 -0
- data/website/index.html +11 -0
- data/website/index.txt +39 -0
- data/website/javascripts/rounded_corners_lite.inc.js +285 -0
- data/website/stylesheets/screen.css +138 -0
- data/website/template.rhtml +48 -0
- metadata +93 -0
data/History.txt
ADDED
data/License.txt
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2008 Jonathan Younger
|
|
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/Manifest.txt
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
History.txt
|
|
2
|
+
License.txt
|
|
3
|
+
Manifest.txt
|
|
4
|
+
README.txt
|
|
5
|
+
Rakefile
|
|
6
|
+
config/hoe.rb
|
|
7
|
+
config/requirements.rb
|
|
8
|
+
lib/kato.rb
|
|
9
|
+
lib/kato/pool_manager.rb
|
|
10
|
+
lib/kato/pool_supervisor.rb
|
|
11
|
+
lib/kato/version.rb
|
|
12
|
+
script/destroy
|
|
13
|
+
script/generate
|
|
14
|
+
script/txt2html
|
|
15
|
+
setup.rb
|
|
16
|
+
spec/kato_spec.rb
|
|
17
|
+
spec/spec.opts
|
|
18
|
+
spec/spec_helper.rb
|
|
19
|
+
tasks/deployment.rake
|
|
20
|
+
tasks/environment.rake
|
|
21
|
+
tasks/rspec.rake
|
|
22
|
+
tasks/website.rake
|
|
23
|
+
website/index.html
|
|
24
|
+
website/index.txt
|
|
25
|
+
website/javascripts/rounded_corners_lite.inc.js
|
|
26
|
+
website/stylesheets/screen.css
|
|
27
|
+
website/template.rhtml
|
data/README.txt
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
= kato
|
|
2
|
+
|
|
3
|
+
* http://kato.rubyforge.org
|
|
4
|
+
|
|
5
|
+
== DESCRIPTION:
|
|
6
|
+
|
|
7
|
+
Kato is a library for managing pools of Amazon EC2 servers.
|
|
8
|
+
It is a ruby port of the java lifeguard http://code.google.com/p/lifeguard/ library.
|
|
9
|
+
|
|
10
|
+
== FEATURES/PROBLEMS:
|
|
11
|
+
|
|
12
|
+
* Manage multiple EC2 pools
|
|
13
|
+
* Minimum number of instances
|
|
14
|
+
* Maximum number of instances
|
|
15
|
+
* Ramp up/down intervals
|
|
16
|
+
|
|
17
|
+
== SYNOPSIS:
|
|
18
|
+
|
|
19
|
+
require 'rubygems'
|
|
20
|
+
require 'kato'
|
|
21
|
+
pool_supervisor = Kato::PoolSupervisor.new(config)
|
|
22
|
+
pool_supervisor.run
|
|
23
|
+
|
|
24
|
+
== REQUIREMENTS:
|
|
25
|
+
|
|
26
|
+
* right_aws
|
|
27
|
+
|
|
28
|
+
== INSTALL:
|
|
29
|
+
|
|
30
|
+
* gem install kato
|
|
31
|
+
|
|
32
|
+
== LICENSE:
|
|
33
|
+
|
|
34
|
+
(The MIT License)
|
|
35
|
+
|
|
36
|
+
Copyright (c) 2008 Jonathan Younger
|
|
37
|
+
|
|
38
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
39
|
+
a copy of this software and associated documentation files (the
|
|
40
|
+
'Software'), to deal in the Software without restriction, including
|
|
41
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
42
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
43
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
44
|
+
the following conditions:
|
|
45
|
+
|
|
46
|
+
The above copyright notice and this permission notice shall be
|
|
47
|
+
included in all copies or substantial portions of the Software.
|
|
48
|
+
|
|
49
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
|
50
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
51
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
52
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
53
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
54
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
55
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
data/config/hoe.rb
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
require 'kato/version'
|
|
2
|
+
|
|
3
|
+
AUTHOR = 'Jonathan Younger' # can also be an array of Authors
|
|
4
|
+
EMAIL = "jonathan@daikini.com"
|
|
5
|
+
DESCRIPTION = "Kato is a library for managing pools of Amazon EC2 servers"
|
|
6
|
+
GEM_NAME = 'kato' # what ppl will type to install your gem
|
|
7
|
+
RUBYFORGE_PROJECT = 'kato' # The unix name for your project
|
|
8
|
+
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
|
|
9
|
+
DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
|
|
10
|
+
|
|
11
|
+
@config_file = "~/.rubyforge/user-config.yml"
|
|
12
|
+
@config = nil
|
|
13
|
+
RUBYFORGE_USERNAME = "poogle"
|
|
14
|
+
def rubyforge_username
|
|
15
|
+
unless @config
|
|
16
|
+
begin
|
|
17
|
+
@config = YAML.load(File.read(File.expand_path(@config_file)))
|
|
18
|
+
rescue
|
|
19
|
+
puts <<-EOS
|
|
20
|
+
ERROR: No rubyforge config file found: #{@config_file}
|
|
21
|
+
Run 'rubyforge setup' to prepare your env for access to Rubyforge
|
|
22
|
+
- See http://newgem.rubyforge.org/rubyforge.html for more details
|
|
23
|
+
EOS
|
|
24
|
+
exit
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
RUBYFORGE_USERNAME.replace @config["username"]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
REV = nil
|
|
32
|
+
# UNCOMMENT IF REQUIRED:
|
|
33
|
+
# REV = `svn info`.each {|line| if line =~ /^Revision:/ then k,v = line.split(': '); break v.chomp; else next; end} rescue nil
|
|
34
|
+
VERS = Kato::VERSION::STRING + (REV ? ".#{REV}" : "")
|
|
35
|
+
RDOC_OPTS = ['--quiet', '--title', 'kato documentation',
|
|
36
|
+
"--opname", "index.html",
|
|
37
|
+
"--line-numbers",
|
|
38
|
+
"--main", "README",
|
|
39
|
+
"--inline-source"]
|
|
40
|
+
|
|
41
|
+
class Hoe
|
|
42
|
+
def extra_deps
|
|
43
|
+
@extra_deps.reject! { |x| Array(x).first == 'hoe' }
|
|
44
|
+
@extra_deps
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Generate all the Rake tasks
|
|
49
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
|
50
|
+
hoe = Hoe.new(GEM_NAME, VERS) do |p|
|
|
51
|
+
p.developer(AUTHOR, EMAIL)
|
|
52
|
+
p.description = DESCRIPTION
|
|
53
|
+
p.summary = DESCRIPTION
|
|
54
|
+
p.url = HOMEPATH
|
|
55
|
+
p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
|
|
56
|
+
p.test_globs = ["test/**/test_*.rb"]
|
|
57
|
+
p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
|
|
58
|
+
|
|
59
|
+
# == Optional
|
|
60
|
+
p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
|
|
61
|
+
p.extra_deps = [['right_aws', '>= 1.6.1']] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
|
|
62
|
+
|
|
63
|
+
#p.spec_extras = {} # A hash of extra values to set in the gemspec.
|
|
64
|
+
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n")
|
|
68
|
+
PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
|
|
69
|
+
hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
|
|
70
|
+
hoe.rsync_args = '-av --delete --ignore-errors'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
include FileUtils
|
|
3
|
+
|
|
4
|
+
require 'rubygems'
|
|
5
|
+
%w[rake hoe newgem rubigen].each do |req_gem|
|
|
6
|
+
begin
|
|
7
|
+
require req_gem
|
|
8
|
+
rescue LoadError
|
|
9
|
+
puts "This Rakefile requires the '#{req_gem}' RubyGem."
|
|
10
|
+
puts "Installation: gem install #{req_gem} -y"
|
|
11
|
+
exit
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
$:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
|
|
16
|
+
|
|
17
|
+
require 'kato'
|
data/lib/kato.rb
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
require 'right_aws'
|
|
2
|
+
|
|
3
|
+
module Kato
|
|
4
|
+
class PoolManager
|
|
5
|
+
attr_accessor :config, :aws_config, :instances
|
|
6
|
+
|
|
7
|
+
def initialize(config, aws_config)
|
|
8
|
+
@config = config
|
|
9
|
+
@aws_config = aws_config
|
|
10
|
+
@keep_running = true
|
|
11
|
+
@instances = []
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run
|
|
15
|
+
add_existing_instances if config[:find_existing_instances?]
|
|
16
|
+
|
|
17
|
+
# fire up the minimum servers first. They take a least 2 minutes to start up
|
|
18
|
+
minimum_number_of_instances = config[:minimum_number_of_instances]
|
|
19
|
+
launch_instances(minimum_number_of_instances - instances.size) if minimum_number_of_instances > instances.size
|
|
20
|
+
|
|
21
|
+
# used to track time pool has no idle capacity
|
|
22
|
+
start_busy_interval = 0
|
|
23
|
+
|
|
24
|
+
# used to track time pool has spare capacity
|
|
25
|
+
start_idle_interval = 0
|
|
26
|
+
|
|
27
|
+
# loopey
|
|
28
|
+
while @keep_running do
|
|
29
|
+
messages = status_queue.receive_messages(config[:receive_count] || 20)
|
|
30
|
+
messages.each do |message|
|
|
31
|
+
break unless @keep_running
|
|
32
|
+
|
|
33
|
+
instance_status = InstanceStatus.parse(message.body)
|
|
34
|
+
if instance = instances.find { |i| i.id == instance_status.instance_id }
|
|
35
|
+
if instance_status.state == "busy"
|
|
36
|
+
instance.last_busy_interval = instance_status.last_interval
|
|
37
|
+
elsif instance_status.state == "idle"
|
|
38
|
+
instance.last_idle_interval = instance_status.last_interval
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
instance.last_report_time = Time.now
|
|
42
|
+
instance.update_load
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
message.delete
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# for servers that haven't reported recently, bump idle interval...
|
|
49
|
+
instances.each do |instance|
|
|
50
|
+
# if more than a minute (arbitrarily) has gone by without a report,
|
|
51
|
+
# increase the last_idle_interval, and recalc the load_estimate
|
|
52
|
+
if instance.last_report_time < (Time.now - config[:idle_bump_interval])
|
|
53
|
+
instance.last_idle_interval += config[:idle_bump_interval]
|
|
54
|
+
instance.last_report_time = Time.now
|
|
55
|
+
instance.update_load
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# calculate pool load average
|
|
60
|
+
sum = instances.inject(0) { |sum, instance| sum + instance.load_estimate }
|
|
61
|
+
number_of_instances = instances.size
|
|
62
|
+
pool_load = number_of_instances == 0 ? 0 : (sum / number_of_instances)
|
|
63
|
+
STDERR.puts "Pool Load Average: #{pool_load}"
|
|
64
|
+
|
|
65
|
+
# now, see if were full busy, or somewhat idle
|
|
66
|
+
if pool_load > 75 # Busy
|
|
67
|
+
start_busy_interval = Time.now if start_busy_interval == 0
|
|
68
|
+
start_idle_interval = 0
|
|
69
|
+
else
|
|
70
|
+
start_idle_interval = Time.now if start_idle_interval == 0
|
|
71
|
+
start_busy_interval = 0
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
queue_depth = work_queue.size
|
|
75
|
+
STDERR.puts "Queue Depth: #{queue_depth}"
|
|
76
|
+
|
|
77
|
+
# fast exit
|
|
78
|
+
break unless @keep_running
|
|
79
|
+
|
|
80
|
+
# now, based on busy/idle timers and queue depth, make a call on
|
|
81
|
+
# whether to start or terminate servers
|
|
82
|
+
idle_interval = start_idle_interval == 0 ? 0 : (Time.now - start_idle_interval)
|
|
83
|
+
busy_interval = start_busy_interval == 0 ? 0 : (Time.now - start_busy_interval)
|
|
84
|
+
|
|
85
|
+
# idle interval has elapsed
|
|
86
|
+
minimum_number_of_instances = config[:minimum_number_of_instances]
|
|
87
|
+
if idle_interval >= config[:ramp_down_delay]
|
|
88
|
+
if number_of_instances > minimum_number_of_instances
|
|
89
|
+
# terminate as many servers (up to the interval)
|
|
90
|
+
number_of_instances_to_kill = [config[:ramp_down_interval], number_of_instances].min
|
|
91
|
+
|
|
92
|
+
# ensure we don't kill too many servers (not below min)
|
|
93
|
+
if (number_of_instances - number_of_instances_to_kill) < minimum_number_of_instances
|
|
94
|
+
number_of_instances_to_kill -= (minimum_number_of_instances - (number_of_instances - number_of_instances_to_kill))
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# if there are still messages in work queue, leave an idle server
|
|
98
|
+
# (this helps prevent cyclic launching and terminating of servers)
|
|
99
|
+
if queue_depth >= 1 && (number_of_instances_to_kill == number_of_instances)
|
|
100
|
+
number_of_instances_to_kill -= 1
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
if number_of_instances_to_kill > 0
|
|
104
|
+
# terminate the instances with the lowest load estimate
|
|
105
|
+
instances_sorted_by_lowest_load_estimate = instances.sort do |a,b|
|
|
106
|
+
# Compare the elapsed lifetime status. If the status differs, instances
|
|
107
|
+
# that have lived beyond the minimum lifetime will be sorted earlier.
|
|
108
|
+
if a.minimum_lifetime_elapsed? != b.minimum_lifetime_elapsed?
|
|
109
|
+
if a.minimum_lifetime_elapsed?
|
|
110
|
+
# This instance has lived long enough, the other hasn't
|
|
111
|
+
-1
|
|
112
|
+
else
|
|
113
|
+
# The other instance has lived long enough, this one hasn't
|
|
114
|
+
1
|
|
115
|
+
end
|
|
116
|
+
else
|
|
117
|
+
a.load_estimate - b.load_estimate
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
terminate_instances(instances_sorted_by_lowest_load_estimate[0...number_of_instances_to_kill], false)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# reset
|
|
125
|
+
start_idle_interval = 0
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# busy interval has elapsed
|
|
130
|
+
maximum_number_of_instances = config[:maximum_number_of_instances]
|
|
131
|
+
if busy_interval >= config[:ramp_up_delay]
|
|
132
|
+
if number_of_instances < maximum_number_of_instances
|
|
133
|
+
number_of_instances_to_launch = config[:ramp_up_interval]
|
|
134
|
+
size_factor = config[:queue_size_factor]
|
|
135
|
+
|
|
136
|
+
# use queue_depth to adjust the number_of_instances_to_launch
|
|
137
|
+
number_of_instances_to_launch = number_of_instances_to_launch * ((queue_depth / (size_factor < 1 ? 1 :size_factor).to_f) +1 ).to_i
|
|
138
|
+
if (number_of_instances + number_of_instances_to_launch) > maximum_number_of_instances
|
|
139
|
+
number_of_instances_to_launch -= ((number_of_instances + number_of_instances_to_launch) - maximum_number_of_instances)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
if number_of_instances_to_launch > 0
|
|
143
|
+
launch_instances(number_of_instances_to_launch)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# this test will get instances started if there is work and zero instances.
|
|
149
|
+
if number_of_instances == 0 && queue_depth > 0 && maximum_number_of_instances > 0
|
|
150
|
+
launch_instances(config[:ramp_up_interval])
|
|
151
|
+
start_idle_interval = 0
|
|
152
|
+
start_busy_interval = 0
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
sleep 2
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def shutdown
|
|
160
|
+
@keep_running = false
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def status_queue
|
|
164
|
+
@status_queue ||= sqs.queue(config[:queue_prefix] + config[:pool_status_queue])
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def work_queue
|
|
168
|
+
@work_queue ||= sqs.queue(config[:queue_prefix] + config[:service_work_queue])
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def sqs
|
|
172
|
+
@sqs ||= RightAws::Sqs.new(aws_config[:access_id], aws_config[:access_key], :server => aws_config[:sqs][:server], :port => aws_config[:sqs][:port], :protocol => aws_config[:sqs][:protocol])
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def ec2
|
|
176
|
+
@ec2 ||= RightAws::Ec2.new(aws_config[:access_id], aws_config[:access_key], :server => aws_config[:ec2][:server], :port => aws_config[:ec2][:port], :protocol => aws_config[:ec2][:protocol])
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def add_existing_instances
|
|
180
|
+
ec2.describe_instances.each do |instance|
|
|
181
|
+
if instance[:aws_image_id] == config[:service_ami] && %w[pending running].include?(instance[:aws_state])
|
|
182
|
+
instances << Instance.new(instance[:aws_instance_id], config[:minimum_lifetime_in_minutes])
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def launch_instances(number_of_instances_to_launch)
|
|
188
|
+
launched_instances = ec2.run_instances(config[:service_ami], 1, number_of_instances_to_launch, nil, config[:key_pair_name], config[:user_data])
|
|
189
|
+
|
|
190
|
+
if launched_instances.size < number_of_instances_to_launch
|
|
191
|
+
STDERR.puts "Failed to launch desired number of instances. (#{launched_instances.size} instead of #{number_of_instances_to_launch})"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
launched_instances.each do |launched_instance|
|
|
195
|
+
instances << Instance.new(launched_instance[:aws_instance_id], config[:minimum_lifetime_in_minutes])
|
|
196
|
+
STDERR.puts "launched instance #{launched_instance[:aws_instance_id]}"
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def terminate_instances(instances_to_terminate, force = false)
|
|
201
|
+
instances_to_terminate = instances_to_terminate.find_all do |instance|
|
|
202
|
+
# Don't stop instances before minimum_lifetime_in_minutes
|
|
203
|
+
force || instance.minimum_lifetime_elapsed?
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
instances_to_terminate.each do |instance|
|
|
207
|
+
STDERR.puts "Terminating instance #{instance.id}"
|
|
208
|
+
instances.delete instance
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
ec2.terminate_instances(instances_to_terminate.collect { |instance| instance.id.to_s }) if instances_to_terminate.any?
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
class Instance
|
|
216
|
+
attr_accessor :id, :load_estimate, :last_idle_interval, :last_busy_interval, :last_report_time, :startup_time, :minimum_lifetime_in_minutes
|
|
217
|
+
|
|
218
|
+
def initialize(id, minimum_lifetime_in_minutes = 55)
|
|
219
|
+
@id = id
|
|
220
|
+
@minimum_lifetime_in_minutes = minimum_lifetime_in_minutes
|
|
221
|
+
@load_estimate = 0
|
|
222
|
+
@last_idle_interval = 0
|
|
223
|
+
@last_busy_interval = 0
|
|
224
|
+
@last_report_time = Time.now
|
|
225
|
+
@startup_time = Time.now
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def update_load
|
|
229
|
+
@load_estimate = (last_busy_interval.to_i / (last_idle_interval.to_i + last_busy_interval.to_i).to_f * 100)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def minimum_lifetime_elapsed?
|
|
233
|
+
(Time.now - startup_time) > (minimum_lifetime_in_minutes * 60)
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
class InstanceStatus
|
|
238
|
+
attr_accessor :instance_id, :state, :last_interval, :timestamp
|
|
239
|
+
|
|
240
|
+
def initialize(instance_id, state, last_interval, timestamp)
|
|
241
|
+
@instance_id, @state, @last_interval, @timestamp = instance_id, state, last_interval, timestamp
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def self.parse(xml_or_yaml)
|
|
245
|
+
if xml_or_yaml =~ /<InstanceStatus>/
|
|
246
|
+
# FIXME Parse the xml
|
|
247
|
+
else
|
|
248
|
+
status = YAML.load(xml_or_yaml)
|
|
249
|
+
new(status[:instance_id], status[:state], status[:last_interval], status[:timestamp])
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|