auser-poolparty 0.0.9 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/CHANGELOG +3 -2
  2. data/Rakefile +61 -4
  3. data/bin/instance +3 -1
  4. data/bin/pool +6 -2
  5. data/config/sample-config.yml +4 -4
  6. data/lib/core/object.rb +3 -0
  7. data/lib/helpers/plugin_spec_helper.rb +59 -0
  8. data/lib/modules/ec2_wrapper.rb +3 -1
  9. data/lib/modules/file_writer.rb +1 -1
  10. data/lib/modules/vlad_override.rb +83 -83
  11. data/lib/poolparty.rb +31 -13
  12. data/lib/poolparty/application.rb +22 -15
  13. data/lib/poolparty/init.rb +1 -1
  14. data/lib/poolparty/master.rb +41 -28
  15. data/lib/poolparty/monitors.rb +1 -3
  16. data/lib/poolparty/monitors/cpu.rb +7 -3
  17. data/lib/poolparty/monitors/memory.rb +14 -7
  18. data/lib/poolparty/monitors/web.rb +11 -5
  19. data/lib/poolparty/provider/packages/essential.rb +1 -1
  20. data/lib/poolparty/provider/packages/heartbeat.rb +1 -1
  21. data/lib/poolparty/provider/packages/ruby.rb +1 -10
  22. data/lib/poolparty/remote_instance.rb +5 -18
  23. data/lib/poolparty/remoter.rb +55 -4
  24. data/lib/poolparty/scheduler.rb +15 -25
  25. data/lib/poolparty/thread_pool.rb +94 -0
  26. data/poolparty.gemspec +9 -6
  27. data/spec/application_spec.rb +32 -13
  28. data/spec/callback_spec.rb +20 -1
  29. data/spec/file_writer_spec.rb +1 -0
  30. data/spec/kernel_spec.rb +13 -0
  31. data/spec/master_spec.rb +50 -20
  32. data/spec/monitors/cpu_monitor_spec.rb +1 -1
  33. data/spec/plugin_manager_spec.rb +9 -17
  34. data/spec/plugin_spec.rb +34 -34
  35. data/spec/poolparty_spec.rb +41 -1
  36. data/spec/remote_instance_spec.rb +5 -18
  37. data/spec/scheduler_spec.rb +7 -6
  38. data/spec/spec_helper.rb +8 -18
  39. metadata +19 -6
  40. data/lib/poolparty/tasks/package.rake +0 -53
@@ -10,12 +10,12 @@ module PoolParty
10
10
  # The application options
11
11
  def options(opts={})
12
12
  @options ||= make_options(opts)
13
- end
13
+ end
14
14
  # Make the options with the config_file overrides included
15
15
  # Default config file assumed to be at config/config.yml
16
16
  def make_options(opts={})
17
17
  loading_options = opts.delete(:optsparse) || {}
18
- loading_options.merge!( {:argv => opts.delete(:argv)} )
18
+ loading_options.merge!( opts.delete(:argv) || {} )
19
19
 
20
20
  config_file_location = (default_options[:config_file] || opts[:config_file])
21
21
  # If the config_file options are specified and not empty
@@ -29,7 +29,7 @@ module PoolParty
29
29
 
30
30
  default_options.merge!(opts)
31
31
  load_options!(loading_options) # Load command-line options
32
- default_options.merge!(local_user_data) unless local_user_data == {}
32
+ default_options.merge!(local_user_data) unless local_user_data.nil?
33
33
 
34
34
  OpenStruct.new(default_options)
35
35
  end
@@ -41,10 +41,13 @@ module PoolParty
41
41
  op.banner = opts[:banner] if opts[:banner]
42
42
  op.on('-A key', '--access-key key', "Ec2 access key (ENV['ACCESS_KEY'])") { |key| default_options[:access_key] = key }
43
43
  op.on('-S key', '--secret-access-key key', "Ec2 secret access key (ENV['SECRET_ACCESS_KEY'])") { |key| default_options[:secret_access_key] = key }
44
- op.on('-I ami', '--image-id id', "AMI instance (default: 'ami-4a46a323')") {|id| default_options[:ami] = id }
44
+ op.on('-I ami', '--image-id id', "AMI instance (default: 'ami-40bc5829')") {|id| default_options[:ami] = id }
45
45
  op.on('-k keypair', '--keypair name', "Keypair name (ENV['KEYPAIR_NAME'])") { |key| default_options[:keypair] = key }
