poolparty 1.4.6 → 1.4.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/VERSION.yml +2 -2
  2. data/bin/cloud-contract +6 -1
  3. data/bin/cloud-list +5 -6
  4. data/bin/cloud-vnc +39 -0
  5. data/examples/rds_cloud.rb +44 -0
  6. data/lib/cloud_providers/connections.rb +0 -1
  7. data/lib/cloud_providers/ec2/ec2.rb +86 -25
  8. data/lib/cloud_providers/ec2/ec2_instance.rb +6 -2
  9. data/lib/cloud_providers/ec2/helpers/ec2_helper.rb +19 -6
  10. data/lib/cloud_providers/ec2/helpers/elastic_load_balancer.rb +3 -3
  11. data/lib/cloud_providers/ec2/helpers/rds_instance.rb +110 -0
  12. data/lib/cloud_providers/remote_instance.rb +5 -3
  13. data/lib/poolparty/cloud.rb +8 -3
  14. data/lib/poolparty/pool.rb +12 -0
  15. data/test/fixtures/clouds/rds_cloud.rb +14 -0
  16. data/test/fixtures/clouds/rds_missing_params.rb +11 -0
  17. data/test/fixtures/ec2/ec2-describe-security-groups_response_body.xml +23 -0
  18. data/test/fixtures/ec2/rds-describe-db-instances-empty_response_body.xml +9 -0
  19. data/test/lib/poolparty/rds_test.rb +35 -0
  20. data/test/test_helper.rb +4 -4
  21. data/vendor/gems/amazon-ec2/ChangeLog +15 -0
  22. data/vendor/gems/amazon-ec2/README.rdoc +27 -24
  23. data/vendor/gems/amazon-ec2/README_dev.rdoc +0 -2
  24. data/vendor/gems/amazon-ec2/Rakefile +8 -39
  25. data/vendor/gems/amazon-ec2/VERSION +1 -1
  26. data/vendor/gems/amazon-ec2/amazon-ec2.gemspec +24 -16
  27. data/vendor/gems/amazon-ec2/bin/setup.rb +1 -0
  28. data/vendor/gems/amazon-ec2/lib/AWS/Autoscaling/autoscaling.rb +6 -16
  29. data/vendor/gems/amazon-ec2/lib/AWS/EC2/availability_zones.rb +8 -0
  30. data/vendor/gems/amazon-ec2/lib/AWS/EC2/console.rb +2 -0
  31. data/vendor/gems/amazon-ec2/lib/AWS/EC2/devpay.rb +18 -0
  32. data/vendor/gems/amazon-ec2/lib/AWS/EC2/elastic_ips.rb +37 -32
  33. data/vendor/gems/amazon-ec2/lib/AWS/EC2/images.rb +43 -27
  34. data/vendor/gems/amazon-ec2/lib/AWS/EC2/instances.rb +136 -99
  35. data/vendor/gems/amazon-ec2/lib/AWS/EC2/keypairs.rb +3 -17
  36. data/vendor/gems/amazon-ec2/lib/AWS/EC2/security_groups.rb +4 -23
  37. data/vendor/gems/amazon-ec2/lib/AWS/EC2/snapshots.rb +38 -17
  38. data/vendor/gems/amazon-ec2/lib/AWS/EC2/volumes.rb +6 -21
  39. data/vendor/gems/amazon-ec2/lib/AWS/EC2.rb +2 -2
  40. data/vendor/gems/amazon-ec2/lib/AWS/ELB/load_balancers.rb +11 -38
  41. data/vendor/gems/amazon-ec2/lib/AWS/RDS/rds.rb +522 -0
  42. data/vendor/gems/amazon-ec2/lib/AWS/RDS.rb +73 -0
  43. data/vendor/gems/amazon-ec2/lib/AWS/exceptions.rb +103 -25
  44. data/vendor/gems/amazon-ec2/lib/AWS.rb +19 -9
  45. data/vendor/gems/amazon-ec2/perftools/ec2prof +0 -0
  46. data/vendor/gems/amazon-ec2/perftools/ec2prof-results.dot +130 -191
  47. data/vendor/gems/amazon-ec2/perftools/ec2prof-results.txt +100 -126
  48. data/vendor/gems/amazon-ec2/perftools/ec2prof.symbols +102 -129
  49. data/vendor/gems/amazon-ec2/test/test_Autoscaling_groups.rb +3 -2
  50. data/vendor/gems/amazon-ec2/test/test_EC2_images.rb +32 -0
  51. data/vendor/gems/amazon-ec2/test/test_EC2_instances.rb +204 -22
  52. data/vendor/gems/amazon-ec2/test/test_EC2_snapshots.rb +1 -1
  53. data/vendor/gems/amazon-ec2/test/test_ELB_load_balancers.rb +2 -2
  54. data/vendor/gems/amazon-ec2/test/test_RDS.rb +354 -0
  55. data/vendor/gems/amazon-ec2/wsdl/2009-10-31.ec2.wsdl +4261 -0
  56. data/vendor/gems/amazon-ec2/wsdl/2009-11-30.ec2.wsdl +4668 -0
  57. metadata +17 -2
