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
@@ -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
|