vm_shepherd 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,23 +1,19 @@
1
- require 'vm_shepherd/shepherd'
1
+ require 'vm_shepherd'
2
2
  require 'recursive_open_struct'
3
3
 
4
4
  module VmShepherd
5
5
  RSpec.describe Shepherd do
6
6
  subject(:manager) { Shepherd.new(settings: settings) }
7
7
 
8
- specify { expect(Shepherd::VCLOUD_IAAS_TYPE).to eq('vcloud') }
9
- specify { expect(Shepherd::VSPHERE_IAAS_TYPE).to eq('vsphere') }
10
- specify { expect(Shepherd::VSPHERE_TEMPLATE_PREFIX).to eq('tpl') }
11
-
12
8
  describe '#deploy' do
13
9
  context 'with vcloud settings' do
14
10
  let(:settings) do
15
11
  RecursiveOpenStruct.new(YAML.load_file(File.join(SPEC_ROOT, 'fixtures', 'shepherd', 'vcloud.yml')))
16
12
  end
17
- let(:deployer) { instance_double(VappManager::Deployer) }
13
+ let(:vcloud_manager) { instance_double(VcloudManager) }
18
14
 
19
- it 'uses VappManager::Deployer to launch a vm' do
20
- expect(VappManager::Deployer).to receive(:new).
15
+ it 'uses VcloudManager::Deployer to launch a vm' do
16
+ expect(VcloudManager).to receive(:new).
21
17
  with(
22
18
  {
23
19
  url: settings.vapp_deployer.creds.url,
@@ -31,9 +27,9 @@ module VmShepherd
31
27
  network: settings.vapp_deployer.vdc.network,
32
28
  },
33
29
  instance_of(Logger)
34
- ).and_return(deployer)
30
+ ).and_return(vcloud_manager)
35
31
 