data/VERSION.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
- :minor: 4
3
- :patch: 6
4
2
  :major: 1
3
+ :minor: 4
5
4
  :build:
5
+ :patch: 7
data/bin/cloud-contract CHANGED
@@ -19,6 +19,11 @@ EOS
19
19
  node_to_terminate = cld.nodes.last
20
20
  # node_to_terminate.cloud = cld
21
21
 
22
+ puts cld.nodes.size
23
+ if cld.nodes.size - 1 < cld.minimum_instances
24
+ puts "Contracting instances by 1 in cloud #{cld.name} will lower the number of instances below specified minimum (#{cld.minimum_instances})"
25
+ next
26
+ end
22
27
  msg = [
23
28
  "Contracting cloud #{cld.name} (#{cld.keypair}) by 1",
24
29
  "#{cld.nodes.size} running instances (#{cld.minimum_instances} - #{cld.maximum_instances})",
@@ -32,4 +37,4 @@ EOS
32
37
  puts "#{node_to_terminate.public_ip} has been terminated"
33
38
  end
34
39
  end
35
- end
40
+ end
data/bin/cloud-list CHANGED
@@ -17,13 +17,14 @@ EOS
17
17
  short_desc "show a list of the current instances on the clouds"
18
18
 
19
19
  run do |command|
20
+ puts "#{pool.name}"
21
+ puts "==="
20
22
  @loaded_clouds.each do |cld|
21
23
  if command[:instance_id]
22
24
  require 'pp'
23
25
  pp result = cld.nodes(:instance_id=>command[:instance_id])
24
26
  else
25
- line = "-----------------------------"
26
- msg = ["Listing cloud #{cld.name}"]
27
+ msg = ["* #{cld.name} cloud, #{cld.image_id}, #{cld.instance_type}"]
27
28
 
28
29
  hsh = {}
29
30
 
@@ -32,14 +33,12 @@ EOS
32
33
  end
33
34
 
34
35
  hsh.each do |k,v|
35
- msg << "#{k.capitalize} nodes"
36
- msg << line
36
+ msg << " - #{k} nodes"
37
37
  v.each do |a|
38
- msg << " #{a.instance_id}\t#{a.status}\t#{a.public_ip}\t#{a.private_ip}\t#{a.dns_name}"
38
+ msg << " - #{a.instance_id}\t#{a.status}\t#{a.public_ip}\t#{a.private_ip}\t#{a.zone}"
39
39
  end
40
40
  msg << ""
41
41
  end
42
-
43
42
  puts msg
44
43
  end
45
44
 
data/bin/cloud-vnc ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.dirname(__FILE__) + "/../lib")
3
+ require "poolparty"
4
+
5
+ require 'git-style-binary/command'
6
+
7
+ GitStyleBinary.command do
8
+ banner <<-EOS
9
+ Usage: #{$0} #{all_options_string}
10
+
11
+ start a vnc server and create an ssh tunnel
12
+ EOS
13
+
14
+ short_desc "ssh into an instance of the cloud"
15
+
16
+ opt :instance_number, "Start VNC server on this instance number", :type => :integer, :default => 0
17
+ opt :local_port, "The local port to tunnel to", :type => :integer, :default => 5900
18
+ opt :display, "The remote display number to start the VNC Server on", :type => :integer, :default => 0
19
+
20
+ run do |command|
21
+ cloud = @loaded_clouds.first
22
+
23
+ if !cloud.nodes.empty?
24
+ inst = cloud.nodes[command[:instance_number]]
25
+ if inst
26
+ inst.ssh(["x11vnc -localhost -nopw -display :#{command[:display]}"],
27
+ {'-X' => nil,
28
+ '-C' => nil,
29
+ ' -o' => 'UserKnownHostsFile=/dev/null',
30
+ '-L' => "#{command[:local_port]}:localhost:5900"})
31
+ else
32
+ puts("Error: The instance number (#{command[:instance_number]}) is too high for the current number of instances")
33
+ end
34
+ else
35
+ puts "No running instances can be found"
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,44 @@
1
+ # example pool with RDS enabled
2
+
3
+ pool :poolparty do
4
+ cloud :app do
5
+ using :ec2
6
+
7
+ # this block will create an RDS DB instance
8
+ # by default, the instance id will match the containing cloud
9
+ # ("poolparty-app" in this example)
10
+ rds do
11
+ username "admin" # required
12
+ password "secret" # required
13
+
14
+ # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
15
+ # these properties have the following overridable defaults:
16
+ #
17
+ # storage 5
18
+ # instance_class "db.m1.small"
19
+ # engine "db.m1.small"
20
+
21
+
22
+
23
+ # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
24
+ # by default, a user DB will be created with a name that matches the containing cloud...
25
+ # ...except RDS does not allow hyphens in DB names, so it will be pool name *underscore* cloud name
26
+ # ("poolparty_app" in this example).
27
+ #
28
+ # to override this, use the database method, like so:
29
+ # database :db1
30
+ # - or -
31
+ # databases :production, :staging # :databases is aliased to :database for nice DSL reading. :)
32
+
33
+
34
+
35
+ # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
36
+ # You are required to explicitly open access to the DB server to IP addresses (for outside EC2) or security groups (for inside EC2)
37
+ # access will be granted to your containing cloud's security groups unless you explicitly list security group authorization.
38
+ #
39
+ # to override, use the :authorize method, like so:
40
+ # authorize :networks => "1.2.3.4/24" # <--- will authorize access to this CIDR block *in addition to* your EC2 security group
41
+ # authorize :networks => ["1.2.3.4/32", "10.10.10.10/24"], :security_groups => "my_ec2_group"
42
+ end
43
+ end
44
+ end
@@ -31,7 +31,6 @@ module CloudProviders
31
31
  def ssh( commands=[], extra_ssh_ops={})