46
- op.on('-b bucket', '--bucket bucket', "Application bucket") { |bucket| default_options[:shared_bucket] = bucket }
47
- op.on('-D ec2 directory', '--ec2-dir dir', "Directory with ec2 data (default: '~/.ec2')") {|id| default_options[:ec2_dir] = id }
46
+ op.on('-b bucket', '--bucket bucket', "Application bucket") { |bucket| default_options[:shared_bucket] = bucket }
47
+ # //THIS IS WHERE YOU LEFT OFF
48
+ op.on('-D working directory', '--dir dir', "Working directory") { |d| default_options[:working_directory] = d }
49
+
50
+ op.on('--ec2-dir dir', "Directory with ec2 data (default: '~/.ec2')") {|id| default_options[:ec2_dir] = id }
48
51
  op.on('-r names', '--services names', "Monitored services (default: '')") {|id| default_options[:services] = id }
49
52
  op.on('-c file', '--config-file file', "Config file (default: '')") {|file| default_options[:config_file] = file }
50
53
  op.on('-l plugin_dir', '--plugin-dir dir', "Plugin directory (default: '')") {|file| default_options[:plugin_dir] = file }
@@ -106,13 +109,14 @@ module PoolParty
106
109
  :username => "root",
107
110
  :ec2_dir => ENV["EC2_HOME"],
108
111
  :keypair => ENV["KEYPAIR_NAME"],
109
- :ami => 'ami-4a46a323',
112
+ :ami => 'ami-44bd592d',
110
113
  :shared_bucket => "",
111
- :expand_when => "web_usage < 1.5\n memory > 0.85",
114
+ :expand_when => "web < 1.5\n memory > 0.85",
112
115
  :contract_when => "cpu < 0.20\n memory < 0.10",
113
116
  :os => "ubuntu",
114
117
  :plugin_dir => "vendor",
115
- :install_on_load => false
118
+ :install_on_load => false,
119
+ :working_directory => Dir.pwd
116
120
  }
117
121
  end
118
122
  # Services monitored by Heartbeat
@@ -126,16 +130,19 @@ module PoolParty
126
130
  :secret_access_key => secret_access_key,
127
131
  :user_data => user_data}.to_yaml
128
132
  end
129
- def local_user_data
130
- @local_user_data ||=
133
+ def local_user_data
131
134
  begin
132
- @@timer.timeout(5.seconds) do
133
- YAML.load(open("http://169.254.169.254/latest/user-data").read)
135
+ @@timer.timeout(3.seconds) do
136
+ @local_user_data ||=YAML.load(open("http://169.254.169.254/latest/user-data").read)
134
137
  end
135
138
  rescue Exception => e
136
- {}
139
+ @local_user_data = {}
137
140
  end
138
141
  end
142
+ # For testing purposes
143
+ def reset!
144
+ @local_user_data = nil
145
+ end
139
146
  # Keypair path
140
147
  # Idiom:
141
148
  # /Users/username/.ec2/id_rsa-name
@@ -168,7 +175,7 @@ module PoolParty
168
175
  end
169
176
  end
170
177
  def version
171
- PoolParty::Version::STRING
178
+ PoolParty::Version.string
172
179
  end
173
180
  def install_on_load?(bool=false)
174
181
  options.install_on_load == true || bool
@@ -1,6 +1,6 @@
1
1
  =begin rdoc
2
2
  Load the files in order
3
3
  =end
4
- %w(optioner application monitors scheduler provider remoter remoting remote_instance master tasks plugin plugin_manager).each do |f|
4
+ %w(optioner application thread_pool scheduler provider remoter remoting remote_instance master monitors tasks plugin plugin_manager).each do |f|
5
5
  require File.join(File.dirname(__FILE__), f)
6
6
  end
@@ -5,17 +5,16 @@ module PoolParty
5
5
  class Master < Remoting
6
6
  include Aska
7
7
  include Callbacks
8
- include Monitors
9
8
  # ############################
10
9
  include Remoter
11
10
  # ############################
12
11
  include FileWriter
13
12
 
14
13
  def initialize
15
- super
14
+ super
16
15
 
17
- self.class.send :rules, :contract_when, Application.options.contract_when
18
- self.class.send :rules, :expand_when, Application.options.expand_when
16
+ self.class.send :rules, :contract_when, Application.options.contract_when unless are_rules?(:contract_when)
17
+ self.class.send :rules, :expand_when, Application.options.expand_when unless are_rules?(:expand_when)
19
18
  end
20
19
  # Start the cloud
21
20
  def start_cloud!
@@ -82,11 +81,16 @@ module PoolParty
82
81
  sudo apt-get update --fix-missing
83
82
  EOE
84
83
 
