cluster 0.5.33

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.
@@ -0,0 +1,206 @@
1
+ require 'cluster'
2
+
3
+ class Cluster
4
+ class Cli
5
+ def initialize(arguments = nil)
6
+ arguments ||= ARGV
7
+
8
+ unless arguments.length >= 1
9
+ puts "#{Cluster::NAME} usage: #{Cluster::NAME} [infa args] command [command args]"
10
+ exit 1
11
+ end
12
+
13
+ if arguments.include?('--version')
14
+ puts "#{Cluster::NAME} version #{Cluster::Version::STRING}"
15
+ exit 0
16
+ end
17
+
18
+ infra = []
19
+ command = nil
20
+ params = []
21
+ for arg in arguments
22
+ if command
23
+ params << arg
24
+ elsif arg =~ /^-/
25
+ infra << arg
26
+ else
27
+ command = arg
28
+ end
29
+ end
30
+
31
+ if arg = infra.detect {|a| a =~ /^(-c|--credentials=)(.+)/ }
32
+ file = $2
33
+ file = (file[0, 1] == File::SEPARATOR) ? file : File.join(ENV['PWD'], file)
34
+
35
+ Cluster::Configuration[:credentials_file] = file
36
+ infra.delete arg
37
+ else
38
+ Cluster.set_credentials_file
39
+ end
40
+
41
+ logger_file = if arg = infra.detect {|a| a =~ /^--logger=(.+)$/ }
42
+ infra.delete arg
43
+ $1
44
+ elsif Cluster::Configuration[:credentials_file]
45
+ File.join(File.dirname(Cluster::Configuration[:credentials_file]), 'cluster.log')
46
+ else
47
+ '/tmp/cluster.log'
48
+ end
49
+
50
+ unless command
51
+ puts "#{Cluster::NAME} usage: #{Cluster::NAME} [infa args] command [command args]"
52
+ exit 1
53
+ end
54
+
55
+ if command.downcase == 'gemurl'
56
+ # We don't need to do any infrastructure for this one...and
57
+ # we may not have it yet...
58
+ puts Cluster::LOCATION
59
+ exit 0
60
+ elsif command.downcase == 'imageurl'
61
+ # We don't need to do any infrastructure for this one...and
62
+ # we may not have it yet...
63
+ puts Cluster::IMAGES
64
+ exit 0
65
+ end
66
+
67
+ file = File.open(logger_file, File::WRONLY | File::APPEND | File::CREAT)
68
+
69
+ $stderr = file
70
+ Cluster::Configuration['logger'] = Logger.new(file, 5, 512000)
71
+
72
+ @sub = Infrastructure.connect(infra)
73
+
74
+ @named_output = (!params.empty? and params[0] == '-d' and params.shift)
75
+ @ip_output = (!params.empty? and params[0] == '-i' and params.shift)
76
+ @id_output = (!params.empty? and params[0] == '-I' and params.shift)
77
+
78
+ @cluster = Cluster.new @sub
79
+ if respond_to? command
80
+ self.send command, *params
81
+ elsif @cluster.respond_to? command
82
+ puts @cluster.send(command, *params)
83
+ else
84
+ STDERR.puts "#{Cluster::NAME} does not understand #{command}."
85
+ end
86
+ end
87
+
88
+ def machines(*params)
89
+ res = @cluster.machines(*params)
90
+ print_instances(res)
91
+ end
92
+
93
+ def services(*params)
94
+ res = @cluster.services(*params)
95
+ print_instances(res)
96
+ end
97
+ alias :service :services
98
+
99
+ def labeled(*params)
100
+ print_instances @cluster.labeled(params.first)
101
+ end
102
+
103
+ def list(*params)
104
+ print_instances(@sub.instances) if params.empty?
105
+
106
+ print_instances @sub.instances.select {|i| params.any? {|p| i.identified_by? p } }
107
+ end
108
+
109
+ def print_instances(instances)
110
+ return if instances.empty?
111
+
112
+ for instance in instances
113
+ if @named_output
114
+ puts instance.dns
115
+ elsif @ip_output
116
+ puts instance.ip
117
+ elsif @id_output
118
+ puts instance.id
119
+ else
120
+ puts instance.to_s(:long)
121
+ end
122
+ end
123
+ end
124
+
125
+ def alter_instances(ids)
126
+ altered = @sub.instances.map {|ins|
127
+ if ids.any? {|p| ins.identified_by? p}
128
+ yield ins
129
+ ins
130
+ else
131
+ nil
132
+ end
133
+ }.compact
134
+
135
+ @sub.alter_instances! altered
136
+ puts "Instances altered :"
137
+ for ins in altered
138
+ puts ins.to_s(:long)
139
+ end
140
+ end
141
+
142
+ def enable(*params)
143
+ service_list = params.shift
144
+
145
+ if !service_list or params.empty?
146
+ STDERR.puts "Usage: #{Cluster::NAME} [infra] enable service[,services] instance [instances]"
147
+ exit 2
148
+ end
149
+
150
+ servs = service_list.split(',')
151
+ alter_instances(params) {|ins| ins.enable servs }
152
+ end
153
+
154
+ def disable(*params)
155
+ service_list = params.shift
156
+
157
+ if !service_list or params.empty?
158
+ STDERR.puts "Usage: #{Cluster::NAME} [infra] disable service[,services] instance [instances]"
159
+ exit 2
160
+ end
161
+
162
+ servs = service_list.split(',')
163
+ alter_instances(params) {|ins| ins.disable servs }
164
+ end
165
+
166
+ def label(name, id)
167
+ alter_instances([id]) {|i| i.friendly_name = name.downcase}
168
+ end
169
+
170
+ def unlabel(id)
171
+ alter_instances([id]) {|i| i.friendly_name = ''}
172
+ end
173
+
174
+ def set_state(state, *ids)
175
+ alter_instances(ids) {|i| i.set_state state }
176
+ end
177
+
178
+ def stop(*ids)
179
+ alter_instances(ids) {|i| i.stop! }
180
+ end
181
+
182
+ def release(*params)
183
+ if params.empty?
184
+ puts "Need an environment to work on for releases."
185
+ exit 1
186
+ end
187
+
188
+ tag_output = (!params.empty? and params.delete('-t'))
189
+ rel = @cluster.release(*params)
190
+ if !rel
191
+ puts "No release found for environment #{params.first}"
192
+ exit 1
193
+ elsif tag_output
194
+ puts rel.tag
195
+ else
196
+ puts "#{rel.environment} was released at #{rel.created_at} with '#{rel.tag}'"
197
+ end
198
+ end
199
+
200
+ def security(*args)
201
+ @cluster.security(*args).each {|k, v|
202
+ puts "#{k}: #{v.join(' ')}"
203
+ }
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,52 @@
1
+ require 'logger'
2
+
3
+ class Cluster
4
+ module Logging
5
+ # Logging should be set up by configuration early, but if it isnt
6
+ # then we will just write to STDERR
7
+ @@logger = nil
8
+
9
+ def logger
10
+ @@logger ||= Cluster::Configuration['logger']
11
+ end
12
+ end
13
+
14
+ class Configuration
15
+ include Cluster::Logging
16
+
17
+ @@system_config = {}
18
+ @@credentials = nil
19
+
20
+ class << self
21
+ def credentials?
22
+ self[:credentials_file]
23
+ end
24
+
25
+ def credentials
26
+ @@credentials ||= credentials? && File.open(self[:credentials_file]) {|f| YAML::load(f) }
27
+ end
28
+
29
+ def []=(key, value)
30
+ @@system_config[key.to_s] = value
31
+ end
32
+
33
+ def [](key)
34
+ @@system_config[key.to_s]
35
+ end
36
+
37
+ def method_missing(meth, *params)
38
+ field = meth.to_s
39
+ if options.respond_to? field
40
+ options.send(field)
41
+ else
42
+ super
43
+ end
44
+ end
45
+
46
+ def options(params = nil)
47
+ @@system_config ||= self.new(params)
48
+ end
49
+ end
50
+ end
51
+
52
+ end
@@ -0,0 +1,160 @@
1
+ %w(ostruct optparse).each {|l| require l }
2
+
3
+ class Infrastructure
4
+ include Cluster::Logging
5
+
6
+ @@subsystem = nil
7
+ @@cluster_name = 'cluster'
8
+ @@in_cluster = false
9
+ @@machine_sizes = %w(minimum basic average power super)
10
+
11
+ def initialize(arguments = nil)
12
+ @arguments = arguments
13
+ end
14
+
15
+ def configure
16
+ @options = OpenStruct.new
17
+ @options.current_instance_id = nil
18
+ @options.original_services = []
19
+
20
+ if credentials?
21
+ cluster = credentials['cluster']
22
+ if cluster
23
+ @options.current_instance_id = cluster['id']
24
+ @options.original_services = cluster['services'].split(',')
25
+ end
26
+ end
27
+
28
+ @@subsystem ||= self
29
+ end
30
+
31
+ def instances
32
+ raise NotImplementedError
33
+ end
34
+
35
+ def current_instance
36
+ return nil unless @options.current_instance_id
37
+
38
+ instances.detect {|ins| ins.id.eql?(@options.current_instance_id) }
39
+ end
40
+
41
+
42
+ def credentials?
43
+ return true if @credentials
44
+
45
+ if Cluster::Configuration.credentials?
46
+ @credentials = Cluster::Configuration.credentials
47
+ end
48
+ end
49
+
50
+ def credentials
51
+ @credentials
52
+ end
53
+
54
+ def in_cluster?
55
+ raise NotImplementedError
56
+ end
57
+
58
+ def names(*roles)
59
+ raise NotImplementedError
60
+ end
61
+
62
+ def release_class
63
+ raise NotImplementedError
64
+ end
65
+
66
+ def machines(filter_groups)
67
+ if filter_groups.empty?
68
+ self.instances
69
+ else
70
+ self.instances.select {|i| i.groups.any? {|g| filter_groups.include? g }}
71
+ end
72
+ end
73
+
74
+ def services(filter_services)
75
+ filter_services = Array(filter_services) unless filter_services.is_a? Array
76
+
77
+ if filter_services.empty?
78
+ puts "No services provided to return a list for #{filter_services.join(' ')}"
79
+ exit 1
80
+ else
81
+ self.instances.select {|i|
82
+ i.services.any? {|s| filter_services.include?(s) and !i.disabled_services.include?(s)}
83
+ }
84
+ end
85
+ end
86
+
87
+ def to_credentials
88
+ opts = @options.marshal_dump.keys.inject({}) {|m, k|
89
+ if k.eql? :role
90
+ m
91
+ else
92
+ m.merge k.to_s => @options.send(k)
93
+ end
94
+ }
95
+ {'cluster_name' => self.class.cluster_name,
96
+ self.class.to_s.downcase => opts }
97
+ end
98
+
99
+ @@dns = nil
100
+ class << self
101
+ def sizes
102
+ @@machine_sizes
103
+ end
104
+ alias :machine_sizes :sizes
105
+
106
+ def current
107
+ @@subsystem
108
+ end
109
+
110
+ def cluster_name
111
+ @@cluster_name
112
+ end
113
+
114
+ def connect(arguments = nil)
115
+ name = nil
116
+ # removes the infrastructure argument, if given, and processes it
117
+ new_args = arguments.map {|arg|
118
+ case arg
119
+ when /^-i([^=]+)$/, /^--infr[^=]+=(.+)$/
120
+ name = $1
121
+ nil
122
+ when /^-n([^=]+)$/, /^--name[^=]+=(.+)$/
123
+ @@cluster_name = $1
124
+ else
125
+ arg
126
+ end
127
+ }.compact
128
+
129
+ name ||= 'amazon'
130
+
131
+ begin
132
+ require File.join('cluster', 'infrastructures', name)
133
+ rescue LoadError => err
134
+ STDERR.puts "Subsystem of #{name} either is not included or not available.\n\t#{err.message}\n#{err.backtrace.join("\n\t")}"
135
+ exit 1
136
+ end
137
+
138
+ begin
139
+ sub = Object.const_get name.capitalize
140
+ rescue NameError
141
+ STDERR.puts "Subsystem of #{name.capitalize} cannot be initialized."
142
+ exit 1
143
+ end
144
+
145
+ sub.new new_args
146
+ end
147
+
148
+ def dns
149
+ return @@dns unless @@dns.nil?
150
+ ns = ''
151
+ File.open('/etc/resolv.conf').each do |line| ns = $1 if line =~ /nameserver\s+([\d\.]+)/; end
152
+
153
+ @@dns = Resolv::DNS.new(:nameserver => ns)
154
+ end
155
+
156
+ def in_cluster?
157
+ @@in_cluster
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,568 @@
1
+ %w(uuidtools yaml right_aws resolv sdb/active_sdb cluster/infrastructures/amazon_release cluster/infrastructures/amazon_instance).each {|l| require l}
2
+
3
+ class Amazon < Infrastructure
4
+
5
+ def configure
6
+ super
7
+ @@in_cluster = in_cluster?
8
+ @@instances = nil
9
+
10
+ if credentials? and credentials.include? 'amazon'
11
+ creds = credentials['amazon']
12
+ @options.key = creds['key']
13
+ @options.secret = creds['secret']
14
+ @options.owner = creds['owner']
15
+ @options.cluster_bucket = creds['cluster_bucket']
16
+ @options.bucket_key = creds['bucket_key']
17
+ @options.cluster_domain = creds['cluster_domain']
18
+ @options.zone = creds['zone']
19
+ @options.volumes = creds['volumes']
20
+ end
21
+
22
+ @options.key ||= ENV['AMAZON_ACCESS_KEY_ID']
23
+ @options.secret ||= ENV['AMAZON_SECRET_ACCESS_KEY']
24
+ @options.owner ||= ENV['AMAZON_OWNER_ID']
25
+ @options.cluster_bucket ||= ENV['CLUSTER_BUCKET']
26
+ @options.bucket_key ||= 'cluster_credentials.yml'
27
+ @options.zone ||= ENV['AMAZON_ZONE']
28
+ @options.cluster_domain ||= ENV['CLUSTER_DOMAIN'] || self.class.cluster_name
29
+ @options.volumes ||= {}
30
+
31
+ @options.role = (ENV['CLUSTER_ROLE'] or ENV['RAILS_ENV'] or 'production')
32
+
33
+ @options.cluster_image_key = 'cluster_images.yml'
34
+ @options.spot_instances = false
35
+ @options.price = false
36
+
37
+ OptionParser.new {|o|
38
+ o.banner = "Amazon Infrastructure Options"
39
+
40
+ o.on('-k', '--key VAL', "Amazon Access Key ID") do |v|
41
+ @options.key = v
42
+ end
43
+
44
+ o.on('-s', '--secret VAL', 'Amazon Access Secret') do |v|
45
+ @options.secret = v
46
+ end
47
+
48
+ o.on('-o', '--owenr VAL', 'Amazon User Code') do |v|
49
+ @options.owner = v
50
+ end
51
+
52
+ o.on('-d', '--domain VAL', 'Amazon Domain to use') do |v|
53
+ @options.cluster_domain = v
54
+ end
55
+
56
+ o.on('-b', '--bucket VAL', 'Cluster Bucket') do |v|
57
+ @options.cluster_bucket = v
58
+ end
59
+
60
+ o.on('-f', '--bucket-credentials-file VAL', 'Cluster credentials file location on the bucket.') do |v|
61
+ @options.bucket_key = v
62
+ end
63
+
64
+ o.on('--source-bucket VAL', 'Bucket that has the host data.') do |v|
65
+ end
66
+
67
+ o.on('-r', '--role VAL', 'Role in which to operate.') do |v|
68
+ @options.role = v
69
+ end
70
+
71
+ o.on('-z', '--zone VAL', "Availability Zone") do |v|
72
+ @options.zone = v
73
+ end
74
+
75
+ o.on('--spot', "Use Spot Instances") do |v|
76
+ @options.spot_instances = true
77
+ end
78
+
79
+ o.on('--price=VAL', 'Maximum price for the spot instance') do |v|
80
+ @options.price = v
81
+ end
82
+
83
+ }.parse(@arguments)
84
+
85
+ unless @options.key and @options.secret
86
+ $stderr.puts "Amazon Infrastructure cannot communicate without secret and key."
87
+ exit 2
88
+ end
89
+
90
+ unless @options.cluster_domain
91
+ $stderr.puts "Amazon Infrastructure needs to know what domain to connect to."
92
+ exit 2
93
+ end
94
+ end
95
+
96
+ def instances
97
+ @@instances ||= load_instances
98
+ end
99
+
100
+ def cost(sizes)
101
+ unless @options.spot_instances
102
+ puts "Cost only works for spot instances currently (ie. supply infrastructure argument of --spot"
103
+ exit 3
104
+ end
105
+ args = {:start_time => (Time.now - (7 * 24 * 60 * 60)),
106
+ :end_time => Time.now,
107
+ :product_description => "Linux/UNIX"}
108
+ sizes.map {|s|
109
+ t = [size_to_instance_type(s)]
110
+ prices = ecc.describe_spot_price_history(args.merge(:instance_types => t))
111
+ p = prices.reduce(0) {|c,p| c + p[:spot_price] } / prices.length
112
+ [s, "%0.3f" % p]
113
+ }
114
+ end
115
+
116
+ def create_file_store(name)
117
+ sss.bucket(name, true)
118
+ end
119
+
120
+ def create_data_store(name)
121
+ sdb.create_domain(name)
122
+ end
123
+
124
+ def load_instances
125
+ terminated = []
126
+ iss = ecc.describe_instances.map {|ins|
127
+ if %w(terminated shutting-down).include? ins[:aws_state]
128
+ terminated << ins[:aws_instance_id]
129
+ nil
130
+ else
131
+ AmazonInstance.new(ins)
132
+ end
133
+ }.compact
134
+
135
+ begin
136
+ res = sdb.select "select * from #{domain} where entry='machine'"
137
+ rescue RightAws::AwsError
138
+ unless domains.include? domain
139
+ sdb.create_domain domain
140
+ retry
141
+ end
142
+ end
143
+ sdbs = self.class.from_sdb_results res
144
+
145
+ sdbs.each do |sd|
146
+ aid = sd[:aws_id]
147
+ iid = sd['ec2_id']
148
+ if !iid
149
+ started = sd['start_time_sorted'] && Time.parse(sd['start_time_sorted'])
150
+ diff = started && (Time.now - started)
151
+ if diff and diff < (12 * 3600)
152
+ ins = AmazonInstance.new
153
+ ins.set_sdb_attributes sd
154
+ iss.push ins
155
+ else
156
+ $stderr.puts "Cannot find machine #{aid} -- old entry being removed."
157
+ sdb.delete_attributes domain, aid
158
+ end
159
+ else
160
+ if ins = iss.detect {|i| i.id.eql? iid }
161
+ ins.set_sdb_attributes sd
162
+ elsif terminated.include? iid
163
+ $stderr.puts "Removing terminated entry #{iid}"
164
+ sdb.delete_attributes domain, aid
165
+ else
166
+ $stderr.puts "Orphaned cluster record of #{aid}. (Just started?) [#{sd.inspect}]"
167
+ end
168
+ end
169
+ end
170
+
171
+ iss.each do |ins|
172
+ if ins.no_sdb?
173
+ puts "Cannot find cluster registration for #{ins.ec2_id} -- creating."
174
+ sdb.put_attributes domain, ins.aws_id, ins.attributes, :replace
175
+ end
176
+ end
177
+
178
+ iss
179
+ end
180
+
181
+ def alter_instances!(*iss)
182
+ list = iss.empty? ? instances : iss
183
+
184
+ for ins in list.flatten
185
+ yield ins if block_given?
186
+ attrs = ins.attributes
187
+ remove = attrs.keys.select {|k| attrs[k].empty? and attrs.delete(k) }
188
+ unless remove.empty?
189
+ sdb.delete_attributes domain, ins.aws_id, remove
190
+ end
191
+ sdb.put_attributes domain, ins.aws_id, ins.attributes, :replace
192
+ end
193
+ end
194
+
195
+ def in_cluster?
196
+ return @cluster_check if @cluster_check
197
+
198
+ check = false
199
+ begin
200
+ Timeout::timeout(1) do
201
+ begin
202
+ s = TCPSocket.new('169.254.169.254', 80)
203
+ s.close
204
+ check = true
205
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
206
+ # NOP
207
+ end
208
+ end
209
+ rescue Timeout::Error
210
+ end
211
+
212
+ @@in_cluster = check
213
+ end
214
+
215
+ def size_to_instance_type(size)
216
+ unless self.class.sizes.include? size.downcase
217
+ puts "#{Cluster::NAME} does not have a machine size of #{size}\n\tAvailable Sizes: (#{self.class.sizes.join(', ')})"
218
+ exit 2
219
+ end
220
+ AmazonInstance.size_to_type(size)
221
+ end
222
+
223
+ def new_instance(size, services)
224
+ args = { :services => services, :size => size }
225
+ if @options.spot_instances
226
+ unless @options.price
227
+ puts "Amazon Spot instances need a '--price=??' argument"
228
+ exit 3
229
+ end
230
+ args.merge! :spot_price => @options.price
231
+ end
232
+ =begin
233
+ type = size_to_type size
234
+ groups = services_to_groups services
235
+ image = type_to_image type
236
+ puts "type #{type} Groups #{groups.inspect} K #{key} I #{image} UD #{user}"
237
+ =end
238
+ ins = AmazonInstance.create args
239
+ puts ins.inspect
240
+ ins
241
+ end
242
+
243
+ def save_monitor(io, key)
244
+ if bucket.put key, io
245
+ old = sdb.select "select * from #{domain} where entry = 'monitor'"
246
+ monitor = unless old[:items].empty?
247
+ self.class.from_sdb_results(old).first
248
+ else
249
+ { 'aws_id' => UUIDTools::UUID.timestamp_create.to_s,
250
+ 'entry' => 'monitor' }
251
+ end
252
+ monitor.merge! 'updated_at' => Time.now.xmlschema(3),
253
+ 'key' => key,
254
+ 'bucket' => @cluster_bucket
255
+ sdb.put_attributes domain, monitor['aws_id'], self.class.to_sdb_attributes(monitor), :replace
256
+ monitor
257
+ end
258
+ end
259
+
260
+ def fetch_monitor
261
+ res = sdb.select "select * from #{domain} where entry = 'monitor'"
262
+ return nil if res[:items].empty?
263
+ monitor = self.class.from_sdb_results(res).first
264
+ bucket.get(monitor['key'])
265
+ end
266
+
267
+ def current_instance
268
+ unless in_cluster?
269
+ puts "Are we in the cluster?"
270
+ exit 3
271
+ end
272
+
273
+ require 'open-uri'
274
+ ec2_id = open('http://169.254.169.254/latest/meta-data/instance-id') {|f| f.read }
275
+ ins = self.instances.detect {|i| i.ec2_id.eql? ec2_id }
276
+
277
+ unless ins
278
+ puts "#{Cluster::NAME} cannot determine the current instance."
279
+ exit 2
280
+ end
281
+
282
+ if @options.current_instance_id
283
+ aws = self.instances.detect {|i| i.aws_id.eql? @options.current_instance_id }
284
+ if aws and aws.aws_id != ins.aws_id
285
+ ins.services = (ins.services + aws.services).uniq
286
+ ins.disabled_services = (ins.disabled_services + aws.disabled_services).uniq
287
+ ins.spot_price = aws.spot_price unless ins.spot_price
288
+ ins.friendly_name = aws.friendly_name unless ins.friendly_name
289
+ sdb.delete_attributes domain, aws.aws_id
290
+ sdb.put_attributes domain, ins.aws_id, ins.attributes, :replace
291
+ end
292
+ end
293
+
294
+ ins
295
+ end
296
+
297
+ def release_class
298
+ AmazonRelease
299
+ end
300
+
301
+ # The methods below here are typically only used internally, but
302
+ # could also be called by anything that would like to access
303
+ # the Amazon tools directly.
304
+
305
+ def key
306
+ @options.key
307
+ end
308
+
309
+ def secret
310
+ @options.secret
311
+ end
312
+
313
+ def owner
314
+ @options.owner
315
+ end
316
+
317
+ def domain
318
+ @options.cluster_domain
319
+ end
320
+
321
+ def sdb(params = {})
322
+ return @sdb if @sdb
323
+ @sdb = RightAws::SdbInterface.new(key, secret, connection_params(params))
324
+ unless @sdb
325
+ puts "Amazon cannot connect to SimpleDB"
326
+ exit 3
327
+ end
328
+ @sdb
329
+ end
330
+
331
+ def connect_to_active_sdb(params = {})
332
+ RightAws::ActiveSdb.establish_connection key, secret, connection_params(params)
333
+ end
334
+
335
+ def sss(params = {})
336
+ return @s3 if @s3
337
+ @s3 = RightAws::S3.new(key, secret, connection_params(params))
338
+ unless @s3
339
+ puts "Amazon cannot connect to S3"
340
+ exit 3
341
+ end
342
+ @s3
343
+ end
344
+
345
+ def sqs(params = {})
346
+ return @sqs if @sqs
347
+ @sqs = RightAws::SqsGen2.new(key, secret, connection_params(params))
348
+ unless @sqs
349
+ puts "Amazon cannot connect to S3"
350
+ exit 3
351
+ end
352
+ @sqs
353
+ end
354
+
355
+ def elb(params = {})
356
+ return @elb if @elb
357
+ @elb = RightAws::ElbInterface.new(key, secret, connection_params(params))
358
+ unless @elb
359
+ puts "Amazon cannot connect to elb"
360
+ exit 3
361
+ end
362
+ @elb
363
+ end
364
+
365
+ def ecc(params = {})
366
+ return @ec2 if @ec2
367
+ @ec2 = RightAws::Ec2.new(key, secret, connection_params(params))
368
+ unless @ec2
369
+ puts "Amazon cannot connect to EC2"
370
+ exit 3
371
+ end
372
+ @ec2
373
+ end
374
+
375
+ def domains(reload = false)
376
+ @sdb_domains = if !@sdb_domains or reload
377
+ sdb.list_domains[:domains]
378
+ else
379
+ @sdb_domains
380
+ end
381
+ end
382
+
383
+ def in_cluster?
384
+ return @check unless @check.nil?
385
+ check = false
386
+ begin
387
+ Timeout::timeout(2) do
388
+ begin
389
+ s = TCPSocket.new('169.254.169.254', 80)
390
+ s.close
391
+ check = true
392
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
393
+ # NOP
394
+ end
395
+ end
396
+ rescue Timeout::Error
397
+ end
398
+
399
+ @check = check
400
+ end
401
+
402
+ def connection_params(params = {})
403
+ params = {:multi_thread => true} if params.empty?
404
+ unless params.include?(:logger)
405
+ params.merge(:logger => logger)
406
+ else
407
+ params
408
+ end
409
+ end
410
+
411
+ def store(key, io)
412
+ bucket.put(key, io)
413
+ end
414
+
415
+ def retrieve(key)
416
+ bucket.get key
417
+ end
418
+
419
+ def save_credentials(creds, key = nil)
420
+ key ||= @options.bucket_key
421
+ bucket.put(key, creds)
422
+ end
423
+
424
+ def save_images(input)
425
+ key ||= @options.cluster_image_key
426
+ bucket.put key, input, {}, 'public-read'
427
+ end
428
+
429
+ def credentials_url(seconds = 1200)
430
+ keygen = RightAws::S3Generator::Key.new(bucket, @options.bucket_key)
431
+ keygen.get(seconds)
432
+ end
433
+
434
+ def period(args)
435
+ shots = {}
436
+ ecc.describe_snapshots.each do |shot|
437
+ next unless shot[:aws_owner].to_s == @options.owner.to_s
438
+ next unless shot[:aws_status] == 'completed'
439
+ shot[:started_at] = Time.parse shot[:aws_started_at]
440
+ aid = shot[:aws_volume_id]
441
+ if shots.include? aid
442
+ shots[aid].push shot
443
+ else
444
+ shots.merge! aid => [shot]
445
+ end
446
+ end
447
+
448
+ stores = @options.volumes['stores'] || []
449
+ defaults = if @options.volumes.include?('defaults')
450
+ @options.volumes['defaults']
451
+ else
452
+ {}
453
+ end
454
+
455
+ shots.keys.each do |k|
456
+ opts = if v = stores.detect {|v| v['aws_id'] == k}
457
+ defaults.merge v
458
+ else
459
+ defaults
460
+ end
461
+ snap_count = opts['snaps'] || 2
462
+ snap_count = 2 unless snap_count > 1
463
+
464
+ ordered = shots[k].sort {|a,b| a[:started_at] <=> b[:started_at] }.reverse
465
+ ordered.slice(snap_count, ordered.length).map {|shot|
466
+ ecc.delete_snapshot shot[:aws_id]
467
+ shot[:aws_id]
468
+ }
469
+ end
470
+ end
471
+
472
+ def bucket
473
+ unless @options.cluster_bucket
474
+ puts "#{Cluster::NAME} has not been configured with a bucket for client materials."
475
+ exit 2
476
+ end
477
+
478
+ @bucket ||= sss.bucket(@options.cluster_bucket, true)
479
+
480
+ unless @bucket
481
+ puts "#{Cluster::NAME} bucket named #{@options.cluster_bucket} cannot be created or accessed."
482
+ exit 2
483
+ else
484
+ @bucket
485
+ end
486
+ end
487
+
488
+ def get_image_file
489
+ require 'open-uri'
490
+ open(Cluster::IMAGES) {|f| YAML::load(f) }
491
+ end
492
+
493
+ def get_image(bits)
494
+ @image_file ||= get_image_file
495
+ case bits
496
+ when 32
497
+ @image_file['thirtytwo']
498
+ when 64
499
+ @image_file['sixtyfour']
500
+ end
501
+ end
502
+
503
+ def options
504
+ @options
505
+ end
506
+
507
+ def security(groups)
508
+ ecc.describe_security_groups(groups).inject({}) {|m,g|
509
+ p = g[:aws_perms].map {|p|
510
+ next unless p[:cidr_ips]
511
+ [p[:cidr_ips], p[:from_port]..p[:to_port]]
512
+ }.compact
513
+
514
+ m.merge g[:aws_group_name] => p
515
+ }
516
+ end
517
+
518
+ def revoke(ips)
519
+ ips.map {|ip|
520
+ ecc.revoke_security_group_IP_ingress('access', 22, 22, 'tcp', ip) and ip
521
+ }
522
+ end
523
+
524
+ def authorize(ips)
525
+ ips.map {|ip|
526
+ ecc.authorize_security_group_IP_ingress('access', 22, 22, 'tcp', ip) and ip
527
+ }
528
+ end
529
+
530
+ def query(qry)
531
+ res = sdb.select qry
532
+ if res
533
+ self.class.from_sdb_results res
534
+ else
535
+ nil
536
+ end
537
+ end
538
+
539
+
540
+ class << self
541
+ def to_sdb_attributes(args)
542
+ attrs = {}
543
+ args.each do |k, v|
544
+ v && attrs.merge!(k => Array(v))
545
+ end
546
+ attrs
547
+ end
548
+
549
+ def from_sdb_results(res)
550
+ res[:items].inject([]) {|m, obj|
551
+ aws_id = obj.keys.first
552
+ attrs = obj[aws_id]
553
+ args = attrs.keys.inject({:aws_id => aws_id}) {|n, attr|
554
+ val = attrs[attr]
555
+ if val.empty?
556
+ n.merge attr => nil
557
+ elsif val.length == 1
558
+ n.merge attr => val.first
559
+ else
560
+ n.merge attr => val
561
+ end
562
+ }
563
+
564
+ m << args
565
+ }
566
+ end
567
+ end
568
+ end