32
32
  commands = commands.compact.join(' && ') if commands.is_a?(Array)
33
33
  cmd_string = "ssh #{user}@#{host} #{ssh_options(extra_ssh_ops)}"
34
- p commands
35
34
  if commands.empty?
36
35
  #TODO: replace this with a IO.popen call with read_nonblocking to show progress, and accept input
37
36
  Kernel.system(cmd_string)
@@ -14,11 +14,11 @@ module CloudProviders
14
14
  class Ec2 < CloudProvider
15
15
  # Set the aws keys from the environment, or load from /etc/poolparty/env.yml if the environment variable is not set
16
16
  def self.default_access_key
17
- ENV['EC2_ACCESS_KEY'] || load_keys_from_file[:access_key]
17
+ ENV['EC2_ACCESS_KEY'] || load_keys_from_file[:access_key] || load_keys_from_credential_file[:access_key]
18
18
  end
19
19
 
20
20
  def self.default_secret_access_key
21
- ENV['EC2_SECRET_KEY'] || load_keys_from_file[:secret_access_key]
21
+ ENV['EC2_SECRET_KEY'] || load_keys_from_file[:secret_access_key] || load_keys_from_credential_file[:secret_access_key]
22
22
  end
23
23
 
24
24
  def self.default_private_key
@@ -42,7 +42,11 @@ module CloudProviders
42
42
  end