85
- execute_tasks do
86
- ssh(update_apt_string)
87
- end
84
+ ssh(update_apt_string)
85
+
88
86
  Provider.install_poolparty(cloud_ips)
89
87
  Provider.install_userpackages(cloud_ips)
88
+
89
+ # For plugins
90
+ nodes.each do |node|
91
+ node.install
92
+ end
93
+
90
94
  end
91
95
  end
92
96
  def cloud_ips
@@ -106,13 +110,14 @@ module PoolParty
106
110
  end
107
111
  # Daemonize only if we are not in the test environment
108
112
  run_thread_loop(:daemonize => !Application.test?) do
109
- add_task {launch_minimum_instances} # If the base instances go down...
113
+ add_task {PoolParty.message "Checking cloud"}
114
+ add_task {launch_minimum_instances}
110
115
  add_task {reconfigure_cloud_when_necessary}
111
116
  add_task {scale_cloud!}
112
117
  add_task {check_stats}
113
118
  end
114
119
  rescue Exception => e
115
- puts "There was an error: #{e.nice_message}"
120
+ Process.kill("HUP", Process.pid)
116
121
  end
117
122
  end
118
123
  alias_method :start_monitor, :start_monitor!
@@ -120,6 +125,8 @@ module PoolParty
120
125
  end
121
126
  # Sole purpose to check the stats, mainly in a plugin
122
127
  def check_stats
128
+ str = registered_monitors.collect {|m| "#{m}"}
129
+ PoolParty.message "Monitors: #{str.join(", ")}"
123
130
  end
124
131
  # Add an instance if the cloud needs one ore terminate one if necessary
125
132
  def scale_cloud!
@@ -132,9 +139,11 @@ module PoolParty
132
139
  # This is a basic check against the local store of the instances that have the
133
140
  # stack installed.
134
141
  def reconfigure_cloud_when_necessary
142
+ PoolParty.message "#{number_of_unconfigured_nodes} unconfigured nodes"
135
143
  configure_cloud if number_of_unconfigured_nodes > 0
136
144
  end
137
145
  def number_of_unconfigured_nodes
146
+ # TODO: Find a better way to tell if the nodes are configured.
138
147
  nodes.reject {|a| a.stack_installed? }.size
139
148
  end
140
149
  def grow_by(num=1)
@@ -147,15 +156,24 @@ module PoolParty
147
156
  end
148
157
  def shrink_by(num=1)
149
158
  num.times do |i|
159
+ # Get the last node that is not the master
150
160
  node = nodes.reject {|a| a.master? }[-1]
151
- request_termination_of_instance(node.instance_id) if node
161
+ res = request_termination_of_instance(node.instance_id) if node
162
+ PoolParty.message "#{res ? "Could" : "Could not"} shutdown instance"
152
163
  end
153
164
  wait_for_all_instances_to_terminate
154
165
  configure_cloud
155
166
  end
156
-
167
+ def make_base_tmp_dir(c)
168
+ `mkdir #{base_tmp_dir}` unless File.directory?(base_tmp_dir)
169
+ end
170
+ before :build_and_send_config_files_in_temp_directory, :make_base_tmp_dir
157
171
  def build_and_send_config_files_in_temp_directory
158
172
  require 'ftools'
173
+ if File.directory?(Application.plugin_dir)
174
+ Kernel.system("tar -czf #{base_tmp_dir}/plugins.tar.gz #{File.basename(Application.plugin_dir)}")
175
+ end
176
+
159
177
  File.copy(get_config_file_for("cloud_master_takeover"), "#{base_tmp_dir}/cloud_master_takeover")
160
178
  File.copy(get_config_file_for("heartbeat.conf"), "#{base_tmp_dir}/ha.cf")
161
179
 
@@ -180,12 +198,12 @@ module PoolParty
180
198
  build_heartbeat_config_file_for(node)
181
199
  build_heartbeat_resources_file_for(node)
182
200
  end
183
- end
201
+ end
184
202
  end
185
203
  def cleanup_tmp_directory(c)
186
204
  Dir["#{base_tmp_dir}/*"].each {|f| FileUtils.rm_rf f} if File.directory?("tmp/")
187
205
  end
188
- before :build_and_send_config_files_in_temp_directory, :cleanup_tmp_directory
206
+ before :build_and_send_config_files_in_temp_directory, :cleanup_tmp_directory
189
207
  # Send the files to the nodes
190
208
  def send_config_files_to_nodes(c)
191
209
  run_array_of_tasks(rsync_tasks("#{base_tmp_dir}/*", "#{remote_base_tmp_dir}"))
