vm_shepherd 0.0.1 → 0.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.
@@ -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