43
43
 
44
44
  def self.default_cloud_cert
45
- ENV['CLOUD_CERT'] || ENV['EUCALYPTUS_CERT'] || load_keys_from_file[:cloud_cert]
45
+ ENV['CLOUD_CERT'] || ENV['EUCALYPTUS_CERT'] || load_keys_from_file[:cloud_cert]
46
+ end
47
+
48
+ def self.default_credential_file
49
+ ENV['AWS_CREDENTIAL_FILE'] || load_keys_from_file[:credential_file]
46
50
  end
47
51
 
48
52
  # Load the yaml file containing keys. If the file does not exist, return an empty hash
@@ -52,6 +56,22 @@ module CloudProviders
52
56
  puts("Reading keys from file: #{filename}")
53
57
  @aws_yml = YAML::load( open(filename).read ) || {}
54
58
  end
59
+
60
+ # Load credentials from file
61
+ def self.load_keys_from_credential_file(filename=default_credential_file, caching=true)
62
+ return {:access_key => @access_key, :secret_access_key => @secret_access_key} if @access_key and @secret_access_key
63
+ return {} if filename.nil? or not File.exists?(filename)
64
+ puts("Reading keys from file: #{filename}")
65
+ File.open(filename).each_line {|line|
66
+ if line =~ /AWSAccessKeyId=([a-zA-Z0-9]+)$/
67
+ @access_key=$1.chomp
68
+ elsif line =~ /AWSSecretKey=([^ ]+)$/
69
+ @secret_access_key=$1.chomp
70
+ end
71
+ }
72
+ return {:access_key => @access_key, :secret_access_key => @secret_access_key}
73
+ end
74
+
55
75
 
56
76
  default_options(
57
77
  :instance_type => 'm1.small',
@@ -65,6 +85,7 @@ module CloudProviders
65
85
  :secret_access_key => default_secret_access_key,
66
86
  :ec2_url => default_ec2_url,
67
87
  :s3_url => default_s3_url,
88
+ :credential_file => default_credential_file,
68
89
  :min_count => 1,
69
90
  :max_count => 1,
70
91
  :user_data => '',
@@ -74,33 +95,40 @@ module CloudProviders
74
95
  :block_device_mappings => nil,
75
96
  :ebs_volumes => [] # The volume id of an ebs volume # TODO: ensure this is consistent with :block_device_mappings
76
97
  )
77
-
98
+
78
99
  # Called when the create command is called on the cloud
79
100
  def create!
80
- [:security_groups, :load_balancers].each do |type|
101
+ [:security_groups, :load_balancers, :rds_instances].each do |type|
81
102
  self.send(type).each {|ele| ele.create! }
82
103
  end
83
104
  end
84
-
85
- def run
105
+
106
+ def run
86
107
  puts " for cloud: #{cloud.name}"
87
108
  puts " minimum_instances: #{minimum_instances}"
88
109
  puts " maximum_instances: #{maximum_instances}"
89
110
  puts " security_groups: #{security_group_names.join(", ")}"
90
111
  puts " using keypair: #{keypair}"
91
112
  puts " user: #{user}\n"
92
-
113
+
93
114
  security_groups.each do |sg|
94
115
  sg.run
95
116
  end
96
-
117
+
97
118
  unless load_balancers.empty?
98
119
  load_balancers.each do |lb|
99
120
  puts " load balancer: #{lb.name}"
100
121
  lb.run
101
122
  end
102
123
  end
103
-
124
+
125
+ unless rds_instances.empty?
126
+ rds_instances.each do |rdsi|
127
+ puts " rds instance: #{rdsi.name}"
128
+ rdsi.run
129
+ end
130
+ end
131
+
104
132
  if autoscalers.empty? # not using autoscaling
105
133
  puts "---- live, running instances (#{nodes.size}) ----"
106
134
  if nodes.size < minimum_instances
@@ -191,6 +219,7 @@ module CloudProviders
191
219
  end
192
220
 
193
221
  def contract_by(num=1)
