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,46 +0,0 @@
1
- require 'ruby_vcloud_sdk'
2
-
3
- module VmShepherd
4
- module VappManager
5
- class Destroyer
6
- def initialize(login_info, location, logger)
7
- @login_info = login_info
8
- @location = location
9
- @logger = logger
10
- end
11
-
12
- def destroy(vapp_name)
13
- delete_vapp(vapp_name)
14
- delete_catalog
15
- end
16
-
17
- private
18
-
19
- def client
20
- @client ||= VCloudSdk::Client.new(
21
- @login_info[:url],
22
- "#{@login_info[:user]}@#{@login_info[:organization]}",
23
- @login_info[:password],
24
- {},
25
- @logger,
26
- )
27
- end
28
-
29
- def vdc
30
- @vdc ||= client.find_vdc_by_name(@location[:vdc])
31
- end
32
-
33
- def delete_vapp(vapp_name)
34
- vapp = vdc.find_vapp_by_name(vapp_name)
35
- vapp.power_off
36
- vapp.delete
37
- rescue VCloudSdk::ObjectNotFoundError => e
38
- @logger.debug "Could not delete vapp '#{vapp_name}': #{e.inspect}"
39
- end
40
-
41
- def delete_catalog
42
- client.delete_catalog_by_name(@location[:catalog]) if client.catalog_exists?(@location[:catalog])
43
- end
44
- end
45
- end
46
- end
@@ -1,56 +0,0 @@
1
- require 'vm_shepherd/ova_manager/base'
2
-
3
- module VmShepherd
4
- module OvaManager
5
- RSpec.describe Base do
6
- subject(:base) { Base.new(vcenter) }
7
-
8
- let(:vcenter) { { host: 'host', user: 'user', password: 'password' } }
9
- let(:search_index) { double('searchIndex') }
10
- let(:connection) { double('RbVmomi::VIM', searchIndex: search_index) }
11
- let(:datacenter) { FakeDatacenter.new }
12
-
13
- class FakeDatacenter < RbVmomi::VIM::Datacenter
14
- def initialize
15
- end
16
- end
17
-
18
- before { allow(RbVmomi::VIM).to receive(:connect).and_return(connection) }
19
-
20
- def stub_search(find_result)
21
- allow(search_index).to receive(:FindByInventoryPath).and_return(find_result)
22
- end
23
-
24
- describe '#find_datacenter' do
25
- it 'should return datacenter with valid name' do
26
- stub_search(datacenter)
27
- expect(base.find_datacenter('valid_datacenter')).to be(datacenter)
28
- end
29
-
30
- it 'should return nil with invalid name' do
31
- stub_search(nil)
32
- expect(base.find_datacenter('does_not_exist')).to be_nil
33
- end
34
-
35
- it 'should return nil when find returns non-datacenter' do
36
- stub_search(double)
37
- expect(base.find_datacenter('non_a_datacenter')).to be_nil
38
- end
39
- end
40
-
41
- describe '#connection' do
42
- it 'should return a connection' do
43
- conn = base.send(:connection)
44
- expect(conn).to be(connection)
45
- end
46
-
47
- it 'should return the same connection on subsequent invocations' do
48
- conn = base.send(:connection)
49
- conn_again = base.send(:connection)
50
- expect(conn).to be(conn_again)
51
- expect(RbVmomi::VIM).to have_received(:connect).once
52
- end
53
- end
54
- end
55
- end
56
- end
@@ -1,134 +0,0 @@
1
- require 'tempfile'
2
- require 'vm_shepherd/ova_manager/deployer'
3
-
4
- module VmShepherd
5
- module OvaManager
6
- RSpec.describe Deployer do
7
- let(:connection) { double('connection') }
8
- let(:cluster) { double('cluster', resourcePool: cluster_resource_pool) }
9
- let(:cluster_resource_pool) { double(:cluster_resource_pool, resourcePool: [a_resource_pool, another_resource_pool]) }
10
-
11
- let(:a_resource_pool) { double('resource_pool', name: 'resource_pool_name') }
12
- let(:another_resource_pool) { double('another_resource_pool', name: 'another_resource_pool_name') }
13
-
14
- let(:target_folder) { double('target_folder') }
15
- let(:datastore) { double('datastore') }
16
- let(:network) { double('network', name: 'network') }
17
- let(:datacenter) { double('datacenter', network: [network]) }
18
- let(:cached_ova_deployer) {
19
- double('CachedOvaDeployer', upload_ovf_as_template: template, linked_clone: linked_clone)
20
- }
21
- let(:template) { double('template', name: 'template name') }
22
- let(:ova_path) { File.join(SPEC_ROOT, 'fixtures', 'ova_manager', 'foo.ova') }
23
- let(:linked_clone) { double('linked clone', guest_ip: '1.1.1.1') }
24
- let(:vcenter_config) {
25
- {
26
- host: 'foo',
27
- user: 'bar',
28
- password: 'secret'
29
- }
30
- }
31
-
32
- subject(:deployer) { Deployer.new(vcenter_config, location) }
33
-
34
- before do
35
- allow(deployer).to receive(:system).with(/cd .* && tar xfv .*/).and_call_original
36
- allow(deployer).to receive(:system).with(/nc -z -w 5 .* 443/).and_return(false)
37
-
38
- allow(datacenter).to receive(:find_compute_resource).with('cluster').and_return(cluster)
39
-
40
- allow(datacenter).to receive(:find_datastore).with('datastore').and_return(datastore)
41
-
42
- allow(linked_clone).to receive_message_chain(:ReconfigVM_Task, :wait_for_completion)
43
- allow(linked_clone).to receive_message_chain(:PowerOnVM_Task, :wait_for_completion)
44
-
45
- allow(RbVmomi::VIM).to receive(:connect).with({
46
- host: vcenter_config[:host],
47
- user: vcenter_config[:user],
48
- password: vcenter_config[:password],
49
- ssl: true,
50
- insecure: true
51
- }).and_return connection
52
-
53
- allow(deployer).to receive(:find_datacenter).and_return(datacenter)
54
-
55
- allow(datacenter).to receive_message_chain(:vmFolder, :traverse).
56
- with('target_folder', RbVmomi::VIM::Folder, true).and_return(target_folder)
57
- allow(datacenter).to receive(:networkFolder).and_return(
58
- double(:networkFolder).tap do |network_folder|
59
- allow(network_folder).to receive(:traverse).with('network').and_return(network)
60
- end
61
- )
62
- end
63
-
64
- context 'resource pool is a parameter' do
65
- let(:location) {
66
- {
67
- connection: connection,
68
- network: 'network',
69
- cluster: 'cluster',
70
- folder: 'target_folder',
71
- datastore: 'datastore',
72
- datacenter: 'datacenter',
73
- resource_pool: resource_pool_name
74
- }
75
- }
76
-
77
- context 'resource pool can be found' do
78
- let(:resource_pool_name) { a_resource_pool.name }
79
-
80
- it 'uses VsphereClient::CachedOvfDeployer to deploy an OVA within a resource pool' do
81
- expect(VsphereClients::CachedOvfDeployer).to receive(:new).with(
82
- connection,
83
- network,
84
- cluster,
85
- a_resource_pool,
86
- target_folder,
87
- target_folder,
88
- datastore
89
- ).and_return(cached_ova_deployer)
90
-
91
- deployer.deploy('foo', ova_path, { ip: '1.1.1.1' })
92
- end
93
- end
94
-
95
- context 'resource pool cannot be found' do
96
- let(:resource_pool_name) { 'i am no body' }
97
-
98
- it 'uses VsphereClient::CachedOvfDeployer to deploy an OVA within a resource pool' do
99
- expect {
100
- deployer.deploy('foo', ova_path, { ip: '1.1.1.1' })
101
- }.to raise_error(/Failed to find resource pool '#{resource_pool_name}'/)
102
- end
103
- end
104
- end
105
-
106
- context 'when there is no resource pool in location' do
107
- let(:location) {
108
- {
109
- connection: connection,
110
- network: 'network',
111
- cluster: 'cluster',
112
- folder: 'target_folder',
113
- datastore: 'datastore',
114
- datacenter: 'datacenter',
115
- }
116
- }
117
-
118
- it 'uses the cluster resource pool' do
119
- expect(VsphereClients::CachedOvfDeployer).to receive(:new).with(
120
- connection,
121
- network,
122
- cluster,
123
- cluster_resource_pool,
124
- target_folder,
125
- target_folder,
126
- datastore
127
- ).and_return(cached_ova_deployer)
128
-
129
- deployer.deploy('foo', ova_path, { ip: '1.1.1.1' })
130
- end
131
- end
132
- end
133
- end
134
- end
@@ -1,42 +0,0 @@
1
- require 'vm_shepherd/ova_manager/destroyer'
2
-
3
- module VmShepherd
4
- module OvaManager
5
- RSpec.describe Destroyer do
6
- subject(:destroyer) { Destroyer.new('datacenter_name', vcenter_config) }
7
- let(:vcenter_config) { { host: 'host', user: 'user', password: 'password' } }
8
-
9
- describe '#clean_folder' do
10
- # Crappy temporary test to at least ensure that Destroyer requires the appropriate files
11
- it 'is wired up correctly' do
12
- connection = double('connection', serviceInstance: double('serviceInstance'))
13
- datacenter = double('datacenter')
14
- allow(datacenter).to receive(:is_a?).with(RbVmomi::VIM::Datacenter).and_return(true)
15
- vm_folder_client = double('vm_folder_client')
16
-
17
- expect(RbVmomi::VIM).to receive(:connect).with(
18
- host: 'host',
19
- user: 'user',
20
- password: 'password',
21
- ssl: true,
22
- insecure: true,
23
- ).and_return(connection)
24
- expect(connection).to receive(:searchIndex).and_return(
25
- double(:searchIndex).tap do |search_index|
26
- expect(search_index).to receive(:FindByInventoryPath).
27
- with(inventoryPath: 'datacenter_name').
28
- and_return(datacenter)
29
- end
30
- )
31
- expect(VsphereClients::VmFolderClient).to receive(:new).
32
- with(datacenter, instance_of(Logger)).
33
- and_return(vm_folder_client)
34
- expect(vm_folder_client).to receive(:delete_folder).with('folder_name')
35
- expect(vm_folder_client).to receive(:create_folder).with('folder_name')
36
-
37
- destroyer.clean_folder('folder_name')
38
- end
39
- end
40
- end
41
- end
42
- end
@@ -1,287 +0,0 @@
1
- require 'vm_shepherd/vapp_manager/deployer'
2
-
3
- module VmShepherd
4
- module VappManager
5
- RSpec.describe Deployer do
6
- let(:login_info) do
7
- {
8
- url: 'FAKE_URL',
9
- organization: 'FAKE_ORGANIZATION',
10
- user: 'FAKE_USER',
11
- password: 'FAKE_PASSWORD',
12
- }
13
- end
14
- let(:location) do
15
- {
16
- catalog: 'FAKE_CATALOG',
17
- network: 'FAKE_NETWORK',
18
- vdc: 'FAKE_VDC',
19
- }
20
- end
21
- let(:logger) { instance_double(Logger).as_null_object }
22
-
23
- let(:deployer) { Deployer.new(login_info, location, logger) }
24
-
25
- describe '#deploy' do
26
- let(:vapp_config) do
27
- {
28
- ip: 'FAKE_IP',
29
- name: 'FAKE_NAME',
30
- gateway: 'FAKE_GATEWAY',
31
- dns: 'FAKE_DNS',
32
- ntp: 'FAKE_NTP',
33
- ip: 'FAKE_IP',
34
- netmask: 'FAKE_NETMASK',
35
- }
36
- end
37
- let(:vapp_template_path) { 'FAKE_VAPP_TEMPLATE_PATH' }
38
- let(:tmpdir) { 'FAKE_TMP_DIR' }
39
-
40
- before { allow(Dir).to receive(:mktmpdir).and_return(tmpdir) }
41
-
42
- context 'when NO host exists at the specified IP' do
43
- let(:expanded_vapp_template_path) { 'FAKE_EXPANDED_VAPP_TEMPLATE_PATH' }
44
-
45
- before do
46
- allow(deployer).to receive(:system).with("ping -c 5 #{vapp_config.fetch(:ip)}").and_return(false)
47
-
48
- allow(File).to receive(:expand_path).with(vapp_template_path).and_return(expanded_vapp_template_path)
49
-
50
- allow(FileUtils).to receive(:remove_entry_secure)
51
- end
52
-
53
- it 'expands the vapp_template into a TMP dir' do
54
- expect(deployer).to receive(:system).with("cd #{tmpdir} && tar xfv '#{expanded_vapp_template_path}'")
55
-
56
- expect { deployer.deploy(vapp_template_path, vapp_config) }.to raise_error
57
- end
58
-
59
- context 'when the template can be expanded' do
60
- let(:client) { instance_double(VCloudSdk::Client) }
61
- let(:catalog) { instance_double(VCloudSdk::Catalog) }
62
- let(:network_config) { instance_double(VCloudSdk::NetworkConfig) }
63
- let(:vapp) { instance_double(VCloudSdk::VApp) }
64
- let(:vm) { instance_double(VCloudSdk::VM) }
65
-
66
- before do
67
- allow(deployer).to receive(:system).with("cd #{tmpdir} && tar xfv '#{expanded_vapp_template_path}'").
68
- and_return(true)
69
- end
70
-
71
- context 'when the vApp can be deployed' do
72
- let(:expected_properties) do
73
- [
74
- {
75
- 'type' => 'string',
76
- 'key' => 'gateway',
77
- 'value' => vapp_config.fetch(:gateway),
78
- 'password' => 'false',
79
- 'userConfigurable' => 'true',
80
- 'Label' => 'Default Gateway',
81
- 'Description' => 'The default gateway address for the VM network. Leave blank if DHCP is desired.'
82
- },
83
- {
84
- 'type' => 'string',
85
- 'key' => 'DNS',
86
- 'value' => vapp_config.fetch(:dns),
87
- 'password' => 'false',
88
- 'userConfigurable' => 'true',
89
- 'Label' => 'DNS',
90
- 'Description' => 'The domain name servers for the VM (comma separated). Leave blank if DHCP is desired.',
91
- },
92
- {
93
- 'type' => 'string',
94
- 'key' => 'ntp_servers',
95
- 'value' => vapp_config.fetch(:ntp),
96
- 'password' => 'false',
97
- 'userConfigurable' => 'true',
98
- 'Label' => 'NTP Servers',
99
- 'Description' => 'Comma-delimited list of NTP servers'
100
- },
101
- {
102
- 'type' => 'string',
103
- 'key' => 'admin_password',
104
- 'value' => 'tempest',
105
- 'password' => 'true',
106
- 'userConfigurable' => 'true',
107
- 'Label' => 'Admin Password',
108
- 'Description' => 'This password is used to SSH into the VM. The username is "tempest".',
109
- },
110
- {
111
- 'type' => 'string',
112
- 'key' => 'ip0',
113
- 'value' => vapp_config.fetch(:ip),
114
- 'password' => 'false',
115
- 'userConfigurable' => 'true',
116
- 'Label' => 'IP Address',
117
- 'Description' => 'The IP address for the VM. Leave blank if DHCP is desired.',
118
- },
119
- {
120
- 'type' => 'string',
121
- 'key' => 'netmask0',
122
- 'value' => vapp_config.fetch(:netmask),
123
- 'password' => 'false',
124
- 'userConfigurable' => 'true',
125
- 'Label' => 'Netmask',
126
- 'Description' => 'The netmask for the VM network. Leave blank if DHCP is desired.'
127
- }
128
- ]
129
- end
130
-
131
- before do
132
- allow(VCloudSdk::Client).to receive(:new).and_return(client)
133
- allow(client).to receive(:catalog_exists?)
134
- allow(client).to receive(:delete_catalog_by_name)
135
-
136
- allow(client).to receive(:create_catalog).and_return(catalog)
137
- allow(catalog).to receive(:upload_vapp_template)
138
- allow(catalog).to receive(:instantiate_vapp_template).and_return(vapp)
139
-
140
- allow(VCloudSdk::NetworkConfig).to receive(:new).and_return(network_config)
141
-
142
- allow(vapp).to receive(:find_vm_by_name).and_return(vm)
143
- allow(vm).to receive(:product_section_properties=)
144
- allow(vapp).to receive(:power_on)
145
- end
146
-
147
- it 'uses VCloudSdk::Client' do
148
- expect(VCloudSdk::Client).to receive(:new).with(
149
- login_info.fetch(:url),
150
- [login_info.fetch(:user), login_info.fetch(:organization)].join('@'),
151
- login_info.fetch(:password),
152
- {},
153
- logger,
154
- ).and_return(client)
155
-
156
- deployer.deploy(vapp_template_path, vapp_config)
157
- end
158
-
159
- describe 'catalog deletion' do
160
- before do
161
- allow(client).to receive(:catalog_exists?).and_return(catalog_exists)
162
- end
163
-
164
- context 'when the catalog exists' do
165
- let(:catalog_exists) { true }
166
-
167
- it 'deletes the catalog' do
168
- expect(client).to receive(:delete_catalog_by_name).with(location.fetch(:catalog))
169
-
170
- deployer.deploy(vapp_template_path, vapp_config)
171
- end
172
- end
173
-
174
- context 'when the catalog does not exist' do
175
- let(:catalog_exists) { false }
176
-
177
- it 'does not delete the catalog' do
178
- expect(client).not_to receive(:delete_catalog_by_name).with(location.fetch(:catalog))
179
-
180
- deployer.deploy(vapp_template_path, vapp_config)
181
- end
182
- end
183
- end
184
-
185
- it 'creates the catalog' do
186
- expect(client).to receive(:create_catalog).with(location.fetch(:catalog)).and_return(catalog)
187
-
188
- deployer.deploy(vapp_template_path, vapp_config)
189
- end
190
-
191
- it 'uploads the vApp template' do
192
- expect(catalog).to receive(:upload_vapp_template).with(
193
- location.fetch(:vdc),
194
- vapp_config.fetch(:name),
195
- tmpdir,
196
- ).and_return(catalog)
197
-
198
- deployer.deploy(vapp_template_path, vapp_config)
199
- end
200
-
201
- it 'creates a VCloudSdk::NetworkConfig' do
202
- expect(VCloudSdk::NetworkConfig).to receive(:new).with(
203
- location.fetch(:network),
204
- 'Network 1',
205
- ).and_return(network_config)
206
-
207
- deployer.deploy(vapp_template_path, vapp_config)
208
- end
209
-
210
- it 'instantiates the vApp template' do
211
- expect(catalog).to receive(:instantiate_vapp_template).with(
212
- vapp_config.fetch(:name),
213
- location.fetch(:vdc),
214
- vapp_config.fetch(:name),
215
- nil,
216
- nil,
217
- network_config
218
- ).and_return(vapp)
219
-
220
- deployer.deploy(vapp_template_path, vapp_config)
221
- end
222
-
223
- it 'sets the product section properties' do
224
- expect(vm).to receive(:product_section_properties=).with(expected_properties)
225
-
226
- deployer.deploy(vapp_template_path, vapp_config)
227
- end
228
-
229
- it 'powers on the vApp' do
230
- expect(vapp).to receive(:power_on)
231
-
232
- deployer.deploy(vapp_template_path, vapp_config)
233
- end
234
-
235
- it 'removes the expanded vApp template' do
236
- expect(FileUtils).to receive(:remove_entry_secure).with(tmpdir, force: true)
237
-
238
- deployer.deploy(vapp_template_path, vapp_config)
239
- end
240
- end
241
-
242
- context 'when the vApp can NOT be deployed' do
243
- it 'removes the expanded vApp template' do
244
- expect(FileUtils).to receive(:remove_entry_secure).with(tmpdir, force: true)
245
-
246
- expect { deployer.deploy(vapp_template_path, vapp_config) }.to raise_error
247
- end
248
- end
249
- end
250
-
251
- context 'when the template can NOT be expanded' do
252
- let(:tar_expand_cmd) { "cd #{tmpdir} && tar xfv '#{expanded_vapp_template_path}'" }
253
- before do
254
- allow(deployer).to receive(:system).with(tar_expand_cmd).and_return(false)
255
- end
256
-
257
- it 'raises an error' do
258
- expect { deployer.deploy(vapp_template_path, vapp_config) }.to raise_error("Error executing: #{tar_expand_cmd.inspect}")
259
- end
260
-
261
- it 'removes the expanded vApp template' do
262
- expect(FileUtils).to receive(:remove_entry_secure).with(tmpdir, force: true)
263
-
264
- expect { deployer.deploy(vapp_template_path, vapp_config) }.to raise_error
265
- end
266
- end
267
- end
268
-
269
- context 'when a host exists at the specified IP' do
270
- before do
271
- allow(deployer).to receive(:system).with("ping -c 5 #{vapp_config.fetch(:ip)}").and_return(true)
272
- end
273
-
274
- it 'raises an error' do
275
- expect { deployer.deploy(vapp_template_path, vapp_config) }.to raise_error("VM exists at #{vapp_config.fetch(:ip)}")
276
- end
277
-
278
- it 'removes the expanded vApp template' do
279
- expect(FileUtils).to receive(:remove_entry_secure).with(tmpdir, force: true)
280
-
281
- expect { deployer.deploy(vapp_template_path, vapp_config) }.to raise_error
282
- end
283
- end
284
- end
285
- end
286
- end
287
- end