vm_shepherd 1.0.3 → 1.1.0
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.
- 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
|