222
+ raise RuntimeError, "Contracting instances by #{num} will lower the number of instances below specified minimum" unless nodes.size - num > minimum_instances
194
223
  num.times do |i|
195
224
  id = nodes[-num].instance_id
196
225
  Ec2Instance.terminate!(:instance_id => id, :cloud => cloud)
@@ -248,9 +277,7 @@ module CloudProviders
248
277
  end
249
278
 
250
279
  def all_nodes
251
- #TODO: need to sort by launch time
252
- #
253
- @nodes ||= describe_instances.select {|i| security_group_names.include?(i.security_groups) }
280
+ @nodes ||= describe_instances.select {|i| security_group_names.include?(i.security_groups) }.sort {|a,b| DateTime.parse(a.launchTime) <=> DateTime.parse(b.launchTime)}
254
281
  end
255
282
 
256
283
  # Describe instances
@@ -266,6 +293,9 @@ module CloudProviders
266
293
  Ec2Instance.new(inst_options)
267
294
  end
268
295
  end.flatten
296
+ rescue AWS::InvalidClientTokenId => e # AWS credentials invalid
297
+ puts "Error contacting AWS: #{e}"
298
+ raise e
269
299
  rescue Exception => e
270
300
  []
271
301
  end
@@ -285,21 +315,37 @@ module CloudProviders
285
315
  def elastic_ip(*ips)
286
316
  ips.each {|ip| elastic_ips << ip}
287
317
  end
288
-
318
+
319
+ def rds(given_name=cloud.proper_name, o={}, &block)
320
+ rds_instances << RdsInstance.new(given_name, sub_opts.merge(o || {}), &block)
321
+ end
322
+
289
323
  # Proxy to the raw Grempe amazon-aws @ec2 instance
290
324
  def ec2
291
- @ec2 ||= AWS::EC2::Base.new( :access_key_id => access_key, :secret_access_key => secret_access_key )
325
+ @ec2 ||= begin
326
+ AWS::EC2::Base.new( :access_key_id => access_key, :secret_access_key => secret_access_key )
327
+ rescue AWS::ArgumentError => e # AWS credentials missing?
328
+ puts "Error contacting AWS: #{e}"
329
+ raise e
330
+ rescue Exception => e
331
+ puts "Generic error #{e.class}: #{e}"
332
+ end
292
333
  end
293
-
334
+
294
335
  # Proxy to the raw Grempe amazon-aws autoscaling instance
295
336
  def as
296
337
  @as = AWS::Autoscaling::Base.new( :access_key_id => access_key, :secret_access_key => secret_access_key )
297
338
  end
298
-
339
+
299
340
  # Proxy to the raw Grempe amazon-aws elastic_load_balancing instance
300
341
  def elb
301
342
  @elb ||= AWS::ELB::Base.new( :access_key_id => access_key, :secret_access_key => secret_access_key )
302
343
  end
344
+
345
+ def awsrds
346
+ @awsrds ||= AWS::RDS::Base.new( :access_key_id => access_key, :secret_access_key => secret_access_key )
347
+ end
348
+
303
349
  def security_group_names
304
350
  security_groups.map {|a| a.to_s }
305
351
  end
@@ -315,11 +361,25 @@ module CloudProviders
315
361
  def elastic_ips
316
362
  @elastic_ips ||= []
317
363
  end
318
-
364
+
365
+ def rds_instances
366
+ @rds_instances ||= []
367
+ end
368
+
319
369
  # Clear the cache
320
370
  def reset!
321
371
  @nodes = @describe_instances = nil
322
372
  end
373
+
374
+ # Read credentials from credential_file if one exists
375
+ def credential_file(file=nil)
376
+ unless file.nil?
377
+ dsl_options[:credential_file]=file
378
+ dsl_options.merge((Ec2.load_keys_from_credential_file(file)))
379
+ else
380
+ fetch(:credential_file)
381
+ end
382
+ end
323
383
 
324
384
  private
325
385
  # Helper to get the options with self as parent
@@ -341,18 +401,19 @@ module CloudProviders
341
401
  end
342
402
  keypair n
343
403
  end
344
-
404
+
345
405
  end
346
406
  end
