vm_shepherd 1.0.3 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/vm_shepherd/aws_manager.rb +89 -40
- data/lib/vm_shepherd/openstack_manager.rb +3 -0
- data/lib/vm_shepherd/shepherd.rb +63 -22
- data/lib/vm_shepherd/vcloud_manager.rb +3 -0
- data/lib/vm_shepherd/version.rb +1 -1
- data/lib/vm_shepherd/vsphere_manager.rb +19 -10
- data/spec/fixtures/shepherd/aws.yml +16 -17
- data/spec/fixtures/shepherd/openstack.yml +36 -35
- data/spec/fixtures/shepherd/vcloud.yml +40 -39
- data/spec/fixtures/shepherd/vsphere.yml +56 -55
- data/spec/vm_shepherd/aws_manager_spec.rb +160 -53
- data/spec/vm_shepherd/shepherd_spec.rb +149 -98
- data/spec/vm_shepherd/vsphere_manager_spec.rb +31 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 316f95673f45c873990de6c9a8313e9f7c30c14d
|
4
|
+
data.tar.gz: a5cacb70390f6f42dfbac300b298cf720eaaaf96
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 21c6f2e27bf812d8349adabb9c91a2f360b29c94a17a359ede7c099d927af52ce80e78ae39f071f47bd07a64ccded8b52ab060be96cf608daf38da7e5050e607
|
7
|
+
data.tar.gz: c43837145203de571a44d3617e5b295036a78b2d956112ea9a5c42c34fd49e35fad534f34f6bddb09a5f7b4a2637992dcd8a655343fde52540c000e06d7894d2
|
@@ -11,67 +11,120 @@ module VmShepherd
|
|
11
11
|
RETRY_INTERVAL = 5
|
12
12
|
DO_NOT_TERMINATE_TAG_KEY = 'do_not_terminate'
|
13
13
|
|
14
|
-
def initialize(
|
14
|
+
def initialize(env_config)
|
15
15
|
AWS.config(
|
16
|
-
access_key_id:
|
17
|
-
secret_access_key:
|
16
|
+
access_key_id: env_config.fetch(:aws_access_key),
|
17
|
+
secret_access_key: env_config.fetch(:aws_secret_key),
|
18
18
|
region: AWS_REGION
|
19
19
|
)
|
20
|
-
@
|
20
|
+
@env_config = env_config
|
21
21
|
end
|
22
22
|
|
23
|
-
def
|
23
|
+
def prepare_environment(cloudformation_template_file)
|
24
|
+
template = File.read(cloudformation_template_file)
|
25
|
+
|
26
|
+
cfm = AWS::CloudFormation.new
|
27
|
+
stack = cfm.stacks.create(env_config.fetch(:stack_name), template, :parameters => env_config.fetch(:parameters), capabilities: ['CAPABILITY_IAM'])
|
28
|
+
|
29
|
+
retry_until(retry_limit: 360) do
|
30
|
+
status = stack.status
|
31
|
+
case status
|
32
|
+
when 'CREATE_COMPLETE'
|
33
|
+
true
|
34
|
+
when 'CREATE_IN_PROGRESS'
|
35
|
+
false
|
36
|
+
when 'ROLLBACK_IN_PROGRESS'
|
37
|
+
false
|
38
|
+
else
|
39
|
+
stack.delete if status == 'ROLLBACK_COMPLETE'
|
40
|
+
raise "Unexpected status for stack #{env_config.fetch(:stack_name)} : #{status}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def deploy(ami_file_path:, vm_config:)
|
24
46
|
image_id = File.read(ami_file_path).strip
|
25
47
|
|
26
48
|
instance =
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
49
|
+
retry_until do
|
50
|
+
begin
|
51
|
+
AWS.ec2.instances.create(
|
52
|
+
image_id: image_id,
|
53
|
+
key_name: env_config.fetch(:outputs).fetch(:ssh_key_name),
|
54
|
+
security_group_ids: [env_config.fetch(:outputs).fetch(:security_group)],
|
55
|
+
subnet: env_config.fetch(:outputs).fetch(:public_subnet_id),
|
56
|
+
instance_type: OPS_MANAGER_INSTANCE_TYPE
|
57
|
+
)
|
58
|
+
rescue AWS::EC2::Errors::InvalidIPAddress::InUse
|
59
|
+
false
|
60
|
+
end
|
35
61
|
end
|
36
62
|
|
37
|
-
|
38
|
-
|
63
|
+
retry_until do
|
64
|
+
begin
|
65
|
+
instance.status == :running
|
66
|
+
rescue AWS::EC2::Errors::InvalidInstanceID::NotFound
|
67
|
+
false
|
68
|
+
end
|
39
69
|
end
|
40
70
|
|
41
|
-
|
42
|
-
instance.
|
71
|
+
elastic_ip = AWS.ec2.elastic_ips.create(vpc: true)
|
72
|
+
instance.associate_elastic_ip(elastic_ip.allocation_id)
|
73
|
+
instance.add_tag('Name', value: vm_config.fetch(:vm_name))
|
43
74
|
end
|
44
75
|
|
45
76
|
def clean_environment
|
46
|
-
subnets = [
|
47
|
-
|
48
|
-
|
49
|
-
]
|
77
|
+
subnets = []
|
78
|
+
subnets << AWS.ec2.subnets[env_config.fetch(:outputs).fetch(:public_subnet_id)] if env_config.fetch(:outputs).fetch(:public_subnet_id)
|
79
|
+
subnets << AWS.ec2.subnets[env_config.fetch(:outputs).fetch(:private_subnet_id)] if env_config.fetch(:outputs).fetch(:private_subnet_id)
|
50
80
|
|
51
81
|
volumes = []
|
52
82
|
subnets.each do |subnet|
|
53
83
|
subnet.instances.each do |instance|
|
54
|
-
|
55
|
-
|
56
|
-
volumes.push(attachment.volume) unless attachment.delete_on_termination
|
57
|
-
end
|
58
|
-
instance.terminate
|
84
|
+
instance.attachments.each do |_, attachment|
|
85
|
+
volumes.push(attachment.volume) unless attachment.delete_on_termination
|
59
86
|
end
|
87
|
+
instance.terminate
|
60
88
|
end
|
61
89
|
end
|
62
90
|
destroy_volumes(volumes)
|
91
|
+
|
92
|
+
cfm = AWS::CloudFormation.new
|
93
|
+
stack = cfm.stacks[env_config.fetch(:stack_name)]
|
94
|
+
stack.delete
|
95
|
+
retry_until(retry_limit: 360) do
|
96
|
+
begin
|
97
|
+
status = stack.status
|
98
|
+
case status
|
99
|
+
when 'DELETE_COMPLETE'
|
100
|
+
true
|
101
|
+
when 'DELETE_IN_PROGRESS'
|
102
|
+
false
|
103
|
+
else
|
104
|
+
raise "Unexpected status for stack #{env_config.fetch(:stack_name)} : #{status}"
|
105
|
+
end
|
106
|
+
rescue AWS::CloudFormation::Errors::ValidationError
|
107
|
+
raise if stack.exists?
|
108
|
+
true
|
109
|
+
end
|
110
|
+
end if stack
|
63
111
|
end
|
64
112
|
|
65
|
-
def destroy
|
66
|
-
AWS.ec2.
|
67
|
-
if
|
68
|
-
elastic_ip
|
113
|
+
def destroy(vm_config)
|
114
|
+
AWS.ec2.instances.each do |instance|
|
115
|
+
if instance.tags.to_h['Name'] == vm_config.fetch(:vm_name)
|
116
|
+
elastic_ip = instance.elastic_ip
|
117
|
+
if elastic_ip
|
118
|
+
elastic_ip.disassociate
|
119
|
+
elastic_ip.delete
|
120
|
+
end
|
121
|
+
instance.terminate
|
69
122
|
end
|
70
123
|
end
|
71
124
|
end
|
72
125
|
|
73
126
|
private
|
74
|
-
attr_reader :
|
127
|
+
attr_reader :env_config
|
75
128
|
|
76
129
|
def destroy_volumes(volumes)
|
77
130
|
volumes.each do |volume|
|
@@ -84,19 +137,15 @@ module VmShepherd
|
|
84
137
|
end
|
85
138
|
end
|
86
139
|
|
87
|
-
def
|
140
|
+
def retry_until(retry_limit: RETRY_LIMIT, &block)
|
88
141
|
tries = 0
|
89
142
|
condition_reached = false
|
90
143
|
loop do
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
condition_reached = block.call
|
95
|
-
sleep RETRY_INTERVAL
|
96
|
-
rescue exception_class
|
97
|
-
retry
|
98
|
-
end
|
144
|
+
tries += 1
|
145
|
+
raise(RetryLimitExceeded) if tries > retry_limit
|
146
|
+
condition_reached = block.call
|
99
147
|
break if condition_reached
|
148
|
+
sleep RETRY_INTERVAL
|
100
149
|
end
|
101
150
|
condition_reached
|
102
151
|
end
|
data/lib/vm_shepherd/shepherd.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
module VmShepherd
|
2
2
|
class Shepherd
|
3
|
-
class InvalidIaas < StandardError;
|
3
|
+
class InvalidIaas < StandardError;
|
4
|
+
end
|
4
5
|
|
5
6
|
def initialize(settings:)
|
6
7
|
@settings = settings
|
@@ -54,13 +55,49 @@ module VmShepherd
|
|
54
55
|
}
|
55
56
|
)
|
56
57
|
when VmShepherd::AWS_IAAS_TYPE then
|
57
|
-
ami_manager
|
58
|
+
ami_manager.deploy(ami_file_path: path, vm_config: vm_shepherd_config.to_h)
|
58
59
|
when VmShepherd::OPENSTACK_IAAS_TYPE then
|
59
60
|
openstack_vm_manager(vm_shepherd_config).deploy(path, openstack_vm_options(vm_shepherd_config))
|
60
61
|
end
|
61
62
|
end
|
62
63
|
end
|
63
64
|
|
65
|
+
def prepare_environment
|
66
|
+
unless valid_iaas_types.include?(settings.iaas_type)
|
67
|
+
fail(InvalidIaas, "Unknown IaaS type: #{settings.iaas_type.inspect}")
|
68
|
+
end
|
69
|
+
case settings.iaas_type
|
70
|
+
when VmShepherd::VCLOUD_IAAS_TYPE then
|
71
|
+
vm_shepherd_configs(settings).each do |vm_shepherd_config|
|
72
|
+
VmShepherd::VcloudManager.new(
|
73
|
+
{
|
74
|
+
url: vm_shepherd_config.creds.url,
|
75
|
+
organization: vm_shepherd_config.creds.organization,
|
76
|
+
user: vm_shepherd_config.creds.user,
|
77
|
+
password: vm_shepherd_config.creds.password,
|
78
|
+
},
|
79
|
+
vm_shepherd_config.vdc.name,
|
80
|
+
error_logger
|
81
|
+
).prepare_environment
|
82
|
+
end
|
83
|
+
when VmShepherd::VSPHERE_IAAS_TYPE then
|
84
|
+
vm_shepherd_configs(settings).each do |vm_shepherd_config|
|
85
|
+
VmShepherd::VsphereManager.new(
|
86
|
+
vm_shepherd_config.vcenter_creds.ip,
|
87
|
+
vm_shepherd_config.vcenter_creds.username,
|
88
|
+
vm_shepherd_config.vcenter_creds.password,
|
89
|
+
vm_shepherd_config.vsphere.datacenter,
|
90
|
+
).prepare_environment
|
91
|
+
end
|
92
|
+
when VmShepherd::AWS_IAAS_TYPE then
|
93
|
+
ami_manager.prepare_environment(settings.vm_shepherd.env_config.json_file)
|
94
|
+
when VmShepherd::OPENSTACK_IAAS_TYPE then
|
95
|
+
vm_shepherd_configs(settings).each do |vm_shepherd_config|
|
96
|
+
openstack_vm_manager(vm_shepherd_config).prepare_environment
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
64
101
|
def destroy
|
65
102
|
unless valid_iaas_types.include?(settings.iaas_type)
|
66
103
|
fail(InvalidIaas, "Unknown IaaS type: #{settings.iaas_type.inspect}")
|
@@ -86,7 +123,7 @@ module VmShepherd
|
|
86
123
|
vm_shepherd_config.vsphere.datacenter,
|
87
124
|
).destroy(vm_shepherd_config.vm.ip, vm_shepherd_config.vsphere.resource_pool)
|
88
125
|
when VmShepherd::AWS_IAAS_TYPE then
|
89
|
-
ami_manager(vm_shepherd_config)
|
126
|
+
ami_manager.destroy(vm_shepherd_config.to_h)
|
90
127
|
when VmShepherd::OPENSTACK_IAAS_TYPE then
|
91
128
|
openstack_vm_manager(vm_shepherd_config).destroy(openstack_vm_options(vm_shepherd_config))
|
92
129
|
end
|
@@ -97,9 +134,9 @@ module VmShepherd
|
|
97
134
|
unless valid_iaas_types.include?(settings.iaas_type)
|
98
135
|
fail(InvalidIaas, "Unknown IaaS type: #{settings.iaas_type.inspect}")
|
99
136
|
end
|
100
|
-
|
101
|
-
|
102
|
-
|
137
|
+
case settings.iaas_type
|
138
|
+
when VmShepherd::VCLOUD_IAAS_TYPE then
|
139
|
+
vm_shepherd_configs(settings).each do |vm_shepherd_config|
|
103
140
|
VmShepherd::VcloudManager.new(
|
104
141
|
{
|
105
142
|
url: vm_shepherd_config.creds.url,
|
@@ -110,7 +147,9 @@ module VmShepherd
|
|
110
147
|
vm_shepherd_config.vdc.name,
|
111
148
|
error_logger,
|
112
149
|
).clean_environment(vm_shepherd_config.vapp.product_names || [], vm_shepherd_config.vapp.product_catalog)
|
113
|
-
|
150
|
+
end
|
151
|
+
when VmShepherd::VSPHERE_IAAS_TYPE then
|
152
|
+
vm_shepherd_configs(settings).each do |vm_shepherd_config|
|
114
153
|
VmShepherd::VsphereManager.new(
|
115
154
|
vm_shepherd_config.vcenter_creds.ip,
|
116
155
|
vm_shepherd_config.vcenter_creds.username,
|
@@ -121,11 +160,13 @@ module VmShepherd
|
|
121
160
|
datastores: vm_shepherd_config.cleanup.datastores,
|
122
161
|
datastore_folders_to_clean: vm_shepherd_config.cleanup.datastore_folders_to_clean,
|
123
162
|
)
|
124
|
-
|
125
|
-
|
126
|
-
|
163
|
+
end
|
164
|
+
when VmShepherd::AWS_IAAS_TYPE then
|
165
|
+
ami_manager.clean_environment
|
166
|
+
when VmShepherd::OPENSTACK_IAAS_TYPE then
|
167
|
+
vm_shepherd_configs(settings).each do |vm_shepherd_config|
|
127
168
|
openstack_vm_manager(vm_shepherd_config).clean_environment
|
128
|
-
|
169
|
+
end
|
129
170
|
end
|
130
171
|
end
|
131
172
|
|
@@ -153,16 +194,16 @@ module VmShepherd
|
|
153
194
|
}
|
154
195
|
end
|
155
196
|
|
156
|
-
def ami_manager
|
157
|
-
VmShepherd::AwsManager.new(
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
197
|
+
def ami_manager
|
198
|
+
@ami_manager ||= VmShepherd::AwsManager.new(
|
199
|
+
{
|
200
|
+
stack_name: settings.vm_shepherd.env_config.stack_name,
|
201
|
+
aws_access_key: settings.vm_shepherd.env_config.aws_access_key,
|
202
|
+
aws_secret_key: settings.vm_shepherd.env_config.aws_secret_key,
|
203
|
+
json_file: settings.vm_shepherd.env_config.json_file,
|
204
|
+
parameters: settings.vm_shepherd.env_config.parameters_as_a_hash,
|
205
|
+
outputs: settings.vm_shepherd.env_config.outputs.to_h
|
206
|
+
}
|
166
207
|
)
|
167
208
|
end
|
168
209
|
|
@@ -188,7 +229,7 @@ module VmShepherd
|
|
188
229
|
end
|
189
230
|
|
190
231
|
def vm_shepherd_configs(settings)
|
191
|
-
settings.
|
232
|
+
settings.vm_shepherd.vm_configs || []
|
192
233
|
end
|
193
234
|
|
194
235
|
def valid_iaas_types
|
data/lib/vm_shepherd/version.rb
CHANGED
@@ -56,6 +56,9 @@ module VmShepherd
|
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
+
def prepare_environment
|
60
|
+
end
|
61
|
+
|
59
62
|
def destroy(ip_address, resource_pool_name)
|
60
63
|
vms = connection.serviceContent.searchIndex.FindAllByIp(ip: ip_address, vmSearch: true)
|
61
64
|
vms = vms.select { |vm| resource_pool_name == vm.resourcePool.name } if resource_pool_name
|
@@ -120,20 +123,26 @@ module VmShepherd
|
|
120
123
|
end
|
121
124
|
|
122
125
|
def delete_folder_and_vms(folder_name)
|
123
|
-
|
126
|
+
3.times do |attempt|
|
127
|
+
break unless (folder = datacenter.vmFolder.traverse(folder_name))
|
124
128
|
|
125
|
-
|
129
|
+
find_vms(folder).each { |vm| power_off_vm(vm) }
|
126
130
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
131
|
+
begin
|
132
|
+
logger.info("BEGIN folder.destroy_task folder=#{folder_name} attempt ##{attempt}")
|
133
|
+
folder.Destroy_Task.wait_for_completion
|
134
|
+
logger.info("END folder.destroy_task folder=#{folder_name}")
|
135
|
+
fail("#{folder_name.inspect} already exists") unless datacenter.vmFolder.traverse(folder_name).nil?
|
136
|
+
rescue RbVmomi::Fault => e
|
137
|
+
logger.info("ERROR folder.destroy_task folder=#{folder_name} #{e.inspect}")
|
138
|
+
sleep 10
|
139
|
+
# give up if it's the 3rd attempt
|
140
|
+
raise if attempt == 2
|
141
|
+
end
|
142
|
+
end
|
135
143
|
end
|
136
144
|
|
145
|
+
|
137
146
|
def find_vms(folder)
|
138
147
|
vms = folder.childEntity.grep(RbVmomi::VIM::VirtualMachine)
|
139
148
|
vms << folder.childEntity.grep(RbVmomi::VIM::Folder).map { |child| find_vms(child) }
|
@@ -1,19 +1,18 @@
|
|
1
1
|
iaas_type: aws
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
vm_name: vm-name-2
|
3
|
+
vm_shepherd:
|
4
|
+
env_config:
|
5
|
+
stack_name: aws-stack-name
|
6
|
+
aws_access_key: aws-access-key
|
7
|
+
aws_secret_key: aws-secret-key
|
8
|
+
json_file: cloudformation.json
|
9
|
+
parameters:
|
10
|
+
key_pair_name: key_pair_name
|
11
|
+
outputs:
|
12
|
+
ssh_key_name: ssh-key-name
|
13
|
+
security_group: security-group-id
|
14
|
+
public_subnet_id: public-subnet-id
|
15
|
+
private_subnet_id: private-subnet-id
|
16
|
+
vm_configs:
|
17
|
+
- vm_name: vm-name
|
18
|
+
- vm_name: vm-name-2
|
@@ -1,36 +1,37 @@
|
|
1
1
|
iaas_type: openstack
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
2
|
+
vm_shepherd:
|
3
|
+
vm_configs:
|
4
|
+
- creds:
|
5
|
+
auth_url: 'http://example.com/version/tokens'
|
6
|
+
username: 'username'
|
7
|
+
api_key: 'api-key'
|
8
|
+
tenant: 'tenant'
|
9
|
+
vm:
|
10
|
+
name: 'some-vm-name'
|
11
|
+
flavor_parameters:
|
12
|
+
min_disk_size: 150
|
13
|
+
network_name: 'some-network'
|
14
|
+
key_name: 'some-key'
|
15
|
+
security_group_names:
|
16
|
+
- 'security-group-A'
|
17
|
+
- 'security-group-B'
|
18
|
+
- 'security-group-C'
|
19
|
+
public_ip: 198.11.195.5
|
20
|
+
private_ip: 192.168.100.100
|
21
|
+
- creds:
|
22
|
+
auth_url: 'http://example.com/version/tokens-2'
|
23
|
+
username: 'username-2'
|
24
|
+
api_key: 'api-key-2'
|
25
|
+
tenant: 'tenant-2'
|
26
|
+
vm:
|
27
|
+
name: 'some-vm-name-2'
|
28
|
+
flavor_parameters:
|
29
|
+
min_disk_size: 152
|
30
|
+
network_name: 'some-network-2'
|
31
|
+
key_name: 'some-key-2'
|
32
|
+
security_group_names:
|
33
|
+
- 'security-group-A-2'
|
34
|
+
- 'security-group-B-2'
|
35
|
+
- 'security-group-C-2'
|
36
|
+
public_ip: 198.11.195.5-2
|
37
|
+
private_ip: 192.168.100.100-2
|