kitchen-vra 3.3.4 → 3.4.1
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/.gitignore +0 -1
- data/.release-please-manifest.json +1 -1
- data/.rspec +2 -0
- data/.rubocop.yml +0 -1
- data/.yardopts +1 -1
- data/CHANGELOG.md +20 -0
- data/Gemfile +0 -4
- data/kitchen-vra.gemspec +1 -2
- data/lib/kitchen/driver/vra.rb +79 -16
- data/lib/kitchen/driver/vra_version.rb +1 -1
- data/spec/spec_helper.rb +43 -5
- data/spec/support/vra_api.rb +120 -0
- data/spec/vra_spec.rb +504 -180
- metadata +13 -25
- /data/{LICENSE.txt → LICENSE} +0 -0
data/spec/vra_spec.rb
CHANGED
|
@@ -18,43 +18,35 @@
|
|
|
18
18
|
# limitations under the License.
|
|
19
19
|
#
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
require "tmpdir"
|
|
23
|
-
require "fileutils"
|
|
24
|
-
require "kitchen/driver/vra"
|
|
25
|
-
require "kitchen/provisioner/dummy"
|
|
26
|
-
require "kitchen/transport/dummy"
|
|
27
|
-
require "kitchen/verifier/dummy"
|
|
28
|
-
|
|
29
|
-
describe Kitchen::Driver::Vra do
|
|
21
|
+
RSpec.describe Kitchen::Driver::Vra do
|
|
30
22
|
let(:logged_output) { StringIO.new }
|
|
31
23
|
let(:logger) { Logger.new(logged_output) }
|
|
32
24
|
let(:platform) { Kitchen::Platform.new(name: "fake_platform") }
|
|
33
25
|
let(:transport) { Kitchen::Transport::Dummy.new }
|
|
34
|
-
let(:driver) {
|
|
26
|
+
let(:driver) { described_class.new(config) }
|
|
35
27
|
|
|
36
28
|
let(:config) do
|
|
37
29
|
{
|
|
38
|
-
base_url:
|
|
39
|
-
username:
|
|
40
|
-
password:
|
|
41
|
-
domain:
|
|
42
|
-
project_id:
|
|
43
|
-
image_mapping:
|
|
44
|
-
flavor_mapping:
|
|
45
|
-
verify_ssl:
|
|
46
|
-
use_dns:
|
|
30
|
+
base_url: "https://vra.corp.local",
|
|
31
|
+
username: "myuser",
|
|
32
|
+
password: "mypassword",
|
|
33
|
+
domain: "mytenant.corp.com",
|
|
34
|
+
project_id: "6ba69375-79d5-42c3-a099-7d32739f71a7",
|
|
35
|
+
image_mapping: "VRA-nc-lnx-ce8.4-Docker",
|
|
36
|
+
flavor_mapping: "Small",
|
|
37
|
+
verify_ssl: true,
|
|
38
|
+
use_dns: false,
|
|
47
39
|
deployment_name: "test-instance",
|
|
48
|
-
catalog_id:
|
|
40
|
+
catalog_id: "1536b95d-68fe-3c68-9417-46ed24bee3ef",
|
|
49
41
|
}
|
|
50
42
|
end
|
|
51
43
|
|
|
52
44
|
let(:instance) do
|
|
53
45
|
instance_double(Kitchen::Instance,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
46
|
+
logger: logger,
|
|
47
|
+
transport: transport,
|
|
48
|
+
platform: platform,
|
|
49
|
+
to_str: "instance_str")
|
|
58
50
|
end
|
|
59
51
|
|
|
60
52
|
before do
|
|
@@ -71,6 +63,54 @@ describe Kitchen::Driver::Vra do
|
|
|
71
63
|
end
|
|
72
64
|
end
|
|
73
65
|
|
|
66
|
+
describe "configuration defaults" do
|
|
67
|
+
let(:config) do
|
|
68
|
+
{
|
|
69
|
+
base_url: "https://vra.corp.local",
|
|
70
|
+
domain: "mytenant.corp.com",
|
|
71
|
+
project_id: "6ba69375-79d5-42c3-a099-7d32739f71a7",
|
|
72
|
+
image_mapping: "VRA-nc-lnx-ce8.4-Docker",
|
|
73
|
+
flavor_mapping: "Small",
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "names the deployment after the platform under test" do
|
|
78
|
+
expect(driver[:deployment_name]).to eq("fake_platform")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "connects by IP address rather than by name" do
|
|
82
|
+
expect(driver[:use_dns]).to be false
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "verifies the appliance certificate" do
|
|
86
|
+
expect(driver[:verify_ssl]).to be true
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
describe "the private key" do
|
|
90
|
+
let(:id_rsa) { File.expand_path("~/.ssh/id_rsa") }
|
|
91
|
+
let(:id_dsa) { File.expand_path("~/.ssh/id_dsa") }
|
|
92
|
+
|
|
93
|
+
before { allow(File).to receive(:exist?).and_return(false) }
|
|
94
|
+
|
|
95
|
+
it "prefers an RSA key" do
|
|
96
|
+
allow(File).to receive(:exist?).with(id_rsa).and_return(true)
|
|
97
|
+
allow(File).to receive(:exist?).with(id_dsa).and_return(true)
|
|
98
|
+
|
|
99
|
+
expect(driver[:private_key_path]).to eq(id_rsa)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it "falls back to a DSA key" do
|
|
103
|
+
allow(File).to receive(:exist?).with(id_dsa).and_return(true)
|
|
104
|
+
|
|
105
|
+
expect(driver[:private_key_path]).to eq(id_dsa)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it "is left unset when neither key exists" do
|
|
109
|
+
expect(driver[:private_key_path]).to be_nil
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
74
114
|
describe "#create" do
|
|
75
115
|
context "when the server is already created" do
|
|
76
116
|
let(:state) { { deployment_id: "48959518-6a0d-46e1-8415-3749696b65f4" } }
|
|
@@ -81,14 +121,8 @@ describe Kitchen::Driver::Vra do
|
|
|
81
121
|
end
|
|
82
122
|
end
|
|
83
123
|
|
|
84
|
-
let(:state)
|
|
85
|
-
let(:resource)
|
|
86
|
-
double("server1",
|
|
87
|
-
deployment_id: "e8706351-cf4c-4c12-acb7-c90cc683b22c",
|
|
88
|
-
name: "test-server",
|
|
89
|
-
ip_address: "1.2.3.4",
|
|
90
|
-
vm?: true)
|
|
91
|
-
end
|
|
124
|
+
let(:state) { {} }
|
|
125
|
+
let(:resource) { vra_resource(deployment_id: "e8706351-cf4c-4c12-acb7-c90cc683b22c") }
|
|
92
126
|
|
|
93
127
|
before do
|
|
94
128
|
allow(driver).to receive(:request_server).and_return(resource)
|
|
@@ -115,22 +149,34 @@ describe Kitchen::Driver::Vra do
|
|
|
115
149
|
expect(driver).to receive(:wait_for_server)
|
|
116
150
|
driver.create(state)
|
|
117
151
|
end
|
|
152
|
+
|
|
153
|
+
context "when a private key is configured" do
|
|
154
|
+
let(:config) { super().merge(private_key_path: "/home/me/.ssh/id_vra") }
|
|
155
|
+
|
|
156
|
+
it "tells the transport which key to connect with" do
|
|
157
|
+
driver.create(state)
|
|
158
|
+
expect(state[:ssh_key]).to eq("/home/me/.ssh/id_vra")
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
context "when no private key was found" do
|
|
163
|
+
let(:config) { super().merge(private_key_path: nil) }
|
|
164
|
+
|
|
165
|
+
it "leaves the transport to work out how to connect" do
|
|
166
|
+
driver.create(state)
|
|
167
|
+
expect(state).not_to have_key(:ssh_key)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
118
170
|
end
|
|
119
171
|
|
|
120
172
|
describe "#hostname_for" do
|
|
121
|
-
let(:server)
|
|
122
|
-
double("server",
|
|
123
|
-
deployment_id: "test_id",
|
|
124
|
-
name: "test_hostname",
|
|
125
|
-
ip_address: "1.2.3.4",
|
|
126
|
-
vm?: true)
|
|
127
|
-
end
|
|
173
|
+
let(:server) { vra_resource(deployment_id: "test_id", name: "test_hostname") }
|
|
128
174
|
|
|
129
175
|
context "when use_dns is true and dns_suffix is defined" do
|
|
130
176
|
let(:config) do
|
|
131
177
|
{
|
|
132
|
-
use_dns:
|
|
133
|
-
dns_suffix:
|
|
178
|
+
use_dns: true,
|
|
179
|
+
dns_suffix: "my.com",
|
|
134
180
|
}
|
|
135
181
|
end
|
|
136
182
|
|
|
@@ -143,20 +189,23 @@ describe Kitchen::Driver::Vra do
|
|
|
143
189
|
let(:config) { { use_dns: true } }
|
|
144
190
|
|
|
145
191
|
it "raises an exception if the server name is nil" do
|
|
146
|
-
|
|
147
|
-
expect { driver.hostname_for(server) }.to raise_error(RuntimeError)
|
|
192
|
+
expect { driver.hostname_for(vra_resource(name: nil)) }.to raise_error(RuntimeError)
|
|
148
193
|
end
|
|
149
194
|
|
|
150
195
|
it "returns the server name" do
|
|
151
196
|
expect(driver.hostname_for(server)).to eq("test_hostname")
|
|
152
197
|
end
|
|
198
|
+
|
|
199
|
+
it "does not go looking for an IP address" do
|
|
200
|
+
expect(server).not_to receive(:ip_address)
|
|
201
|
+
driver.hostname_for(server)
|
|
202
|
+
end
|
|
153
203
|
end
|
|
154
204
|
|
|
155
205
|
context "when use_dns is false" do
|
|
156
206
|
it "falls back to the server name if no IP address exists" do
|
|
157
|
-
allow(server).to receive(:ip_address).and_return(nil)
|
|
158
207
|
expect(driver).to receive(:warn)
|
|
159
|
-
expect(driver.hostname_for(
|
|
208
|
+
expect(driver.hostname_for(vra_resource(ip_address: nil))).to eq("test-server")
|
|
160
209
|
end
|
|
161
210
|
|
|
162
211
|
it "returns the IP address if it exists" do
|
|
@@ -166,47 +215,32 @@ describe Kitchen::Driver::Vra do
|
|
|
166
215
|
end
|
|
167
216
|
|
|
168
217
|
describe "#request_server" do
|
|
169
|
-
let(:
|
|
170
|
-
let(:catalog_request)
|
|
171
|
-
let(:resource1)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
name: "server1",
|
|
175
|
-
ip_address: "1.2.3.4",
|
|
176
|
-
vm?: true)
|
|
177
|
-
end
|
|
178
|
-
let(:resource2) do
|
|
179
|
-
double("server2",
|
|
180
|
-
id: "9e2364cf-7af4-4b85-93fd-1f03ee2ac865",
|
|
181
|
-
name: "server2",
|
|
182
|
-
ip_address: "4.3.2.1",
|
|
183
|
-
vm?: true)
|
|
184
|
-
end
|
|
185
|
-
let(:resources) { [resource1] }
|
|
218
|
+
let(:deployment) { vra_deployment(id: "74e26af9-2d2f-4889-a472-95dbcedb70b8", resources: resources) }
|
|
219
|
+
let(:catalog_request) { vra_deployment_request(submit: deployment) }
|
|
220
|
+
let(:resource1) { vra_resource(id: "e8706351-cf4c-4c12-acb7-c90cc683b22c", name: "server1") }
|
|
221
|
+
let(:resource2) { vra_resource(id: "9e2364cf-7af4-4b85-93fd-1f03ee2ac865", name: "server2") }
|
|
222
|
+
let(:resources) { [resource1] }
|
|
186
223
|
|
|
187
224
|
before do
|
|
188
225
|
allow(driver).to receive(:catalog_request).and_return(catalog_request)
|
|
189
|
-
allow(
|
|
190
|
-
allow(submitted_request).to receive(:id).and_return("74e26af9-2d2f-4889-a472-95dbcedb70b8")
|
|
191
|
-
allow(submitted_request).to receive(:resources).and_return(resources)
|
|
192
|
-
allow(submitted_request).to receive(:failed?).and_return(false)
|
|
193
|
-
allow(driver).to receive(:wait_for_request).with(submitted_request)
|
|
226
|
+
allow(driver).to receive(:wait_for_request).with(deployment)
|
|
194
227
|
end
|
|
195
228
|
|
|
196
229
|
it "submits a catalog request" do
|
|
197
|
-
expect(
|
|
230
|
+
expect(catalog_request).to receive(:submit).and_return(deployment)
|
|
198
231
|
driver.request_server
|
|
199
232
|
end
|
|
200
233
|
|
|
201
234
|
it "waits for the request to complete" do
|
|
202
|
-
expect(driver).to receive(:wait_for_request).with(
|
|
235
|
+
expect(driver).to receive(:wait_for_request).with(deployment)
|
|
203
236
|
driver.request_server
|
|
204
237
|
end
|
|
205
238
|
|
|
206
239
|
it "raises an exception if the request failed" do
|
|
207
|
-
allow(
|
|
208
|
-
allow(
|
|
209
|
-
|
|
240
|
+
allow(deployment).to receive(:failed?).and_return(true)
|
|
241
|
+
allow(deployment).to receive(:completion_details).and_return("it failed")
|
|
242
|
+
|
|
243
|
+
expect { driver.request_server }.to raise_error(RuntimeError, /it failed/)
|
|
210
244
|
end
|
|
211
245
|
|
|
212
246
|
describe "getting the server from the request" do
|
|
@@ -217,16 +251,26 @@ describe Kitchen::Driver::Vra do
|
|
|
217
251
|
end
|
|
218
252
|
|
|
219
253
|
context "when multiple servers are returned" do
|
|
254
|
+
let(:resources) { [resource1, resource2] }
|
|
255
|
+
|
|
220
256
|
it "raises an exception" do
|
|
221
|
-
|
|
222
|
-
expect { driver.request_server }.to raise_error(RuntimeError)
|
|
257
|
+
expect { driver.request_server }.to raise_error(RuntimeError, /more than one server/)
|
|
223
258
|
end
|
|
224
259
|
end
|
|
225
260
|
|
|
226
261
|
context "when no servers are returned" do
|
|
262
|
+
let(:resources) { [] }
|
|
263
|
+
|
|
227
264
|
it "raises an exception" do
|
|
228
|
-
|
|
229
|
-
|
|
265
|
+
expect { driver.request_server }.to raise_error(RuntimeError, /did not create any servers/)
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
context "when the deployment also contains things that are not VMs" do
|
|
270
|
+
let(:resources) { [vra_resource(id: "net-1", vm?: false), resource1] }
|
|
271
|
+
|
|
272
|
+
it "picks out the one VM" do
|
|
273
|
+
expect(driver.request_server).to eq(resource1)
|
|
230
274
|
end
|
|
231
275
|
end
|
|
232
276
|
end
|
|
@@ -234,18 +278,25 @@ describe Kitchen::Driver::Vra do
|
|
|
234
278
|
it "returns the the single server resource object" do
|
|
235
279
|
expect(driver.request_server).to eq(resource1)
|
|
236
280
|
end
|
|
281
|
+
|
|
282
|
+
context "when unique_name is set" do
|
|
283
|
+
let(:config) { super().merge(unique_name: true) }
|
|
284
|
+
|
|
285
|
+
it "reports the name vRA will give the deployment" do
|
|
286
|
+
allow(driver).to receive(:info)
|
|
287
|
+
|
|
288
|
+
driver.request_server
|
|
289
|
+
|
|
290
|
+
expect(driver).to have_received(:info)
|
|
291
|
+
.with("Deployment name is deployment_74e26af9-2d2f-4889-a472-95dbcedb70b8")
|
|
292
|
+
end
|
|
293
|
+
end
|
|
237
294
|
end
|
|
238
295
|
|
|
239
296
|
describe "#wait_for_server" do
|
|
240
297
|
let(:connection) { instance.transport.connection(state) }
|
|
241
298
|
let(:state) { {} }
|
|
242
|
-
let(:resource1)
|
|
243
|
-
double("server1",
|
|
244
|
-
id: "test_id",
|
|
245
|
-
name: "server1",
|
|
246
|
-
ip_address: "1.2.3.4",
|
|
247
|
-
vm?: true)
|
|
248
|
-
end
|
|
299
|
+
let(:resource1) { vra_resource(id: "test_id", name: "server1") }
|
|
249
300
|
|
|
250
301
|
before do
|
|
251
302
|
allow(transport).to receive(:connection).and_return(connection)
|
|
@@ -330,45 +381,91 @@ describe Kitchen::Driver::Vra do
|
|
|
330
381
|
end
|
|
331
382
|
end
|
|
332
383
|
|
|
384
|
+
describe "#status" do
|
|
385
|
+
let(:deployments) { instance_double(Vra::Deployments) }
|
|
386
|
+
let(:client) { vra_client(deployments: deployments) }
|
|
387
|
+
|
|
388
|
+
before { allow(driver).to receive(:vra_client).and_return(client) }
|
|
389
|
+
|
|
390
|
+
it "reports an unknown status when state names no deployment" do
|
|
391
|
+
expect(driver.status({})).to include(live: nil, state: "unknown")
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
it "reports an unknown status when vRA has never heard of the deployment" do
|
|
395
|
+
allow(deployments).to receive(:by_id).with("gone")
|
|
396
|
+
.and_raise(Vra::Exception::NotFound.new("nope"))
|
|
397
|
+
|
|
398
|
+
expect(driver.status(deployment_id: "gone")).to include(state: "unknown")
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
it "reports a successfully created deployment as live" do
|
|
402
|
+
allow(deployments).to receive(:by_id).with("dep-1").and_return(vra_deployment)
|
|
403
|
+
|
|
404
|
+
expect(driver.status(deployment_id: "dep-1")).to include(
|
|
405
|
+
live: true, state: "CREATE_SUCCESSFUL", source: "driver",
|
|
406
|
+
resource_id: "dep-1"
|
|
407
|
+
)
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
it "stamps when the check happened" do
|
|
411
|
+
allow(deployments).to receive(:by_id).and_return(vra_deployment)
|
|
412
|
+
|
|
413
|
+
expect(driver.status(deployment_id: "dep-1")[:checked_at])
|
|
414
|
+
.to match(/\A\d{4}-\d{2}-\d{2}T/)
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
it "reports a deployment still building as not live" do
|
|
418
|
+
allow(deployments).to receive(:by_id).and_return(vra_deployment(status: "CREATE_INPROGRESS"))
|
|
419
|
+
|
|
420
|
+
expect(driver.status(deployment_id: "dep-1"))
|
|
421
|
+
.to include(live: false, state: "CREATE_INPROGRESS")
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
it "names a failed deployment rather than reporting a bare false" do
|
|
425
|
+
allow(deployments).to receive(:by_id).and_return(vra_deployment(status: "CREATE_FAILED", failed?: true))
|
|
426
|
+
|
|
427
|
+
expect(driver.status(deployment_id: "dep-1"))
|
|
428
|
+
.to include(live: false, state: "CREATE_FAILED")
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
it "reports an unknown status when vRA cannot be reached" do
|
|
432
|
+
allow(deployments).to receive(:by_id).and_raise(StandardError.new("boom"))
|
|
433
|
+
|
|
434
|
+
expect(driver.status(deployment_id: "dep-1")).to include(state: "unknown")
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
|
|
333
438
|
describe "#destroy" do
|
|
334
|
-
let(:deployment_id)
|
|
439
|
+
let(:deployment_id) { "8c1a833a-5844-4100-b58c-9cab3543c958" }
|
|
335
440
|
let(:state) { { deployment_id: deployment_id } }
|
|
336
|
-
let(:
|
|
337
|
-
let(:
|
|
338
|
-
let(:destroy_request) {
|
|
339
|
-
let(:
|
|
340
|
-
double("server1",
|
|
341
|
-
deployment_id: deployment_id,
|
|
342
|
-
name: "server1",
|
|
343
|
-
ip_address: "5.6.7.8",
|
|
344
|
-
vm?: true)
|
|
345
|
-
end
|
|
441
|
+
let(:deployments) { instance_double(Vra::Deployments) }
|
|
442
|
+
let(:client) { vra_client(deployments: deployments) }
|
|
443
|
+
let(:destroy_request) { vra_request(id: "6da65982-7c33-4e6e-b346-fdf4bcbf01ab") }
|
|
444
|
+
let(:deployment) { vra_deployment(id: deployment_id, destroy: destroy_request) }
|
|
346
445
|
|
|
347
446
|
before do
|
|
348
|
-
allow(driver).to receive(:vra_client).and_return(
|
|
447
|
+
allow(driver).to receive(:vra_client).and_return(client)
|
|
349
448
|
allow(driver).to receive(:wait_for_request).with(destroy_request)
|
|
350
|
-
allow(
|
|
351
|
-
allow(deployments).to receive(:by_id).and_return(resource)
|
|
352
|
-
allow(resource).to receive(:destroy).and_return(destroy_request)
|
|
353
|
-
allow(destroy_request).to receive(:id).and_return("6da65982-7c33-4e6e-b346-fdf4bcbf01ab")
|
|
449
|
+
allow(deployments).to receive(:by_id).and_return(deployment)
|
|
354
450
|
end
|
|
355
451
|
|
|
356
452
|
context "when the resource is not created" do
|
|
357
453
|
let(:state) { {} }
|
|
454
|
+
|
|
358
455
|
it "does not look up the resource if no resource ID exists" do
|
|
359
|
-
expect(
|
|
456
|
+
expect(deployments).not_to receive(:by_id)
|
|
360
457
|
driver.destroy(state)
|
|
361
458
|
end
|
|
362
459
|
end
|
|
363
460
|
|
|
364
461
|
it "looks up the resource record" do
|
|
365
|
-
expect(
|
|
462
|
+
expect(deployments).to receive(:by_id).with(deployment_id).and_return(deployment)
|
|
366
463
|
driver.destroy(state)
|
|
367
464
|
end
|
|
368
465
|
|
|
369
466
|
context "when the resource record cannot be found" do
|
|
370
467
|
it "does not raise an exception" do
|
|
371
|
-
allow(
|
|
468
|
+
allow(deployments).to receive(:by_id).with(deployment_id).and_raise(Vra::Exception::NotFound)
|
|
372
469
|
expect { driver.destroy(state) }.not_to raise_error
|
|
373
470
|
end
|
|
374
471
|
end
|
|
@@ -376,13 +473,13 @@ describe Kitchen::Driver::Vra do
|
|
|
376
473
|
describe "creating the destroy request" do
|
|
377
474
|
context "when the destroy method or server is not found" do
|
|
378
475
|
it "does not raise an exception" do
|
|
379
|
-
allow(
|
|
476
|
+
allow(deployment).to receive(:destroy).and_raise(Vra::Exception::NotFound)
|
|
380
477
|
expect { driver.destroy(state) }.not_to raise_error
|
|
381
478
|
end
|
|
382
479
|
end
|
|
383
480
|
|
|
384
481
|
it "calls #destroy on the server" do
|
|
385
|
-
expect(
|
|
482
|
+
expect(deployment).to receive(:destroy).and_return(destroy_request)
|
|
386
483
|
driver.destroy(state)
|
|
387
484
|
end
|
|
388
485
|
end
|
|
@@ -391,81 +488,201 @@ describe Kitchen::Driver::Vra do
|
|
|
391
488
|
expect(driver).to receive(:wait_for_request).with(destroy_request)
|
|
392
489
|
driver.destroy(state)
|
|
393
490
|
end
|
|
394
|
-
end
|
|
395
491
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
492
|
+
describe "the cached credentials" do
|
|
493
|
+
let(:cache_dir) { Dir.mktmpdir("kitchen-vra-spec") }
|
|
494
|
+
|
|
495
|
+
around { |example| Dir.chdir(cache_dir) { example.run } }
|
|
496
|
+
after { FileUtils.remove_entry(cache_dir) }
|
|
497
|
+
|
|
498
|
+
it "removes the cache file left behind by a previous run" do
|
|
499
|
+
FileUtils.mkdir_p(".kitchen")
|
|
500
|
+
File.write(".kitchen/cached_vra", "cached")
|
|
501
|
+
|
|
502
|
+
driver.destroy(state)
|
|
503
|
+
|
|
504
|
+
expect(File.exist?(".kitchen/cached_vra")).to be false
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
it "does not claim to have removed a cache file that was never written" do
|
|
508
|
+
expect(driver).not_to receive(:info).with(/Removed.*cached/i)
|
|
509
|
+
|
|
510
|
+
driver.destroy(state)
|
|
406
511
|
end
|
|
407
512
|
end
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
describe "#catalog_request" do
|
|
516
|
+
let(:deployment_request) { vra_deployment_request }
|
|
517
|
+
let(:catalog) { instance_double(Vra::Catalog, request: deployment_request) }
|
|
518
|
+
let(:client) { vra_client(catalog: catalog) }
|
|
519
|
+
|
|
520
|
+
before { allow(driver).to receive(:vra_client).and_return(client) }
|
|
408
521
|
|
|
409
522
|
it "creates a catalog_request" do
|
|
410
|
-
expect(
|
|
523
|
+
expect(catalog).to receive(:request).and_return(deployment_request)
|
|
411
524
|
driver.catalog_request
|
|
412
525
|
end
|
|
413
526
|
|
|
414
|
-
|
|
527
|
+
it "asks for the configured image, flavor, project and version" do
|
|
528
|
+
expect(catalog).to receive(:request).with(
|
|
529
|
+
"1536b95d-68fe-3c68-9417-46ed24bee3ef",
|
|
530
|
+
hash_including(
|
|
531
|
+
image_mapping: "VRA-nc-lnx-ce8.4-Docker",
|
|
532
|
+
flavor_mapping: "Small",
|
|
533
|
+
project_id: "6ba69375-79d5-42c3-a099-7d32739f71a7",
|
|
534
|
+
version: nil
|
|
535
|
+
)
|
|
536
|
+
).and_return(deployment_request)
|
|
537
|
+
|
|
538
|
+
driver.catalog_request
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
it "names the deployment" do
|
|
542
|
+
expect(catalog).to receive(:request)
|
|
543
|
+
.with(anything, hash_including(name: "test-instance"))
|
|
544
|
+
.and_return(deployment_request)
|
|
545
|
+
|
|
546
|
+
driver.catalog_request
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
context "when unique_name is set" do
|
|
550
|
+
let(:config) { super().merge(unique_name: true) }
|
|
551
|
+
|
|
552
|
+
it "leaves vRA to name the deployment after the request" do
|
|
553
|
+
expect(catalog).to receive(:request)
|
|
554
|
+
.with(anything, hash_excluding(:name))
|
|
555
|
+
.and_return(deployment_request)
|
|
556
|
+
|
|
557
|
+
driver.catalog_request
|
|
558
|
+
end
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
context "when the catalog item is named rather than identified" do
|
|
415
562
|
let(:config) do
|
|
416
|
-
{
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
563
|
+
super().reject { |key, _value| key == :catalog_id }
|
|
564
|
+
.merge(catalog_name: "Ubuntu Server")
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
it "looks the name up and requests the ID it resolves to" do
|
|
568
|
+
allow(catalog).to receive(:fetch_catalog_items).with("Ubuntu Server")
|
|
569
|
+
.and_return([vra_catalog_item(id: "cat-9")])
|
|
570
|
+
expect(catalog).to receive(:request).with("cat-9", anything).and_return(deployment_request)
|
|
571
|
+
|
|
572
|
+
driver.catalog_request
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
context "when the name matches nothing in the catalog" do
|
|
576
|
+
before do
|
|
577
|
+
allow(catalog).to receive(:fetch_catalog_items).and_return([])
|
|
578
|
+
allow(driver).to receive(:error)
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
it "fails rather than requesting an unknown catalog item" do
|
|
582
|
+
expect { driver.catalog_request }
|
|
583
|
+
.to raise_error(Kitchen::InstanceFailure, /without a valid catalog/)
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
it "says which name it could not resolve" do
|
|
587
|
+
expect { driver.catalog_request }.to raise_error(Kitchen::InstanceFailure)
|
|
588
|
+
|
|
589
|
+
expect(driver).to have_received(:error).with(/Ubuntu Server/)
|
|
590
|
+
end
|
|
427
591
|
end
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
context "when neither a catalog ID nor a catalog name is given" do
|
|
595
|
+
let(:config) { super().reject { |key, _value| key == :catalog_id } }
|
|
428
596
|
|
|
597
|
+
it "fails before talking to vRA" do
|
|
598
|
+
expect(catalog).not_to receive(:request)
|
|
599
|
+
expect { driver.catalog_request }.to raise_error(Kitchen::InstanceFailure)
|
|
600
|
+
end
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
context "when option parameters are not supplied" do
|
|
429
604
|
it "does not attempt to set params on the catalog_request" do
|
|
430
|
-
expect(
|
|
605
|
+
expect(deployment_request).not_to receive(:set_parameters)
|
|
431
606
|
driver.catalog_request
|
|
432
607
|
end
|
|
433
608
|
end
|
|
434
609
|
|
|
435
610
|
context "when extra parameters are set" do
|
|
436
611
|
let(:config) do
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
project_id: "6ba69375-79d5-42c3-a099-7d32739f71a7",
|
|
444
|
-
image_mapping: "VRA-nc-lnx-ce8.4-Docker",
|
|
445
|
-
flavor_mapping: "Small",
|
|
446
|
-
catalog_id: "2kabc423-af89-56fc-990a-82fab34ed12",
|
|
447
|
-
extra_parameters: { "key1" => { type: "string", value: "value1" },
|
|
448
|
-
"key2" => { type: "integer", value: 123 } },
|
|
449
|
-
}
|
|
612
|
+
super().merge(
|
|
613
|
+
extra_parameters: {
|
|
614
|
+
"key1" => { type: "string", value: "value1" },
|
|
615
|
+
"key2" => { type: "integer", value: 123 },
|
|
616
|
+
}
|
|
617
|
+
)
|
|
450
618
|
end
|
|
451
619
|
|
|
452
620
|
it "sets extra parameters" do
|
|
453
|
-
expect(
|
|
454
|
-
expect(
|
|
621
|
+
expect(deployment_request).to receive(:set_parameters).with("key1", { type: "string", value: "value1" })
|
|
622
|
+
expect(deployment_request).to receive(:set_parameters).with("key2", { type: "integer", value: 123 })
|
|
455
623
|
driver.catalog_request
|
|
456
624
|
end
|
|
457
625
|
end
|
|
458
626
|
end
|
|
459
627
|
|
|
460
628
|
describe "#vra_client" do
|
|
629
|
+
let(:client) { vra_client }
|
|
630
|
+
|
|
461
631
|
it "sets up a client object" do
|
|
462
632
|
expect(Vra::Client).to receive(:new).with(base_url: config[:base_url],
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
633
|
+
username: config[:username],
|
|
634
|
+
password: config[:password],
|
|
635
|
+
domain: config[:domain],
|
|
636
|
+
verify_ssl: config[:verify_ssl])
|
|
467
637
|
driver.vra_client
|
|
468
638
|
end
|
|
639
|
+
|
|
640
|
+
it "builds the client once and hands the same one back" do
|
|
641
|
+
expect(Vra::Client).to receive(:new).once.and_return(client)
|
|
642
|
+
|
|
643
|
+
expect(driver.vra_client).to eq(client)
|
|
644
|
+
expect(driver.vra_client).to eq(client)
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
context "when cache_credentials is enabled" do
|
|
648
|
+
let(:config) { super().merge(cache_credentials: true) }
|
|
649
|
+
|
|
650
|
+
before { allow(driver).to receive(:c_save) }
|
|
651
|
+
|
|
652
|
+
it "does not prompt for credentials it already has" do
|
|
653
|
+
allow(Vra::Client).to receive(:new).and_return(client)
|
|
654
|
+
expect(driver).not_to receive(:ask)
|
|
655
|
+
|
|
656
|
+
driver.vra_client
|
|
657
|
+
end
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
context "when the client cannot be built" do
|
|
661
|
+
before do
|
|
662
|
+
allow(driver).to receive(:warn)
|
|
663
|
+
allow(driver).to receive(:check_config)
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
it "re-prompts for the credentials and tries once more" do
|
|
667
|
+
attempts = 0
|
|
668
|
+
allow(Vra::Client).to receive(:new) do
|
|
669
|
+
attempts += 1
|
|
670
|
+
raise ArgumentError, "Username and password are required" if attempts == 1
|
|
671
|
+
|
|
672
|
+
client
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
expect(driver.vra_client).to eq(client)
|
|
676
|
+
expect(driver).to have_received(:check_config).with(true)
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
it "raises rather than handing back something that is not a client" do
|
|
680
|
+
allow(Vra::Client).to receive(:new)
|
|
681
|
+
.and_raise(ArgumentError, "Username and password are required")
|
|
682
|
+
|
|
683
|
+
expect { driver.vra_client }.to raise_error(ArgumentError)
|
|
684
|
+
end
|
|
685
|
+
end
|
|
469
686
|
end
|
|
470
687
|
|
|
471
688
|
describe "#wait_for_request" do
|
|
@@ -476,21 +693,42 @@ describe Kitchen::Driver::Vra do
|
|
|
476
693
|
|
|
477
694
|
context "when the requests completes normally, 3 loops" do
|
|
478
695
|
it "only refreshes the request 3 times" do
|
|
479
|
-
request =
|
|
480
|
-
allow(request).to receive(:
|
|
481
|
-
allow(request).to receive(:completed?).exactly(3).times.and_return(false, false, true)
|
|
482
|
-
expect(request).to receive(:refresh).exactly(3).times
|
|
696
|
+
request = vra_request(status: nil)
|
|
697
|
+
allow(request).to receive(:completed?).and_return(false, false, true)
|
|
483
698
|
|
|
484
699
|
driver.wait_for_request(request)
|
|
700
|
+
|
|
701
|
+
expect(request).to have_received(:refresh).exactly(3).times
|
|
485
702
|
end
|
|
486
703
|
end
|
|
487
704
|
|
|
488
705
|
context "when the request is completed on the first loop" do
|
|
489
706
|
it "only refreshes the request 1 time" do
|
|
490
|
-
request =
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
707
|
+
request = vra_request(status: nil)
|
|
708
|
+
|
|
709
|
+
driver.wait_for_request(request)
|
|
710
|
+
|
|
711
|
+
expect(request).to have_received(:refresh).once
|
|
712
|
+
end
|
|
713
|
+
end
|
|
714
|
+
|
|
715
|
+
it "reports a status once, not on every poll" do
|
|
716
|
+
request = vra_request(status: "IN_PROGRESS")
|
|
717
|
+
allow(request).to receive(:completed?).and_return(false, false, true)
|
|
718
|
+
allow(driver).to receive(:info)
|
|
719
|
+
|
|
720
|
+
driver.wait_for_request(request)
|
|
721
|
+
|
|
722
|
+
expect(driver).to have_received(:info).with("Current request status: IN_PROGRESS").once
|
|
723
|
+
end
|
|
724
|
+
|
|
725
|
+
context "with a slower refresh rate configured" do
|
|
726
|
+
let(:config) { super().merge(request_refresh_rate: 10) }
|
|
727
|
+
|
|
728
|
+
it "waits that long between polls" do
|
|
729
|
+
request = vra_request(status: nil)
|
|
730
|
+
allow(request).to receive(:completed?).and_return(false, true)
|
|
731
|
+
expect(driver).to receive(:sleep).with(10)
|
|
494
732
|
|
|
495
733
|
driver.wait_for_request(request)
|
|
496
734
|
end
|
|
@@ -498,16 +736,19 @@ describe Kitchen::Driver::Vra do
|
|
|
498
736
|
|
|
499
737
|
context "when the timeout is exceeded" do
|
|
500
738
|
it "prints a warning and exits" do
|
|
501
|
-
request = double("request")
|
|
502
739
|
allow(Timeout).to receive(:timeout).and_raise(Timeout::Error)
|
|
503
|
-
|
|
740
|
+
allow(driver).to receive(:error)
|
|
741
|
+
|
|
742
|
+
expect { driver.wait_for_request(vra_request) }.to raise_error(Timeout::Error)
|
|
743
|
+
expect(driver).to have_received(:error).with(/did not complete in 600 seconds/)
|
|
504
744
|
end
|
|
505
745
|
end
|
|
506
746
|
|
|
507
747
|
context "when a non-timeout exception is raised" do
|
|
508
748
|
it "raises the original exception" do
|
|
509
|
-
request =
|
|
749
|
+
request = vra_request
|
|
510
750
|
allow(request).to receive(:refresh).and_raise(RuntimeError)
|
|
751
|
+
|
|
511
752
|
expect { driver.wait_for_request(request) }.to raise_error(RuntimeError)
|
|
512
753
|
end
|
|
513
754
|
end
|
|
@@ -525,6 +766,13 @@ describe Kitchen::Driver::Vra do
|
|
|
525
766
|
|
|
526
767
|
after { FileUtils.remove_entry(cache_dir) }
|
|
527
768
|
|
|
769
|
+
# A second driver reading the cache the first one wrote, which is what the
|
|
770
|
+
# next `kitchen` run is.
|
|
771
|
+
def reader(overrides = {})
|
|
772
|
+
described_class.new(config.merge(overrides).reject { |k, _v| %i{username password}.include?(k) })
|
|
773
|
+
.tap { |driver| allow(driver).to receive(:instance).and_return(instance) }
|
|
774
|
+
end
|
|
775
|
+
|
|
528
776
|
describe "#c_save" do
|
|
529
777
|
it "creates the cache file" do
|
|
530
778
|
driver.c_save
|
|
@@ -544,18 +792,29 @@ describe Kitchen::Driver::Vra do
|
|
|
544
792
|
driver.c_save
|
|
545
793
|
expect(File.read(cache_file)).not_to include("mypassword")
|
|
546
794
|
end
|
|
795
|
+
|
|
796
|
+
it "does not write the username in the clear either" do
|
|
797
|
+
driver.c_save
|
|
798
|
+
expect(File.read(cache_file)).not_to include("myuser")
|
|
799
|
+
end
|
|
800
|
+
|
|
801
|
+
it "reports a cache it could not write rather than failing the run" do
|
|
802
|
+
allow(FileUtils).to receive(:mkdir_p).and_raise(Errno::EACCES)
|
|
803
|
+
expect(driver).to receive(:warn).with(/Unable to save credentials/)
|
|
804
|
+
|
|
805
|
+
expect { driver.c_save }.not_to raise_error
|
|
806
|
+
end
|
|
547
807
|
end
|
|
548
808
|
|
|
549
809
|
describe "#c_load" do
|
|
550
810
|
it "restores credentials written by #c_save" do
|
|
551
811
|
driver.c_save
|
|
552
812
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
reader.c_load
|
|
813
|
+
loader = reader
|
|
814
|
+
loader.c_load
|
|
556
815
|
|
|
557
|
-
expect(
|
|
558
|
-
expect(
|
|
816
|
+
expect(loader[:username]).to eq("myuser")
|
|
817
|
+
expect(loader[:password]).to eq("mypassword")
|
|
559
818
|
end
|
|
560
819
|
|
|
561
820
|
it "does nothing when no cache file exists" do
|
|
@@ -566,9 +825,7 @@ describe Kitchen::Driver::Vra do
|
|
|
566
825
|
it "refuses a cache written against a different base_url" do
|
|
567
826
|
driver.c_save
|
|
568
827
|
|
|
569
|
-
other =
|
|
570
|
-
.reject { |k, _v| %i{username password}.include?(k) })
|
|
571
|
-
allow(other).to receive(:instance).and_return(instance)
|
|
828
|
+
other = reader(base_url: "https://other.corp.local")
|
|
572
829
|
expect(other).to receive(:warn).with(/Failed to load cached credentials/)
|
|
573
830
|
other.c_load
|
|
574
831
|
|
|
@@ -580,30 +837,97 @@ describe Kitchen::Driver::Vra do
|
|
|
580
837
|
driver.c_save
|
|
581
838
|
File.write(cache_file, File.read(cache_file).succ)
|
|
582
839
|
|
|
583
|
-
loader =
|
|
584
|
-
allow(loader).to receive(:instance).and_return(instance)
|
|
840
|
+
loader = reader
|
|
585
841
|
expect(loader).to receive(:warn).with(/Failed to load cached credentials/)
|
|
586
842
|
loader.c_load
|
|
587
843
|
|
|
588
844
|
expect(loader[:username]).to be_nil
|
|
589
845
|
end
|
|
846
|
+
|
|
847
|
+
it "refuses a cache written in a layout it does not recognize" do
|
|
848
|
+
FileUtils.mkdir_p(File.dirname(cache_file))
|
|
849
|
+
File.write(cache_file, "v0:whatever")
|
|
850
|
+
|
|
851
|
+
loader = reader
|
|
852
|
+
expect(loader).to receive(:warn).with(/unrecognized cache format/)
|
|
853
|
+
loader.c_load
|
|
854
|
+
|
|
855
|
+
expect(loader[:username]).to be_nil
|
|
856
|
+
end
|
|
590
857
|
end
|
|
591
858
|
|
|
592
859
|
describe "#check_config" do
|
|
593
|
-
it "
|
|
594
|
-
driver.
|
|
860
|
+
it "keeps the credentials it was configured with" do
|
|
861
|
+
allow(driver).to receive(:ask)
|
|
595
862
|
|
|
596
|
-
|
|
597
|
-
allow(reader).to receive(:instance).and_return(instance)
|
|
598
|
-
allow(ENV).to receive(:[]).and_call_original
|
|
599
|
-
allow(ENV).to receive(:[]).with("VRA_USER_NAME").and_return(nil)
|
|
600
|
-
allow(ENV).to receive(:[]).with("VRA_USER_PASSWORD").and_return(nil)
|
|
601
|
-
expect(reader).not_to receive(:ask)
|
|
863
|
+
driver.check_config
|
|
602
864
|
|
|
603
|
-
|
|
865
|
+
expect(driver[:username]).to eq("myuser")
|
|
866
|
+
expect(driver[:password]).to eq("mypassword")
|
|
867
|
+
expect(driver).not_to have_received(:ask)
|
|
868
|
+
end
|
|
869
|
+
|
|
870
|
+
it "asks again when told to, even though it has credentials" do
|
|
871
|
+
allow(driver).to receive(:ask).and_return("newuser", "newpass")
|
|
872
|
+
|
|
873
|
+
driver.check_config(true)
|
|
874
|
+
|
|
875
|
+
expect(driver[:username]).to eq("newuser")
|
|
876
|
+
expect(driver[:password]).to eq("newpass")
|
|
877
|
+
end
|
|
878
|
+
|
|
879
|
+
it "does not write a cache file unless asked to" do
|
|
880
|
+
driver.check_config
|
|
881
|
+
|
|
882
|
+
expect(File.exist?(cache_file)).to be false
|
|
883
|
+
end
|
|
884
|
+
|
|
885
|
+
context "when cache_credentials is enabled" do
|
|
886
|
+
let(:config) { super().merge(cache_credentials: true) }
|
|
887
|
+
|
|
888
|
+
it "writes the credentials out for the next run" do
|
|
889
|
+
driver.check_config
|
|
890
|
+
|
|
891
|
+
expect(File.exist?(cache_file)).to be true
|
|
892
|
+
end
|
|
893
|
+
end
|
|
604
894
|
|
|
605
|
-
|
|
606
|
-
|
|
895
|
+
context "when the config names no credentials" do
|
|
896
|
+
it "reads them from the environment" do
|
|
897
|
+
stub_const("ENV", ENV.to_hash.merge("VRA_USER_NAME" => "envuser",
|
|
898
|
+
"VRA_USER_PASSWORD" => "envpass"))
|
|
899
|
+
loader = reader
|
|
900
|
+
expect(loader).not_to receive(:ask)
|
|
901
|
+
|
|
902
|
+
loader.check_config
|
|
903
|
+
|
|
904
|
+
expect(loader[:username]).to eq("envuser")
|
|
905
|
+
expect(loader[:password]).to eq("envpass")
|
|
906
|
+
end
|
|
907
|
+
|
|
908
|
+
it "uses the cache instead of prompting" do
|
|
909
|
+
driver.c_save
|
|
910
|
+
|
|
911
|
+
stub_const("ENV", ENV.to_hash.reject { |key, _v| key.start_with?("VRA_USER_") })
|
|
912
|
+
loader = reader
|
|
913
|
+
expect(loader).not_to receive(:ask)
|
|
914
|
+
|
|
915
|
+
loader.check_config
|
|
916
|
+
|
|
917
|
+
expect(loader[:username]).to eq("myuser")
|
|
918
|
+
expect(loader[:password]).to eq("mypassword")
|
|
919
|
+
end
|
|
920
|
+
|
|
921
|
+
it "prompts as a last resort" do
|
|
922
|
+
stub_const("ENV", ENV.to_hash.reject { |key, _v| key.start_with?("VRA_USER_") })
|
|
923
|
+
loader = reader
|
|
924
|
+
allow(loader).to receive(:ask).and_return("askeduser", "askedpass")
|
|
925
|
+
|
|
926
|
+
loader.check_config
|
|
927
|
+
|
|
928
|
+
expect(loader[:username]).to eq("askeduser")
|
|
929
|
+
expect(loader[:password]).to eq("askedpass")
|
|
930
|
+
end
|
|
607
931
|
end
|
|
608
932
|
end
|
|
609
933
|
end
|