CloudyScripts 0.0.14 → 1.4.15
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/Rakefile +1 -1
- data/lib/help/ec2_helper.rb +37 -0
- data/lib/help/remote_command_handler.rb +28 -2
- data/lib/help/state_transition_helper.rb +70 -23
- data/lib/scripts/ec2/copy_snapshot.rb +188 -0
- data/lib/scripts/ec2/download_snapshot.rb +1 -0
- metadata +6 -5
data/Rakefile
CHANGED
|
@@ -12,7 +12,7 @@ require 'rake/testtask'
|
|
|
12
12
|
|
|
13
13
|
spec = Gem::Specification.new do |s|
|
|
14
14
|
s.name = 'CloudyScripts'
|
|
15
|
-
s.version = '
|
|
15
|
+
s.version = '1.4.15'
|
|
16
16
|
s.has_rdoc = true
|
|
17
17
|
s.extra_rdoc_files = ['README.rdoc', 'LICENSE']
|
|
18
18
|
s.summary = 'Scripts to facilitate programming for infrastructure clouds.'
|
data/lib/help/ec2_helper.rb
CHANGED
|
@@ -59,4 +59,41 @@ class Ec2Helper
|
|
|
59
59
|
end
|
|
60
60
|
return vols['volumeSet']['item'][0][prop.to_s]
|
|
61
61
|
end
|
|
62
|
+
|
|
63
|
+
def snapshot_prop(snapshot_id, prop)
|
|
64
|
+
snaps = @ec2_api.describe_snapshots(:snapshot_id => snapshot_id)
|
|
65
|
+
begin
|
|
66
|
+
if snaps['snapshotSet']['item'].size == 0
|
|
67
|
+
raise Exception.new("snapshot #{snapshot_id} not found")
|
|
68
|
+
end
|
|
69
|
+
return snaps['snapshotSet']['item'][0][prop.to_s]
|
|
70
|
+
rescue
|
|
71
|
+
raise Exception.new("snapshot #{snapshot_id} not found")
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def ami_prop(ami_id, prop)
|
|
76
|
+
amis = @ec2_api.describe_images(:image_id => ami_id)
|
|
77
|
+
begin
|
|
78
|
+
if amis['imagesSet']['item'].size == 0
|
|
79
|
+
raise Exception.new("image #{ami_id} not found")
|
|
80
|
+
end
|
|
81
|
+
return amis['imagesSet']['item'][0][prop.to_s]
|
|
82
|
+
rescue
|
|
83
|
+
raise Exception.new("image #{ami_id} not found")
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def instance_prop(instance_id, prop)
|
|
88
|
+
instances = @ec2_api.describe_instances(:instance_id => instance_id)
|
|
89
|
+
begin
|
|
90
|
+
if instances['reservationSet']['item'][0]['instancesSet']['item'].size == 0
|
|
91
|
+
raise Exception.new("instance #{instance_id} not found")
|
|
92
|
+
end
|
|
93
|
+
return instances['reservationSet']['item'][0]['instancesSet']['item'][prop.to_s]
|
|
94
|
+
rescue
|
|
95
|
+
raise Exception.new("instance #{instance_id} not found")
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
62
99
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
require 'rubygems'
|
|
2
2
|
require 'net/ssh'
|
|
3
|
+
require 'net/scp'
|
|
4
|
+
require 'timeout'
|
|
3
5
|
|
|
4
6
|
# Provides methods to be executed via ssh to remote instances.
|
|
5
7
|
class RemoteCommandHandler
|
|
@@ -102,14 +104,21 @@ class RemoteCommandHandler
|
|
|
102
104
|
end
|
|
103
105
|
|
|
104
106
|
# Copy directory using options -avHx
|
|
105
|
-
def
|
|
107
|
+
def local_rsync(source_path, dest_path, exclude_path = nil)
|
|
106
108
|
exclude = ""
|
|
107
109
|
if exclude_path != nil
|
|
108
110
|
exclude = "--exclude #{exclude_path}"
|
|
109
111
|
end
|
|
110
112
|
e = "rsync -avHx #{exclude} #{source_path} #{dest_path}"
|
|
111
113
|
@logger.debug "going to execute #{e}"
|
|
112
|
-
remote_exec_helper(e, nil, nil, false)
|
|
114
|
+
remote_exec_helper(e, nil, nil, false) #TODO: handle output in stderr?
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Copy directory via an ssh-tunnel.
|
|
118
|
+
def remote_rsync(keyfile, source_path, dest_ip, dest_path)
|
|
119
|
+
e = "rsync -rlpgoDzq -e "+'"'+"ssh -o stricthostkeychecking=no -i #{keyfile}"+'"'+" #{source_path} root@#{dest_ip}:#{dest_path}"
|
|
120
|
+
@logger.debug "going to execute #{e}"
|
|
121
|
+
remote_exec_helper(e, nil, nil, false) #TODO: handle output in stderr?
|
|
113
122
|
end
|
|
114
123
|
|
|
115
124
|
# Zip the complete contents of the source path into the destination file.
|
|
@@ -122,6 +131,15 @@ class RemoteCommandHandler
|
|
|
122
131
|
end
|
|
123
132
|
end
|
|
124
133
|
|
|
134
|
+
def echo(data, file)
|
|
135
|
+
exec = "echo #{data} > #{file}"
|
|
136
|
+
@logger.debug "going to execute #{exec}"
|
|
137
|
+
remote_execute(exec, nil, true)
|
|
138
|
+
if !file_exists?(file)
|
|
139
|
+
raise Exception.new("file #{file} could not be created")
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
125
143
|
# Executes the specified #exec_string on a remote session specified.
|
|
126
144
|
# When #push_data is specified, the data will be used as input for the
|
|
127
145
|
# command and thus allow to respond in advance to commands that ask the user
|
|
@@ -168,6 +186,14 @@ class RemoteCommandHandler
|
|
|
168
186
|
stdout.join()
|
|
169
187
|
end
|
|
170
188
|
|
|
189
|
+
def upload(ip, user, key_data, local_file, destination_file, timeout = 60)
|
|
190
|
+
Timeout::timeout(timeout) {
|
|
191
|
+
Net::SCP.start(ip, user, {:key_data => [key_data], :timeout => timeout}) do |scp|
|
|
192
|
+
scp.upload!(local_file, destination_file)
|
|
193
|
+
end
|
|
194
|
+
}
|
|
195
|
+
end
|
|
196
|
+
|
|
171
197
|
private
|
|
172
198
|
|
|
173
199
|
# Executes the specified #exec_string on the opened remote session.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
require 'net/scp'
|
|
2
|
+
|
|
1
3
|
# Contains methods that are used by the scripts in the state-machines. Since
|
|
2
4
|
# they are reused by different scripts, they are factored into this module.
|
|
3
|
-
#
|
|
5
|
+
#
|
|
4
6
|
# Note: it is supposed that a hash named @context exists @context[:script]
|
|
5
7
|
# must be set to a script object to pass information and messages
|
|
6
8
|
# to listeners.
|
|
@@ -20,34 +22,39 @@ module StateTransitionHelper
|
|
|
20
22
|
def connect(dns_name, ssh_keyfile = nil, ssh_keydata = nil)
|
|
21
23
|
post_message("connecting to #{dns_name}...")
|
|
22
24
|
connected = false
|
|
23
|
-
|
|
25
|
+
last_connection_problem = ""
|
|
26
|
+
remaining_trials = 5
|
|
24
27
|
while !connected && remaining_trials > 0
|
|
25
28
|
remaining_trials -= 1
|
|
26
29
|
if ssh_keyfile != nil
|
|
27
30
|
begin
|
|
31
|
+
@logger.info("connecting using keyfile")
|
|
28
32
|
remote_handler().connect_with_keyfile(dns_name, ssh_keyfile)
|
|
29
33
|
connected = true
|
|
30
34
|
rescue Exception => e
|
|
31
35
|
@logger.info("connection failed due to #{e}")
|
|
32
|
-
|
|
36
|
+
last_connection_problem = "#{e}"
|
|
37
|
+
@logger.debug(e.backtrace.select(){|line| line.include?("state_transition_helper")}.join("\n"))
|
|
33
38
|
end
|
|
34
39
|
elsif ssh_keydata != nil
|
|
35
40
|
begin
|
|
41
|
+
@logger.info("connecting using keydata")
|
|
36
42
|
remote_handler().connect(dns_name, "root", ssh_keydata)
|
|
37
43
|
connected = true
|
|
38
44
|
rescue Exception => e
|
|
39
45
|
@logger.info("connection failed due to #{e}")
|
|
40
|
-
|
|
46
|
+
last_connection_problem = "#{e}"
|
|
47
|
+
@logger.debug(e.backtrace.select(){|line| line.include?("state_transition_helper")}.join("\n"))
|
|
41
48
|
end
|
|
42
49
|
else
|
|
43
50
|
raise Exception.new("no key information specified")
|
|
44
51
|
end
|
|
45
52
|
if !connected
|
|
46
|
-
sleep(
|
|
53
|
+
sleep(20) #try again
|
|
47
54
|
end
|
|
48
55
|
end
|
|
49
56
|
if !connected
|
|
50
|
-
raise Exception.new("connection attempts stopped")
|
|
57
|
+
raise Exception.new("connection attempts stopped (#{last_connection_problem})")
|
|
51
58
|
end
|
|
52
59
|
os = remote_handler().retrieve_os()
|
|
53
60
|
post_message("connected to #{dns_name}. OS installed is #{os}")
|
|
@@ -55,6 +62,15 @@ module StateTransitionHelper
|
|
|
55
62
|
return os
|
|
56
63
|
end
|
|
57
64
|
|
|
65
|
+
# If a remote command handler is connected, disconnect him silently.
|
|
66
|
+
def disconnect
|
|
67
|
+
begin
|
|
68
|
+
remote_handler().disconnect()
|
|
69
|
+
rescue
|
|
70
|
+
end
|
|
71
|
+
self.remote_handler= nil
|
|
72
|
+
end
|
|
73
|
+
|
|
58
74
|
# Launch an instance based on an AMI ID
|
|
59
75
|
# Input Parameters:
|
|
60
76
|
# * ami_id => ID of the AMI to be launched
|
|
@@ -67,11 +83,12 @@ module StateTransitionHelper
|
|
|
67
83
|
# * kernel_id => EC2 Kernel ID of the started instance
|
|
68
84
|
# * ramdisk_id => EC2 Ramdisk ID of the started instance
|
|
69
85
|
# * architecture => architecture (e.g. 386i, 64x) of the started instance
|
|
70
|
-
def launch_instance(ami_id, key_name, security_group_name)
|
|
86
|
+
def launch_instance(ami_id, key_name, security_group_name, ec2_handler = nil)
|
|
87
|
+
ec2_handler = ec2_handler() if ec2_handler == nil
|
|
71
88
|
post_message("starting up instance to execute the script (AMI = #{ami_id}) ...")
|
|
72
89
|
@logger.debug "start up AMI #{ami_id}"
|
|
73
90
|
# find out the image architecture first
|
|
74
|
-
image_props = ec2_handler
|
|
91
|
+
image_props = ec2_handler.describe_images(:image_id => ami_id)
|
|
75
92
|
architecture = image_props['imagesSet']['item'][0]['architecture']
|
|
76
93
|
instance_type = "m1.small"
|
|
77
94
|
if architecture != "i386"
|
|
@@ -81,7 +98,7 @@ module StateTransitionHelper
|
|
|
81
98
|
@logger.info arch_log_msg
|
|
82
99
|
post_message(arch_log_msg)
|
|
83
100
|
# now start it
|
|
84
|
-
res = ec2_handler
|
|
101
|
+
res = ec2_handler.run_instances(:image_id => ami_id,
|
|
85
102
|
:security_group => security_group_name, :key_name => key_name,
|
|
86
103
|
:instance_type => instance_type
|
|
87
104
|
)
|
|
@@ -92,7 +109,7 @@ module StateTransitionHelper
|
|
|
92
109
|
started = false
|
|
93
110
|
while started == false
|
|
94
111
|
sleep(5)
|
|
95
|
-
res = ec2_handler
|
|
112
|
+
res = ec2_handler.describe_instances(:instance_id => instance_id)
|
|
96
113
|
state = res['reservationSet']['item'][0]['instancesSet']['item'][0]['instanceState']
|
|
97
114
|
@logger.info "instance is in state #{state['name']} (#{state['code']})"
|
|
98
115
|
if state['code'].to_i == 16
|
|
@@ -252,10 +269,11 @@ module StateTransitionHelper
|
|
|
252
269
|
# * volume_id => EC2 ID for the EBS volume to be snapshotted
|
|
253
270
|
# Returns:
|
|
254
271
|
# * snapshot_id => EC2 ID for the snapshot created
|
|
255
|
-
def create_snapshot(volume_id)
|
|
272
|
+
def create_snapshot(volume_id, description = "")
|
|
256
273
|
post_message("going to create a snapshot for volume #{volume_id}...")
|
|
257
274
|
@logger.debug "create snapshot for volume #{volume_id}"
|
|
258
|
-
res = ec2_handler().create_snapshot(:volume_id => volume_id
|
|
275
|
+
res = ec2_handler().create_snapshot(:volume_id => volume_id,
|
|
276
|
+
:description => description)
|
|
259
277
|
snapshot_id = res['snapshotId']
|
|
260
278
|
@logger.info "snapshot_id = #{snapshot_id}"
|
|
261
279
|
done = false
|
|
@@ -358,8 +376,8 @@ module StateTransitionHelper
|
|
|
358
376
|
post_message("going to start copying files to #{destination_path}. This may take quite a time...")
|
|
359
377
|
@logger.debug "start copying to #{destination_path}"
|
|
360
378
|
start = Time.new.to_i
|
|
361
|
-
remote_handler().
|
|
362
|
-
remote_handler().
|
|
379
|
+
remote_handler().local_rsync("/", "#{destination_path}", "#{destination_path}")
|
|
380
|
+
remote_handler().local_rsync("/dev/", "#{destination_path}/dev/")
|
|
363
381
|
endtime = Time.new.to_i
|
|
364
382
|
@logger.info "copy took #{(endtime-start)}s"
|
|
365
383
|
post_message("copying is done (took #{endtime-start})s")
|
|
@@ -376,23 +394,52 @@ module StateTransitionHelper
|
|
|
376
394
|
post_message("EBS volume successfully zipped")
|
|
377
395
|
end
|
|
378
396
|
|
|
379
|
-
|
|
397
|
+
def remote_copy(keyname, source_dir, dest_machine, dest_dir)
|
|
398
|
+
post_message("going to remote copy all files from volume. This may take some time...")
|
|
399
|
+
remote_handler().remote_rsync("/root/.ssh/#{keyname}.pem", source_dir, dest_machine, dest_dir)
|
|
400
|
+
post_message("remote copy operation done")
|
|
401
|
+
end
|
|
380
402
|
|
|
381
|
-
def
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
end
|
|
403
|
+
def upload_file(ip, user, key_data, file, target_file)
|
|
404
|
+
post_message("going to upload #{file} to #{ip}:/#{target_file}")
|
|
405
|
+
remote_handler().upload(ip, user, key_data, file, target_file)
|
|
385
406
|
end
|
|
386
407
|
|
|
408
|
+
#setting/retrieving handlers
|
|
409
|
+
|
|
387
410
|
def remote_handler()
|
|
388
|
-
if @
|
|
389
|
-
@context[:remote_command_handler]
|
|
411
|
+
if @remote_handler == nil
|
|
412
|
+
if @context[:remote_command_handler] == nil
|
|
413
|
+
@context[:remote_command_handler] = RemoteCommandHandler.new
|
|
414
|
+
else
|
|
415
|
+
@remote_handler = @context[:remote_command_handler]
|
|
416
|
+
end
|
|
390
417
|
end
|
|
391
|
-
@
|
|
418
|
+
@remote_handler
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
def remote_handler=(remote_handler)
|
|
422
|
+
@remote_handler = remote_handler
|
|
392
423
|
end
|
|
393
424
|
|
|
394
425
|
def ec2_handler()
|
|
395
|
-
@
|
|
426
|
+
if @ec2_handler == nil
|
|
427
|
+
@ec2_handler = @context[:ec2_api_handler]
|
|
428
|
+
end
|
|
429
|
+
@ec2_handler
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
def ec2_handler=(ec2_handler)
|
|
433
|
+
@ec2_handler = ec2_handler
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
protected
|
|
438
|
+
|
|
439
|
+
def post_message(msg)
|
|
440
|
+
if @context[:script] != nil
|
|
441
|
+
@context[:script].post_message(msg)
|
|
442
|
+
end
|
|
396
443
|
end
|
|
397
444
|
|
|
398
445
|
end
|
|
@@ -0,0 +1,188 @@
|
|
|
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 "help/ec2_helper"
|
|
6
|
+
require "AWS"
|
|
7
|
+
|
|
8
|
+
# Copy a given snapshot to another region
|
|
9
|
+
# * start up instance in source-region, create volume from snapshot, attach volume, and mount it
|
|
10
|
+
# * start up instance in destination-region, create empty volume of same size, attache volume, and mount it
|
|
11
|
+
# * copy the destination key to the source instance
|
|
12
|
+
# * perform an rsynch
|
|
13
|
+
# sync -PHAXaz --rsh "ssh -i /home/${src_user}/.ssh/id_${dst_keypair}" --rsync-path "sudo rsync" ${src_dir}/ ${dst_user}@${dst_public_fqdn}:${dst_dir}/
|
|
14
|
+
# * create a snapshot of the volume
|
|
15
|
+
# * clean-up everything
|
|
16
|
+
|
|
17
|
+
class CopySnapshot< Ec2Script
|
|
18
|
+
# context information needed
|
|
19
|
+
# * the EC2 credentials (see #Ec2Script)
|
|
20
|
+
# * snapshot_id => The ID of the snapshot to be downloaded
|
|
21
|
+
# * target_ec2_handler => The EC2 handler connected to the region where the snapshot is being copied to
|
|
22
|
+
# * source_key_name => Key name of the instance that manages the snaphot-volume in the source region
|
|
23
|
+
# * source_ssh_key_data => Key information for the security group that starts the AMI [if not set, use ssh_key_files]
|
|
24
|
+
# * source_ssh_key_files => Key information for the security group that starts the AMI
|
|
25
|
+
# * target_key_name => Key name of the instance that manages the snaphot-volume in the target region
|
|
26
|
+
# * target_ssh_key_data => Key information for the security group that starts the AMI [if not set, use ssh_key_files]
|
|
27
|
+
# * target_ssh_key_files => Key information for the security group that starts the AMI
|
|
28
|
+
# * source_ami_id => ID of the AMI to start in the source region
|
|
29
|
+
# * target_ami_id => ID of the AMI to start in the target region
|
|
30
|
+
|
|
31
|
+
def initialize(input_params)
|
|
32
|
+
super(input_params)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def check_input_parameters()
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Load the initial state for the script.
|
|
39
|
+
# Abstract method to be implemented by extending classes.
|
|
40
|
+
def load_initial_state()
|
|
41
|
+
CopySnapshotState.load_state(@input_params)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
# Here begins the state machine implementation
|
|
47
|
+
class CopySnapshotState < ScriptExecutionState
|
|
48
|
+
|
|
49
|
+
def self.load_state(context)
|
|
50
|
+
InitialState.new(context)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def local_region
|
|
54
|
+
self.ec2_handler=(@context[:ec2_api_handler])
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def remote_region
|
|
58
|
+
self.ec2_handler=(@context[:target_ec2_handler])
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Initial state: start up AMI in source region
|
|
63
|
+
class InitialState < CopySnapshotState
|
|
64
|
+
def enter()
|
|
65
|
+
result = launch_instance(@context[:source_ami_id], @context[:source_key_name], "default")
|
|
66
|
+
@context[:source_instance_id] = result.first
|
|
67
|
+
@context[:source_dns_name] = result[1]
|
|
68
|
+
@context[:source_availability_zone] = result[2]
|
|
69
|
+
SourceInstanceLaunchedState.new(@context)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Source is started. Create a volume from the snapshot, attach and mount the volume
|
|
74
|
+
class SourceInstanceLaunchedState < CopySnapshotState
|
|
75
|
+
def enter()
|
|
76
|
+
@context[:source_volume_id] = create_volume_from_snapshot(@context[:snapshot_id],
|
|
77
|
+
@context[:source_availability_zone])
|
|
78
|
+
device = "/dev/sdj" #TODO: make device configurable
|
|
79
|
+
mount_point = "/mnt/tmp_#{@context[:source_volume_id]}"
|
|
80
|
+
attach_volume(@context[:source_volume_id], @context[:source_instance_id], device)
|
|
81
|
+
connect(@context[:source_dns_name], nil, @context[:source_ssh_keydata])
|
|
82
|
+
mount_fs(mount_point, device)
|
|
83
|
+
disconnect()
|
|
84
|
+
SourceVolumeReadyState.new(@context)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Source is ready. Now start instance in the target region
|
|
89
|
+
class SourceVolumeReadyState < CopySnapshotState
|
|
90
|
+
def enter()
|
|
91
|
+
remote_region()
|
|
92
|
+
result = launch_instance(@context[:target_ami_id], @context[:target_key_name],
|
|
93
|
+
"default")
|
|
94
|
+
@context[:target_instance_id] = result.first
|
|
95
|
+
@context[:target_dns_name] = result[1]
|
|
96
|
+
@context[:target_availability_zone] = result[2]
|
|
97
|
+
TargetInstanceLaunchedState.new(@context)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Destination instance is started. Now configure storage.
|
|
102
|
+
class TargetInstanceLaunchedState < CopySnapshotState
|
|
103
|
+
def enter()
|
|
104
|
+
local_region()
|
|
105
|
+
ec2_helper = Ec2Helper.new(@context[:ec2_api_handler])
|
|
106
|
+
volume_size = ec2_helper.snapshot_prop(@context[:snapshot_id], :volumeSize).to_i
|
|
107
|
+
#
|
|
108
|
+
remote_region()
|
|
109
|
+
@context[:target_volume_id] = create_volume(@context[:target_availability_zone], volume_size)
|
|
110
|
+
device = "/dev/sdj" #TODO: make device configurable
|
|
111
|
+
mount_point = "/mnt/tmp_#{@context[:target_volume_id]}"
|
|
112
|
+
attach_volume(@context[:target_volume_id], @context[:target_instance_id], device)
|
|
113
|
+
connect(@context[:target_dns_name], nil, @context[:target_ssh_keydata])
|
|
114
|
+
create_fs(@context[:target_dns_name], device)
|
|
115
|
+
mount_fs(mount_point, device)
|
|
116
|
+
disconnect()
|
|
117
|
+
TargetVolumeReadyState.new(@context)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Storages are ready. Only thing missing: the key of the target region
|
|
122
|
+
# must be available on the instance in the source region to be able to perform
|
|
123
|
+
# a remote copy.
|
|
124
|
+
class TargetVolumeReadyState < CopySnapshotState
|
|
125
|
+
def enter()
|
|
126
|
+
post_message("upload key of target-instance to source-instance...")
|
|
127
|
+
upload_file(@context[:source_dns_name], "root", @context[:source_ssh_keydata],
|
|
128
|
+
@context[:target_ssh_keyfile], "/root/.ssh/#{@context[:target_key_name]}.pem")
|
|
129
|
+
post_message("credentials are in place to connect source and target.")
|
|
130
|
+
KeyInPlaceState.new(@context)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Now we can copy.
|
|
135
|
+
class KeyInPlaceState < CopySnapshotState
|
|
136
|
+
def enter()
|
|
137
|
+
connect(@context[:source_dns_name], nil, @context[:source_ssh_keydata])
|
|
138
|
+
source_dir = "/mnt/tmp_#{@context[:source_volume_id]}/"
|
|
139
|
+
dest_dir = "/mnt/tmp_#{@context[:target_volume_id]}"
|
|
140
|
+
remote_copy(@context[:target_key_name], source_dir, @context[:target_dns_name], dest_dir)
|
|
141
|
+
disconnect()
|
|
142
|
+
DataCopiedState.new(@context)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Data of snapshot now copied to the new volume. Create a snapshot of the
|
|
147
|
+
# new volume.
|
|
148
|
+
class DataCopiedState < CopySnapshotState
|
|
149
|
+
def enter()
|
|
150
|
+
remote_region()
|
|
151
|
+
@context[:new_snapshot_id] = create_snapshot(@context[:target_volume_id], "Created by Cloudy_Scripts - copy_snapshot")
|
|
152
|
+
@context[:result][:snapshot_id] = @context[:new_snapshot_id]
|
|
153
|
+
SnapshotCreatedState.new(@context)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Operation done. Now only cleanup is missing, i.e. shut down instances and
|
|
158
|
+
# remote the volumes that were created. Start with cleaning the ressources
|
|
159
|
+
# in the local region.
|
|
160
|
+
class SnapshotCreatedState < CopySnapshotState
|
|
161
|
+
def enter()
|
|
162
|
+
local_region()
|
|
163
|
+
shut_down_instance(@context[:source_instance_id])
|
|
164
|
+
delete_volume(@context[:source_volume_id])
|
|
165
|
+
SourceCleanedUpState.new(@context)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Cleanup the resources in the target region.
|
|
170
|
+
class SourceCleanedUpState < CopySnapshotState
|
|
171
|
+
def enter()
|
|
172
|
+
remote_region()
|
|
173
|
+
shut_down_instance(@context[:target_instance_id])
|
|
174
|
+
delete_volume(@context[:target_volume_id])
|
|
175
|
+
Done.new(@context)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
#Cloudy_Script: copy snapshots between regions
|
|
182
|
+
#start up instance in source-region, create volume from snapshot, attach volume, and mount it
|
|
183
|
+
#start up instance in destination-region, create empty volume of same size, attache volume, and mount it
|
|
184
|
+
#copy the destination key to the source instance
|
|
185
|
+
#perform an rsynch
|
|
186
|
+
#sync -PHAXaz --rsh "ssh -i /home/${src_user}/.ssh/id_${dst_keypair}" --rsync-path "sudo rsync" ${src_dir}/ ${dst_user}@${dst_public_fqdn}:${dst_dir}/
|
|
187
|
+
#create a snapshot of the volume
|
|
188
|
+
#clean-up everything
|
|
@@ -21,6 +21,7 @@ class DownloadSnapshot < Ec2Script
|
|
|
21
21
|
# * the EC2 credentials (see #Ec2Script)
|
|
22
22
|
# * ami_id: the ID of the AMI to be started to perform the operations and to run the web-server for download
|
|
23
23
|
# * security_group_name => name of the security group used to start the AMI (should open ports for SSH and HTTP)
|
|
24
|
+
# * key_name => Name of the key to be used to access the instance providing the download
|
|
24
25
|
# * ssh_key_data => Key information for the security group that starts the AMI [if not set, use ssh_key_files]
|
|
25
26
|
# * ssh_key_files => Key information for the security group that starts the AMI
|
|
26
27
|
# * snapshot_id => The ID of the snapshot to be downloaded
|
metadata
CHANGED
|
@@ -3,10 +3,10 @@ name: CloudyScripts
|
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
|
5
5
|
segments:
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
version:
|
|
6
|
+
- 1
|
|
7
|
+
- 4
|
|
8
|
+
- 15
|
|
9
|
+
version: 1.4.15
|
|
10
10
|
platform: ruby
|
|
11
11
|
authors:
|
|
12
12
|
- Matthias Jung
|
|
@@ -14,7 +14,7 @@ autorequire:
|
|
|
14
14
|
bindir: bin
|
|
15
15
|
cert_chain: []
|
|
16
16
|
|
|
17
|
-
date: 2010-
|
|
17
|
+
date: 2010-04-27 00:00:00 +02:00
|
|
18
18
|
default_executable:
|
|
19
19
|
dependencies:
|
|
20
20
|
- !ruby/object:Gem::Dependency
|
|
@@ -63,6 +63,7 @@ files:
|
|
|
63
63
|
- lib/help/state_change_listener.rb
|
|
64
64
|
- lib/help/state_transition_helper.rb
|
|
65
65
|
- lib/scripts/ec2/ami2_ebs_conversion.rb
|
|
66
|
+
- lib/scripts/ec2/copy_snapshot.rb
|
|
66
67
|
- lib/scripts/ec2/dm_encrypt.rb
|
|
67
68
|
- lib/scripts/ec2/download_snapshot.rb
|
|
68
69
|
- lib/scripts/ec2/ec2_script.rb
|