kitchen-google 1.5.0 → 2.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.
- checksums.yaml +4 -4
- data/lib/kitchen/driver/gce.rb +46 -26
- data/lib/kitchen/driver/gce_version.rb +2 -2
- metadata +19 -39
- data/.gitignore +0 -5
- data/.rubocop.yml +0 -1
- data/.rubocop_todo.yml +0 -11
- data/.travis.yml +0 -11
- data/CHANGELOG.md +0 -153
- data/Gemfile +0 -6
- data/README.md +0 -446
- data/Rakefile +0 -14
- data/kitchen-google.gemspec +0 -29
- data/spec/kitchen/driver/gce_spec.rb +0 -1278
- data/spec/spec_helper.rb +0 -21
data/Rakefile
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
|
3
|
-
require "bundler/gem_tasks"
|
4
|
-
require "rspec/core/rake_task"
|
5
|
-
|
6
|
-
RSpec::Core::RakeTask.new(:spec)
|
7
|
-
|
8
|
-
require "chefstyle"
|
9
|
-
require "rubocop/rake_task"
|
10
|
-
RuboCop::RakeTask.new(:style) do |task|
|
11
|
-
task.options << "--display-cop-names"
|
12
|
-
end
|
13
|
-
|
14
|
-
task default: [:spec, :style]
|
data/kitchen-google.gemspec
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
$:.push File.expand_path("../lib", __FILE__)
|
3
|
-
require "kitchen/driver/gce_version"
|
4
|
-
|
5
|
-
Gem::Specification.new do |s|
|
6
|
-
s.name = "kitchen-google"
|
7
|
-
s.version = Kitchen::Driver::GCE_VERSION
|
8
|
-
s.date = "2016-03-10"
|
9
|
-
s.summary = "Kitchen::Driver::Gce"
|
10
|
-
s.description = "A Test-Kitchen driver for Google Compute Engine"
|
11
|
-
s.authors = ["Andrew Leonard", "Chef Partner Engineering"]
|
12
|
-
s.email = ["andy@hurricane-ridge.com", "partnereng@chef.io"]
|
13
|
-
s.files = `git ls-files`.split($/)
|
14
|
-
s.homepage = "https://github.com/test-kitchen/kitchen-google"
|
15
|
-
s.license = "Apache 2.0"
|
16
|
-
|
17
|
-
s.add_dependency "gcewinpass", "~> 1.1"
|
18
|
-
s.add_dependency "google-api-client", "~> 0.19"
|
19
|
-
s.add_dependency "test-kitchen"
|
20
|
-
|
21
|
-
s.add_development_dependency "bundler"
|
22
|
-
s.add_development_dependency "pry"
|
23
|
-
s.add_development_dependency "rake", "~> 10.5"
|
24
|
-
s.add_development_dependency "rspec"
|
25
|
-
s.add_development_dependency "rubocop"
|
26
|
-
s.add_development_dependency "byebug"
|
27
|
-
|
28
|
-
s.required_ruby_version = ">= 2.0"
|
29
|
-
end
|
@@ -1,1278 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
#
|
3
|
-
# Author:: Andrew Leonard (<andy@hurricane-ridge.com>)
|
4
|
-
# Author:: Chef Partner Engineering (<partnereng@chef.io>)
|
5
|
-
#
|
6
|
-
# Copyright (C) 2013-2016, Andrew Leonard and Chef Software, Inc.
|
7
|
-
#
|
8
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
9
|
-
# you may not use this file except in compliance with the License.
|
10
|
-
# You may obtain a copy of the License at
|
11
|
-
#
|
12
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
13
|
-
#
|
14
|
-
# Unless required by applicable law or agreed to in writing, software
|
15
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
16
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
17
|
-
# See the License for the specific language governing permissions and
|
18
|
-
# limitations under the License.
|
19
|
-
|
20
|
-
require "spec_helper"
|
21
|
-
require "google/apis/compute_v1"
|
22
|
-
require "kitchen/driver/gce"
|
23
|
-
require "kitchen/provisioner/dummy"
|
24
|
-
require "kitchen/transport/dummy"
|
25
|
-
require "kitchen/verifier/dummy"
|
26
|
-
|
27
|
-
shared_examples_for "a validity checker" do |config_key, api_method, *args|
|
28
|
-
it "returns false if the config value is nil" do
|
29
|
-
expect(driver).to receive(:config).and_return({})
|
30
|
-
expect(subject).to eq(false)
|
31
|
-
end
|
32
|
-
|
33
|
-
it "checks the outcome of the API call" do
|
34
|
-
connection = double("connection")
|
35
|
-
allow(driver).to receive(:config).and_return({ config_key => "test_value" })
|
36
|
-
expect(driver).to receive(:connection).and_return(connection)
|
37
|
-
expect(connection).to receive(api_method).with(*args, "test_value")
|
38
|
-
expect(driver).to receive(:check_api_call).and_call_original
|
39
|
-
expect(subject).to eq(true)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
describe Kitchen::Driver::Gce do
|
44
|
-
let(:logged_output) { StringIO.new }
|
45
|
-
let(:logger) { Logger.new(logged_output) }
|
46
|
-
let(:platform) { Kitchen::Platform.new(name: "fake_platform") }
|
47
|
-
let(:transport) { Kitchen::Transport::Dummy.new }
|
48
|
-
let(:driver) { Kitchen::Driver::Gce.new(config) }
|
49
|
-
|
50
|
-
let(:project) { "test_project" }
|
51
|
-
let(:zone) { "test_zone" }
|
52
|
-
|
53
|
-
let(:config) do
|
54
|
-
{
|
55
|
-
project: project,
|
56
|
-
zone: zone,
|
57
|
-
image_name: "test_image",
|
58
|
-
}
|
59
|
-
end
|
60
|
-
|
61
|
-
let(:instance) do
|
62
|
-
instance_double(Kitchen::Instance,
|
63
|
-
logger: logger,
|
64
|
-
transport: transport,
|
65
|
-
platform: platform,
|
66
|
-
to_str: "instance_str"
|
67
|
-
)
|
68
|
-
end
|
69
|
-
|
70
|
-
before do
|
71
|
-
allow(driver).to receive(:instance).and_return(instance)
|
72
|
-
allow(driver).to receive(:project).and_return("test_project")
|
73
|
-
allow(driver).to receive(:zone).and_return("test_zone")
|
74
|
-
allow(driver).to receive(:region).and_return("test_region")
|
75
|
-
end
|
76
|
-
|
77
|
-
it "driver API version is 2" do
|
78
|
-
expect(driver.diagnose_plugin[:api_version]).to eq(2)
|
79
|
-
end
|
80
|
-
|
81
|
-
describe "#name" do
|
82
|
-
it "has an overridden name" do
|
83
|
-
expect(driver.name).to eq("Google Compute (GCE)")
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
describe "#create" do
|
88
|
-
let(:connection) { double("connection") }
|
89
|
-
let(:operation) { double("operation", name: "test_operation") }
|
90
|
-
let(:state) { {} }
|
91
|
-
|
92
|
-
before do
|
93
|
-
allow(driver).to receive(:validate!)
|
94
|
-
allow(driver).to receive(:connection).and_return(connection)
|
95
|
-
allow(driver).to receive(:generate_server_name)
|
96
|
-
allow(driver).to receive(:wait_for_operation)
|
97
|
-
allow(driver).to receive(:server_instance)
|
98
|
-
allow(driver).to receive(:create_instance_object)
|
99
|
-
allow(driver).to receive(:ip_address_for)
|
100
|
-
allow(driver).to receive(:update_windows_password)
|
101
|
-
allow(driver).to receive(:wait_for_server)
|
102
|
-
allow(connection).to receive(:insert_instance).and_return(operation)
|
103
|
-
end
|
104
|
-
|
105
|
-
it "does not create the server if the hostname is in the state file" do
|
106
|
-
expect(connection).not_to receive(:insert_instance)
|
107
|
-
driver.create(server_name: "server_exists")
|
108
|
-
end
|
109
|
-
|
110
|
-
it "generates a unique server name and sets the state" do
|
111
|
-
expect(driver).to receive(:generate_server_name).and_return("server_1")
|
112
|
-
driver.create(state)
|
113
|
-
expect(state[:server_name]).to eq("server_1")
|
114
|
-
end
|
115
|
-
|
116
|
-
it "creates the instance via the API and waits for it to complete" do
|
117
|
-
expect(driver).to receive(:generate_server_name).and_return("server_1")
|
118
|
-
expect(driver).to receive(:create_instance_object).with("server_1").and_return("create_obj")
|
119
|
-
expect(connection).to receive(:insert_instance).with("test_project", "test_zone", "create_obj").and_return(operation)
|
120
|
-
expect(driver).to receive(:wait_for_operation).with(operation)
|
121
|
-
|
122
|
-
driver.create(state)
|
123
|
-
end
|
124
|
-
|
125
|
-
it "sets the correct data in the state object" do
|
126
|
-
expect(driver).to receive(:generate_server_name).and_return("server_1")
|
127
|
-
expect(driver).to receive(:server_instance).with("server_1").and_return("server_obj")
|
128
|
-
expect(driver).to receive(:ip_address_for).with("server_obj").and_return("1.2.3.4")
|
129
|
-
driver.create(state)
|
130
|
-
|
131
|
-
expect(state[:server_name]).to eq("server_1")
|
132
|
-
expect(state[:hostname]).to eq("1.2.3.4")
|
133
|
-
expect(state[:zone]).to eq("test_zone")
|
134
|
-
end
|
135
|
-
|
136
|
-
it "updates the windows password" do
|
137
|
-
expect(driver).to receive(:generate_server_name).and_return("server_1")
|
138
|
-
expect(driver).to receive(:update_windows_password).with("server_1")
|
139
|
-
driver.create(state)
|
140
|
-
end
|
141
|
-
|
142
|
-
it "waits for the server to be ready" do
|
143
|
-
expect(driver).to receive(:wait_for_server)
|
144
|
-
driver.create(state)
|
145
|
-
end
|
146
|
-
|
147
|
-
it "destroys the server if any exceptions are raised" do
|
148
|
-
expect(connection).to receive(:insert_instance).and_raise(RuntimeError)
|
149
|
-
expect(driver).to receive(:destroy).with(state)
|
150
|
-
expect { driver.create(state) }.to raise_error(RuntimeError)
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
describe "#destroy" do
|
155
|
-
let(:connection) { double("connection") }
|
156
|
-
let(:state) { { server_name: "server_1", hostname: "test_host", zone: "test_zone" } }
|
157
|
-
|
158
|
-
before do
|
159
|
-
allow(driver).to receive(:connection).and_return(connection)
|
160
|
-
allow(driver).to receive(:server_exist?).and_return(true)
|
161
|
-
allow(driver).to receive(:wait_for_operation)
|
162
|
-
allow(connection).to receive(:delete_instance)
|
163
|
-
end
|
164
|
-
|
165
|
-
it "does not attempt to delete the instance if there is no server_name" do
|
166
|
-
expect(connection).not_to receive(:delete_instance)
|
167
|
-
driver.destroy({})
|
168
|
-
end
|
169
|
-
|
170
|
-
it "does not attempt to delete the instance if it does not exist" do
|
171
|
-
expect(driver).to receive(:server_exist?).with("server_1").and_return(false)
|
172
|
-
expect(connection).not_to receive(:delete_instance)
|
173
|
-
driver.destroy(state)
|
174
|
-
end
|
175
|
-
|
176
|
-
it "deletes the instance via the API and waits for it to complete" do
|
177
|
-
expect(connection).to receive(:delete_instance).with("test_project", "test_zone", "server_1").and_return("operation")
|
178
|
-
expect(driver).to receive(:wait_for_operation).with("operation")
|
179
|
-
driver.destroy(state)
|
180
|
-
end
|
181
|
-
|
182
|
-
it "deletes the state keys" do
|
183
|
-
driver.destroy(state)
|
184
|
-
expect(state.key?(:server_name)).to eq(false)
|
185
|
-
expect(state.key?(:hostname)).to eq(false)
|
186
|
-
expect(state.key?(:zone)).to eq(false)
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
describe "#validate!" do
|
191
|
-
let(:config) do
|
192
|
-
{
|
193
|
-
project: "test_project",
|
194
|
-
zone: "test_zone",
|
195
|
-
machine_type: "test_machine_type",
|
196
|
-
disk_type: "test_disk_type",
|
197
|
-
image_name: "test_image",
|
198
|
-
network: "test_network",
|
199
|
-
}
|
200
|
-
end
|
201
|
-
|
202
|
-
before do
|
203
|
-
allow(driver).to receive(:valid_project?).and_return(true)
|
204
|
-
allow(driver).to receive(:valid_zone?).and_return(true)
|
205
|
-
allow(driver).to receive(:valid_region?).and_return(true)
|
206
|
-
allow(driver).to receive(:valid_machine_type?).and_return(true)
|
207
|
-
allow(driver).to receive(:valid_disk_type?).and_return(true)
|
208
|
-
allow(driver).to receive(:boot_disk_source_image).and_return("image")
|
209
|
-
allow(driver).to receive(:valid_network?).and_return(true)
|
210
|
-
allow(driver).to receive(:valid_subnet?).and_return(true)
|
211
|
-
allow(driver).to receive(:winrm_transport?).and_return(false)
|
212
|
-
allow(driver).to receive(:config).and_return(config)
|
213
|
-
end
|
214
|
-
|
215
|
-
it "does not raise an exception when all validations are successful" do
|
216
|
-
expect { driver.validate! }.not_to raise_error
|
217
|
-
end
|
218
|
-
|
219
|
-
context "when neither zone nor region are specified" do
|
220
|
-
let(:config) { { image_name: "test_image" } }
|
221
|
-
it "raises an exception" do
|
222
|
-
expect { driver.validate! }.to raise_error(RuntimeError, "Either zone or region must be specified")
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
|
-
context "when neither image_family nor image_name are specified" do
|
227
|
-
let(:config) { { zone: "test_zone" } }
|
228
|
-
it "raises an exception" do
|
229
|
-
expect { driver.validate! }.to raise_error(RuntimeError, "Either image family or name must be specified")
|
230
|
-
end
|
231
|
-
end
|
232
|
-
|
233
|
-
context "when zone and region are both set" do
|
234
|
-
let(:config) { { zone: "test_zone", region: "test_region", image_project: "test_project", image_name: "test_image" } }
|
235
|
-
|
236
|
-
it "warns the user that the region will be ignored" do
|
237
|
-
expect(driver).to receive(:warn).with("Both zone and region specified - region will be ignored.")
|
238
|
-
driver.validate!
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
|
-
context "when image family and name are both set" do
|
243
|
-
let(:config) { { image_project: "test_project", image_name: "test_image", image_family: "test_image_family", zone: "test_zone" } }
|
244
|
-
|
245
|
-
it "warns the user that the image family will be ignored" do
|
246
|
-
expect(driver).to receive(:warn).with("Both image family and name specified - image family will be ignored")
|
247
|
-
driver.validate!
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
context "when region is set to 'any'" do
|
252
|
-
let(:config) { { region: "any" } }
|
253
|
-
it "raises an exception" do
|
254
|
-
expect { driver.validate! }.to raise_error(RuntimeError, "'any' is no longer a valid region")
|
255
|
-
end
|
256
|
-
end
|
257
|
-
|
258
|
-
context "when zone is set" do
|
259
|
-
let(:config) { { zone: "test_zone" } }
|
260
|
-
|
261
|
-
it "raises an exception if the zone is not valid" do
|
262
|
-
expect(driver).to receive(:valid_zone?).and_return(false)
|
263
|
-
expect { driver.validate! }.to raise_error(RuntimeError, "Zone test_zone is not a valid zone")
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
|
-
context "when region is set" do
|
268
|
-
let(:config) { { region: "test_region" } }
|
269
|
-
|
270
|
-
it "raises an exception if the region is not valid" do
|
271
|
-
expect(driver).to receive(:valid_region?).and_return(false)
|
272
|
-
expect { driver.validate! }.to raise_error(RuntimeError, "Region test_region is not a valid region")
|
273
|
-
end
|
274
|
-
end
|
275
|
-
|
276
|
-
context "when subnet is set" do
|
277
|
-
let(:config) do
|
278
|
-
{
|
279
|
-
project: "test_project",
|
280
|
-
zone: "test_zone",
|
281
|
-
image_name: "test_image",
|
282
|
-
machine_type: "test_machine_type",
|
283
|
-
disk_type: "test_disk_type",
|
284
|
-
network: "test_network",
|
285
|
-
subnet: "test_subnet",
|
286
|
-
}
|
287
|
-
end
|
288
|
-
|
289
|
-
it "raises an exception if the subnet is invalid" do
|
290
|
-
expect(driver).to receive(:valid_subnet?).and_return(false)
|
291
|
-
expect { driver.validate! }.to raise_error(RuntimeError, "Subnet test_subnet is not valid")
|
292
|
-
end
|
293
|
-
end
|
294
|
-
|
295
|
-
it "raises an exception if the project is invalid" do
|
296
|
-
expect(driver).to receive(:valid_project?).and_return(false)
|
297
|
-
expect { driver.validate! }.to raise_error(RuntimeError, "Project test_project is not a valid project")
|
298
|
-
end
|
299
|
-
|
300
|
-
it "raises an exception if the machine_type is invalid" do
|
301
|
-
expect(driver).to receive(:valid_machine_type?).and_return(false)
|
302
|
-
expect { driver.validate! }.to raise_error(RuntimeError, "Machine type test_machine_type is not valid")
|
303
|
-
end
|
304
|
-
|
305
|
-
it "raises an exception if the boot disk source image is invalid" do
|
306
|
-
expect(driver).to receive(:boot_disk_source_image).and_return(nil)
|
307
|
-
expect { driver.validate! }.to raise_error(RuntimeError, "Disk image test_image is not valid - check your image name and image project")
|
308
|
-
end
|
309
|
-
|
310
|
-
context "both disk configurations are active" do
|
311
|
-
let(:config) do
|
312
|
-
{
|
313
|
-
project: "test_project",
|
314
|
-
zone: "test_zone",
|
315
|
-
image_name: "test_image",
|
316
|
-
machine_type: "test_machine_type",
|
317
|
-
autodelete_disk: true,
|
318
|
-
disks: {
|
319
|
-
disk0: {
|
320
|
-
autodelete_disk: true,
|
321
|
-
},
|
322
|
-
},
|
323
|
-
}
|
324
|
-
end
|
325
|
-
it "raises an exception if parameters from both disk configurations are present" do
|
326
|
-
expect { driver.validate! }.to raise_error(RuntimeError, "You cannot use autodelete_disk, disk_size or disk_type with the new disks configuration")
|
327
|
-
end
|
328
|
-
end
|
329
|
-
|
330
|
-
it "raises an exception if the network is invalid" do
|
331
|
-
expect(driver).to receive(:valid_network?).and_return(false)
|
332
|
-
expect { driver.validate! }.to raise_error(RuntimeError, "Network test_network is not valid")
|
333
|
-
end
|
334
|
-
|
335
|
-
it "raises an exception if WinRM transport is used but no email is set" do
|
336
|
-
expect(driver).to receive(:winrm_transport?).and_return(true)
|
337
|
-
expect { driver.validate! }.to raise_error(RuntimeError, "Email address of GCE user is not set")
|
338
|
-
end
|
339
|
-
end
|
340
|
-
|
341
|
-
describe "#connection" do
|
342
|
-
it "returns a properly configured ComputeService" do
|
343
|
-
compute_service = double("compute_service")
|
344
|
-
client_options = double("client_options")
|
345
|
-
|
346
|
-
expect(Google::Apis::ClientOptions).to receive(:new).and_return(client_options)
|
347
|
-
expect(client_options).to receive(:application_name=).with("GoogleChefTestKitchen")
|
348
|
-
expect(client_options).to receive(:application_version=).with(Kitchen::Driver::GCE_VERSION)
|
349
|
-
|
350
|
-
expect(Google::Apis::ComputeV1::ComputeService).to receive(:new).and_return(compute_service)
|
351
|
-
expect(driver).to receive(:authorization).and_return("authorization_object")
|
352
|
-
expect(compute_service).to receive(:authorization=).with("authorization_object")
|
353
|
-
expect(compute_service).to receive(:client_options=).with(client_options)
|
354
|
-
|
355
|
-
expect(driver.connection).to eq(compute_service)
|
356
|
-
end
|
357
|
-
end
|
358
|
-
|
359
|
-
describe "#authorization" do
|
360
|
-
it "returns a Google::Auth authorization object" do
|
361
|
-
auth_object = double("auth_object")
|
362
|
-
expect(Google::Auth).to receive(:get_application_default).and_return(auth_object)
|
363
|
-
expect(driver.authorization).to eq(auth_object)
|
364
|
-
end
|
365
|
-
end
|
366
|
-
|
367
|
-
describe "#winrm_transport?" do
|
368
|
-
it "returns true if the transport name is Winrm" do
|
369
|
-
expect(transport).to receive(:name).and_return("Winrm")
|
370
|
-
expect(driver.winrm_transport?).to eq(true)
|
371
|
-
end
|
372
|
-
|
373
|
-
it "returns false if the transport name is not Winrm" do
|
374
|
-
expect(transport).to receive(:name).and_return("Ssh")
|
375
|
-
expect(driver.winrm_transport?).to eq(false)
|
376
|
-
end
|
377
|
-
end
|
378
|
-
|
379
|
-
describe "#update_windows_password" do
|
380
|
-
it "does not attempt to reset the password if the transport is not WinRM" do
|
381
|
-
expect(driver).to receive(:winrm_transport?).and_return(false)
|
382
|
-
expect(GoogleComputeWindowsPassword).not_to receive(:new)
|
383
|
-
|
384
|
-
driver.update_windows_password("server_1")
|
385
|
-
end
|
386
|
-
|
387
|
-
it "resets the password and puts it in the state object if the transport is WinRM" do
|
388
|
-
state = {}
|
389
|
-
winpass = double("winpass")
|
390
|
-
winpass_config = {
|
391
|
-
project: "test_project",
|
392
|
-
zone: "test_zone",
|
393
|
-
instance_name: "server_1",
|
394
|
-
email: "test_email",
|
395
|
-
username: "test_username",
|
396
|
-
}
|
397
|
-
|
398
|
-
allow(driver).to receive(:state).and_return(state)
|
399
|
-
expect(transport).to receive(:config).and_return(username: "test_username")
|
400
|
-
expect(driver).to receive(:config).and_return(email: "test_email")
|
401
|
-
expect(driver).to receive(:winrm_transport?).and_return(true)
|
402
|
-
expect(GoogleComputeWindowsPassword).to receive(:new).with(winpass_config).and_return(winpass)
|
403
|
-
expect(winpass).to receive(:new_password).and_return("password123")
|
404
|
-
driver.update_windows_password("server_1")
|
405
|
-
expect(state[:password]).to eq("password123")
|
406
|
-
end
|
407
|
-
end
|
408
|
-
|
409
|
-
describe "#check_api_call" do
|
410
|
-
it "returns false and logs a debug message if the block raises a ClientError" do
|
411
|
-
expect(driver).to receive(:debug).with("API error: whoops")
|
412
|
-
expect(driver.check_api_call { raise Google::Apis::ClientError.new("whoops") }).to eq(false)
|
413
|
-
end
|
414
|
-
|
415
|
-
it "raises an exception if the block raises something other than a ClientError" do
|
416
|
-
expect { driver.check_api_call { raise "whoops" } }.to raise_error(RuntimeError)
|
417
|
-
end
|
418
|
-
|
419
|
-
it "returns true if the block does not raise an exception" do
|
420
|
-
expect(driver.check_api_call { true }).to eq(true)
|
421
|
-
end
|
422
|
-
end
|
423
|
-
|
424
|
-
describe "#valid_machine_type?" do
|
425
|
-
subject { driver.valid_machine_type? }
|
426
|
-
it_behaves_like "a validity checker", :machine_type, :get_machine_type, "test_project", "test_zone"
|
427
|
-
end
|
428
|
-
|
429
|
-
describe "#valid_network?" do
|
430
|
-
subject { driver.valid_network? }
|
431
|
-
it_behaves_like "a validity checker", :network, :get_network, "test_project"
|
432
|
-
end
|
433
|
-
|
434
|
-
describe "#valid_subnet?" do
|
435
|
-
subject { driver.valid_subnet? }
|
436
|
-
it_behaves_like "a validity checker", :subnet, :get_subnetwork, "test_project", "test_region"
|
437
|
-
end
|
438
|
-
|
439
|
-
describe "#valid_zone?" do
|
440
|
-
subject { driver.valid_zone? }
|
441
|
-
it_behaves_like "a validity checker", :zone, :get_zone, "test_project"
|
442
|
-
end
|
443
|
-
|
444
|
-
describe "#valid_region?" do
|
445
|
-
subject { driver.valid_region? }
|
446
|
-
it_behaves_like "a validity checker", :region, :get_region, "test_project"
|
447
|
-
end
|
448
|
-
|
449
|
-
describe "#image_exist?" do
|
450
|
-
it "checks the outcome of the API call" do
|
451
|
-
connection = double("connection")
|
452
|
-
expect(driver).to receive(:connection).and_return(connection)
|
453
|
-
expect(connection).to receive(:get_image).with("test_project", "test_image")
|
454
|
-
expect(driver).to receive(:check_api_call).and_call_original
|
455
|
-
expect(driver.image_exist?).to eq(true)
|
456
|
-
end
|
457
|
-
end
|
458
|
-
|
459
|
-
describe "#server_exist?" do
|
460
|
-
it "checks the outcome of the API call" do
|
461
|
-
expect(driver).to receive(:server_instance).with("server_1")
|
462
|
-
expect(driver).to receive(:check_api_call).and_call_original
|
463
|
-
expect(driver.server_exist?("server_1")).to eq(true)
|
464
|
-
end
|
465
|
-
end
|
466
|
-
|
467
|
-
describe "#project" do
|
468
|
-
it "returns the project from the config" do
|
469
|
-
allow(driver).to receive(:project).and_call_original
|
470
|
-
expect(driver).to receive(:config).and_return(project: "my_project")
|
471
|
-
expect(driver.project).to eq("my_project")
|
472
|
-
end
|
473
|
-
end
|
474
|
-
|
475
|
-
describe "#region" do
|
476
|
-
it "returns the region from the config if specified" do
|
477
|
-
allow(driver).to receive(:region).and_call_original
|
478
|
-
allow(driver).to receive(:config).and_return(region: "my_region")
|
479
|
-
expect(driver.region).to eq("my_region")
|
480
|
-
end
|
481
|
-
|
482
|
-
it "returns the region for the zone if no region is specified" do
|
483
|
-
allow(driver).to receive(:region).and_call_original
|
484
|
-
allow(driver).to receive(:config).and_return({})
|
485
|
-
expect(driver).to receive(:region_for_zone).and_return("zone_region")
|
486
|
-
expect(driver.region).to eq("zone_region")
|
487
|
-
end
|
488
|
-
end
|
489
|
-
|
490
|
-
describe "#region_for_zone" do
|
491
|
-
it "returns the region for a given zone" do
|
492
|
-
connection = double("connection")
|
493
|
-
zone_obj = double("zone_obj", region: "/path/to/test_region")
|
494
|
-
|
495
|
-
expect(driver).to receive(:connection).and_return(connection)
|
496
|
-
expect(connection).to receive(:get_zone).with(project, zone).and_return(zone_obj)
|
497
|
-
expect(driver.region_for_zone).to eq("test_region")
|
498
|
-
end
|
499
|
-
end
|
500
|
-
|
501
|
-
describe "#zone" do
|
502
|
-
before do
|
503
|
-
allow(driver).to receive(:zone).and_call_original
|
504
|
-
end
|
505
|
-
|
506
|
-
context "when a zone exists in the state" do
|
507
|
-
let(:state) { { zone: "state_zone" } }
|
508
|
-
|
509
|
-
it "returns the zone from the state" do
|
510
|
-
expect(driver).to receive(:state).and_return(state)
|
511
|
-
expect(driver.zone).to eq("state_zone")
|
512
|
-
end
|
513
|
-
end
|
514
|
-
|
515
|
-
context "when a zone does not exist in the state" do
|
516
|
-
let(:state) { {} }
|
517
|
-
|
518
|
-
before do
|
519
|
-
allow(driver).to receive(:state).and_return(state)
|
520
|
-
end
|
521
|
-
|
522
|
-
it "returns the zone from the config if it exists" do
|
523
|
-
expect(driver).to receive(:config).and_return(zone: "config_zone")
|
524
|
-
expect(driver.zone).to eq("config_zone")
|
525
|
-
end
|
526
|
-
|
527
|
-
it "returns the zone from find_zone if it does not exist in the config" do
|
528
|
-
expect(driver).to receive(:config).and_return({})
|
529
|
-
expect(driver).to receive(:find_zone).and_return("found_zone")
|
530
|
-
expect(driver.zone).to eq("found_zone")
|
531
|
-
end
|
532
|
-
end
|
533
|
-
end
|
534
|
-
|
535
|
-
describe "#find_zone" do
|
536
|
-
let(:zones_in_region) { double("zones_in_region") }
|
537
|
-
|
538
|
-
before do
|
539
|
-
expect(driver).to receive(:zones_in_region).and_return(zones_in_region)
|
540
|
-
end
|
541
|
-
|
542
|
-
it "returns a random zone from the list of zones in the region" do
|
543
|
-
zone = double("zone", name: "random_zone")
|
544
|
-
expect(zones_in_region).to receive(:sample).and_return(zone)
|
545
|
-
expect(driver.find_zone).to eq("random_zone")
|
546
|
-
end
|
547
|
-
|
548
|
-
it "raises an exception if no zones are found" do
|
549
|
-
expect(zones_in_region).to receive(:sample).and_return(nil)
|
550
|
-
expect(driver).to receive(:region).and_return("test_region")
|
551
|
-
expect { driver.find_zone }.to raise_error(RuntimeError, "Unable to find a suitable zone in test_region")
|
552
|
-
end
|
553
|
-
end
|
554
|
-
|
555
|
-
describe "#zones_in_region" do
|
556
|
-
it "returns a correct list of available zones" do
|
557
|
-
zone1 = double("zone1", status: "UP", region: "a/b/c/test_region")
|
558
|
-
zone2 = double("zone2", status: "UP", region: "a/b/c/test_region")
|
559
|
-
zone3 = double("zone3", status: "DOWN", region: "a/b/c/test_region")
|
560
|
-
zone4 = double("zone4", status: "UP", region: "a/b/c/wrong_region")
|
561
|
-
zone5 = double("zone5", status: "UP", region: "a/b/c/test_region")
|
562
|
-
connection = double("connection")
|
563
|
-
response = double("response", items: [zone1, zone2, zone3, zone4, zone5])
|
564
|
-
|
565
|
-
allow(driver).to receive(:region).and_return("test_region")
|
566
|
-
expect(driver).to receive(:connection).and_return(connection)
|
567
|
-
expect(connection).to receive(:list_zones).and_return(response)
|
568
|
-
expect(driver.zones_in_region).to eq([zone1, zone2, zone5])
|
569
|
-
end
|
570
|
-
end
|
571
|
-
|
572
|
-
describe "#server_instance" do
|
573
|
-
it "returns the instance from the API" do
|
574
|
-
connection = double("connection")
|
575
|
-
expect(driver).to receive(:connection).and_return(connection)
|
576
|
-
expect(connection).to receive(:get_instance).with("test_project", "test_zone", "server_1").and_return("instance")
|
577
|
-
expect(driver.server_instance("server_1")).to eq("instance")
|
578
|
-
end
|
579
|
-
end
|
580
|
-
|
581
|
-
describe "#ip_address_for" do
|
582
|
-
it "returns the private IP if use_private_ip is true" do
|
583
|
-
expect(driver).to receive(:config).and_return(use_private_ip: true)
|
584
|
-
expect(driver).to receive(:private_ip_for).with("server").and_return("1.2.3.4")
|
585
|
-
expect(driver.ip_address_for("server")).to eq("1.2.3.4")
|
586
|
-
end
|
587
|
-
|
588
|
-
it "returns the public IP if use_private_ip is false" do
|
589
|
-
expect(driver).to receive(:config).and_return(use_private_ip: false)
|
590
|
-
expect(driver).to receive(:public_ip_for).with("server").and_return("4.3.2.1")
|
591
|
-
expect(driver.ip_address_for("server")).to eq("4.3.2.1")
|
592
|
-
end
|
593
|
-
end
|
594
|
-
|
595
|
-
describe "#private_ip_for" do
|
596
|
-
it "returns the IP address if it exists" do
|
597
|
-
network_interface = double("network_interface", network_ip: "1.2.3.4")
|
598
|
-
server = double("server", network_interfaces: [network_interface])
|
599
|
-
|
600
|
-
expect(driver.private_ip_for(server)).to eq("1.2.3.4")
|
601
|
-
end
|
602
|
-
|
603
|
-
it "raises an exception if the IP cannot be found" do
|
604
|
-
server = double("server")
|
605
|
-
|
606
|
-
expect(server).to receive(:network_interfaces).and_raise(NoMethodError)
|
607
|
-
expect { driver.private_ip_for(server) }.to raise_error(RuntimeError, "Unable to determine private IP for instance")
|
608
|
-
end
|
609
|
-
end
|
610
|
-
|
611
|
-
describe "#public_ip_for" do
|
612
|
-
it "returns the IP address if it exists" do
|
613
|
-
access_config = double("access_config", nat_ip: "4.3.2.1")
|
614
|
-
network_interface = double("network_interface", access_configs: [access_config])
|
615
|
-
server = double("server", network_interfaces: [network_interface])
|
616
|
-
|
617
|
-
expect(driver.public_ip_for(server)).to eq("4.3.2.1")
|
618
|
-
end
|
619
|
-
|
620
|
-
it "raises an exception if the IP cannot be found" do
|
621
|
-
network_interface = double("network_interface")
|
622
|
-
server = double("server", network_interfaces: [network_interface])
|
623
|
-
|
624
|
-
expect(network_interface).to receive(:access_configs).and_raise(NoMethodError)
|
625
|
-
expect { driver.public_ip_for(server) }.to raise_error(RuntimeError, "Unable to determine public IP for instance")
|
626
|
-
end
|
627
|
-
end
|
628
|
-
|
629
|
-
describe "#generate_server_name" do
|
630
|
-
it "generates and returns a server name" do
|
631
|
-
expect(instance).to receive(:name).and_return("ABC123")
|
632
|
-
expect(SecureRandom).to receive(:hex).with(3).and_return("abcdef")
|
633
|
-
expect(driver.generate_server_name).to eq("tk-abc123-abcdef")
|
634
|
-
end
|
635
|
-
|
636
|
-
it "uses a UUID-based server name if the instance name is too long" do
|
637
|
-
expect(instance).to receive(:name).twice.and_return("123456789012345678901234567890123456789012345678901235467890")
|
638
|
-
expect(driver).to receive(:warn)
|
639
|
-
expect(SecureRandom).to receive(:hex).with(3).and_return("abcdef")
|
640
|
-
expect(SecureRandom).to receive(:uuid).and_return("lmnop")
|
641
|
-
expect(driver.generate_server_name).to eq("tk-lmnop")
|
642
|
-
end
|
643
|
-
|
644
|
-
it "returns a specific name for the server if given in the config" do
|
645
|
-
config[:inst_name] = "the_instance_name"
|
646
|
-
|
647
|
-
expect(driver.generate_server_name).to eq("the-instance-name")
|
648
|
-
end
|
649
|
-
end
|
650
|
-
|
651
|
-
describe "#create_disks_config" do
|
652
|
-
it "creates the new disk config from the old one" do
|
653
|
-
config = {
|
654
|
-
disk_size: 30,
|
655
|
-
}
|
656
|
-
|
657
|
-
allow(driver).to receive(:config).and_return(config)
|
658
|
-
allow(driver).to receive(:valid_disk_type?).and_return(true)
|
659
|
-
driver.create_disks_config
|
660
|
-
expect(config[:disks][:disk1][:autodelete_disk]).to eq(true)
|
661
|
-
expect(config[:disks][:disk1][:disk_type]).to eq("pd-standard")
|
662
|
-
expect(config[:disks][:disk1][:disk_size]).to eq(30)
|
663
|
-
end
|
664
|
-
|
665
|
-
it "creates the default disk config, with an incomplete new configuration" do
|
666
|
-
config = {
|
667
|
-
disks: {
|
668
|
-
disk1: {
|
669
|
-
boot: true,
|
670
|
-
},
|
671
|
-
},
|
672
|
-
}
|
673
|
-
|
674
|
-
allow(driver).to receive(:config).and_return(config)
|
675
|
-
allow(driver).to receive(:valid_disk_type?).and_return(true)
|
676
|
-
driver.create_disks_config
|
677
|
-
expect(config[:disks][:disk1][:autodelete_disk]).to eq(true)
|
678
|
-
expect(config[:disks][:disk1][:disk_type]).to eq("pd-standard")
|
679
|
-
expect(config[:disks][:disk1][:disk_size]).to eq(10)
|
680
|
-
end
|
681
|
-
|
682
|
-
it "creates the default disk config with no config" do
|
683
|
-
config = {}
|
684
|
-
|
685
|
-
allow(driver).to receive(:config).and_return(config)
|
686
|
-
allow(driver).to receive(:valid_disk_type?).and_return(true)
|
687
|
-
driver.create_disks_config
|
688
|
-
expect(config[:disks][:disk1][:autodelete_disk]).to eq(true)
|
689
|
-
expect(config[:disks][:disk1][:disk_type]).to eq("pd-standard")
|
690
|
-
expect(config[:disks][:disk1][:disk_size]).to eq(10)
|
691
|
-
end
|
692
|
-
|
693
|
-
it "raises an error if disk_size is specified for a local ssd" do
|
694
|
-
config = {
|
695
|
-
disks: {
|
696
|
-
disk0: {
|
697
|
-
boot: true,
|
698
|
-
},
|
699
|
-
disk1: {
|
700
|
-
disk_type: "local-ssd",
|
701
|
-
disk_size: "15",
|
702
|
-
},
|
703
|
-
},
|
704
|
-
}
|
705
|
-
allow(driver).to receive(:config).and_return(config)
|
706
|
-
allow(driver).to receive(:valid_disk_type?).and_return(true)
|
707
|
-
expect { driver.create_disks_config }.to raise_error("disk1: Cannot use 'disk_size' with local SSD. They always have 375 GB (https://cloud.google.com/compute/docs/disks/#localssds).")
|
708
|
-
end
|
709
|
-
|
710
|
-
it "raises an error if the boot disk is specified as local ssd" do
|
711
|
-
config = {
|
712
|
-
disks: {
|
713
|
-
disk0: {
|
714
|
-
boot: true,
|
715
|
-
disk_type: "local-ssd",
|
716
|
-
},
|
717
|
-
},
|
718
|
-
}
|
719
|
-
allow(driver).to receive(:config).and_return(config)
|
720
|
-
allow(driver).to receive(:valid_disk_type?).and_return(true)
|
721
|
-
expect { driver.create_disks_config }.to raise_error("Boot disk cannot be local SSD.")
|
722
|
-
end
|
723
|
-
|
724
|
-
it "raises an error if the disk_name is not valid" do
|
725
|
-
config = {
|
726
|
-
disks: {
|
727
|
-
'my-invalid&disk/name': {
|
728
|
-
boot: true,
|
729
|
-
},
|
730
|
-
},
|
731
|
-
}
|
732
|
-
allow(driver).to receive(:config).and_return(config)
|
733
|
-
allow(driver).to receive(:valid_disk_type?).and_return(true)
|
734
|
-
expect { driver.create_disks_config }.to raise_error("Disk name invalid. Must match (?-mix:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?).")
|
735
|
-
end
|
736
|
-
end
|
737
|
-
|
738
|
-
describe "#create_disks" do
|
739
|
-
it "sets up a boot disk if boot is present and returns it" do
|
740
|
-
|
741
|
-
config = {
|
742
|
-
disks: {
|
743
|
-
disk1: {
|
744
|
-
boot: true,
|
745
|
-
},
|
746
|
-
},
|
747
|
-
}
|
748
|
-
|
749
|
-
connection = double("connection")
|
750
|
-
image = double("image")
|
751
|
-
allow(image).to receive(:name).and_return("test_image")
|
752
|
-
allow(connection).to receive(:get_image_from_family).and_return(image)
|
753
|
-
allow(connection).to receive(:get_image).and_return(image)
|
754
|
-
allow(driver).to receive(:config).and_return(config)
|
755
|
-
allow(driver).to receive(:connection).and_return(connection)
|
756
|
-
disk = driver.create_disks("server_1")
|
757
|
-
expect(disk.first.initialize_params.disk_name).to eq("server_1-disk1")
|
758
|
-
expect(disk.first.initialize_params.source_image).to eq("projects/test_project/global/images/test_image")
|
759
|
-
expect(disk.first.is_a?(Google::Apis::ComputeV1::AttachedDisk)).to eq(true)
|
760
|
-
expect(disk.first.boot).to eq(true)
|
761
|
-
end
|
762
|
-
|
763
|
-
it "sets up an local ssd as scratch disk and returns it" do
|
764
|
-
config = {
|
765
|
-
disks: {
|
766
|
-
disk1: {
|
767
|
-
disk_type: "local-ssd",
|
768
|
-
},
|
769
|
-
},
|
770
|
-
}
|
771
|
-
|
772
|
-
connection = double("connection")
|
773
|
-
allow(driver).to receive(:config).and_return(config)
|
774
|
-
allow(driver).to receive(:connection).and_return(connection)
|
775
|
-
disk = driver.create_disks("server_1")
|
776
|
-
expect(disk.first.initialize_params.disk_name).to eq(nil)
|
777
|
-
expect(disk.first.initialize_params.source_image).to eq(nil)
|
778
|
-
expect(disk.first.initialize_params.disk_size_gb).to eq(nil)
|
779
|
-
expect(disk.first.initialize_params.disk_type).to eq("zones/test_zone/diskTypes/local-ssd")
|
780
|
-
expect(disk.first.type).to eq("SCRATCH")
|
781
|
-
expect(disk.first.is_a?(Google::Apis::ComputeV1::AttachedDisk)).to eq(true)
|
782
|
-
end
|
783
|
-
|
784
|
-
it "sets up an attached disk if boot is not present and returns it" do
|
785
|
-
config = {
|
786
|
-
disks: {
|
787
|
-
disk1: {
|
788
|
-
autodelete_disk: false,
|
789
|
-
},
|
790
|
-
},
|
791
|
-
}
|
792
|
-
connection = double("connection")
|
793
|
-
item = double("item")
|
794
|
-
allow(driver).to receive(:connection).and_return(connection)
|
795
|
-
allow(driver).to receive(:wait_for_operation).and_return(true)
|
796
|
-
allow(item).to receive(:status).and_return("READY")
|
797
|
-
allow(connection).to receive(:insert_disk).and_return("DONE")
|
798
|
-
allow(connection).to receive(:get_disk).with(project, zone, "server_1-disk1").and_return(item)
|
799
|
-
allow(driver).to receive(:config).and_return(config)
|
800
|
-
disk = driver.create_disks("server_1")
|
801
|
-
expect(disk.first.is_a?(Google::Apis::ComputeV1::AttachedDisk)).to eq(true)
|
802
|
-
expect(disk.first.source).to eq("projects/#{project}/zones/#{zone}/disks/server_1-disk1")
|
803
|
-
end
|
804
|
-
|
805
|
-
it "sets up a boot disk and an attached disk if two disks are defined" do
|
806
|
-
config = {
|
807
|
-
disks: {
|
808
|
-
disk1: {
|
809
|
-
autodelete_disk: false,
|
810
|
-
},
|
811
|
-
disk2: {
|
812
|
-
disk_size: 15,
|
813
|
-
},
|
814
|
-
},
|
815
|
-
}
|
816
|
-
connection = double("connection")
|
817
|
-
item = double("item")
|
818
|
-
allow(driver).to receive(:connection).and_return(connection)
|
819
|
-
allow(driver).to receive(:wait_for_operation).and_return(true)
|
820
|
-
allow(item).to receive(:status).and_return("READY")
|
821
|
-
allow(connection).to receive(:insert_disk).and_return("DONE")
|
822
|
-
allow(connection).to receive(:get_disk).with(project, zone, "server_1-disk2").and_return(item)
|
823
|
-
allow(connection).to receive(:get_disk).with(project, zone, "server_1-disk1").and_return(item)
|
824
|
-
allow(driver).to receive(:config).and_return(config)
|
825
|
-
disks = driver.create_disks("server_1")
|
826
|
-
expect(disks.first.is_a?(Google::Apis::ComputeV1::AttachedDisk)).to eq(true)
|
827
|
-
expect(disks.first.source).to eq("projects/#{project}/zones/#{zone}/disks/server_1-disk1")
|
828
|
-
expect(disks.first.auto_delete).to eq(false)
|
829
|
-
expect(disks.last.is_a?(Google::Apis::ComputeV1::AttachedDisk)).to eq(true)
|
830
|
-
expect(disks.last.auto_delete).to eq(nil)
|
831
|
-
expect(disks.last.source).to eq("projects/#{project}/zones/#{zone}/disks/server_1-disk2")
|
832
|
-
end
|
833
|
-
end
|
834
|
-
|
835
|
-
describe "#disk_type_url_for" do
|
836
|
-
it "returns a disk URL" do
|
837
|
-
expect(driver.disk_type_url_for("my_type")).to eq("zones/test_zone/diskTypes/my_type")
|
838
|
-
end
|
839
|
-
end
|
840
|
-
|
841
|
-
describe "#image_name" do
|
842
|
-
before do
|
843
|
-
allow(driver).to receive(:config).and_return(config)
|
844
|
-
end
|
845
|
-
|
846
|
-
context "when the user supplies an image name" do
|
847
|
-
let(:config) { { image_project: "my_image_project", image_name: "my_image" } }
|
848
|
-
|
849
|
-
it "returns the image name supplied" do
|
850
|
-
expect(driver.image_name).to eq("my_image")
|
851
|
-
end
|
852
|
-
end
|
853
|
-
|
854
|
-
context "when the user supplies an image family" do
|
855
|
-
let(:config) { { image_project: "my_image_project", image_family: "my_image_family" } }
|
856
|
-
|
857
|
-
it "returns the image found in the family" do
|
858
|
-
expect(driver).to receive(:image_name_for_family).with("my_image_family").and_return("my_image")
|
859
|
-
expect(driver.image_name).to eq("my_image")
|
860
|
-
end
|
861
|
-
end
|
862
|
-
end
|
863
|
-
|
864
|
-
describe "#image_url" do
|
865
|
-
it "returns nil if the image does not exist" do
|
866
|
-
expect(driver).to receive(:image_exist?).and_return(false)
|
867
|
-
expect(driver.image_url).to eq(nil)
|
868
|
-
end
|
869
|
-
|
870
|
-
it "returns a properly formatted image URL if the image exists" do
|
871
|
-
expect(driver).to receive(:image_exist?).and_return(true)
|
872
|
-
expect(driver.image_url).to eq("projects/test_project/global/images/test_image")
|
873
|
-
end
|
874
|
-
end
|
875
|
-
|
876
|
-
describe "#machine_type_url" do
|
877
|
-
it "returns a machine type URL" do
|
878
|
-
expect(driver).to receive(:config).and_return(machine_type: "machine_type")
|
879
|
-
expect(driver.machine_type_url).to eq("zones/test_zone/machineTypes/machine_type")
|
880
|
-
end
|
881
|
-
end
|
882
|
-
|
883
|
-
describe "#instance_metadata" do
|
884
|
-
let(:item1 ) { double("item1") }
|
885
|
-
let(:item2 ) { double("item2") }
|
886
|
-
let(:item3 ) { double("item3") }
|
887
|
-
let(:metadata) { double("metadata") }
|
888
|
-
|
889
|
-
it "returns a properly-configured metadata object" do
|
890
|
-
|
891
|
-
expect(instance).to receive(:name).and_return("instance_name")
|
892
|
-
expect(driver).to receive(:env_user).and_return("env_user")
|
893
|
-
expect(Google::Apis::ComputeV1::Metadata).to receive(:new).and_return(metadata)
|
894
|
-
expect(Google::Apis::ComputeV1::Metadata::Item).to receive(:new).and_return(item1)
|
895
|
-
expect(Google::Apis::ComputeV1::Metadata::Item).to receive(:new).and_return(item2)
|
896
|
-
expect(Google::Apis::ComputeV1::Metadata::Item).to receive(:new).and_return(item3)
|
897
|
-
expect(item1).to receive(:key=).with("created-by")
|
898
|
-
expect(item1).to receive(:value=).with("test-kitchen")
|
899
|
-
expect(item2).to receive(:key=).with("test-kitchen-instance")
|
900
|
-
expect(item2).to receive(:value=).with("instance_name")
|
901
|
-
expect(item3).to receive(:key=).with("test-kitchen-user")
|
902
|
-
expect(item3).to receive(:value=).with("env_user")
|
903
|
-
expect(metadata).to receive(:items=).with([item1, item2, item3])
|
904
|
-
|
905
|
-
expect(driver.instance_metadata).to eq(metadata)
|
906
|
-
end
|
907
|
-
|
908
|
-
it "accepts custom metadata" do
|
909
|
-
foo = double("foo")
|
910
|
-
config[:metadata] = { "foo" => "bar" }
|
911
|
-
|
912
|
-
expect(instance).to receive(:name).and_return("instance_name")
|
913
|
-
expect(driver).to receive(:env_user).and_return("env_user")
|
914
|
-
|
915
|
-
expect(Google::Apis::ComputeV1::Metadata).to receive(:new).and_return(metadata)
|
916
|
-
expect(Google::Apis::ComputeV1::Metadata::Item).to receive(:new).and_return(foo)
|
917
|
-
expect(Google::Apis::ComputeV1::Metadata::Item).to receive(:new).and_return(item1)
|
918
|
-
expect(Google::Apis::ComputeV1::Metadata::Item).to receive(:new).and_return(item2)
|
919
|
-
expect(Google::Apis::ComputeV1::Metadata::Item).to receive(:new).and_return(item3)
|
920
|
-
expect(item1).to receive(:key=).with("created-by")
|
921
|
-
expect(item1).to receive(:value=).with("test-kitchen")
|
922
|
-
expect(item2).to receive(:key=).with("test-kitchen-instance")
|
923
|
-
expect(item2).to receive(:value=).with("instance_name")
|
924
|
-
expect(item3).to receive(:key=).with("test-kitchen-user")
|
925
|
-
expect(item3).to receive(:value=).with("env_user")
|
926
|
-
expect(foo).to receive(:key=).with("foo")
|
927
|
-
expect(foo).to receive(:value=).with("bar")
|
928
|
-
|
929
|
-
expect(metadata).to receive(:items=).with([foo, item1, item2, item3])
|
930
|
-
|
931
|
-
expect(driver.instance_metadata).to eq(metadata)
|
932
|
-
end
|
933
|
-
end
|
934
|
-
|
935
|
-
describe "#env_user" do
|
936
|
-
it "returns the current user from the environment" do
|
937
|
-
expect(ENV).to receive(:[]).with("USER").and_return("test_user")
|
938
|
-
expect(driver.env_user).to eq("test_user")
|
939
|
-
end
|
940
|
-
|
941
|
-
it "returns 'unknown' if there is no USER present" do
|
942
|
-
expect(ENV).to receive(:[]).with("USER").and_return(nil)
|
943
|
-
expect(driver.env_user).to eq("unknown")
|
944
|
-
end
|
945
|
-
end
|
946
|
-
|
947
|
-
describe "#instance_network_interfaces" do
|
948
|
-
let(:interface) { double("interface") }
|
949
|
-
|
950
|
-
before do
|
951
|
-
allow(Google::Apis::ComputeV1::NetworkInterface).to receive(:new).and_return(interface)
|
952
|
-
allow(driver).to receive(:network_url)
|
953
|
-
allow(driver).to receive(:subnet_url)
|
954
|
-
allow(driver).to receive(:interface_access_configs)
|
955
|
-
allow(interface).to receive(:network=)
|
956
|
-
allow(interface).to receive(:subnetwork=)
|
957
|
-
allow(interface).to receive(:access_configs=)
|
958
|
-
end
|
959
|
-
|
960
|
-
it "creates a network interface object and returns it" do
|
961
|
-
expect(Google::Apis::ComputeV1::NetworkInterface).to receive(:new).and_return(interface)
|
962
|
-
expect(driver.instance_network_interfaces).to eq([interface])
|
963
|
-
end
|
964
|
-
|
965
|
-
it "sets the network" do
|
966
|
-
expect(driver).to receive(:network_url).and_return("network_url")
|
967
|
-
expect(interface).to receive(:network=).with("network_url")
|
968
|
-
driver.instance_network_interfaces
|
969
|
-
end
|
970
|
-
|
971
|
-
it "sets the access configs" do
|
972
|
-
expect(driver).to receive(:interface_access_configs).and_return("access_configs")
|
973
|
-
expect(interface).to receive(:access_configs=).with("access_configs")
|
974
|
-
driver.instance_network_interfaces
|
975
|
-
end
|
976
|
-
|
977
|
-
it "does not set a subnetwork by default" do
|
978
|
-
allow(driver).to receive(:subnet_url).and_return(nil)
|
979
|
-
expect(interface).not_to receive(:subnetwork=)
|
980
|
-
driver.instance_network_interfaces
|
981
|
-
end
|
982
|
-
|
983
|
-
it "sets a subnetwork if one was specified" do
|
984
|
-
allow(driver).to receive(:subnet_url).and_return("subnet_url")
|
985
|
-
expect(interface).to receive(:subnetwork=).with("subnet_url")
|
986
|
-
driver.instance_network_interfaces
|
987
|
-
end
|
988
|
-
end
|
989
|
-
|
990
|
-
describe "#network_url" do
|
991
|
-
context "when the user does not provide network_project" do
|
992
|
-
it "returns a network URL" do
|
993
|
-
allow(driver).to receive(:config).and_return(network: "test_network")
|
994
|
-
expect(driver.network_url).to eq("projects/test_project/global/networks/test_network")
|
995
|
-
end
|
996
|
-
end
|
997
|
-
|
998
|
-
context "when the user provides network_project" do
|
999
|
-
it "returns a network URL" do
|
1000
|
-
allow(driver).to receive(:config).and_return(network: "test_network", network_project: "test_xpn_project")
|
1001
|
-
expect(driver.network_url).to eq("projects/test_xpn_project/global/networks/test_network")
|
1002
|
-
end
|
1003
|
-
end
|
1004
|
-
end
|
1005
|
-
|
1006
|
-
describe "#subnet_url_for" do
|
1007
|
-
it "returns nil if no subnet is specified" do
|
1008
|
-
expect(driver).to receive(:config).and_return({})
|
1009
|
-
expect(driver.subnet_url).to eq(nil)
|
1010
|
-
end
|
1011
|
-
|
1012
|
-
context "when the user does not provide subnet_project" do
|
1013
|
-
it "returns a properly-formatted subnet URL" do
|
1014
|
-
allow(driver).to receive(:config).and_return(subnet: "test_subnet")
|
1015
|
-
expect(driver).to receive(:region).and_return("test_region")
|
1016
|
-
expect(driver.subnet_url).to eq("projects/test_project/regions/test_region/subnetworks/test_subnet")
|
1017
|
-
end
|
1018
|
-
end
|
1019
|
-
|
1020
|
-
context "when the user provides subnet_project" do
|
1021
|
-
it "returns a properly-formatted subnet URL" do
|
1022
|
-
allow(driver).to receive(:config).and_return(subnet_project: "test_xpn_project", subnet: "test_subnet")
|
1023
|
-
expect(driver).to receive(:region).and_return("test_region")
|
1024
|
-
expect(driver.subnet_url).to eq("projects/test_xpn_project/regions/test_region/subnetworks/test_subnet")
|
1025
|
-
end
|
1026
|
-
end
|
1027
|
-
end
|
1028
|
-
|
1029
|
-
describe "#interface_access_configs" do
|
1030
|
-
it "returns a properly-configured access config object" do
|
1031
|
-
access_config = double("access_config")
|
1032
|
-
|
1033
|
-
expect(driver).to receive(:config).and_return({})
|
1034
|
-
expect(Google::Apis::ComputeV1::AccessConfig).to receive(:new).and_return(access_config)
|
1035
|
-
expect(access_config).to receive(:name=).with("External NAT")
|
1036
|
-
expect(access_config).to receive(:type=).with("ONE_TO_ONE_NAT")
|
1037
|
-
|
1038
|
-
expect(driver.interface_access_configs).to eq([access_config])
|
1039
|
-
end
|
1040
|
-
|
1041
|
-
it "returns an empty array if use_private_ip is true" do
|
1042
|
-
expect(driver).to receive(:config).and_return(use_private_ip: true)
|
1043
|
-
expect(driver.interface_access_configs).to eq([])
|
1044
|
-
end
|
1045
|
-
end
|
1046
|
-
|
1047
|
-
describe "#instance_scheduling" do
|
1048
|
-
it "returns a properly-configured scheduling object" do
|
1049
|
-
scheduling = double("scheduling")
|
1050
|
-
|
1051
|
-
expect(driver).to receive(:auto_restart?).and_return("restart")
|
1052
|
-
expect(driver).to receive(:preemptible?).and_return("preempt")
|
1053
|
-
expect(driver).to receive(:migrate_setting).and_return("host_maintenance")
|
1054
|
-
expect(Google::Apis::ComputeV1::Scheduling).to receive(:new).and_return(scheduling)
|
1055
|
-
expect(scheduling).to receive(:automatic_restart=).with("restart")
|
1056
|
-
expect(scheduling).to receive(:preemptible=).with("preempt")
|
1057
|
-
expect(scheduling).to receive(:on_host_maintenance=).with("host_maintenance")
|
1058
|
-
expect(driver.instance_scheduling).to eq(scheduling)
|
1059
|
-
end
|
1060
|
-
end
|
1061
|
-
|
1062
|
-
describe "#preemptible?" do
|
1063
|
-
it "returns the preemptible setting from the config" do
|
1064
|
-
expect(driver).to receive(:config).and_return(preemptible: "test_preempt")
|
1065
|
-
expect(driver.preemptible?).to eq("test_preempt")
|
1066
|
-
end
|
1067
|
-
end
|
1068
|
-
|
1069
|
-
describe "#auto_migrate?" do
|
1070
|
-
it "returns false if the instance is preemptible" do
|
1071
|
-
expect(driver).to receive(:preemptible?).and_return(true)
|
1072
|
-
expect(driver.auto_migrate?).to eq(false)
|
1073
|
-
end
|
1074
|
-
|
1075
|
-
it "returns the setting from the config if preemptible is false" do
|
1076
|
-
expect(driver).to receive(:config).and_return(auto_migrate: "test_migrate")
|
1077
|
-
expect(driver).to receive(:preemptible?).and_return(false)
|
1078
|
-
expect(driver.auto_migrate?).to eq("test_migrate")
|
1079
|
-
end
|
1080
|
-
end
|
1081
|
-
|
1082
|
-
describe "#auto_restart?" do
|
1083
|
-
it "returns false if the instance is preemptible" do
|
1084
|
-
expect(driver).to receive(:preemptible?).and_return(true)
|
1085
|
-
expect(driver.auto_restart?).to eq(false)
|
1086
|
-
end
|
1087
|
-
|
1088
|
-
it "returns the setting from the config if preemptible is false" do
|
1089
|
-
expect(driver).to receive(:config).and_return(auto_restart: "test_restart")
|
1090
|
-
expect(driver).to receive(:preemptible?).and_return(false)
|
1091
|
-
expect(driver.auto_restart?).to eq("test_restart")
|
1092
|
-
end
|
1093
|
-
end
|
1094
|
-
|
1095
|
-
describe "#migrate_setting" do
|
1096
|
-
it "returns MIGRATE if auto_migrate is true" do
|
1097
|
-
expect(driver).to receive(:auto_migrate?).and_return(true)
|
1098
|
-
expect(driver.migrate_setting).to eq("MIGRATE")
|
1099
|
-
end
|
1100
|
-
|
1101
|
-
it "returns TERMINATE if auto_migrate is false" do
|
1102
|
-
expect(driver).to receive(:auto_migrate?).and_return(false)
|
1103
|
-
expect(driver.migrate_setting).to eq("TERMINATE")
|
1104
|
-
end
|
1105
|
-
end
|
1106
|
-
|
1107
|
-
describe "#instance_service_accounts" do
|
1108
|
-
it "returns nil if service_account_scopes is nil" do
|
1109
|
-
allow(driver).to receive(:config).and_return({})
|
1110
|
-
expect(driver.instance_service_accounts).to eq(nil)
|
1111
|
-
end
|
1112
|
-
|
1113
|
-
it "returns nil if service_account_scopes is empty" do
|
1114
|
-
allow(driver).to receive(:config).and_return(service_account_scopes: [])
|
1115
|
-
expect(driver.instance_service_accounts).to eq(nil)
|
1116
|
-
end
|
1117
|
-
|
1118
|
-
it "returns an array containing a properly-formatted service account" do
|
1119
|
-
service_account = double("service_account")
|
1120
|
-
|
1121
|
-
allow(driver).to receive(:config).and_return(service_account_name: "account_name", service_account_scopes: %w{scope1 scope2})
|
1122
|
-
expect(Google::Apis::ComputeV1::ServiceAccount).to receive(:new).and_return(service_account)
|
1123
|
-
expect(service_account).to receive(:email=).with("account_name")
|
1124
|
-
expect(driver).to receive(:service_account_scope_url).with("scope1").and_return("https://www.googleapis.com/auth/scope1")
|
1125
|
-
expect(driver).to receive(:service_account_scope_url).with("scope2").and_return("https://www.googleapis.com/auth/scope2")
|
1126
|
-
expect(service_account).to receive(:scopes=).with([
|
1127
|
-
"https://www.googleapis.com/auth/scope1",
|
1128
|
-
"https://www.googleapis.com/auth/scope2",
|
1129
|
-
])
|
1130
|
-
|
1131
|
-
expect(driver.instance_service_accounts).to eq([service_account])
|
1132
|
-
end
|
1133
|
-
end
|
1134
|
-
|
1135
|
-
describe "#service_account_scope_url" do
|
1136
|
-
it "returns the passed-in scope if it already looks like a scope URL" do
|
1137
|
-
scope = "https://www.googleapis.com/auth/fake_scope"
|
1138
|
-
expect(driver.service_account_scope_url(scope)).to eq(scope)
|
1139
|
-
end
|
1140
|
-
|
1141
|
-
it "returns a properly-formatted scope URL if a short-name or alias is provided" do
|
1142
|
-
expect(driver).to receive(:translate_scope_alias).with("scope_alias").and_return("real_scope")
|
1143
|
-
expect(driver.service_account_scope_url("scope_alias")).to eq("https://www.googleapis.com/auth/real_scope")
|
1144
|
-
end
|
1145
|
-
end
|
1146
|
-
|
1147
|
-
describe "#translate_scope_alias" do
|
1148
|
-
it "returns a scope for a given alias" do
|
1149
|
-
expect(driver.translate_scope_alias("storage-rw")).to eq("devstorage.read_write")
|
1150
|
-
end
|
1151
|
-
|
1152
|
-
it "returns the passed-in scope alias if nothing matches in the alias map" do
|
1153
|
-
expect(driver.translate_scope_alias("fake_scope")).to eq("fake_scope")
|
1154
|
-
end
|
1155
|
-
end
|
1156
|
-
|
1157
|
-
describe "#instance_tags" do
|
1158
|
-
it "returns a properly-formatted tags object" do
|
1159
|
-
tags_obj = double("tags_obj")
|
1160
|
-
|
1161
|
-
expect(driver).to receive(:config).and_return(tags: "test_tags")
|
1162
|
-
expect(Google::Apis::ComputeV1::Tags).to receive(:new).and_return(tags_obj)
|
1163
|
-
expect(tags_obj).to receive(:items=).with("test_tags")
|
1164
|
-
|
1165
|
-
expect(driver.instance_tags).to eq(tags_obj)
|
1166
|
-
end
|
1167
|
-
end
|
1168
|
-
|
1169
|
-
describe "#wait_time" do
|
1170
|
-
it "returns the configured wait time" do
|
1171
|
-
expect(driver).to receive(:config).and_return(wait_time: 123)
|
1172
|
-
expect(driver.wait_time).to eq(123)
|
1173
|
-
end
|
1174
|
-
end
|
1175
|
-
|
1176
|
-
describe "#refresh_rate" do
|
1177
|
-
it "returns the configured refresh rate" do
|
1178
|
-
expect(driver).to receive(:config).and_return(refresh_rate: 321)
|
1179
|
-
expect(driver.refresh_rate).to eq(321)
|
1180
|
-
end
|
1181
|
-
end
|
1182
|
-
|
1183
|
-
describe "#wait_for_status" do
|
1184
|
-
let(:item) { double("item") }
|
1185
|
-
|
1186
|
-
before do
|
1187
|
-
allow(driver).to receive(:wait_time).and_return(600)
|
1188
|
-
allow(driver).to receive(:refresh_rate).and_return(2)
|
1189
|
-
|
1190
|
-
# don"t actually sleep
|
1191
|
-
allow(driver).to receive(:sleep)
|
1192
|
-
end
|
1193
|
-
|
1194
|
-
context "when the items completes normally, 3 loops" do
|
1195
|
-
it "only refreshes the item 3 times" do
|
1196
|
-
allow(item).to receive(:status).exactly(3).times.and_return("PENDING", "RUNNING", "DONE")
|
1197
|
-
|
1198
|
-
driver.wait_for_status("DONE") { item }
|
1199
|
-
end
|
1200
|
-
end
|
1201
|
-
|
1202
|
-
context "when the item is completed on the first loop" do
|
1203
|
-
it "only refreshes the item 1 time" do
|
1204
|
-
allow(item).to receive(:status).once.and_return("DONE")
|
1205
|
-
|
1206
|
-
driver.wait_for_status("DONE") { item }
|
1207
|
-
end
|
1208
|
-
end
|
1209
|
-
|
1210
|
-
context "when the timeout is exceeded" do
|
1211
|
-
it "prints a warning and exits" do
|
1212
|
-
allow(Timeout).to receive(:timeout).and_raise(Timeout::Error)
|
1213
|
-
expect(driver).to receive(:error)
|
1214
|
-
.with("Request did not complete in 600 seconds. Check the Google Cloud Console for more info.")
|
1215
|
-
expect { driver.wait_for_status("DONE") { item } }.to raise_error(RuntimeError)
|
1216
|
-
end
|
1217
|
-
end
|
1218
|
-
|
1219
|
-
context "when a non-timeout exception is raised" do
|
1220
|
-
it "raises the original exception" do
|
1221
|
-
allow(item).to receive(:status).and_raise(NoMethodError)
|
1222
|
-
expect { driver.wait_for_status("DONE") { item } }.to raise_error(NoMethodError)
|
1223
|
-
end
|
1224
|
-
end
|
1225
|
-
end
|
1226
|
-
|
1227
|
-
describe "#wait_for_operation" do
|
1228
|
-
let(:operation) { double("operation", name: "operation-123") }
|
1229
|
-
|
1230
|
-
it "raises a properly-formatted exception when errors exist" do
|
1231
|
-
error1 = double("error1", code: "ERROR1", message: "error 1")
|
1232
|
-
error2 = double("error2", code: "ERROR2", message: "error 2")
|
1233
|
-
expect(driver).to receive(:wait_for_status).with("DONE")
|
1234
|
-
expect(driver).to receive(:operation_errors).with("operation-123").and_return([error1, error2])
|
1235
|
-
expect(driver).to receive(:error).with("ERROR1: error 1")
|
1236
|
-
expect(driver).to receive(:error).with("ERROR2: error 2")
|
1237
|
-
|
1238
|
-
expect { driver.wait_for_operation(operation) }.to raise_error(RuntimeError, "Operation operation-123 failed.")
|
1239
|
-
end
|
1240
|
-
|
1241
|
-
it "does not raise an exception if no errors are encountered" do
|
1242
|
-
expect(driver).to receive(:wait_for_status).with("DONE")
|
1243
|
-
expect(driver).to receive(:operation_errors).with("operation-123").and_return([])
|
1244
|
-
expect(driver).not_to receive(:error)
|
1245
|
-
|
1246
|
-
expect { driver.wait_for_operation(operation) }.not_to raise_error
|
1247
|
-
end
|
1248
|
-
end
|
1249
|
-
|
1250
|
-
describe "#zone_operation" do
|
1251
|
-
it "fetches the operation from the API and returns it" do
|
1252
|
-
connection = double("connection")
|
1253
|
-
expect(driver).to receive(:connection).and_return(connection)
|
1254
|
-
expect(connection).to receive(:get_zone_operation).with(project, zone, "operation-123").and_return("operation")
|
1255
|
-
expect(driver.zone_operation("operation-123")).to eq("operation")
|
1256
|
-
end
|
1257
|
-
end
|
1258
|
-
|
1259
|
-
describe "#operation_errors" do
|
1260
|
-
let(:operation) { double("operation") }
|
1261
|
-
let(:error_obj) { double("error_obj") }
|
1262
|
-
|
1263
|
-
before do
|
1264
|
-
expect(driver).to receive(:zone_operation).with("operation-123").and_return(operation)
|
1265
|
-
end
|
1266
|
-
|
1267
|
-
it "returns an empty array if there are no errors" do
|
1268
|
-
expect(operation).to receive(:error).and_return(nil)
|
1269
|
-
expect(driver.operation_errors("operation-123")).to eq([])
|
1270
|
-
end
|
1271
|
-
|
1272
|
-
it "returns the errors from the operation if they exist" do
|
1273
|
-
expect(operation).to receive(:error).twice.and_return(error_obj)
|
1274
|
-
expect(error_obj).to receive(:errors).and_return("some errors")
|
1275
|
-
expect(driver.operation_errors("operation-123")).to eq("some errors")
|
1276
|
-
end
|
1277
|
-
end
|
1278
|
-
end
|