347
407
 
348
408
  require "#{File.dirname(__FILE__)}/ec2_instance"
349
409
  require "#{File.dirname(__FILE__)}/helpers/ec2_helper"
350
- %w( security_group
351
- authorize
352
- elastic_auto_scaler
353
- elastic_block_store
354
- elastic_load_balancer
410
+ %w( security_group
411
+ authorize
412
+ elastic_auto_scaler
413
+ elastic_block_store
414
+ elastic_load_balancer
355
415
  elastic_ip
416
+ rds_instance
356
417
  revoke).each do |lib|
357
418
  require "#{File.dirname(__FILE__)}/helpers/#{lib}"
358
- end
419
+ end
@@ -12,7 +12,7 @@ module CloudProviders
12
12
  :availability_zones => []
13
13
  )
14
14
 
15
- def initialize(raw_response={})
15
+ def initialize(raw_response={})
16
16
  @raw_response = raw_response
17
17
  self.instance_id = raw_response["instanceId"] rescue nil
18
18
  self.security_groups = raw_response.groupSet.item[0].groupId rescue nil
@@ -32,6 +32,10 @@ module CloudProviders
32
32
  @keypair ||= Keypair.new(self.key_name)
33
33
  end
34
34
 
35
+ def zone
36
+ availability_zones.first
37
+ end
38
+
35
39
  def reachable?
36
40
  ping_port self.public_ip, 22
37
41
  end
@@ -45,7 +49,7 @@ module CloudProviders
45
49
  :min_count => min_count,
46
50
  :max_count => max_count,
47
51
  :key_name => keypair.basename,
48
- :group_id => cloud.security_group_names,
52
+ :security_group => cloud.security_group_names,
49
53
  :user_data => user_data,
50
54
  :instance_type => instance_type,
51
55
  :availability_zone => availability_zone,
@@ -1,6 +1,6 @@
1
1
  module CloudProviders
2
2
  class Ec2Helper < CloudProvider
3
-
3
+
4
4
  def initialize(name=cloud.proper_name, init_opts={}, &block)
5
5
  @name = name
6
6
  if name.is_a?(Hash)
@@ -13,22 +13,35 @@ module CloudProviders
13
13
  instance_eval &block if block
14
14
  after_initialized
15
15
  end
16
-
16
+
17
17
  def elb
18
18
  cloud.elb
19
19
  end
20
-
20
+
21
21
  def ec2
22
22
  cloud.ec2
23
23
  end
24
-
24
+
25
25
  def as
26
26
  cloud.as
27
27
  end
28
-
28
+
29
+ def rds
30
+ cloud.awsrds
31
+ end
32
+
29
33
  def pool
30
34
  cloud.parent
31
35
  end
32
-
36
+
37
+ def self.property(*names)
38
+ names.each do |name|
39
+ define_method name do |*args|
40
+ instance_variable_set("@#{name}", args.first) unless args.empty?
41
+ instance_variable_get("@#{name}")
42
+ end
43
+ end
44
+ end
45
+
33
46
  end
34
47
  end
@@ -21,7 +21,7 @@ module CloudProviders
21
21
  # Remove old nodes that are no longer alive
22
22
  detach_instances_if_necessary
23
23
  # Try to unregister and reregister nodes that are out of service, perhaps it was just a setup bug that the setup took too long
24
- out_of_service_node_listing = instance_healths.select {|a| a[:state] == "OutOfService" }.map {|a| {:instance_id => a[:instance_id]}}
24
+ out_of_service_node_listing = instance_healths.select {|a| a[:state] == "OutOfService" }.map {|a| a[:instance_id] }
25
25
  reset!
26
26
  out_of_service_nodes = nodes.select {|n| out_of_service_node_listing.include?(n.instance_id)}
27
27
  unless out_of_service_nodes.empty?
@@ -63,13 +63,13 @@ module CloudProviders
63
63
  public
64
64
  def attach_instances_if_necessary
65
65
  parent.reset!