@@ -205,12 +223,18 @@ chmod +x #{script_file}
205
223
  end
206
224
  # Add an instance if the load is high
207
225
  def add_instance_if_load_is_high
208
- grow_by(1) if expand?
226
+ if expand?
227
+ PoolParty.message "Cloud needs expansion"
228
+ grow_by(1)
229
+ end
209
230
  end
210
231
  alias_method :add_instance, :add_instance_if_load_is_high
211
232
  # Teardown an instance if the load is pretty low
212
233
  def terminate_instance_if_load_is_low
213
- shrink_by(1) if contract?
234
+ if contract?
235
+ PoolParty.message "Cloud to shrink"
236
+ shrink_by(1)
237
+ end
214
238
  end
215
239
  alias_method :terminate_instance, :terminate_instance_if_load_is_low
216
240
  # FOR MONITORING
@@ -270,7 +294,7 @@ chmod +x #{script_file}
270
294
  def get_config_file_for(name)
271
295
  if File.exists?("#{user_dir}/config/#{name}")
272
296
  "#{user_dir}/config/#{name}"
273
- else
297
+ else
274
298
  "#{root_dir}/config/#{name}"
275
299
  end
276
300
  end
@@ -356,6 +380,7 @@ chmod +x #{script_file}
356
380
 
357
381
  class << self
358
382
  include PoolParty
383
+ include FileWriter
359
384
 
360
385
  def with_nodes(&block)
361
386
  new.nodes.each &block
@@ -377,18 +402,6 @@ chmod +x #{script_file}
377
402
  def get_next_node(node)
378
403
  new.get_next_node(node)
379
404
  end
380
- # Build a heartbeat_config_file from the config file in the config directory and return a tempfile
381
- def build_heartbeat_config_file_for(node)
382
- return nil unless node
383
- new.build_heartbeat_config_file_for(node)
384
- end
385
- # Build a heartbeat resources file from the config directory and return a tempfile
386
- def build_heartbeat_resources_file_for(node)
387
- return nil unless node && get_next_node(node)
388
- new.write_to_file_for("haresources", node) do
389
- "#{node.haproxy_resources_entry}\n#{get_next_node(node).haproxy_resources_entry}"
390
- end
391
- end
392
405
  def set_hosts(c, remotetask=nil)
393
406
  unless remotetask.nil?
394
407
  rt = remotetask
@@ -8,6 +8,4 @@ module PoolParty
8
8
  module Remote
9
9
  end
10
10
  end
11
- end
12
-
13
- Dir["monitors/*"].each {|f| require f}
11
+ end
@@ -1,7 +1,9 @@
1
1
  =begin rdoc
2
2
  Basic monitor on the cpu stats
3
3
  =end
4
- module Cpu
4
+ require "poolparty"
5
+
6
+ module Cpu
5
7
  module Master
6
8
  def cpu
7
9
  nodes.size > 0 ? nodes.inject(0) {|i,a| i+=a.cpu } / nodes.size : 0.0
@@ -9,8 +11,10 @@ module Cpu
9
11
  end
10
12
 
11
13
  module Remote
12
- def cpu
13
- ssh("uptime").split(/\s+/)[-3].to_f rescue 0.0
14
+ def cpu
15
+ str = run("uptime").split(/\s+/)[-3].to_f rescue 0.0
16
+ PoolParty.message "Cpu usage: #{str}"
17
+ str
14
18
  end
15
19
  end
16
20
 
@@ -1,6 +1,8 @@
1
1
  =begin rdoc
2
2
  Basic monitor on the cpu stats
3
3
  =end
4
+ require "poolparty"
5
+
4
6
  module Memory
5
7
  module Master
6
8
  # Get the average memory usage over the cloud
@@ -11,14 +13,19 @@ module Memory
11
13
 
12
14
  module Remote
13
15
  def memory
14
- str = ssh("free -m | grep -i mem")
15
- total_memory = str.split[1].to_f
16
- used_memory = str.split[2].to_f
16
+ out = begin
17
+ str = run("free -m | grep -i mem")
18
+
19
+ total_memory = str.split[1].to_f
20
+ used_memory = str.split[2].to_f
17
21
 
18
- used_memory / total_memory
19
- rescue
20
- 0.0
21
- end
22
+ used_memory / total_memory
23
+ rescue Exception => e
24
+ 0.0
25
+ end
26
+ PoolParty.message "Memory: #{out}"
27
+ out
28
+ end
22
29
  end
23
30
 
24
31
  end
@@ -1,6 +1,8 @@
1
1
  =begin rdoc
