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.
data/spec/vra_spec.rb CHANGED
@@ -18,43 +18,35 @@
18
18
  # limitations under the License.
19
19
  #
20
20
 
21
- require "spec_helper"
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) { Kitchen::Driver::Vra.new(config) }
26
+ let(:driver) { described_class.new(config) }
35
27
 
36
28
  let(:config) do
37
29
  {
38
- base_url: "https://vra.corp.local",
39
- username: "myuser",
40
- password: "mypassword",
41
- domain: "mytenant.corp.com",
42
- project_id: "6ba69375-79d5-42c3-a099-7d32739f71a7",
43
- image_mapping: "VRA-nc-lnx-ce8.4-Docker",
44
- flavor_mapping: "Small",
45
- verify_ssl: true,
46
- use_dns: false,
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: "1536b95d-68fe-3c68-9417-46ed24bee3ef",
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
- logger: logger,
55
- transport: transport,
56
- platform: platform,
57
- to_str: "instance_str")
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) do
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) do
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: true,
133
- dns_suffix: "my.com",
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
- allow(server).to receive(:name).and_return(nil)
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(server)).to eq("test_hostname")
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(:submitted_request) { double("submitted_request") }
170
- let(:catalog_request) { double("catalog_request") }
171
- let(:resource1) do
172
- double("server1",
173
- id: "e8706351-cf4c-4c12-acb7-c90cc683b22c",
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(catalog_request).to receive(:submit).and_return(submitted_request)
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(driver.catalog_request).to receive(:submit).and_return(submitted_request)
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(submitted_request)
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(submitted_request).to receive(:failed?).and_return(true)
208
- allow(submitted_request).to receive(:completion_details).and_return("it failed")
209
- expect { driver.request_server }.to raise_error(RuntimeError)
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
- allow(submitted_request).to receive(:resources).and_return([ resource1, resource2 ])
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
- allow(submitted_request).to receive(:resources).and_return([])
229
- expect { driver.request_server }.to raise_error(RuntimeError)
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) do
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) { "8c1a833a-5844-4100-b58c-9cab3543c958" }
439
+ let(:deployment_id) { "8c1a833a-5844-4100-b58c-9cab3543c958" }
335
440
  let(:state) { { deployment_id: deployment_id } }
336
- let(:vra_client) { double("vra_client") }
337
- let(:deployments) { double("resources") }
338
- let(:destroy_request) { double("destroy_request") }
339
- let(:resource) do
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(vra_client)
447
+ allow(driver).to receive(:vra_client).and_return(client)
349
448
  allow(driver).to receive(:wait_for_request).with(destroy_request)
350
- allow(vra_client).to receive(:deployments).and_return(deployments)
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(vra_client.deployments).not_to receive(:by_id)
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(vra_client.deployments).to receive(:by_id).with(deployment_id).and_return(resource)
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(vra_client.deployments).to receive(:by_id).with(deployment_id).and_raise(Vra::Exception::NotFound)
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(resource).to receive(:destroy).and_raise(Vra::Exception::NotFound)
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(resource).to receive(:destroy).and_return(destroy_request)
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
- describe "#catalog_request" do
397
- let(:catalog_request) { double("catalog_request") }
398
- let(:vra_client) { double("vra_client") }
399
- let(:catalog) { double("catalog") }
400
- before do
401
- allow(driver).to receive(:vra_client).and_return(vra_client)
402
- allow(vra_client).to receive(:catalog).and_return(catalog)
403
- allow(catalog).to receive(:request).and_return(catalog_request)
404
- %i{catalog_id= set_parameter}.each do |method|
405
- allow(catalog_request).to receive(method)
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(vra_client.catalog).to receive(:request).and_return(catalog_request)
523
+ expect(catalog).to receive(:request).and_return(deployment_request)
411
524
  driver.catalog_request
412
525
  end
413
526
 
414
- context "when option parameters are not supplied" do
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
- base_url: "https://vra.corp.local",
418
- username: "myuser",
419
- password: "mypassword",
420
- domain: "domain.corp.com",
421
- verify_ssl: true,
422
- project_id: "6ba69375-79d5-42c3-a099-7d32739f71a7",
423
- image_mapping: "VRA-nc-lnx-ce8.4-Docker",
424
- flavor_mapping: "Small",
425
- catalog_id: "2kabc423-af89-56fc-990a-82fab34ed12",
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(catalog_request).not_to receive(:set_parameters)
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
- base_url: "https://vra.corp.local",
439
- username: "myuser",
440
- password: "mypassword",
441
- domain: "mydomain.corp.com",
442
- verify_ssl: true,
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(catalog_request).to receive(:set_parameters).with("key1", { type: "string", value: "value1" })
454
- expect(catalog_request).to receive(:set_parameters).with("key2", { type: "integer", value: 123 })
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
- username: config[:username],
464
- password: config[:password],
465
- domain: config[:domain],
466
- verify_ssl: config[:verify_ssl])
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 = double("request")
480
- allow(request).to receive(:status)
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 = double("request")
491
- allow(request).to receive(:status)
492
- allow(request).to receive(:completed?).once.and_return(true)
493
- expect(request).to receive(:refresh).once
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
- expect { driver.wait_for_request(request) }.to raise_error(Timeout::Error)
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 = double("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
- reader = Kitchen::Driver::Vra.new(config.reject { |k, _v| %i{username password}.include?(k) })
554
- allow(reader).to receive(:instance).and_return(instance)
555
- reader.c_load
813
+ loader = reader
814
+ loader.c_load
556
815
 
557
- expect(reader[:username]).to eq("myuser")
558
- expect(reader[:password]).to eq("mypassword")
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 = Kitchen::Driver::Vra.new(config.merge(base_url: "https://other.corp.local")
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 = Kitchen::Driver::Vra.new(config.reject { |k, _v| %i{username password}.include?(k) })
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 "uses the cache instead of prompting" do
594
- driver.c_save
860
+ it "keeps the credentials it was configured with" do
861
+ allow(driver).to receive(:ask)
595
862
 
596
- reader = Kitchen::Driver::Vra.new(config.reject { |k, _v| %i{username password}.include?(k) })
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
- reader.check_config
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
- expect(reader[:username]).to eq("myuser")
606
- expect(reader[:password]).to eq("mypassword")
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