36
- expect(deployer).to receive(:deploy).with(
32
+ expect(vcloud_manager).to receive(:deploy).with(
37
33
  'FAKE_PATH',
38
34
  {
39
35
  name: settings.vapp_deployer.vapp.name,
@@ -53,28 +49,17 @@ module VmShepherd
53
49
  let(:settings) do
54
50
  RecursiveOpenStruct.new(YAML.load_file(File.join(SPEC_ROOT, 'fixtures', 'shepherd', 'vsphere.yml')))
55
51
  end
56
- let(:deployer) { instance_double(OvaManager::Deployer) }
52
+ let(:ova_manager) { instance_double(VsphereManager) }
57
53
 
58
- it 'uses OvaManager::Deployer to launch a vm' do
59
- expect(OvaManager::Deployer).to receive(:new).
60
- with(
61
- {
62
- host: settings.vm_deployer.vcenter_creds.ip,
63
- user: settings.vm_deployer.vcenter_creds.username,
64
- password: settings.vm_deployer.vcenter_creds.password,
65
- },
66
- {
67
- datacenter: settings.vm_deployer.vsphere.datacenter,
68
- cluster: settings.vm_deployer.vsphere.cluster,
69
- resource_pool: settings.vm_deployer.vsphere.resource_pool,
70
- datastore: settings.vm_deployer.vsphere.datastore,
71
- network: settings.vm_deployer.vsphere.network,
72
- folder: settings.vm_deployer.vsphere.folder,
73
- },
74
- ).and_return(deployer)
54
+ it 'uses VsphereManager::Deployer to launch a vm' do
55
+ expect(VsphereManager).to receive(:new).with(
56
+ settings.vm_deployer.vcenter_creds.ip,
57
+ settings.vm_deployer.vcenter_creds.username,
58
+ settings.vm_deployer.vcenter_creds.password,
59
+ settings.vm_deployer.vsphere.datacenter,
60
+ ).and_return(ova_manager)
75
61
 
76
- expect(deployer).to receive(:deploy).with(
77
- Shepherd::VSPHERE_TEMPLATE_PREFIX,
62
+ expect(ova_manager).to receive(:deploy).with(
78
63
  'FAKE_PATH',
79
64
  {
80
65
  ip: settings.vm_deployer.vm.ip,
@@ -82,7 +67,14 @@ module VmShepherd
82
67
  netmask: settings.vm_deployer.vm.netmask,
83
68
  dns: settings.vm_deployer.vm.dns,
84
69
  ntp_servers: settings.vm_deployer.vm.ntp_servers,
85
- }
70
+ },
71
+ {
72
+ cluster: settings.vm_deployer.vsphere.cluster,
73
+ resource_pool: settings.vm_deployer.vsphere.resource_pool,
74
+ datastore: settings.vm_deployer.vsphere.datastore,
75
+ network: settings.vm_deployer.vsphere.network,
76
+ folder: settings.vm_deployer.vsphere.folder,
77
+ },
86
78
  )
87
79
 
88
80
  manager.deploy(path: 'FAKE_PATH')
@@ -93,7 +85,7 @@ module VmShepherd
93
85
  let(:settings) do
94
86
  RecursiveOpenStruct.new(YAML.load_file(File.join(SPEC_ROOT, 'fixtures', 'shepherd', 'aws.yml')))
95
87
  end
96
- let(:ams_manager) { instance_double(AmiManager) }
88
+ let(:ams_manager) { instance_double(AwsManager) }
97
89
  let(:ami_file_path) { 'PATH_TO_AMI_FILE' }
98
90
  let(:aws_options) do
99
91
  {
@@ -109,12 +101,49 @@ module VmShepherd
109
101
  end
110
102
 
111
103
  it 'uses AwsManager::Deployer to launch a VM' do
112
- expect(AmiManager).to receive(:new).with(aws_options).and_return(ams_manager)
104
+ expect(AwsManager).to receive(:new).with(aws_options).and_return(ams_manager)
113
105
  expect(ams_manager).to receive(:deploy).with(ami_file_path)
114
106
  manager.deploy(path: ami_file_path)
115
107
  end
116
108
  end
117
109
 
110
+ context 'with OpenStack settings' do
111
+ let(:settings) do
112
+ RecursiveOpenStruct.new(YAML.load_file(File.join(SPEC_ROOT, 'fixtures', 'shepherd', 'openstack.yml')))
113
+ end
114
+ let(:qcow2_manager) { instance_double(OpenstackManager) }
115
+ let(:qcow2_file_path) { 'PATH_TO_QCOW2_FILE' }
116
+ let(:openstack_options) do
117
+ {
118
+ auth_url: 'http://example.com/version/tokens',
119
+ username: 'username',
120
+ api_key: 'api-key',
121
+ tenant: 'tenant',
122
+ }
123
+ end
124
+ let(:openstack_vm_options) do
125
+ {
126
+ name: 'some-vm-name',
127
+ min_disk_size: 150,
128
+ network_name: 'some-network',
129
+ key_name: 'some-key',
130
+ security_group_names: [
131
+ 'security-group-A',
132
+ 'security-group-B',
133
+ 'security-group-C',
134
+ ],
135
+ public_ip: '198.11.195.5',
136
+ private_ip: '192.168.100.100',
137
+ }
138
+ end
139
+
140
+ it 'uses QCow2Manager to launch a VM' do
141
+ expect(OpenstackManager).to receive(:new).with(openstack_options).and_return(qcow2_manager)
142
+ expect(qcow2_manager).to receive(:deploy).with(qcow2_file_path, openstack_vm_options)
143
+ manager.deploy(path: qcow2_file_path)
144
+ end
145
+ end
146
+
118
147
  context 'when IAAS is unknown' do
119
148
  let(:settings) do
120
149
  RecursiveOpenStruct.new(YAML.load_file(File.join(SPEC_ROOT, 'fixtures', 'shepherd', 'unknown.yml')))
@@ -131,10 +160,10 @@ module VmShepherd
131
160
  let(:settings) do
132
161
  RecursiveOpenStruct.new(YAML.load_file(File.join(SPEC_ROOT, 'fixtures', 'shepherd', 'vcloud.yml')))
133
162
  end
134
- let(:destroyer) { instance_double(VappManager::Destroyer) }
163
+ let(:vcloud_manager) { instance_double(VcloudManager) }
135
164
 
136
- it 'uses VappManager::Destroyer to destroy a vm' do
137
- expect(VappManager::Destroyer).to receive(:new).with(
165
+ it 'uses VcloudManager::Destroyer to destroy a vm' do
166
+ expect(VcloudManager).to receive(:new).with(
138
167
  {
139
168
  url: settings.vapp_deployer.creds.url,
140
169
  organization: settings.vapp_deployer.creds.organization,
@@ -144,10 +173,11 @@ module VmShepherd
144
173
  {
145
174
  vdc: settings.vapp_deployer.vdc.name,
146
175
  catalog: settings.vapp_deployer.vdc.catalog,
176
+ network: settings.vapp_deployer.vdc.network,
147
177
  },
148
178
  instance_of(Logger)
149
- ).and_return(destroyer)
150
- expect(destroyer).to receive(:destroy).with(settings.vapp_deployer.vapp.name)
179
+ ).and_return(vcloud_manager)
180
+ expect(vcloud_manager).to receive(:destroy).with(settings.vapp_deployer.vapp.name)
151
181
 
152
182
  manager.destroy
153
183
  end
@@ -157,18 +187,16 @@ module VmShepherd
157
187
  let(:settings) do
158
188
  RecursiveOpenStruct.new(YAML.load_file(File.join(SPEC_ROOT, 'fixtures', 'shepherd', 'vsphere.yml')))
159
189
  end
160
- let(:destroyer) { instance_double(OvaManager::Destroyer) }
190
+ let(:ova_manager) { instance_double(VsphereManager) }
161
191
 
162
- it 'uses OvaManager::Destroyer to destroy a vm' do
163
- expect(OvaManager::Destroyer).to receive(:new).with(
192
+ it 'uses VsphereManager::Destroyer to destroy a vm' do
193
+ expect(VsphereManager).to receive(:new).with(
194
+ settings.vm_deployer.vcenter_creds.ip,
195
+ settings.vm_deployer.vcenter_creds.username,
196
+ settings.vm_deployer.vcenter_creds.password,
164
197
  settings.vm_deployer.vsphere.datacenter,
165
- {
166
- host: settings.vm_deployer.vcenter_creds.ip,
167
- user: settings.vm_deployer.vcenter_creds.username,
168
- password: settings.vm_deployer.vcenter_creds.password,
169
- }
170
- ).and_return(destroyer)
171
- expect(destroyer).to receive(:clean_folder).with(settings.vm_deployer.vsphere.folder)
198
+ ).and_return(ova_manager)
199
+ expect(ova_manager).to receive(:destroy).with(settings.vm_deployer.vsphere.folder)
172
200
 
173
201
  manager.destroy
174
202
  end
@@ -178,7 +206,7 @@ module VmShepherd
178
206
  let(:settings) do
179
207
  RecursiveOpenStruct.new(YAML.load_file(File.join(SPEC_ROOT, 'fixtures', 'shepherd', 'aws.yml')))
180
208
  end
181
- let(:ams_manager) { instance_double(AmiManager) }
209
+ let(:ams_manager) { instance_double(AwsManager) }
182
210
  let(:ami_destroy_options) do
183
211
  {
184
212
  aws_access_key: 'aws-access-key',
@@ -193,12 +221,49 @@ module VmShepherd
193
221
  end
194
222
 
195
223
  it 'uses AwsManager::Deployer to launch a VM' do
196
- expect(AmiManager).to receive(:new).with(ami_destroy_options).and_return(ams_manager)
224
+ expect(AwsManager).to receive(:new).with(ami_destroy_options).and_return(ams_manager)
197
225
  expect(ams_manager).to receive(:destroy)
198
226
  manager.destroy
199
227
  end
200
228
  end
201
229
 
230
+ context 'when IAAS is Openstack' do
231
+ let(:settings) do
232
+ RecursiveOpenStruct.new(YAML.load_file(File.join(SPEC_ROOT, 'fixtures', 'shepherd', 'openstack.yml')))
233
+ end
234
+ let(:qcow2_manager) { instance_double(OpenstackManager) }
235
+ let(:qcow2_file_path) { 'PATH_TO_QCOW2_FILE' }
236
+ let(:openstack_options) do
237
+ {
238
+ auth_url: 'http://example.com/version/tokens',
239
+ username: 'username',
240
+ api_key: 'api-key',
241
+ tenant: 'tenant',
242
+ }
243
+ end
244
+ let(:openstack_vm_options) do
245
+ {
246
+ name: 'some-vm-name',
247
+ min_disk_size: 150,
248
+ network_name: 'some-network',
249
+ key_name: 'some-key',
250
+ security_group_names: [
251
+ 'security-group-A',
252
+ 'security-group-B',
253
+ 'security-group-C',
254
+ ],
255
+ public_ip: '198.11.195.5',
256
+ private_ip: '192.168.100.100',
257
+ }
258
+ end
259
+
260
+ it 'uses QCow2Manager to destroy a VM' do
261
+ expect(OpenstackManager).to receive(:new).with(openstack_options).and_return(qcow2_manager)
262
+ expect(qcow2_manager).to receive(:destroy).with(openstack_vm_options)
263
+ manager.destroy
264
+ end
265
+ end
266
+
202
267
  context 'when IAAS is unknown' do
203
268
  let(:settings) do
204
269
  RecursiveOpenStruct.new(YAML.load_file(File.join(SPEC_ROOT, 'fixtures', 'shepherd', 'unknown.yml')))
@@ -0,0 +1,364 @@
1
+ require 'vm_shepherd/vcloud_manager'
2
+
3
+ module VmShepherd
4
+ RSpec.describe VcloudManager do
5
+ let(:login_info) do
6
+ {
7
+ url: 'FAKE_URL',
8
+ organization: 'FAKE_ORGANIZATION',
9
+ user: 'FAKE_USER',
10
+ password: 'FAKE_PASSWORD',
11
+ }
12
+ end
13
+ let(:location) do
14
+ {
15
+ catalog: 'FAKE_CATALOG',
16
+ network: 'FAKE_NETWORK',
17
+ vdc: 'FAKE_VDC',
18
+ }
19
+ end
20
+ let(:logger) { instance_double(Logger).as_null_object }
21
+
22
+ let(:vcloud_manager) { VcloudManager.new(login_info, location, logger) }
23
+
24
+ describe '#deploy' do
25
+ let(:vapp_config) do
26
+ {
27
+ ip: 'FAKE_IP',
28
+ name: 'FAKE_NAME',
29
+ gateway: 'FAKE_GATEWAY',
30
+ dns: 'FAKE_DNS',
31
+ ntp: 'FAKE_NTP',
32
+ ip: 'FAKE_IP',
33
+ netmask: 'FAKE_NETMASK',
34
+ }
35
+ end
36
+ let(:vapp_template_path) { 'FAKE_VAPP_TEMPLATE_PATH' }
37
+ let(:tmpdir) { 'FAKE_TMP_DIR' }
38
+
39
+ before { allow(Dir).to receive(:mktmpdir).and_return(tmpdir) }
40
+
41
+ context 'when NO host exists at the specified IP' do
42
+ let(:expanded_vapp_template_path) { 'FAKE_EXPANDED_VAPP_TEMPLATE_PATH' }
43
+
44
+ before do
45
+ allow(vcloud_manager).to receive(:system).with("ping -c 5 #{vapp_config.fetch(:ip)}").and_return(false)
46
+
47
+ allow(File).to receive(:expand_path).with(vapp_template_path).and_return(expanded_vapp_template_path)
48
+
49
+ allow(FileUtils).to receive(:remove_entry_secure)
50
+ end
51
+
52
+ it 'expands the vapp_template into a TMP dir' do
53
+ expect(vcloud_manager).to receive(:system).with("cd #{tmpdir} && tar xfv '#{expanded_vapp_template_path}'")
54
+
55
+ expect { vcloud_manager.deploy(vapp_template_path, vapp_config) }.to raise_error
56
+ end
57
+
58
+ context 'when the template can be expanded' do
59
+ let(:client) { instance_double(VCloudSdk::Client) }
60
+ let(:catalog) { instance_double(VCloudSdk::Catalog) }
61
+ let(:network_config) { instance_double(VCloudSdk::NetworkConfig) }
62
+ let(:vapp) { instance_double(VCloudSdk::VApp) }
63
+ let(:vm) { instance_double(VCloudSdk::VM) }
64
+
65
+ before do
66
+ allow(vcloud_manager).to receive(:system).with("cd #{tmpdir} && tar xfv '#{expanded_vapp_template_path}'").
67
+ and_return(true)
68
+ end
69
+
70
+ context 'when the vApp can be deployed' do
71
+ let(:expected_properties) do
72
+ [
73
+ {
74
+ 'type' => 'string',
75
+ 'key' => 'gateway',
76
+ 'value' => vapp_config.fetch(:gateway),
77
+ 'password' => 'false',
78
+ 'userConfigurable' => 'true',
79
+ 'Label' => 'Default Gateway',
80
+ 'Description' => 'The default gateway address for the VM network. Leave blank if DHCP is desired.'
81
+ },
82
+ {
83
+ 'type' => 'string',
84
+ 'key' => 'DNS',
85
+ 'value' => vapp_config.fetch(:dns),
86
+ 'password' => 'false',
87
+ 'userConfigurable' => 'true',
88
+ 'Label' => 'DNS',
89
+ 'Description' => 'The domain name servers for the VM (comma separated). Leave blank if DHCP is desired.',
90
+ },
91
+ {
92
+ 'type' => 'string',
93
+ 'key' => 'ntp_servers',
94
+ 'value' => vapp_config.fetch(:ntp),
95
+ 'password' => 'false',
96
+ 'userConfigurable' => 'true',
97
+ 'Label' => 'NTP Servers',
98
+ 'Description' => 'Comma-delimited list of NTP servers'
99
+ },
100
+ {
101
+ 'type' => 'string',
102
+ 'key' => 'admin_password',
103
+ 'value' => 'tempest',
104
+ 'password' => 'true',
105
+ 'userConfigurable' => 'true',
106
+ 'Label' => 'Admin Password',
107
+ 'Description' => 'This password is used to SSH into the VM. The username is "tempest".',
108
+ },
109
+ {
110
+ 'type' => 'string',
111
+ 'key' => 'ip0',
112
+ 'value' => vapp_config.fetch(:ip),
113
+ 'password' => 'false',
114
+ 'userConfigurable' => 'true',
115
+ 'Label' => 'IP Address',
116
+ 'Description' => 'The IP address for the VM. Leave blank if DHCP is desired.',
117
+ },
118
+ {
119
+ 'type' => 'string',
120
+ 'key' => 'netmask0',
121
+ 'value' => vapp_config.fetch(:netmask),
122
+ 'password' => 'false',
123
+ 'userConfigurable' => 'true',
124
+ 'Label' => 'Netmask',
125
+ 'Description' => 'The netmask for the VM network. Leave blank if DHCP is desired.'
126
+ }
127
+ ]
128
+ end
129
+
130
+ before do
131
+ allow(VCloudSdk::Client).to receive(:new).and_return(client)
132
+ allow(client).to receive(:catalog_exists?)
133
+ allow(client).to receive(:delete_catalog_by_name)
134
+
135
+ allow(client).to receive(:create_catalog).and_return(catalog)
136
+ allow(catalog).to receive(:upload_vapp_template)
137
+ allow(catalog).to receive(:instantiate_vapp_template).and_return(vapp)
138
+
139
+ allow(VCloudSdk::NetworkConfig).to receive(:new).and_return(network_config)
140
+
141
+ allow(vapp).to receive(:find_vm_by_name).and_return(vm)
142
+ allow(vm).to receive(:product_section_properties=)
143
+ allow(vapp).to receive(:power_on)
144
+ end
145
+
146
+ it 'uses VCloudSdk::Client' do
147
+ expect(VCloudSdk::Client).to receive(:new).with(
148
+ login_info.fetch(:url),
149
+ [login_info.fetch(:user), login_info.fetch(:organization)].join('@'),
150
+ login_info.fetch(:password),
151
+ {},
152
+ logger,
153
+ ).and_return(client)
154
+
155
+ vcloud_manager.deploy(vapp_template_path, vapp_config)
156
+ end
157
+
158
+ describe 'catalog deletion' do
159
+ before do
160
+ allow(client).to receive(:catalog_exists?).and_return(catalog_exists)
161
+ end
162
+
163
+ context 'when the catalog exists' do
164
+ let(:catalog_exists) { true }
165
+
166
+ it 'deletes the catalog' do
167
+ expect(client).to receive(:delete_catalog_by_name).with(location.fetch(:catalog))
168
+
169
+ vcloud_manager.deploy(vapp_template_path, vapp_config)
170
+ end
171
+ end
172
+
173
+ context 'when the catalog does not exist' do
174
+ let(:catalog_exists) { false }
175
+
176
+ it 'does not delete the catalog' do
177
+ expect(client).not_to receive(:delete_catalog_by_name).with(location.fetch(:catalog))
178
+
179
+ vcloud_manager.deploy(vapp_template_path, vapp_config)
180
+ end
181
+ end
182
+ end
183
+
184
+ it 'creates the catalog' do
185
+ expect(client).to receive(:create_catalog).with(location.fetch(:catalog)).and_return(catalog)
186
+
187
+ vcloud_manager.deploy(vapp_template_path, vapp_config)
188
+ end
189
+
190
+ it 'uploads the vApp template' do
191
+ expect(catalog).to receive(:upload_vapp_template).with(
192
+ location.fetch(:vdc),
193
+ vapp_config.fetch(:name),
194
+ tmpdir,
195
+ ).and_return(catalog)
196
+
197
+ vcloud_manager.deploy(vapp_template_path, vapp_config)
198
+ end
199
+
200
+ it 'creates a VCloudSdk::NetworkConfig' do
201
+ expect(VCloudSdk::NetworkConfig).to receive(:new).with(
202
+ location.fetch(:network),
203
+ 'Network 1',
204
+ ).and_return(network_config)
205
+
206
+ vcloud_manager.deploy(vapp_template_path, vapp_config)
207
+ end
208
+
209
+ it 'instantiates the vApp template' do
210
+ expect(catalog).to receive(:instantiate_vapp_template).with(
211
+ vapp_config.fetch(:name),
212
+ location.fetch(:vdc),
213
+ vapp_config.fetch(:name),
214
+ nil,
215
+ nil,
216
+ network_config
217
+ ).and_return(vapp)
218
+
219
+ vcloud_manager.deploy(vapp_template_path, vapp_config)
220
+ end
221
+
222
+ it 'sets the product section properties' do
223
+ expect(vm).to receive(:product_section_properties=).with(expected_properties)
224
+
225
+ vcloud_manager.deploy(vapp_template_path, vapp_config)
226
+ end
227
+
228
+ it 'powers on the vApp' do
229
+ expect(vapp).to receive(:power_on)
230
+
231
+ vcloud_manager.deploy(vapp_template_path, vapp_config)
232
+ end
233
+
234
+ it 'removes the expanded vApp template' do
235
+ expect(FileUtils).to receive(:remove_entry_secure).with(tmpdir, force: true)
236
+
237
+ vcloud_manager.deploy(vapp_template_path, vapp_config)
238
+ end
239
+ end
240
+
241
+ context 'when the vApp can NOT be deployed' do
242
+ it 'removes the expanded vApp template' do
243
+ expect(FileUtils).to receive(:remove_entry_secure).with(tmpdir, force: true)
244
+
245
+ expect { vcloud_manager.deploy(vapp_template_path, vapp_config) }.to raise_error
246
+ end
247
+ end
248
+ end
249
+
250
+ context 'when the template can NOT be expanded' do
251
+ let(:tar_expand_cmd) { "cd #{tmpdir} && tar xfv '#{expanded_vapp_template_path}'" }
252
+ before do
253
+ allow(vcloud_manager).to receive(:system).with(tar_expand_cmd).and_return(false)
254
+ end
255
+
256
+ it 'raises an error' do
257
+ expect { vcloud_manager.deploy(vapp_template_path, vapp_config) }.to raise_error("Error executing: #{tar_expand_cmd.inspect}")
258
+ end
259
+
260
+ it 'removes the expanded vApp template' do
261
+ expect(FileUtils).to receive(:remove_entry_secure).with(tmpdir, force: true)
262
+
263
+ expect { vcloud_manager.deploy(vapp_template_path, vapp_config) }.to raise_error
264
+ end
265
+ end
266
+ end
267
+
268
+ context 'when a host exists at the specified IP' do
269
+ before do
270
+ allow(vcloud_manager).to receive(:system).with("ping -c 5 #{vapp_config.fetch(:ip)}").and_return(true)
271
+ end
272
+
273
+ it 'raises an error' do
274
+ expect { vcloud_manager.deploy(vapp_template_path, vapp_config) }.to raise_error("VM exists at #{vapp_config.fetch(:ip)}")
275
+ end
276
+
277
+ it 'removes the expanded vApp template' do
278
+ expect(FileUtils).to receive(:remove_entry_secure).with(tmpdir, force: true)
279
+
280
+ expect { vcloud_manager.deploy(vapp_template_path, vapp_config) }.to raise_error
281
+ end
282
+ end
283
+ end
284
+
285
+ describe '#destroy' do
286
+ let(:client) { instance_double(VCloudSdk::Client) }
287
+ let(:vdc) { instance_double(VCloudSdk::VDC) }
288
+ let(:vapp) { instance_double(VCloudSdk::VApp) }
289
+ let(:vapp_name) { 'FAKE_VAPP_NAME' }
290
+
291
+ context 'when the catalog exists' do
292
+ before do
293
+ allow(client).to receive(:catalog_exists?).with(location.fetch(:catalog)).and_return(true)
294
+ end
295
+
296
+ it 'uses VCloudSdk::Client to delete the vApp' do
297
+ expect(client).to receive(:find_vdc_by_name).with(location.fetch(:vdc)).and_return(vdc)
298
+ expect(vdc).to receive(:find_vapp_by_name).with(vapp_name).and_return(vapp)
299
+ expect(vapp).to receive(:power_off)
300
+ expect(vapp).to receive(:delete)
301
+ expect(client).to receive(:delete_catalog_by_name).with(location.fetch(:catalog))
302
+
303
+ expect(VCloudSdk::Client).to receive(:new).with(
304
+ login_info.fetch(:url),
305
+ [login_info.fetch(:user), login_info.fetch(:organization)].join('@'),
306
+ login_info.fetch(:password),
307
+ {},
308
+ logger,
309
+ ).and_return(client)
310
+
311
+ vcloud_manager.destroy(vapp_name)
312
+ end
313
+
314
+ context 'when an VCloudSdk::ObjectNotFoundError is thrown' do
315
+ before do
316
+ allow(VCloudSdk::Client).to receive(:new).and_return(client)
317
+ allow(client).to receive(:find_vdc_by_name).and_return(vdc)
318
+ allow(vdc).to receive(:find_vapp_by_name).and_return(vapp)
319
+ allow(vapp).to receive(:power_off)
320
+ allow(vapp).to receive(:delete)
321
+
322
+ allow(client).to receive(:delete_catalog_by_name)
323
+ end
324
+
325
+ it 'catches the error' do
326
+ allow(client).to receive(:find_vdc_by_name).and_raise(VCloudSdk::ObjectNotFoundError)
327
+
328
+ expect { vcloud_manager.destroy(vapp_name) }.not_to raise_error
329
+ end
330
+
331
+ it 'deletes to catalog' do
332
+ expect(client).to receive(:delete_catalog_by_name).with(location.fetch(:catalog))
333
+
334
+ vcloud_manager.destroy(vapp_name)
335
+ end
336
+ end
337
+ end
338
+
339
+ context 'when the catalog does not exist' do
340
+ before do
341
+ allow(client).to receive(:catalog_exists?).with(location.fetch(:catalog)).and_return(false)
342
+ end
343
+
344
+ it 'uses VCloudSdk::Client to delete the vApp' do
345
+ expect(client).to receive(:find_vdc_by_name).with(location.fetch(:vdc)).and_return(vdc)
346
+ expect(vdc).to receive(:find_vapp_by_name).with(vapp_name).and_return(vapp)
347
+ expect(vapp).to receive(:power_off)
348
+ expect(vapp).to receive(:delete)
349
+ expect(client).not_to receive(:delete_catalog_by_name).with(location.fetch(:catalog))
350
+
351
+ expect(VCloudSdk::Client).to receive(:new).with(
352
+ login_info.fetch(:url),
353
+ [login_info.fetch(:user), login_info.fetch(:organization)].join('@'),
354
+ login_info.fetch(:password),
355
+ {},
356
+ logger,
357
+ ).and_return(client)
358
+
359
+ vcloud_manager.destroy(vapp_name)
360
+ end
361
+ end
362
+ end
363
+ end
364
+ end