66
- instances = parent.nodes.map {|a| {:instance_id => a.instance_id} }
66
+ instances = parent.nodes.map {|a| a.instance_id } # ec2 gem requires this be an array of names
67
67
  elb.register_instances_with_load_balancer(:instances => instances, :load_balancer_name => "#{name}") unless instances.empty?
68
68
  end
69
69
  def detach_instances_if_necessary
70
70
  parent.reset!
71
71
  begin
72
- instances = parent.all_nodes.select {|a| !a.running? }.map {|a| {:instance_id => a.instance_id} }
72
+ instances = parent.all_nodes.select {|a| !a.running? }.map {|a| a.instance_id }
73
73
  elb.deregister_instances_from_load_balancer(:instances => instances, :load_balancer_name => "#{name}") unless instances.empty?
74
74
  rescue Exception => e
75
75
  end
@@ -0,0 +1,110 @@
1
+ module CloudProviders
2
+ class RdsInstance < Ec2Helper
3
+ property :username, :password, :storage, :instance_class, :engine
4
+
5
+ def authorize(options)
6
+ options.each do |key, value|
7
+ authorizations[key.to_s] = value
8
+ end
9
+ end
10
+
11
+ def database(*db_names)
12
+ @databases ||= []
13
+ @databases = @databases + db_names unless db_names.empty?
14
+ @databases
15
+ end
16
+ alias_method :databases, :database
17
+
18
+ def create!
19
+ if should_create_rds_instance?
20
+ puts "-----> Creating RDS Instance: #{instance_id}"
21
+ create_rds_instance!
22
+ end
23
+ end
24
+
25
+ def run
26
+ create! # Just for now, while we migrate to 2 commands
27
+ authorize_access
28
+ # TODO : wait until accessible?
29
+ end
30
+
31
+ def teardown
32
+ puts "-----> Tearing down RDS Instance: #{instance_id}"
33
+ delete_rds_instance!
34
+ end
35
+
36
+ private
37
+
38
+ def authorizations
39
+ @authorizations ||= {}
40
+ end
41
+
42
+ def after_initialized
43
+ raise "username must be specified" if self.username.nil?
44
+ raise "password must be specified" if self.password.nil?
45
+ raise "invalid password format (letters and digits only)" unless self.password =~ /[a-z][a-z0-9]*/i
46
+ raise "EC2 user id must be defined in ENV or config" if Ec2.default_user_id.nil?
47
+ end
48
+
49
+ def rds_instances
50
+ @rds_instances ||= (rds.describe_db_instances.DescribeDBInstancesResult.DBInstances || {})['DBInstance'] || []
51
+ @rds_instances = [@rds_instances] unless @rds_instances.is_a?(Array)
52
+ @rds_instances
53
+ end
54
+
55
+ def should_create_rds_instance?
56
+ rds_instances.select{|i| i.DBInstanceIdentifier == instance_id }.empty?
57
+ end
58
+
59
+ def instance_id
60
+ name.to_s
61
+ end
62
+
63
+ def create_rds_instance!
64
+ db_name = (databases.shift || instance_id).to_s.gsub(/\-/, '_')
65
+ params = {
66
+ :db_instance_identifier => instance_id,
67
+ :allocated_storage => storage || 5,
68
+ :db_instance_class => instance_class || "db.m1.small",
69
+ :engine => engine || "MySQL5.1",
70
+ :master_username => username,
71
+ :master_user_password => password,
72
+ :db_name => db_name
73
+ }
74
+
75
+ # TODO : optional params : :port, :db_parameter_group, :db_security_groups, :availability_zone, :preferred_backup_window, :backend_retention_period
76
+ rds.create_db_instance(params)
77
+
78
+ # TODO : create additional databases
79
+ end
80
+
81
+ def delete_rds_instance!
82
+ rds.delete_db_instance(:db_instance_identifier => instance_id, :skip_final_snapshot => "true")
83
+ end
84
+
85
+ def authorize_access
86
+ authorizations[:security_groups] = cloud.security_groups.map{|sg| sg.name } if authorizations[:security_groups].nil?
87
+ authorizations.each do |type, values|
88
+ [*values].each do |value|
89
+ begin
90
+ # TODO : allow customization of db sec group name
91
+ params = {:db_security_group_name => "default"}
92
+
93
+ if type.to_s =~ /network/
94
+ puts "authorizing NET access for #{value}..."
95
+ params[:cidrip] = value
96
+ else
97
+ puts "authorizing SECGRP access for #{value}/#{Ec2.default_user_id}..."
98
+ params[:ec2_security_group_name] = value
99
+ params[:ec2_security_group_owner_id] = Ec2.default_user_id
100
+ end
101
+
102
+ rds.authorize_db_security_group(params)
103
+ rescue AWS::Error => e
104
+ raise e unless e.message =~ /Authorization already exists/
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -53,9 +53,11 @@ module CloudProviders
53
53
  end
