cluster 0.5.33

Sign up to get free protection for your applications and to get access to all the features.
@@ -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