bguthrie-awsymandias 0.2.1 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/.specification +57 -0
  2. data/README.rdoc +25 -21
  3. data/Rakefile +20 -4
  4. data/VERSION +1 -1
  5. data/awsymandias.gemspec +37 -12
  6. data/lib/awsymandias.rb +36 -331
  7. data/lib/awsymandias/addons/right_elb_interface.rb +375 -0
  8. data/lib/awsymandias/ec2.rb +49 -0
  9. data/lib/awsymandias/ec2/application_stack.rb +261 -0
  10. data/lib/awsymandias/extensions/class_extension.rb +18 -0
  11. data/lib/awsymandias/extensions/net_http_extension.rb +9 -0
  12. data/lib/awsymandias/instance.rb +149 -0
  13. data/lib/awsymandias/load_balancer.rb +157 -0
  14. data/lib/awsymandias/right_aws.rb +73 -0
  15. data/lib/awsymandias/right_elb.rb +16 -0
  16. data/lib/awsymandias/simple_db.rb +46 -0
  17. data/lib/awsymandias/snapshot.rb +33 -0
  18. data/lib/awsymandias/stack_definition.rb +60 -0
  19. data/lib/awsymandias/volume.rb +70 -0
  20. data/spec/integration/instance_spec.rb +37 -0
  21. data/spec/unit/addons/right_elb_interface_spec.rb +45 -0
  22. data/spec/unit/awsymandias_spec.rb +61 -0
  23. data/spec/unit/ec2/application_stack_spec.rb +634 -0
  24. data/spec/unit/instance_spec.rb +365 -0
  25. data/spec/unit/load_balancer_spec.rb +250 -0
  26. data/spec/unit/right_aws_spec.rb +90 -0
  27. data/spec/unit/simple_db_spec.rb +63 -0
  28. data/spec/unit/snapshot_spec.rb +39 -0
  29. data/spec/unit/stack_definition_spec.rb +113 -0
  30. data/tags +368 -0
  31. metadata +39 -13
  32. data/spec/awsymandias_spec.rb +0 -815
  33. data/vendor/aws-sdb/LICENSE +0 -19
  34. data/vendor/aws-sdb/README +0 -1
  35. data/vendor/aws-sdb/Rakefile +0 -20
  36. data/vendor/aws-sdb/lib/aws_sdb.rb +0 -3
  37. data/vendor/aws-sdb/lib/aws_sdb/error.rb +0 -42
  38. data/vendor/aws-sdb/lib/aws_sdb/service.rb +0 -191
  39. data/vendor/aws-sdb/spec/aws_sdb/service_spec.rb +0 -183
  40. data/vendor/aws-sdb/spec/spec_helper.rb +0 -4