2
2
  Basic monitor on the cpu stats
3
3
  =end
4
+ require "poolparty"
5
+
4
6
  module Web
5
7
  module Master
6
8
  # Get the average web request capabilities over the cloud
@@ -11,11 +13,15 @@ module Web
11
13
 
12
14
  module Remote
13
15
  def web
14
- str = ssh("httperf --server localhost --port #{Application.client_port} --num-conn 3 --timeout 5 | grep 'Request rate'")
15
- str[/[.]* ([\d]*\.[\d]*) [.]*/, 0].chomp.to_f
16
- rescue
17
- 0.0
18
- end
16
+ out = begin
17
+ str = run("httperf --server localhost --port #{Application.client_port} --num-conn 3 --timeout 5 | grep 'Request rate'")
18
+ str[/[.]* ([\d]*\.[\d]*) [.]*/, 0].chomp.to_f
19
+ rescue Exception => e
20
+ 0.0
21
+ end
22
+ PoolParty.message "Web requests: #{out}"
23
+ out
24
+ end
19
25
  end
20
26
 
21
27
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  package :build_essential do
4
4
  description 'Build tools'
5
- apt %w(build-essential)
5
+ apt %w( build-essential )
6
6
  end
@@ -1,4 +1,4 @@
1
1
  package :heartbeat, :provides => :failover do
2
2
  description "Heartbeat Linux HA project"
3
- apt %w(heartbeat-2)
3
+ apt %w( heartbeat-2 )
4
4
  end
@@ -22,16 +22,7 @@ package :rubygems do
22
22
  requires :ruby
23
23
  end
24
24
 
25
- package :poolparty_required_gems do
26
- description "required gems"
27
- gems %w( SQS aws-s3 amazon-ec2 aska rake rcov auser-poolparty )
28
- end
29
-
30
25
  package :required_gems do
31
26
  description "Pool party gem"
32
- gem "poolparty" do
33
- source 'http://gems.github.com -y'
34
- end
35
-
36
- required :poolparty_required_gems
27
+ gems %w( SQS aws-s3 amazon-ec2 auser-aska rake rcov auser-poolparty vlad --no-ri --no-rdoc)
37
28
  end
@@ -22,7 +22,7 @@ module PoolParty
22
22
  @number = obj[:number] || 0 # Defaults to the master
23
23
  @status = obj[:status] || "running"
24
24
  @launching_time = obj[:launching_time] || Time.now
25
- @keypair = obj[:keypair] || Application.keypair
25
+ @keypair = obj[:keypair] || Application.keypair rescue ""
26
26
  end
27
27
 
28
28
  # Host entry for this instance
@@ -159,18 +159,9 @@ module PoolParty
159
159
  mkdir -p /data && /usr/bin/s3fs #{Application.shared_bucket} -o accessKeyId=#{Application.access_key} -o secretAccessKey=#{Application.secret_access_key} -o nonempty /data
160
160
  EOC
161
161
  end
162
- end
163
-
162
+ end
164
163
  # Installs with one commandline and an scp, rather than 10
165
164
  def install
166
- # unless stack_installed?
167
- # execute_tasks do
168
- # # scp(base_install_script, "~/base_install.sh")
169
- # ssh("chmod +x base_install.sh && /bin/sh base_install.sh && rm base_install.sh && echo 'installed!' ")
170
- # end
171
- # PoolParty.message "After install execute_tasks"
172
- # mark_installed
173
- # end
174
165
  end
175
166
  # Login to store the authenticity
176
167
  def login_once
@@ -192,12 +183,8 @@ module PoolParty
192
183
  def configure
193
184
  end
194
185
  def update_plugin_string
195
- reset!
196
- str = "mkdir -p #{Application.plugin_dir} && cd #{Application.plugin_dir}\n"
197
- installed_plugins.each do |plugin_source|
198
- str << "git clone #{plugin_source}\n"
199
- end
200
- str
186
+ dir = File.basename(Application.plugin_dir)
187
+ "if [[ -f plugins.tar.gz ]]; then mkdir -p #{dir} && tar -zxf plugins.tar.gz -C #{dir}; fi"
201
188
  end
202
189
  # Is this the master and if not, is the master running?
203
190
  def is_not_master_and_master_is_not_running?
@@ -224,7 +211,7 @@ module PoolParty
224
211
  end
225
212
  end
226
213
  def stack_installed?
227
- @stack_installed ||= false
214
+ @stack_installed ||= run("cat ~/.installed")
228
215
  end
229
216
  def mark_installed(caller=nil)
230
217
  run_now("echo 'installed' > ~/.installed")