54
54
 
55
55
  def run_chef!
56
- ssh([
57
- "chef-solo -j /etc/chef/dna.json -c /etc/chef/solo.rb"
58
- ])
56
+ chef_solo_cmd = <<-CMD
57
+ GEM_BIN=$(gem env | grep "EXECUTABLE DIRECTORY" | awk "{print \\$4}") \
58
+ && $GEM_BIN/chef-solo -j /etc/chef/dna.json -c /etc/chef/solo.rb
59
+ CMD
60
+ ssh([chef_solo_cmd.strip.squeeze(' ')])
59
61
  end
60
62
 
61
63
  def run
@@ -180,6 +180,11 @@ log_level :info
180
180
  puts "-----> Tearing down load balancer: #{lb.name}"
181
181
  lb.teardown
182
182
  end
183
+
184
+ rds_instances.each do |rds|
185
+ puts "-----> Tearing down RDS Instance: #{rds.name}"
186
+ rds.teardown
187
+ end
183
188
  # instances belonging to an auto_scaling group must be deleted before the auto_scaling group
184
189
  #THIS SCARES ME! nodes.each{|n| n.terminate_instance!}
185
190
  # loop {nodes.size>0 ? sleep(4) : break }
@@ -272,11 +277,11 @@ No autoscalers defined
272
277
  # Run command/s on all nodes in the cloud.
273
278
  # Returns a hash of instance_id=>result pairs
274
279
  def cmd(commands, opts={})
275
- opts[:key_by]= :instance_id unless opts[:key_by]
280
+ key_by = opts.delete(:key_by) || :instance_id
276
281
  results = {}
277
282
  threads = nodes.collect do |n|
278
- puts "result for #{n.instance_id} ==> #{n.ssh(commands, opts)}"
279
- Thread.new{ results[ n.send(opts[:key_by]) ] = n.ssh(commands, opts) }
283
+ puts "result for #{n.instance_id} ==> n.ssh(#{commands.inspect}, #{opts.inspect})"
284
+ Thread.new{ results[ n.send(key_by) ] = n.ssh(commands, opts) }
280
285
  end
281
286
  threads.each{ |aThread| aThread.join }
282
287
  results
@@ -6,9 +6,21 @@ module PoolParty
6
6
  def cloud(name, &block)
7
7
  clouds[name.to_s] = Cloud.new(name.to_s, {:parent => self}, &block)
8
8
  end
9
+
9
10
  def clouds
10
11
  @clouds ||= {}
11
12
  end
13
+
14
+ # Run command/s on all nodes in the pool.
15
+ # Returns a hash in the form of {cloud => [{instance_id=>result}]}
16
+ def cmd(commands, opts={})
17
+ results = {}
18
+ threads = clouds.collect do |name, c|
19
+ Thread.new{ results[ name ] = c.cmd(commands, opts) }
20
+ end
21
+ threads.each{ |aThread| aThread.join }
22
+ results
23
+ end
12
24
 
13
25
  at_exit do
14
26
  if pool.auto_execute
@@ -0,0 +1,14 @@
1
+ # test RDS pool
2
+
3
+ pool :poolparty do
4
+ cloud :fake_cloud do
5
+ keypair File.dirname(__FILE__)+"/../keys/test_key"
6
+ using :ec2
7
+
8
+ rds :db1 do
9
+ username "admin"
10
+ password "secret"
11
+ storage 5
12
+ end
13
+ end
14
+ end