@@ -0,0 +1,18 @@
1
+ module ClassExtension
2
+
3
+ if !Class.respond_to?("hash_initializer")
4
+ def hash_initializer(*attribute_names, &block)
5
+ define_method(:initialize) do |*args|
6
+ data = args.first || {}
7
+ data.symbolize_keys!
8
+ attribute_names.each do |attribute_name|
9
+ instance_variable_set "@#{attribute_name}", data[attribute_name]
10
+ end
11
+ instance_eval &block if block
12
+ end
13
+ end
14
+ end
15
+
16
+ end
17
+
18
+ Class.send :include, ClassExtension
@@ -0,0 +1,9 @@
1
+ # Supress warning about SSL peer verification
2
+ class Net::HTTP
3
+ alias_method :old_initialize, :initialize
4
+ def initialize(*args)
5
+ old_initialize(*args)
6
+ @ssl_context = OpenSSL::SSL::SSLContext.new
7
+ @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
8
+ end
9
+ end
@@ -0,0 +1,149 @@
1
+ # An instance represents an AWS instance as derived from a call to EC2's describe-instances methods.
2
+ # It wraps the simple hash structures returned by the EC2 gem with a domain model.
3
+ # It inherits from ARes::B in order to provide simple XML <-> domain model mapping.
4
+ module Awsymandias
5
+ class Instance < ActiveResource::Base
6
+ attr_accessor :name
7
+
8
+ self.site = "mu"
9
+
10
+ def id; instance_id; end
11
+ def instance_id; aws_instance_id; end
12
+ def public_dns; dns_name; end
13
+ def private_dns; private_dns_name; end
14
+ def dns_hostname; name.to_s.gsub(/_/,'-'); end
15
+
16
+ def summarize
17
+ output = []
18
+ output << " Instance '#{name}'\t#{instance_id}\t#{public_dns}\tLaunched #{aws_launch_time}"
19
+ output << " #{aws_state}\t#{aws_availability_zone}\t#{aws_instance_type.name}\t#{aws_image_id}"
20
+ attached_volumes.each do |volume|
21
+ output << " #{volume.aws_id} -> #{volume.aws_device}"
22
+ end
23
+ output.join("\n")
24
+ end
25
+
26
+ def attached_volumes
27
+ Awsymandias::RightAws.describe_volumes.select { |volume| volume.aws_instance_id == instance_id }
28
+ end
29
+
30
+ def key_name
31
+ @attributes['key_name'] || nil
32
+ end
33
+
34
+ def pending?
35
+ aws_state == "pending"
36
+ end
37
+
38
+ def running?
39
+ aws_state == "running"
40
+ end
41
+
42
+ def port_open?(port)
43
+ Net::Telnet.new("Host" => public_dns, "Port" => port, "Timeout" => 5)
44
+ true
45
+ rescue Timeout::Error, Errno::ECONNREFUSED
46
+ false
47
+ end
48
+
49
+ def snapshot_attached?(snapshot_id)
50
+ Awsymandias::RightAws.describe_volumes.each do |volume|
51
+ return true if volume.snapshot_id == snapshot_id && volume.aws_instance_id == instance_id
52
+ end
53
+ false
54
+ end
55
+
56
+ def terminated?
57
+ aws_state == "terminated"
58
+ end
59
+
60
+ def terminate!
61
+ Awsymandias::RightAws.connection.terminate_instances self.instance_id
62
+ reload
63
+ end
64
+
65
+ def volume_attached_to_unix_device(unix_device)
66
+ attached_volumes.select { |vol| vol.aws_device == unix_device }.first
67
+ end
68
+
69
+ def reload
70
+ load( RightAws.connection.describe_instances(self.aws_instance_id).first )
71
+ end
72
+
73
+ def to_params
74
+ {
75
+ :aws_image_id => self.aws_image_id,
76
+ :ssh_key_name => self.ssh_key_name,
77
+ :aws_instance_type => self.aws_instance_type,
78
+ :aws_availability_zone => self.aws_availability_zone
79
+ }
80
+ end
81
+
82
+ def to_simpledb
83
+ { :aws_instance_id => aws_instance_id,
84
+ :name => name,
85
+ :attached_volumes => attached_volumes.map { |vol| vol.to_simpledb }
86
+ }
87
+ end
88
+
89
+ def aws_instance_type
90
+ Awsymandias::EC2.instance_types[@attributes['aws_instance_type']]
91
+ end
92
+
93
+ def aws_launch_time
94
+ Time.parse(@attributes['aws_launch_time'])
95
+ end
96
+
97
+ def uptime
98
+ return 0.seconds if (pending? || terminated?)
99
+ Time.now - self.aws_launch_time
100
+ end
101
+
102
+ def running_cost
103
+ return Money.new(0) if pending?
104
+ aws_instance_type.price_per_hour * (uptime / 1.hour).ceil
105
+ end
106
+
107
+ class << self
108
+ def find(*args)
109
+ opts = args.extract_options!
110
+ ids = args.first == :all ? opts[:instance_ids] : [args.first].flatten
111
+
112
+ found = RightAws.connection.describe_instances(ids).map do |instance_attributes|
113
+ instantiate_record instance_attributes
114
+ end
115
+
116
+ raise ActiveResource::ResourceNotFound.new("Couldn't find instance #{ids.first}.") if (ids.size == 1 && found.size == 0)
117
+ (found.size == 1 && args.first != :all) ? found.first : found
118
+ end
119
+
120
+ def launch(opts={})
121
+ opts.assert_valid_keys :image_id, :key_name, :instance_type, :availability_zone, :user_data
122
+ opts[:instance_type] = opts[:instance_type].name if opts[:instance_type].is_a?(Awsymandias::EC2::InstanceType)
123
+
124
+ response = Awsymandias::RightAws.connection.run_instances *run_instance_opts_to_args(opts)
125
+ find(response.first[:aws_instance_id])
126
+ end
127
+
128
+ private
129
+
130
+ def run_instance_opts_to_args(opts)
131
+ [
132
+ opts[:image_id],
133
+ opts[:min_count] || 1,
134
+ opts[:max_count] || 1,
135
+ opts[:group_ids] || 'default',
136
+ opts[:key_name],
137
+
138
+ opts[:user_data],
139
+ opts[:addressing_type],
140
+ opts[:instance_type],
141
+ opts[:kernel_id],
142
+ opts[:ramdisk_id],
143
+ opts[:availability_zone],
144
+ opts[:block_device_mappings]
145
+ ]
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,157 @@
1
+ module Awsymandias
2
+ class LoadBalancer < ActiveResource::Base
3
+ attr_reader :name, :aws_created_at, :availability_zones, :dns_name, :name, :instances, :listeners, :health_check
4
+
5
+ def self.find(*names)
6
+ names = nil if names.is_a?(Array) && names.empty?
7
+ Awsymandias::RightElb.connection.describe_lbs(names).map { |lb| Awsymandias::LoadBalancer.new lb }
8
+ end
9
+
10
+ def self.launch(attribs)
11
+ new_lb = LoadBalancer.new(attribs)
12
+ new_lb.launch
13
+ new_lb
14
+ end
15
+
16
+ def self.valid_load_balancer_name?(lb_name); (lb_name.to_s =~ /\A[a-zA-Z0-9-]+\Z/) != nil; end
17
+
18
+ def initialize(attribs)
19
+ raise "Load balancer name can only contain alphanumeric characters or a dash." unless LoadBalancer.valid_load_balancer_name?(attribs[:name])
20
+
21
+ @listeners = [attribs[:listeners]].flatten.map { |listener| Listener.new listener }
22
+ @terminated = false
23
+
24
+ [:availability_zones, :dns_name, :name, :instances].each do |attribute_name|
25
+ instance_variable_set "@#{attribute_name.to_s}", attribs[attribute_name]
26
+ end
27
+
28
+ self.health_check = attribs[:health_check]
29
+ end
30
+
31
+ def availability_zones=(zones = [])
32
+ zones = [zones].flatten
33
+
34
+ zones_to_disable = @availability_zones - zones
35
+ Awsymandias::RightElb.connection.disable_availability_zones_for_lb @name, zones_to_disable if !zones_to_disable.empty?
36
+
37
+ zones_to_enable = zones - @availability_zones
38
+ Awsymandias::RightElb.connection.enable_availability_zones_for_lb @name, zones_to_enable if !zones_to_enable.empty?
39
+
40
+ @availability_zones = Awsymandias::RightElb.connection.describe_lbs([@name]).first[:availability_zones]
41
+ end
42
+
43
+ def health_check=(attribs)
44
+ need_to_save = !@health_check.nil?
45
+ @health_check = HealthCheck.new(self, attribs || {})
46
+ @health_check.save if launched? && need_to_save
47
+ end
48
+
49
+ def instances=(instance_ids = [])
50
+ instance_ids = [instance_ids].flatten
51
+
52
+ instances_to_deregister = @instances - instance_ids
53
+ Awsymandias::RightElb.connection.deregister_instances_from_lb @name, instances_to_deregister if !instances_to_deregister.empty?
54
+
55
+ instances_to_register = instance_ids - @instances
56
+ Awsymandias::RightElb.connection.register_instances_with_lb @name, instances_to_register if !instances_to_register.empty?
57
+
58
+ @instances = Awsymandias::RightElb.connection.describe_lbs([@name]).first[:instances]
59
+ end
60
+
61
+ def instance_health
62
+ Awsymandias::RightElb.connection.describe_instance_health @name
63
+ end
64
+
65
+ def launch
66
+ raise "Load balancers must have at least one listener defined." if @listeners.empty?
67
+ raise "Load balancers must have at least one availability zone defined." if @availability_zones.empty?
68
+
69
+ listener_params = @listeners.map { |l| l.attributes }
70
+ @dns_name = Awsymandias::RightElb.connection.create_lb @name, @availability_zones, listener_params
71
+ end
72
+
73
+ def launched?
74
+ !@dns_name.nil?
75
+ end
76
+
77
+ def reload
78
+ return unless launched?
79
+ data = Awsymandias::RightElb.connection.describe_lbs([self.name]).first
80
+ data.symbolize_keys!
81
+ data.keys.each do |attribute_name|
82
+ instance_variable_set "@#{attribute_name}", data[attribute_name]
83
+ end
84
+ self
85
+ end
86
+
87
+ def summarize
88
+ output = []
89
+ output << " Load Balancer '#{name}':\t#{dns_name || "Not Launched"}"
90
+ output << " Health Check: "
91
+ health_check.attributes.each_pair {|attrib, value| output.last << "#{attrib}: #{value}\t"}
92
+ output << " Avail. Zones: #{availability_zones.join "," }"
93
+ output << " Instances: #{instances.join "," }"
94
+ output << " Listeners:"
95
+ listeners.each do |listener|
96
+ output << " "
97
+ listener.attributes.each_pair {|attrib, value| output.last << "#{attrib}: #{value}\t"}
98
+ end
99
+ output.join("\n")
100
+ end
101
+
102
+ def terminate!
103
+ Awsymandias::RightElb.connection.delete_lb name
104
+ @terminated = true
105
+ end
106
+
107
+ def terminated?
108
+ @terminated
109
+ end
110
+
111
+ def to_simpledb
112
+ name
113
+ end
114
+
115
+ class HealthCheck
116
+ ATTRIBUTE_NAMES = [:healthy_threshold, :unhealthy_threshold, :interval, :target, :timeout]
117
+ attr_accessor *ATTRIBUTE_NAMES
118
+
119
+ DEFAULT_SETTINGS = {:healthy_threshold => 3,
120
+ :unhealthy_threshold => 5,
121
+ :interval => 30,
122
+ :target => "HTTP:80",
123
+ :timeout => 5
124
+ }
125
+
126
+ def initialize(lb, attribs = {})
127
+ @lb = lb
128
+ attribs.merge!(HealthCheck::DEFAULT_SETTINGS)
129
+
130
+ HealthCheck::DEFAULT_SETTINGS.each_pair { |key, value| instance_variable_set "@#{key}", value }
131
+ end
132
+
133
+ def attributes
134
+ returning({}) do |attribs|
135
+ HealthCheck::ATTRIBUTE_NAMES.each { |attrib| attribs[attrib] = instance_variable_get "@#{attrib}"}
136
+ end
137
+ end
138
+
139
+ def save
140
+ Awsymandias::RightElb.connection.configure_health_check attributes
141
+ end
142
+ end
143
+
144
+ class Listener
145
+ ATTRIBUTE_NAMES = [:protocol, :load_balancer_port, :instance_port]
146
+ hash_initializer *ATTRIBUTE_NAMES
147
+ attr_reader *ATTRIBUTE_NAMES
148
+
149
+ def attributes
150
+ returning({}) do |attribs|
151
+ Listener::ATTRIBUTE_NAMES.each { |attrib| attribs[attrib] = instance_variable_get "@#{attrib}"}
152
+ end
153
+ end
154
+ end
155
+
156
+ end
157
+ end
@@ -0,0 +1,73 @@
1
+ module Awsymandias
2
+ module RightAws
3
+ class << self
4
+
5
+ def attach_volume(volume_id, instance_id, unix_device)
6
+ Awsymandias::Volume.new connection.attach_volume(volume_id, instance_id, unix_device)
7
+ end
8
+
9
+ def connection
10
+ @connection ||= ::RightAws::Ec2.new(Awsymandias.access_key_id,
11
+ Awsymandias.secret_access_key,
12
+ {:logger => Logger.new("/dev/null")})
13
+ end
14
+
15
+ def delete_volume(volume_id)
16
+ connection.delete_volume volume_id
17
+ end
18
+
19
+ def detach_volume(volume_id, instance_id, unix_device)
20
+ Awsymandias::Volume.new connection.detach_volume(volume_id, instance_id, unix_device)
21
+ end
22
+
23
+ def describe_instances(list = [])
24
+ connection.describe_instances(list).map { |i| Awsymandias::Instance.new i }
25
+ end
26
+
27
+ def describe_snapshots(list = [])
28
+ Snapshot.find(*list)
29
+ end
30
+
31
+ def describe_volumes(list = [])
32
+ connection.describe_volumes(list).map { |v| Awsymandias::Volume.new v }
33
+ end
34
+
35
+ def latest_snapshot_based_on(volume_id, raise_if_no_snapshot = true)
36
+ snapshots_for_volume = connection.describe_snapshots.select { |snapshot| snapshot[:aws_volume_id] == volume_id }
37
+ snapshot = snapshots_for_volume.empty? ?
38
+ nil :
39
+ snapshots_for_volume.sort { |a,b| a[:aws_started_at] <=> b[:aws_started_at] }.last
40
+ raise "Can't find snapshot for master volume #{volume_id}." if (!snapshot || snapshot[:aws_id].nil?) && raise_if_no_snapshot
41
+ snapshot ? Awsymandias::Snapshot.new(snapshot) : nil
42
+ end
43
+
44
+ def snapshot_size(snapshot_id)
45
+ connection.describe_volumes([connection.describe_snapshots([snapshot_id]).first[:aws_volume_id]]).first[:aws_size]
46
+ end
47
+
48
+ def delete_snapshot(snapshot_id)
49
+ connection.delete_snapshot snapshot_id
50
+ end
51
+
52
+ def wait_for_create_volume(snapshot_id, availability_zone)
53
+ new_volume = connection.create_volume snapshot_id, snapshot_size(snapshot_id), availability_zone
54
+
55
+ Awsymandias.wait_for "new volume #{new_volume[:aws_id]} from snapshot #{snapshot_id} to become available..", 3 do
56
+ connection.describe_volumes(new_volume[:aws_id]).first[:aws_status] == 'available'
57
+ end
58
+
59
+ Awsymandias::Volume.new new_volume
60
+ end
61
+
62
+ def wait_for_create_snapshot(volume_id)
63
+ new_snapshot = connection.create_snapshot volume_id
64
+
65
+ Awsymandias.wait_for "new snapshot of volume #{volume_id}", 3 do
66
+ connection.describe_snapshots(new_snapshot[:aws_id]).first[:aws_status] == 'completed'
67
+ end
68
+
69
+ Awsymandias::Snapshot.new new_snapshot
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,16 @@
1
+ module Awsymandias
2
+ module RightElb
3
+ class << self
4
+ def connection
5
+ @connection ||= ::RightAws::ElbInterface.new(Awsymandias.access_key_id,
6
+ Awsymandias.secret_access_key,
7
+ {:logger => Logger.new("/dev/null")})
8
+ end
9
+
10
+ def describe_lbs(list = [])
11
+ LoadBalancer.find(*list)
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,46 @@
1
+ module Awsymandias
2
+ module SimpleDB # :nodoc
3
+ class << self
4
+ def connection(opts={})
5
+ @connection ||= ::RightAws::SdbInterface.new Awsymandias.access_key_id || ENV['AMAZON_ACCESS_KEY_ID'],
6
+ Awsymandias.secret_access_key || ENV['AMAZON_SECRET_ACCESS_KEY'],
7
+ { :logger => Logger.new("/dev/null") }.merge(opts)
8
+ end
9
+
10
+ def put(domain, name, stuff, replace = true)
11
+ stuff.each_pair { |key, value| stuff[key] = value.to_yaml.gsub("\n","\\n") }
12
+ connection.put_attributes handle_domain(domain), name, stuff, replace
13
+ end
14
+
15
+ def get(domain, name)
16
+ stuff = connection.get_attributes(handle_domain(domain), name)[:attributes] || {}
17
+ stuff.inject({}) do |hash, (key, value)|
18
+ hash[key.to_sym] = YAML.load(value.first.gsub("\\n","\n"))
19
+ hash
20
+ end
21
+ end
22
+
23
+ def delete(domain, name)
24
+ connection.delete_attributes handle_domain(domain), name
25
+ end
26
+
27
+ def query(domain_name, query_expression = nil, max_number_of_items = nil, next_token = nil)
28
+ connection.query(domain_name, query_expression, max_number_of_items, next_token)[:items]
29
+ end
30
+
31
+ def query_with_attributes(domain_name, attributes=[], query_expression = nil, max_number_of_items = nil, next_token = nil)
32
+ connection.query_with_attributes(domain_name, attributes=[], query_expression, max_number_of_items, next_token)[:items]
33
+ end
34
+
35
+ private
36
+
37
+ def domain_exists?(domain)
38
+ connection.list_domains[:domains].include?(domain)
39
+ end
40
+
41
+ def handle_domain(domain)
42
+ returning(domain) { connection.create_domain(domain) unless domain_exists?(domain) }
43
+ end
44
+ end
45
+ end
46
+ end