CloudyScripts 0.0.8 → 0.0.9
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.
- data/LICENSE +7 -7
- data/README.rdoc +27 -27
- data/Rakefile +50 -50
- data/lib/cloudyscripts.rb +26 -26
- data/lib/help/dm_crypt_helper.rb +204 -204
- data/lib/help/ec2_helper.rb +54 -54
- data/lib/help/remote_command_handler.rb +203 -190
- data/lib/help/script_execution_state.rb +97 -95
- data/lib/help/state_change_listener.rb +13 -13
- data/lib/scripts/ec2/ami2_ebs_conversion.rb +446 -442
- data/lib/scripts/ec2/dm_encrypt.rb +194 -190
- data/lib/scripts/ec2/ec2_script.rb +41 -41
- metadata +2 -2
@@ -1,95 +1,97 @@
|
|
1
|
-
# Implements a little state-machine.
|
2
|
-
# Usage: for every state you need, extend this class.
|
3
|
-
# The method enter() must be implemented for every state you code and
|
4
|
-
# return another state.
|
5
|
-
class ScriptExecutionState
|
6
|
-
# context information for the state (hash)
|
7
|
-
attr_reader :context, :logger
|
8
|
-
|
9
|
-
def initialize(context)
|
10
|
-
@context = context
|
11
|
-
@state_change_listeners = []
|
12
|
-
@logger = context[:logger]
|
13
|
-
if @logger == nil
|
14
|
-
@logger = Logger.new(STDOUT)
|
15
|
-
@logger.level = Logger::WARN
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
# Listener should extend #StateChangeListener (or implement a
|
20
|
-
# state_changed(state) method). Note: calls are synchronous.
|
21
|
-
def register_state_change_listener(listener)
|
22
|
-
@state_change_listeners << listener
|
23
|
-
end
|
24
|
-
|
25
|
-
# Start the state machine using this state as initial state.
|
26
|
-
def start_state_machine
|
27
|
-
@current_state = self
|
28
|
-
@logger.info "start state machine with #{@current_state.
|
29
|
-
while !@current_state.done? && !@current_state.failed?
|
30
|
-
begin
|
31
|
-
@logger.info "state machine: current state = #{@current_state.
|
32
|
-
@current_state = @current_state.enter()
|
33
|
-
notify_state_change_listeners(@current_state)
|
34
|
-
rescue Exception => e
|
35
|
-
@context[:
|
36
|
-
|
37
|
-
|
38
|
-
@
|
39
|
-
@
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
end
|
1
|
+
# Implements a little state-machine.
|
2
|
+
# Usage: for every state you need, extend this class.
|
3
|
+
# The method enter() must be implemented for every state you code and
|
4
|
+
# return another state.
|
5
|
+
class ScriptExecutionState
|
6
|
+
# context information for the state (hash)
|
7
|
+
attr_reader :context, :logger
|
8
|
+
|
9
|
+
def initialize(context)
|
10
|
+
@context = context
|
11
|
+
@state_change_listeners = []
|
12
|
+
@logger = context[:logger]
|
13
|
+
if @logger == nil
|
14
|
+
@logger = Logger.new(STDOUT)
|
15
|
+
@logger.level = Logger::WARN
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Listener should extend #StateChangeListener (or implement a
|
20
|
+
# state_changed(state) method). Note: calls are synchronous.
|
21
|
+
def register_state_change_listener(listener)
|
22
|
+
@state_change_listeners << listener
|
23
|
+
end
|
24
|
+
|
25
|
+
# Start the state machine using this state as initial state.
|
26
|
+
def start_state_machine
|
27
|
+
@current_state = self
|
28
|
+
@logger.info "start state machine with #{@current_state.to_s}"
|
29
|
+
while !@current_state.done? && !@current_state.failed?
|
30
|
+
begin
|
31
|
+
@logger.info "state machine: current state = #{@current_state.to_s}"
|
32
|
+
@current_state = @current_state.enter()
|
33
|
+
notify_state_change_listeners(@current_state)
|
34
|
+
rescue Exception => e
|
35
|
+
if @context[:result] != nil
|
36
|
+
@context[:result][:details] = e.backtrace().join("\n")
|
37
|
+
end
|
38
|
+
@current_state = FailedState.new(@context, e.to_s, @current_state)
|
39
|
+
notify_state_change_listeners(@current_state)
|
40
|
+
@logger.warn "StateMachine exception during execution: #{e}"
|
41
|
+
@logger.warn "#{e.backtrace.join("\n")}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
@current_state
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns the state that is reached after execution.
|
48
|
+
def end_state
|
49
|
+
@current_state
|
50
|
+
end
|
51
|
+
|
52
|
+
# To be implemented. Executes the code for this state.
|
53
|
+
def enter
|
54
|
+
raise Exception.new("TaskExecutionState is abstract")
|
55
|
+
end
|
56
|
+
|
57
|
+
# To be implemented. Indicates if the final state is reached.
|
58
|
+
def done?
|
59
|
+
false
|
60
|
+
end
|
61
|
+
|
62
|
+
# To be implemented. Indicates if the final state is a failure state.
|
63
|
+
def failed?
|
64
|
+
false
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_s
|
68
|
+
s = self.class.to_s
|
69
|
+
s.sub(/.*\:\:/,'')
|
70
|
+
end
|
71
|
+
|
72
|
+
# Standard state reached when an exception occurs.
|
73
|
+
class FailedState < ScriptExecutionState
|
74
|
+
attr_accessor :failure_reason, :from_state
|
75
|
+
def initialize(context, failure_reason, from_state)
|
76
|
+
super(context)
|
77
|
+
@failure_reason = failure_reason
|
78
|
+
@from_state = from_state
|
79
|
+
end
|
80
|
+
def done?
|
81
|
+
true
|
82
|
+
end
|
83
|
+
def failed?
|
84
|
+
true
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
# Notifies all listeners of state changes
|
91
|
+
def notify_state_change_listeners(state)
|
92
|
+
@state_change_listeners.each() {|listener|
|
93
|
+
listener.state_changed(state)
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
@@ -1,13 +1,13 @@
|
|
1
|
-
# Defines a template for a class that allows to be notified
|
2
|
-
# on state-changes in #ScriptExecutionState. A listener must be
|
3
|
-
# registered via #ScriptExecutionState::register_state_change_listener.
|
4
|
-
# When a state changes the method #state_changed is called.
|
5
|
-
|
6
|
-
class StateChangeListener
|
7
|
-
# Method called when a state changes. Note: calls are synchronous, the
|
8
|
-
# listener should return quickly and handle more complicated routines
|
9
|
-
# in a different thread.
|
10
|
-
def state_changed(state)
|
11
|
-
raise Exception.new("StateChangeListener: state change notification not implemented")
|
12
|
-
end
|
13
|
-
end
|
1
|
+
# Defines a template for a class that allows to be notified
|
2
|
+
# on state-changes in #ScriptExecutionState. A listener must be
|
3
|
+
# registered via #ScriptExecutionState::register_state_change_listener.
|
4
|
+
# When a state changes the method #state_changed is called.
|
5
|
+
|
6
|
+
class StateChangeListener
|
7
|
+
# Method called when a state changes. Note: calls are synchronous, the
|
8
|
+
# listener should return quickly and handle more complicated routines
|
9
|
+
# in a different thread.
|
10
|
+
def state_changed(state)
|
11
|
+
raise Exception.new("StateChangeListener: state change notification not implemented")
|
12
|
+
end
|
13
|
+
end
|
@@ -1,442 +1,446 @@
|
|
1
|
-
require "help/script_execution_state"
|
2
|
-
require "scripts/ec2/ec2_script"
|
3
|
-
require "help/remote_command_handler"
|
4
|
-
#require "help/dm_crypt_helper"
|
5
|
-
require "AWS"
|
6
|
-
|
7
|
-
class AWS::EC2::Base
|
8
|
-
def register_image_updated(options)
|
9
|
-
params = {}
|
10
|
-
params["Name"] = options[:name].to_s
|
11
|
-
params["BlockDeviceMapping.1.Ebs.SnapshotId"] = options[:snapshot_id].to_s
|
12
|
-
params["BlockDeviceMapping.1.DeviceName"] = options[:root_device_name].to_s
|
13
|
-
params["Description"] = options[:description].to_s
|
14
|
-
params["KernelId"] = options[:kernel_id].to_s
|
15
|
-
params["RamdiskId"] = options[:ramdisk_id].to_s
|
16
|
-
params["Architecture"] = options[:architecture].to_s
|
17
|
-
params["RootDeviceName"] = options[:root_device_name].to_s
|
18
|
-
return response_generator(:action => "RegisterImage", :params => params)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
# Creates a bootable EBS storage from an existing AMI.
|
23
|
-
#
|
24
|
-
|
25
|
-
class Ami2EbsConversion < Ec2Script
|
26
|
-
# Input parameters
|
27
|
-
# * aws_access_key => the Amazon AWS Access Key (see Your Account -> Security Credentials)
|
28
|
-
# * aws_secret_key => the Amazon AWS Secret Key
|
29
|
-
# * ami_id => the ID of the AMI to be converted
|
30
|
-
# * security_group_name => name of the security group to start
|
31
|
-
# * ssh_key_data => Key information for the security group that starts the AMI [if not set, use ssh_key_files]
|
32
|
-
# * ssh_key_files => Key information for the security group that starts the AMI
|
33
|
-
# * remote_command_handler => object that allows to connect via ssh and execute commands (optional)
|
34
|
-
# * ec2_api_handler => object that allows to access the EC2 API (optional)
|
35
|
-
# * ec2_api_server => server to connect to (option, default is us-east-1.ec2.amazonaws.com)
|
36
|
-
# * name => the name of the AMI to be created
|
37
|
-
# * description => description on AMI to be created (optional)
|
38
|
-
# * temp_device_name => [default /dev/sdj] device name used to attach the temporary storage; change this only if there's already a volume attacged as /dev/sdj (optional, default is /dev/sdj)
|
39
|
-
# * root_device_name"=> [default /dev/sda1] device name used for the root device (optional)
|
40
|
-
def initialize(input_params)
|
41
|
-
super(input_params)
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
end_state
|
68
|
-
|
69
|
-
@result[:
|
70
|
-
@result[:
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
@logger.warn
|
77
|
-
|
78
|
-
err
|
79
|
-
|
80
|
-
@result[:
|
81
|
-
@result[:
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
#
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
connected
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
@logger.
|
131
|
-
end
|
132
|
-
else
|
133
|
-
raise Exception.new("no key information specified")
|
134
|
-
end
|
135
|
-
if !connected
|
136
|
-
sleep(5) #try again
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
@context[:
|
159
|
-
@
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
@context[:
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
1
|
+
require "help/script_execution_state"
|
2
|
+
require "scripts/ec2/ec2_script"
|
3
|
+
require "help/remote_command_handler"
|
4
|
+
#require "help/dm_crypt_helper"
|
5
|
+
require "AWS"
|
6
|
+
|
7
|
+
class AWS::EC2::Base
|
8
|
+
def register_image_updated(options)
|
9
|
+
params = {}
|
10
|
+
params["Name"] = options[:name].to_s
|
11
|
+
params["BlockDeviceMapping.1.Ebs.SnapshotId"] = options[:snapshot_id].to_s
|
12
|
+
params["BlockDeviceMapping.1.DeviceName"] = options[:root_device_name].to_s
|
13
|
+
params["Description"] = options[:description].to_s
|
14
|
+
params["KernelId"] = options[:kernel_id].to_s
|
15
|
+
params["RamdiskId"] = options[:ramdisk_id].to_s
|
16
|
+
params["Architecture"] = options[:architecture].to_s
|
17
|
+
params["RootDeviceName"] = options[:root_device_name].to_s
|
18
|
+
return response_generator(:action => "RegisterImage", :params => params)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Creates a bootable EBS storage from an existing AMI.
|
23
|
+
#
|
24
|
+
|
25
|
+
class Ami2EbsConversion < Ec2Script
|
26
|
+
# Input parameters
|
27
|
+
# * aws_access_key => the Amazon AWS Access Key (see Your Account -> Security Credentials)
|
28
|
+
# * aws_secret_key => the Amazon AWS Secret Key
|
29
|
+
# * ami_id => the ID of the AMI to be converted
|
30
|
+
# * security_group_name => name of the security group to start
|
31
|
+
# * ssh_key_data => Key information for the security group that starts the AMI [if not set, use ssh_key_files]
|
32
|
+
# * ssh_key_files => Key information for the security group that starts the AMI
|
33
|
+
# * remote_command_handler => object that allows to connect via ssh and execute commands (optional)
|
34
|
+
# * ec2_api_handler => object that allows to access the EC2 API (optional)
|
35
|
+
# * ec2_api_server => server to connect to (option, default is us-east-1.ec2.amazonaws.com)
|
36
|
+
# * name => the name of the AMI to be created
|
37
|
+
# * description => description on AMI to be created (optional)
|
38
|
+
# * temp_device_name => [default /dev/sdj] device name used to attach the temporary storage; change this only if there's already a volume attacged as /dev/sdj (optional, default is /dev/sdj)
|
39
|
+
# * root_device_name"=> [default /dev/sda1] device name used for the root device (optional)
|
40
|
+
def initialize(input_params)
|
41
|
+
super(input_params)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Executes the script.
|
45
|
+
def start_script()
|
46
|
+
begin
|
47
|
+
# optional parameters and initialization
|
48
|
+
if @input_params[:name] == nil
|
49
|
+
@input_params[:name] = "Boot EBS (for AMI #{@input_params[:ami_id]}) at #{Time.now.strftime('%d/%m/%Y %H.%M.%S')}"
|
50
|
+
else
|
51
|
+
end
|
52
|
+
if @input_params[:description] == nil
|
53
|
+
@input_params[:description] = @input_params[:name]
|
54
|
+
end
|
55
|
+
if @input_params[:temp_device_name] == nil
|
56
|
+
@input_params[:temp_device_name] = "/dev/sdj"
|
57
|
+
end
|
58
|
+
if @input_params[:root_device_name] == nil
|
59
|
+
@input_params[:root_device_name] = "/dev/sda1"
|
60
|
+
end
|
61
|
+
# start state machine
|
62
|
+
current_state = Ami2EbsConversionState.load_state(@input_params)
|
63
|
+
@state_change_listeners.each() {|listener|
|
64
|
+
current_state.register_state_change_listener(listener)
|
65
|
+
}
|
66
|
+
end_state = current_state.start_state_machine()
|
67
|
+
if end_state.failed?
|
68
|
+
@result[:failed] = true
|
69
|
+
@result[:failure_reason] = current_state.end_state.failure_reason
|
70
|
+
@result[:end_state] = current_state.end_state
|
71
|
+
else
|
72
|
+
@result[:failed] = false
|
73
|
+
end
|
74
|
+
rescue Exception => e
|
75
|
+
@logger.warn "exception during encryption: #{e}"
|
76
|
+
@logger.warn e.backtrace.join("\n")
|
77
|
+
err = e.to_s
|
78
|
+
err += " (in #{current_state.end_state.to_s})" unless current_state == nil
|
79
|
+
@result[:failed] = true
|
80
|
+
@result[:failure_reason] = err
|
81
|
+
@result[:end_state] = current_state.end_state unless current_state == nil
|
82
|
+
ensure
|
83
|
+
begin
|
84
|
+
@input_params[:remote_command_handler].disconnect
|
85
|
+
rescue Exception => e2
|
86
|
+
end
|
87
|
+
end
|
88
|
+
#
|
89
|
+
@result[:done] = true
|
90
|
+
end
|
91
|
+
|
92
|
+
# Returns a hash with the following information:
|
93
|
+
# :done => if execution is done
|
94
|
+
#
|
95
|
+
def get_execution_result
|
96
|
+
@result
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
# Here begins the state machine implementation
|
102
|
+
class Ami2EbsConversionState < ScriptExecutionState
|
103
|
+
def self.load_state(context)
|
104
|
+
state = context[:initial_state] == nil ? InitialState.new(context) : context[:initial_state]
|
105
|
+
state
|
106
|
+
end
|
107
|
+
|
108
|
+
def connect
|
109
|
+
if @context[:remote_command_handler] == nil
|
110
|
+
@context[:remote_command_handler] = RemoteCommandHandler.new
|
111
|
+
end
|
112
|
+
connected = false
|
113
|
+
remaining_trials = 3
|
114
|
+
while !connected && remaining_trials > 0
|
115
|
+
remaining_trials -= 1
|
116
|
+
if @context[:ssh_keyfile] != nil
|
117
|
+
begin
|
118
|
+
@context[:remote_command_handler].connect_with_keyfile(@context[:dns_name], @context[:ssh_keyfile])
|
119
|
+
connected = true
|
120
|
+
rescue Exception => e
|
121
|
+
@logger.info("connection failed due to #{e}")
|
122
|
+
@logger.debug(e.backtrace.join("\n"))
|
123
|
+
end
|
124
|
+
elsif @context[:ssh_keydata] != nil
|
125
|
+
begin
|
126
|
+
@context[:remote_command_handler].connect(@context[:dns_name], "root", @context[:ssh_keydata])
|
127
|
+
connected = true
|
128
|
+
rescue Exception => e
|
129
|
+
@logger.info("connection failed due to #{e}")
|
130
|
+
@logger.debug(e.backtrace.join("\n"))
|
131
|
+
end
|
132
|
+
else
|
133
|
+
raise Exception.new("no key information specified")
|
134
|
+
end
|
135
|
+
if !connected
|
136
|
+
sleep(5) #try again
|
137
|
+
end
|
138
|
+
end
|
139
|
+
if !connected
|
140
|
+
raise Exception.new("connection attempts stopped")
|
141
|
+
end
|
142
|
+
@context[:result][:os] = @context[:remote_command_handler].retrieve_os()
|
143
|
+
@logger.info "connected to #{@context[:dns_name]}"
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
# Nothing done yet. Start by instantiating an AMI (in the right zone?)
|
149
|
+
# which serves to create
|
150
|
+
class InitialState < Ami2EbsConversionState
|
151
|
+
def enter
|
152
|
+
startup_ami()
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
def startup_ami()
|
158
|
+
@logger.debug "start up AMI #{@context[:ami_id]}"
|
159
|
+
res = @context[:ec2_api_handler].run_instances(:image_id => @context[:ami_id],
|
160
|
+
:security_group => @context[:security_group_name], :key_name => @context[:key_name])
|
161
|
+
instance_id = res['instancesSet']['item'][0]['instanceId']
|
162
|
+
@context[:instance_id] = instance_id
|
163
|
+
@logger.info "started instance #{instance_id}"
|
164
|
+
#availability_zone , key_name/group_name
|
165
|
+
started = false
|
166
|
+
while started == false
|
167
|
+
sleep(5)
|
168
|
+
res = @context[:ec2_api_handler].describe_instances(:instance_id => @context[:instance_id])
|
169
|
+
state = res['reservationSet']['item'][0]['instancesSet']['item'][0]['instanceState']
|
170
|
+
@logger.info "instance in state #{state['name']} (#{state['code']})"
|
171
|
+
if state['code'].to_i == 16
|
172
|
+
started = true
|
173
|
+
@context[:dns_name] = res['reservationSet']['item'][0]['instancesSet']['item'][0]['dnsName']
|
174
|
+
@context[:availability_zone] = res['reservationSet']['item'][0]['instancesSet']['item'][0]['placement']['availabilityZone']
|
175
|
+
@context[:kernel_id] = res['reservationSet']['item'][0]['instancesSet']['item'][0]['kernelId']
|
176
|
+
@context[:ramdisk_id] = res['reservationSet']['item'][0]['instancesSet']['item'][0]['ramdiskId']
|
177
|
+
@context[:architecture] = res['reservationSet']['item'][0]['instancesSet']['item'][0]['architecture']
|
178
|
+
elsif state['code'].to_i != 0
|
179
|
+
raise Exception.new('instance failed to start up')
|
180
|
+
end
|
181
|
+
end
|
182
|
+
AmiStarted.new(@context)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Ami started. Create a storage
|
187
|
+
class AmiStarted < Ami2EbsConversionState
|
188
|
+
def enter
|
189
|
+
create_storage()
|
190
|
+
end
|
191
|
+
|
192
|
+
private
|
193
|
+
|
194
|
+
def create_storage()
|
195
|
+
@logger.debug "create volume in zone #{@context[:availability_zone]}"
|
196
|
+
res = @context[:ec2_api_handler].create_volume(:availability_zone => @context[:availability_zone], :size => "10")
|
197
|
+
@context[:volume_id] = res['volumeId']
|
198
|
+
started = false
|
199
|
+
while !started
|
200
|
+
sleep(5)
|
201
|
+
#TODO: check for timeout?
|
202
|
+
res = @context[:ec2_api_handler].describe_volumes(:volume_id => @context[:volume_id])
|
203
|
+
state = res['volumeSet']['item'][0]['status']
|
204
|
+
@logger.debug "volume state #{state}"
|
205
|
+
if state == 'available'
|
206
|
+
started = true
|
207
|
+
end
|
208
|
+
end
|
209
|
+
StorageCreated.new(@context)
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
|
214
|
+
# Storage created. Attach it.
|
215
|
+
class StorageCreated < Ami2EbsConversionState
|
216
|
+
def enter
|
217
|
+
attach_storage()
|
218
|
+
end
|
219
|
+
|
220
|
+
private
|
221
|
+
|
222
|
+
def attach_storage()
|
223
|
+
@logger.debug "attach volume #{@context[:volume_id]} to instance #{@context[:instance_id]} on device #{@context[:temp_device_name]}"
|
224
|
+
@context[:ec2_api_handler].attach_volume(:volume_id => @context[:volume_id],
|
225
|
+
:instance_id => @context[:instance_id],
|
226
|
+
:device => @context[:temp_device_name]
|
227
|
+
)
|
228
|
+
done = false
|
229
|
+
while !done
|
230
|
+
sleep(5)
|
231
|
+
#TODO: check for timeout?
|
232
|
+
res = @context[:ec2_api_handler].describe_volumes(:volume_id => @context[:volume_id])
|
233
|
+
state = res['volumeSet']['item'][0]['status']
|
234
|
+
@logger.debug "storage attaching: #{state}"
|
235
|
+
if state == 'in-use'
|
236
|
+
done = true
|
237
|
+
end
|
238
|
+
end
|
239
|
+
StorageAttached.new(@context)
|
240
|
+
end
|
241
|
+
|
242
|
+
end
|
243
|
+
|
244
|
+
# Storage attached. Create a file-system and moun it
|
245
|
+
class StorageAttached < Ami2EbsConversionState
|
246
|
+
def enter
|
247
|
+
create_fs()
|
248
|
+
end
|
249
|
+
|
250
|
+
private
|
251
|
+
|
252
|
+
def create_fs()
|
253
|
+
@logger.debug "create filesystem on #{@context[:dns_name]} to #{@context[:temp_device_name]}"
|
254
|
+
connect()
|
255
|
+
@context[:remote_command_handler].create_filesystem("ext3", @context[:temp_device_name])
|
256
|
+
FileSystemCreated.new(@context)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
# File system created. Mount it.
|
261
|
+
class FileSystemCreated < Ami2EbsConversionState
|
262
|
+
def enter
|
263
|
+
mount_fs()
|
264
|
+
end
|
265
|
+
|
266
|
+
private
|
267
|
+
|
268
|
+
def mount_fs()
|
269
|
+
@context[:path] = "/mnt/tmp_#{@context[:volume_id]}"
|
270
|
+
@logger.debug "mount #{@context[:temp_device_name]} on #{@context[:path]}"
|
271
|
+
@context[:remote_command_handler].mkdir(@context[:path])
|
272
|
+
@context[:remote_command_handler].mount(@context[:temp_device_name], @context[:path])
|
273
|
+
sleep(2) #give mount some time
|
274
|
+
if !@context[:remote_command_handler].drive_mounted?(@context[:path])
|
275
|
+
raise Exception.new("drive #{@context[:path]} not mounted")
|
276
|
+
end
|
277
|
+
FileSystemMounted.new(@context)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# File system created and mounted. Copy the root partition.
|
282
|
+
class FileSystemMounted < Ami2EbsConversionState
|
283
|
+
def enter
|
284
|
+
copy()
|
285
|
+
end
|
286
|
+
|
287
|
+
private
|
288
|
+
|
289
|
+
def copy()
|
290
|
+
@logger.debug "start copying to #{@context[:path]}"
|
291
|
+
start = Time.new.to_i
|
292
|
+
@context[:remote_command_handler].rsync("/", "#{@context[:path]}", "/mnt/")
|
293
|
+
@context[:remote_command_handler].rsync("/dev/", "#{@context[:path]}/dev/")
|
294
|
+
endtime = Time.new.to_i
|
295
|
+
@logger.info "copy took #{(endtime-start)}s"
|
296
|
+
CopyDone.new(@context)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# Copy operation done. Unmount volume.
|
301
|
+
class CopyDone < Ami2EbsConversionState
|
302
|
+
def enter
|
303
|
+
unmount()
|
304
|
+
end
|
305
|
+
|
306
|
+
private
|
307
|
+
|
308
|
+
def unmount()
|
309
|
+
@logger.debug "unmount #{@context[:path]}"
|
310
|
+
@context[:remote_command_handler].umount(@context[:path])
|
311
|
+
sleep(2) #give umount some time
|
312
|
+
if @context[:remote_command_handler].drive_mounted?(@context[:path])
|
313
|
+
raise Exception.new("drive #{@context[:path]} not unmounted")
|
314
|
+
end
|
315
|
+
VolumeUnmounted.new(@context)
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
# Volume unmounted. Detach it.
|
320
|
+
class VolumeUnmounted < Ami2EbsConversionState
|
321
|
+
def enter
|
322
|
+
detach()
|
323
|
+
end
|
324
|
+
|
325
|
+
private
|
326
|
+
|
327
|
+
def detach()
|
328
|
+
@logger.debug "detach volume #{@context[:volume_id]}"
|
329
|
+
@context[:ec2_api_handler].detach_volume(:volume_id => @context[:volume_id],
|
330
|
+
:instance_id => @context[:instance_id]
|
331
|
+
)
|
332
|
+
done = false
|
333
|
+
while !done
|
334
|
+
sleep(3)
|
335
|
+
#TODO: check for timeout?
|
336
|
+
res = @context[:ec2_api_handler].describe_volumes(:volume_id => @context[:volume_id])
|
337
|
+
@logger.debug "volume detaching: #{res.inspect}"
|
338
|
+
if res['volumeSet']['item'][0]['status'] == 'available'
|
339
|
+
done = true
|
340
|
+
end
|
341
|
+
end
|
342
|
+
VolumeDetached.new(@context)
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
|
347
|
+
# VolumeDetached. Create snaphot
|
348
|
+
class VolumeDetached < Ami2EbsConversionState
|
349
|
+
def enter
|
350
|
+
create_snapshot()
|
351
|
+
end
|
352
|
+
|
353
|
+
private
|
354
|
+
|
355
|
+
def create_snapshot()
|
356
|
+
@logger.debug "create snapshot for volume #{@context[:volume_id]}"
|
357
|
+
res = @context[:ec2_api_handler].create_snapshot(:volume_id => @context[:volume_id])
|
358
|
+
@context[:snapshot_id] = res['snapshotId']
|
359
|
+
@logger.info "snapshot_id = #{@context[:snapshot_id]}"
|
360
|
+
done = false
|
361
|
+
while !done
|
362
|
+
sleep(5)
|
363
|
+
#TODO: check for timeout?
|
364
|
+
res = @context[:ec2_api_handler].describe_snapshots(:snapshot_id => @context[:snapshot_id])
|
365
|
+
@logger.debug "snapshot creating: #{res.inspect}"
|
366
|
+
if res['snapshotSet']['item'][0]['status'] == 'completed'
|
367
|
+
done = true
|
368
|
+
end
|
369
|
+
end
|
370
|
+
SnapshotCreated.new(@context)
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
# Snapshot created. Delete volume.
|
375
|
+
class SnapshotCreated < Ami2EbsConversionState
|
376
|
+
def enter
|
377
|
+
delete_volume()
|
378
|
+
end
|
379
|
+
|
380
|
+
private
|
381
|
+
|
382
|
+
def delete_volume
|
383
|
+
@logger.debug "delete volume #{@context[:volume_id]}"
|
384
|
+
res = @context[:ec2_api_handler].delete_volume(:volume_id => @context[:volume_id])
|
385
|
+
VolumeDeleted.new(@context)
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
# Volume deleted. Register snapshot.
|
390
|
+
class VolumeDeleted < Ami2EbsConversionState
|
391
|
+
def enter
|
392
|
+
register()
|
393
|
+
end
|
394
|
+
|
395
|
+
private
|
396
|
+
|
397
|
+
def register()
|
398
|
+
@logger.debug "register snapshot #{@context[:snapshot_id]} as #{@context[:name]}"
|
399
|
+
res = @context[:ec2_api_handler].register_image_updated(:snapshot_id => @context[:snapshot_id],
|
400
|
+
:kernel_id => @context[:kernel_id], :architecture => @context[:architecture],
|
401
|
+
:root_device_name => @context[:root_device_name],
|
402
|
+
:description => @context[:description], :name => @context[:name],
|
403
|
+
:ramdisk_id => @context[:ramdisk_id]
|
404
|
+
)
|
405
|
+
@logger.debug "result of registration = #{res.inspect}"
|
406
|
+
@context[:result][:image_id] = res['imageId']
|
407
|
+
@logger.info "resulting image_id = #{@context[:result][:image_id]}"
|
408
|
+
SnapshotRegistered.new(@context)
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
# Snapshot registered. Shutdown instance.
|
413
|
+
class SnapshotRegistered < Ami2EbsConversionState
|
414
|
+
def enter
|
415
|
+
shut_down()
|
416
|
+
end
|
417
|
+
|
418
|
+
private
|
419
|
+
|
420
|
+
def shut_down()
|
421
|
+
@logger.debug "shutdown instance #{@context[:instance_id]}"
|
422
|
+
res = @context[:ec2_api_handler].terminate_instances(:instance_id => @context[:instance_id])
|
423
|
+
done = false
|
424
|
+
while done == false
|
425
|
+
sleep(5)
|
426
|
+
res = @context[:ec2_api_handler].describe_instances(:instance_id => @context[:instance_id])
|
427
|
+
state = res['reservationSet']['item'][0]['instancesSet']['item'][0]['instanceState']
|
428
|
+
@logger.debug "instance in state #{state['name']} (#{state['code']})"
|
429
|
+
if state['code'].to_i == 48
|
430
|
+
done = true
|
431
|
+
elsif state['code'].to_i != 32
|
432
|
+
raise Exception.new('instance failed to shut down')
|
433
|
+
end
|
434
|
+
end
|
435
|
+
Done.new(@context)
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
# Instance shutdown. Done.
|
440
|
+
class Done < Ami2EbsConversionState
|
441
|
+
def done?
|
442
|
+
true
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
end
|