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
@@ -1,40 +1,41 @@
|
|
1
1
|
iaas_type: vcloud
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
2
|
+
vm_shepherd:
|
3
|
+
vm_configs:
|
4
|
+
- creds:
|
5
|
+
url: VAPP_URL
|
6
|
+
organization: VAPP_ORGANIZATION
|
7
|
+
user: VAPP_USERNAME
|
8
|
+
password: VAPP_PASSWORD
|
9
|
+
vdc:
|
10
|
+
name: VDC_NAME
|
11
|
+
catalog: VDC_CATALOG
|
12
|
+
network: VDC_NETWORK
|
13
|
+
vapp:
|
14
|
+
ops_manager_name: VAPP_NAME
|
15
|
+
product_catalog: PRODUCT_CATALOG
|
16
|
+
product_names:
|
17
|
+
- PRODUCT_1
|
18
|
+
- PRODUCT_2
|
19
|
+
ip: VAPP_IP
|
20
|
+
public_ip:
|
21
|
+
gateway: VAPP_GATEWAY
|
22
|
+
netmask: VAPP_NETMASK
|
23
|
+
dns: VAPP_DNS
|
24
|
+
ntp: VAPP_NTP
|
25
|
+
- creds:
|
26
|
+
url: VAPP_URL-2
|
27
|
+
organization: VAPP_ORGANIZATION-2
|
28
|
+
user: VAPP_USERNAME-2
|
29
|
+
password: VAPP_PASSWORD-2
|
30
|
+
vdc:
|
31
|
+
name: VDC_NAME-2
|
32
|
+
catalog: VDC_CATALOG-2
|
33
|
+
network: VDC_NETWORK-2
|
34
|
+
vapp:
|
35
|
+
ops_manager_name: VAPP_NAME-2
|
36
|
+
ip: VAPP_IP-2
|
37
|
+
public_ip:
|
38
|
+
gateway: VAPP_GATEWAY-2
|
39
|
+
netmask: VAPP_NETMASK-2
|
40
|
+
dns: VAPP_DNS-2
|
41
|
+
ntp: VAPP_NTP-2
|
@@ -1,56 +1,57 @@
|
|
1
1
|
iaas_type: vsphere
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
2
|
+
vm_shepherd:
|
3
|
+
vm_configs:
|
4
|
+
- vcenter_creds:
|
5
|
+
ip: OVA_URL
|
6
|
+
username: OVA_ORGANIZATION
|
7
|
+
password: OVA_PASSWORD
|
8
|
+
vsphere:
|
9
|
+
datacenter: VSPHERE_DATACENTER
|
10
|
+
cluster: VSPHERE_CLUSTER
|
11
|
+
network: VSPHERE_NETWORK
|
12
|
+
resource_pool: VSPHERE_RESOURCE_POOL
|
13
|
+
datastore: VSPHERE_DATASTORE
|
14
|
+
folder: VSPHERE_FOLDER
|
15
|
+
vm:
|
16
|
+
ip: OVA_IP
|
17
|
+
gateway: OVA_GATEWAY
|
18
|
+
netmask: OVA_NETMASK
|
19
|
+
dns: OVA_DNS
|
20
|
+
ntp_servers: OVA_NTP
|
21
|
+
cleanup:
|
22
|
+
datacenter: VSPHERE_OTHER_DATACENTER
|
23
|
+
datastores:
|
24
|
+
- VSPHERE_DATASTORE_ONE
|
25
|
+
- VSPHERE_DATASTORE_TWO
|
26
|
+
datacenter_folders_to_clean:
|
27
|
+
- DC_FOLDER_ONE
|
28
|
+
- DC_FOLDER_TWO
|
29
|
+
datastore_folders_to_clean:
|
30
|
+
- DS_DISK_FOLDER
|
31
|
+
- vcenter_creds:
|
32
|
+
ip: OVA_URL-2
|
33
|
+
username: OVA_ORGANIZATION-2
|
34
|
+
password: OVA_PASSWORD-2
|
35
|
+
vsphere:
|
36
|
+
datacenter: VSPHERE_DATACENTER-2
|
37
|
+
cluster: VSPHERE_CLUSTER-2
|
38
|
+
network: VSPHERE_NETWORK-2
|
39
|
+
resource_pool: VSPHERE_RESOURCE_POOL-2
|
40
|
+
datastore: VSPHERE_DATASTORE-2
|
41
|
+
folder: VSPHERE_FOLDER-2
|
42
|
+
vm:
|
43
|
+
ip: OVA_IP-2
|
44
|
+
gateway: OVA_GATEWAY-2
|
45
|
+
netmask: OVA_NETMASK-2
|
46
|
+
dns: OVA_DNS-2
|
47
|
+
ntp_servers: OVA_NTP-2
|
48
|
+
cleanup:
|
49
|
+
datacenter: VSPHERE_OTHER_DATACENTER-2
|
50
|
+
datastores:
|
51
|
+
- VSPHERE_DATASTORE_ONE-2
|
52
|
+
- VSPHERE_DATASTORE_TWO-2
|
53
|
+
datacenter_folders_to_clean:
|
54
|
+
- DC_FOLDER_ONE-2
|
55
|
+
- DC_FOLDER_TWO-2
|
56
|
+
datastore_folders_to_clean:
|
57
|
+
- DS_DISK_FOLDER-2
|
@@ -9,38 +9,97 @@ module VmShepherd
|
|
9
9
|
let(:elastic_ip_id) { 'elastic-ip-id' }
|
10
10
|
let(:ec2) { double('AWS.ec2') }
|
11
11
|
|
12
|
-
let(:
|
12
|
+
let(:env_config) do
|
13
13
|
{
|
14
|
+
stack_name: 'aws-stack-name',
|
14
15
|
aws_access_key: 'aws-access-key',
|
15
16
|
aws_secret_key: 'aws-secret-key',
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
json_file: 'cloudformation.json',
|
18
|
+
parameters: {
|
19
|
+
'some_parameter' => 'some-answer',
|
20
|
+
},
|
21
|
+
outputs: {
|
22
|
+
ssh_key_name: 'ssh-key-name',
|
23
|
+
security_group: 'security-group-id',
|
24
|
+
public_subnet_id: 'public-subnet-id',
|
25
|
+
private_subnet_id: 'private-subnet-id',
|
26
|
+
},
|
22
27
|
}
|
23
28
|
end
|
24
29
|
|
25
|
-
|
30
|
+
let(:vm_config) do
|
31
|
+
{
|
32
|
+
vm_name: 'some-vm-name',
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
subject(:ami_manager) { AwsManager.new(env_config) }
|
26
37
|
|
27
38
|
before do
|
28
39
|
expect(AWS).to receive(:config).with(
|
29
|
-
access_key_id:
|
30
|
-
secret_access_key:
|
40
|
+
access_key_id: env_config.fetch(:aws_access_key),
|
41
|
+
secret_access_key: env_config.fetch(:aws_secret_key),
|
31
42
|
region: 'us-east-1',
|
32
43
|
)
|
33
44
|
|
34
45
|
allow(AWS).to receive(:ec2).and_return(ec2)
|
46
|
+
allow(ami_manager).to receive(:sleep) # speed up retry logic
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '#prepare_environment' do
|
50
|
+
let(:cloudformation_template_file) { Tempfile.new('cloudformation_template_file').tap { |f| f.write('{}'); f.close } }
|
51
|
+
let(:cfm) { instance_double(AWS::CloudFormation, stacks: stack_collection) }
|
52
|
+
let(:stack) { instance_double(AWS::CloudFormation::Stack, status: 'CREATE_COMPLETE') }
|
53
|
+
let(:stack_collection) { instance_double(AWS::CloudFormation::StackCollection) }
|
54
|
+
|
55
|
+
before do
|
56
|
+
allow(AWS::CloudFormation).to receive(:new).and_return(cfm)
|
57
|
+
allow(stack_collection).to receive(:create).and_return(stack)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'creates the stack with the correct parameters' do
|
61
|
+
expect(stack_collection).to receive(:create).with(
|
62
|
+
'aws-stack-name',
|
63
|
+
'{}',
|
64
|
+
parameters: {
|
65
|
+
'some_parameter' => 'some-answer',
|
66
|
+
},
|
67
|
+
capabilities: ['CAPABILITY_IAM']
|
68
|
+
)
|
69
|
+
ami_manager.prepare_environment(cloudformation_template_file.path)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'waits for the stack to finish creating' do
|
73
|
+
expect(stack).to receive(:status).and_return('CREATE_IN_PROGRESS', 'CREATE_IN_PROGRESS', 'CREATE_IN_PROGRESS', 'CREATE_COMPLETE')
|
74
|
+
|
75
|
+
ami_manager.prepare_environment(cloudformation_template_file.path)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'stops retrying after 360 times' do
|
79
|
+
expect(stack).to receive(:status).and_return('CREATE_IN_PROGRESS').
|
80
|
+
exactly(360).times
|
81
|
+
|
82
|
+
expect { ami_manager.prepare_environment(cloudformation_template_file.path) }.to raise_error(AwsManager::RetryLimitExceeded)
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'aborts if stack fails to create' do
|
86
|
+
expect(stack).to receive(:status).and_return('CREATE_IN_PROGRESS', 'ROLLBACK_IN_PROGRESS', 'ROLLBACK_IN_PROGRESS', 'ROLLBACK_COMPLETE').ordered
|
87
|
+
expect(stack).to receive(:delete)
|
88
|
+
expect {
|
89
|
+
ami_manager.prepare_environment(cloudformation_template_file.path)
|
90
|
+
}.to raise_error('Unexpected status for stack aws-stack-name : ROLLBACK_COMPLETE')
|
91
|
+
end
|
35
92
|
end
|
36
93
|
|
37
94
|
describe '#deploy' do
|
38
95
|
let(:instance) { instance_double(AWS::EC2::Instance, status: :running, associate_elastic_ip: nil, add_tag: nil) }
|
96
|
+
let(:elastic_ip) { instance_double(AWS::EC2::ElasticIp, allocation_id: 'allocation-id') }
|
39
97
|
let(:instances) { instance_double(AWS::EC2::InstanceCollection, create: instance) }
|
98
|
+
let(:elastic_ips) { instance_double(AWS::EC2::ElasticIpCollection, create: elastic_ip) }
|
40
99
|
|
41
100
|
before do
|
42
101
|
allow(ec2).to receive(:instances).and_return(instances)
|
43
|
-
allow(
|
102
|
+
allow(ec2).to receive(:elastic_ips).and_return(elastic_ips)
|
44
103
|
end
|
45
104
|
|
46
105
|
it 'creates an instance using AWS SDK v1' do
|
@@ -48,10 +107,10 @@ module VmShepherd
|
|
48
107
|
image_id: ami_id,
|
49
108
|
key_name: 'ssh-key-name',
|
50
109
|
security_group_ids: ['security-group-id'],
|
51
|
-
subnet:
|
110
|
+
subnet: 'public-subnet-id',
|
52
111
|
instance_type: 'm3.medium').and_return(instance)
|
53
112
|
|
54
|
-
ami_manager.deploy(ami_file_path)
|
113
|
+
ami_manager.deploy(ami_file_path: ami_file_path, vm_config: vm_config)
|
55
114
|
end
|
56
115
|
|
57
116
|
context 'when the ip address is in use' do
|
@@ -59,21 +118,21 @@ module VmShepherd
|
|
59
118
|
expect(instances).to receive(:create).and_raise(AWS::EC2::Errors::InvalidIPAddress::InUse).once
|
60
119
|
expect(instances).to receive(:create).and_return(instance).once
|
61
120
|
|
62
|
-
ami_manager.deploy(ami_file_path)
|
121
|
+
ami_manager.deploy(ami_file_path: ami_file_path, vm_config: vm_config)
|
63
122
|
end
|
64
123
|
|
65
124
|
it 'stops retrying after 60 times' do
|
66
125
|
expect(instances).to receive(:create).and_raise(AWS::EC2::Errors::InvalidIPAddress::InUse).
|
67
126
|
exactly(AwsManager::RETRY_LIMIT).times
|
68
127
|
|
69
|
-
expect { ami_manager.deploy(ami_file_path) }.to raise_error(AwsManager::RetryLimitExceeded)
|
128
|
+
expect { ami_manager.deploy(ami_file_path: ami_file_path, vm_config: vm_config) }.to raise_error(AwsManager::RetryLimitExceeded)
|
70
129
|
end
|
71
130
|
end
|
72
131
|
|
73
132
|
it 'does not return until the instance is running' do
|
74
133
|
expect(instance).to receive(:status).and_return(:pending, :pending, :pending, :running)
|
75
134
|
|
76
|
-
ami_manager.deploy(ami_file_path)
|
135
|
+
ami_manager.deploy(ami_file_path: ami_file_path, vm_config: vm_config)
|
77
136
|
end
|
78
137
|
|
79
138
|
it 'handles API endpoints not knowing (right away) about the instance created' do
|
@@ -81,26 +140,29 @@ module VmShepherd
|
|
81
140
|
exactly(AwsManager::RETRY_LIMIT - 1).times
|
82
141
|
expect(instance).to receive(:status).and_return(:running).once
|
83
142
|
|
84
|
-
ami_manager.deploy(ami_file_path)
|
143
|
+
ami_manager.deploy(ami_file_path: ami_file_path, vm_config: vm_config)
|
85
144
|
end
|
86
145
|
|
87
146
|
it 'stops retrying after 60 times' do
|
88
147
|
expect(instance).to receive(:status).and_return(:pending).
|
89
148
|
exactly(AwsManager::RETRY_LIMIT).times
|
90
149
|
|
91
|
-
expect { ami_manager.deploy(ami_file_path) }.to raise_error(AwsManager::RetryLimitExceeded)
|
150
|
+
expect { ami_manager.deploy(ami_file_path: ami_file_path, vm_config: vm_config) }.to raise_error(AwsManager::RetryLimitExceeded)
|
92
151
|
end
|
93
152
|
|
94
|
-
it 'attaches
|
95
|
-
expect(
|
153
|
+
it 'creates and attaches an elastic IP' do
|
154
|
+
expect(ec2).to receive_message_chain(:elastic_ips, :create).with(
|
155
|
+
vpc: true).and_return(elastic_ip)
|
96
156
|
|
97
|
-
|
157
|
+
expect(instance).to receive(:associate_elastic_ip).with(elastic_ip.allocation_id)
|
158
|
+
|
159
|
+
ami_manager.deploy(ami_file_path: ami_file_path, vm_config: vm_config)
|
98
160
|
end
|
99
161
|
|
100
162
|
it 'tags the instance with a name' do
|
101
|
-
expect(instance).to receive(:add_tag).with('Name', value:
|
163
|
+
expect(instance).to receive(:add_tag).with('Name', value: 'some-vm-name')
|
102
164
|
|
103
|
-
ami_manager.deploy(ami_file_path)
|
165
|
+
ami_manager.deploy(ami_file_path: ami_file_path, vm_config: vm_config)
|
104
166
|
end
|
105
167
|
end
|
106
168
|
|
@@ -112,19 +174,28 @@ module VmShepherd
|
|
112
174
|
let(:instance2) { instance_double(AWS::EC2::Instance, tags: {}) }
|
113
175
|
let(:subnet1_instances) { [instance1] }
|
114
176
|
let(:subnet2_instances) { [instance2] }
|
177
|
+
let(:cfm) { instance_double(AWS::CloudFormation, stacks: stack_collection) }
|
178
|
+
let(:stack) { instance_double(AWS::CloudFormation::Stack, status: 'DELETE_COMPLETE', delete: nil) }
|
179
|
+
let(:stack_collection) { instance_double(AWS::CloudFormation::StackCollection) }
|
115
180
|
|
116
|
-
let(:instance1_volume) { instance_double(AWS::EC2::Volume)}
|
181
|
+
let(:instance1_volume) { instance_double(AWS::EC2::Volume) }
|
117
182
|
let(:instance1_attachment) do
|
118
183
|
instance_double(AWS::EC2::Attachment, volume: instance1_volume, delete_on_termination: true)
|
119
184
|
end
|
120
185
|
|
121
186
|
before do
|
187
|
+
allow(AWS::CloudFormation).to receive(:new).and_return(cfm)
|
188
|
+
allow(stack_collection).to receive(:[]).and_return(stack)
|
189
|
+
|
122
190
|
allow(ec2).to receive(:subnets).and_return(subnets)
|
123
191
|
allow(subnets).to receive(:[]).with('public-subnet-id').and_return(subnet1)
|
124
192
|
allow(subnets).to receive(:[]).with('private-subnet-id').and_return(subnet2)
|
125
193
|
|
126
194
|
allow(instance1).to receive(:attachments).and_return({'/dev/test' => instance1_attachment})
|
127
195
|
allow(instance2).to receive(:attachments).and_return({})
|
196
|
+
|
197
|
+
allow(instance1).to receive(:terminate)
|
198
|
+
allow(instance2).to receive(:terminate)
|
128
199
|
end
|
129
200
|
|
130
201
|
it 'terminates all VMs in the subnet' do
|
@@ -134,20 +205,46 @@ module VmShepherd
|
|
134
205
|
ami_manager.clean_environment
|
135
206
|
end
|
136
207
|
|
137
|
-
|
138
|
-
|
139
|
-
|
208
|
+
it 'deletes the stack' do
|
209
|
+
expect(stack_collection).to receive(:[]).with('aws-stack-name').and_return(stack)
|
210
|
+
expect(stack).to receive(:delete)
|
211
|
+
ami_manager.clean_environment
|
212
|
+
end
|
140
213
|
|
141
|
-
|
142
|
-
|
143
|
-
it 'does not attempt to terminate this instance' do
|
144
|
-
expect(instance1).to receive(:terminate)
|
145
|
-
expect(instance2).to receive(:terminate)
|
146
|
-
expect(persistent_instance).not_to receive(:terminate)
|
214
|
+
it 'waits for stack deletion to complete' do
|
215
|
+
expect(stack).to receive(:status).and_return('DELETE_IN_PROGRESS', 'DELETE_IN_PROGRESS', 'DELETE_IN_PROGRESS', 'DELETE_COMPLETE')
|
147
216
|
|
148
|
-
|
149
|
-
|
150
|
-
|
217
|
+
ami_manager.clean_environment
|
218
|
+
end
|
219
|
+
|
220
|
+
it 'stops retrying after 360 times' do
|
221
|
+
expect(stack).to receive(:status).and_return('DELETE_IN_PROGRESS').
|
222
|
+
exactly(360).times
|
223
|
+
|
224
|
+
expect { ami_manager.clean_environment }.to raise_error(AwsManager::RetryLimitExceeded)
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'aborts if stack reports unexpected status' do
|
228
|
+
expect(stack).to receive(:status).and_return('DELETE_IN_PROGRESS', 'UNEXPECTED_STATUS').ordered
|
229
|
+
expect {
|
230
|
+
ami_manager.clean_environment
|
231
|
+
}.to raise_error('Unexpected status for stack aws-stack-name : UNEXPECTED_STATUS')
|
232
|
+
end
|
233
|
+
|
234
|
+
it 'aborts if stack throws error' do
|
235
|
+
expect(stack).to receive(:status).and_raise(AWS::CloudFormation::Errors::ValidationError)
|
236
|
+
allow(stack).to receive(:exists?).and_return(true)
|
237
|
+
expect {
|
238
|
+
ami_manager.clean_environment
|
239
|
+
}.to raise_error(AWS::CloudFormation::Errors::ValidationError)
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'succeeds if stack throws error and stack deletion has completed' do
|
243
|
+
expect(stack).to receive(:status).and_raise(AWS::CloudFormation::Errors::ValidationError)
|
244
|
+
allow(stack).to receive(:exists?).and_return(false)
|
245
|
+
expect {
|
246
|
+
ami_manager.clean_environment
|
247
|
+
}.not_to raise_error
|
151
248
|
end
|
152
249
|
|
153
250
|
context 'when the instance has volumes that are NOT delete_on_termination' do
|
@@ -181,33 +278,43 @@ module VmShepherd
|
|
181
278
|
end
|
182
279
|
|
183
280
|
describe '#destroy' do
|
184
|
-
let(:
|
185
|
-
let(:
|
186
|
-
let(:
|
281
|
+
let(:elastic_ip) { nil }
|
282
|
+
let(:instance) { instance_double(AWS::EC2::Instance, tags: {'Name' => 'some-vm-name'}, elastic_ip: elastic_ip) }
|
283
|
+
let(:non_terminated_instance) { instance_double(AWS::EC2::Instance, tags: {}) }
|
284
|
+
let(:instances) { [non_terminated_instance, instance] }
|
187
285
|
|
188
286
|
before do
|
189
|
-
allow(ec2).to receive(:
|
190
|
-
allow(
|
287
|
+
allow(ec2).to receive(:instances).and_return(instances)
|
288
|
+
allow(instance).to receive(:terminate)
|
191
289
|
end
|
192
290
|
|
193
|
-
it 'terminates the VM
|
291
|
+
it 'terminates the VM with the specified name' do
|
292
|
+
expect(non_terminated_instance).not_to receive(:terminate)
|
194
293
|
expect(instance).to receive(:terminate)
|
195
294
|
|
196
|
-
ami_manager.destroy
|
295
|
+
ami_manager.destroy(vm_config)
|
197
296
|
end
|
198
297
|
|
199
|
-
context 'when
|
200
|
-
let(:
|
201
|
-
let(:instances) { [instance1, instance2, persistent_instance] }
|
298
|
+
context 'when there is an elastic ip' do
|
299
|
+
let(:elastic_ip) { instance_double(AWS::EC2::ElasticIp) }
|
202
300
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
301
|
+
before do
|
302
|
+
allow(elastic_ip).to receive(:delete)
|
303
|
+
allow(elastic_ip).to receive(:disassociate)
|
304
|
+
end
|
207
305
|
|
208
|
-
|
209
|
-
|
210
|
-
|
306
|
+
it 'terminates the VM with the specified name' do
|
307
|
+
expect(non_terminated_instance).not_to receive(:terminate)
|
308
|
+
expect(instance).to receive(:terminate)
|
309
|
+
|
310
|
+
ami_manager.destroy(vm_config)
|
311
|
+
end
|
312
|
+
|
313
|
+
it 'disassociates and deletes the ip associated with the terminated vm' do
|
314
|
+
expect(elastic_ip).to receive(:disassociate).ordered
|
315
|
+
expect(elastic_ip).to receive(:delete).ordered
|
316
|
+
|
317
|
+
ami_manager.destroy(vm_config)
|
211
318
|
end
|
212
319
|
end
|
213
320
|
end
|