kitchen-google 0.3.0 → 1.0.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/.travis.yml +1 -1
- data/CHANGELOG.md +14 -0
- data/Gemfile +3 -1
- data/README.md +215 -88
- data/Rakefile +9 -3
- data/kitchen-google.gemspec +22 -20
- data/lib/kitchen/driver/gce.rb +457 -97
- data/lib/kitchen/driver/gce_version.rb +23 -0
- data/spec/kitchen/driver/gce_spec.rb +839 -276
- data/spec/spec_helper.rb +2 -2
- metadata +36 -46
@@ -0,0 +1,23 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Chef Partner Engineering (<partnereng@chef.io>)
|
3
|
+
# Copyright:: Copyright (c) 2015 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
module Kitchen
|
20
|
+
module Driver
|
21
|
+
GCE_VERSION = "1.0.0".freeze
|
22
|
+
end
|
23
|
+
end
|
@@ -1,8 +1,9 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
#
|
3
3
|
# Author:: Andrew Leonard (<andy@hurricane-ridge.com>)
|
4
|
+
# Author:: Chef Partner Engineering (<partnereng@chef.io>)
|
4
5
|
#
|
5
|
-
# Copyright (C) 2013-
|
6
|
+
# Copyright (C) 2013-2016, Andrew Leonard and Chef Software, Inc.
|
6
7
|
#
|
7
8
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
9
|
# you may not use this file except in compliance with the License.
|
@@ -16,383 +17,945 @@
|
|
16
17
|
# See the License for the specific language governing permissions and
|
17
18
|
# limitations under the License.
|
18
19
|
|
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
|
20
32
|
|
21
|
-
|
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
|
22
42
|
|
23
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" }
|
24
52
|
|
25
53
|
let(:config) do
|
26
|
-
{
|
27
|
-
|
54
|
+
{
|
55
|
+
project: project,
|
56
|
+
zone: zone,
|
57
|
+
image_name: "test_image",
|
28
58
|
}
|
29
59
|
end
|
30
60
|
|
31
|
-
let(:
|
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
|
32
69
|
|
33
|
-
|
34
|
-
|
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
|
+
end
|
35
75
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
name: 'default-distro-12'
|
40
|
-
)
|
41
|
-
end
|
42
|
-
|
43
|
-
let(:driver) do
|
44
|
-
d = Kitchen::Driver::Gce.new(config)
|
45
|
-
allow(d).to receive(:instance) { instance }
|
46
|
-
allow(d).to receive(:wait_for_sshd) { true }
|
47
|
-
d
|
48
|
-
end
|
49
|
-
|
50
|
-
let(:fog) do
|
51
|
-
Fog::Compute::Google::Mock.new({})
|
52
|
-
end
|
53
|
-
|
54
|
-
let(:disk) do
|
55
|
-
fog.disks.create(
|
56
|
-
name: 'rspec-test-disk',
|
57
|
-
size_gb: 10,
|
58
|
-
zone_name: 'us-central1-b',
|
59
|
-
source_image: 'debian-7-wheezy-v20130816'
|
60
|
-
)
|
61
|
-
end
|
62
|
-
|
63
|
-
let(:server) do
|
64
|
-
fog.servers.create(
|
65
|
-
name: 'rspec-test-instance',
|
66
|
-
disks: [disk],
|
67
|
-
machine_type: 'n1-standard-1',
|
68
|
-
zone_name: 'us-central1-b'
|
69
|
-
)
|
70
|
-
end
|
71
|
-
|
72
|
-
before(:each) do
|
73
|
-
Fog.mock!
|
74
|
-
Fog::Mock.reset
|
75
|
-
Fog::Mock.delay = 0
|
76
|
-
end
|
77
|
-
|
78
|
-
describe '#initialize' do
|
79
|
-
context 'with default options' do
|
80
|
-
|
81
|
-
defaults = {
|
82
|
-
area: 'us-central1',
|
83
|
-
autodelete_disk: true,
|
84
|
-
disk_size: 10,
|
85
|
-
inst_name: nil,
|
86
|
-
machine_type: 'n1-standard-1',
|
87
|
-
network: 'default',
|
88
|
-
region: nil,
|
89
|
-
service_accounts: nil,
|
90
|
-
tags: [],
|
91
|
-
username: ENV['USER'],
|
92
|
-
zone_name: nil,
|
93
|
-
google_key_location: nil,
|
94
|
-
google_json_key_location: nil,
|
95
|
-
preemptible: false,
|
96
|
-
auto_restart: false
|
97
|
-
}
|
76
|
+
it "driver API version is 2" do
|
77
|
+
expect(driver.diagnose_plugin[:api_version]).to eq(2)
|
78
|
+
end
|
98
79
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
80
|
+
describe '#name' do
|
81
|
+
it "has an overridden name" do
|
82
|
+
expect(driver.name).to eq("Google Compute (GCE)")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe '#create' do
|
87
|
+
let(:connection) { double("connection") }
|
88
|
+
let(:operation) { double("operation", name: "test_operation") }
|
89
|
+
let(:state) { {} }
|
90
|
+
|
91
|
+
before do
|
92
|
+
allow(driver).to receive(:validate!)
|
93
|
+
allow(driver).to receive(:connection).and_return(connection)
|
94
|
+
allow(driver).to receive(:generate_server_name)
|
95
|
+
allow(driver).to receive(:wait_for_operation)
|
96
|
+
allow(driver).to receive(:server_instance)
|
97
|
+
allow(driver).to receive(:create_instance_object)
|
98
|
+
allow(driver).to receive(:ip_address_for)
|
99
|
+
allow(driver).to receive(:update_windows_password)
|
100
|
+
allow(driver).to receive(:wait_for_server)
|
101
|
+
allow(connection).to receive(:insert_instance).and_return(operation)
|
102
|
+
end
|
103
|
+
|
104
|
+
it "does not create the server if the hostname is in the state file" do
|
105
|
+
expect(connection).not_to receive(:insert_instance)
|
106
|
+
driver.create(server_name: "server_exists")
|
107
|
+
end
|
108
|
+
|
109
|
+
it "generates a unique server name and sets the state" do
|
110
|
+
expect(driver).to receive(:generate_server_name).and_return("server_1")
|
111
|
+
driver.create(state)
|
112
|
+
expect(state[:server_name]).to eq("server_1")
|
104
113
|
end
|
105
114
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
115
|
+
it "creates the instance via the API and waits for it to complete" do
|
116
|
+
expect(driver).to receive(:generate_server_name).and_return("server_1")
|
117
|
+
expect(driver).to receive(:create_instance_object).with("server_1").and_return("create_obj")
|
118
|
+
expect(connection).to receive(:insert_instance).with("test_project", "test_zone", "create_obj").and_return(operation)
|
119
|
+
expect(driver).to receive(:wait_for_operation).with(operation)
|
120
|
+
|
121
|
+
driver.create(state)
|
122
|
+
end
|
123
|
+
|
124
|
+
it "sets the correct data in the state object" do
|
125
|
+
expect(driver).to receive(:generate_server_name).and_return("server_1")
|
126
|
+
expect(driver).to receive(:server_instance).with("server_1").and_return("server_obj")
|
127
|
+
expect(driver).to receive(:ip_address_for).with("server_obj").and_return("1.2.3.4")
|
128
|
+
driver.create(state)
|
129
|
+
|
130
|
+
expect(state[:server_name]).to eq("server_1")
|
131
|
+
expect(state[:hostname]).to eq("1.2.3.4")
|
132
|
+
expect(state[:zone]).to eq("test_zone")
|
133
|
+
end
|
134
|
+
|
135
|
+
it "updates the windows password" do
|
136
|
+
expect(driver).to receive(:generate_server_name).and_return("server_1")
|
137
|
+
expect(driver).to receive(:update_windows_password).with("server_1")
|
138
|
+
driver.create(state)
|
139
|
+
end
|
140
|
+
|
141
|
+
it "waits for the server to be ready" do
|
142
|
+
expect(driver).to receive(:wait_for_server)
|
143
|
+
driver.create(state)
|
144
|
+
end
|
145
|
+
|
146
|
+
it "destroys the server if any exceptions are raised" do
|
147
|
+
expect(connection).to receive(:insert_instance).and_raise(RuntimeError)
|
148
|
+
expect(driver).to receive(:destroy).with(state)
|
149
|
+
expect { driver.create(state) }.to raise_error(RuntimeError)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe '#destroy' do
|
154
|
+
let(:connection) { double("connection") }
|
155
|
+
let(:state) { { server_name: "server_1", hostname: "test_host", zone: "test_zone" } }
|
156
|
+
|
157
|
+
before do
|
158
|
+
allow(driver).to receive(:connection).and_return(connection)
|
159
|
+
allow(driver).to receive(:server_exist?).and_return(true)
|
160
|
+
allow(driver).to receive(:wait_for_operation)
|
161
|
+
allow(connection).to receive(:delete_instance)
|
162
|
+
end
|
163
|
+
|
164
|
+
it "does not attempt to delete the instance if there is no server_name" do
|
165
|
+
expect(connection).not_to receive(:delete_instance)
|
166
|
+
driver.destroy({})
|
167
|
+
end
|
168
|
+
|
169
|
+
it "does not attempt to delete the instance if it does not exist" do
|
170
|
+
expect(driver).to receive(:server_exist?).with("server_1").and_return(false)
|
171
|
+
expect(connection).not_to receive(:delete_instance)
|
172
|
+
driver.destroy(state)
|
173
|
+
end
|
174
|
+
|
175
|
+
it "deletes the instance via the API and waits for it to complete" do
|
176
|
+
expect(connection).to receive(:delete_instance).with("test_project", "test_zone", "server_1").and_return("operation")
|
177
|
+
expect(driver).to receive(:wait_for_operation).with("operation")
|
178
|
+
driver.destroy(state)
|
179
|
+
end
|
180
|
+
|
181
|
+
it "deletes the state keys" do
|
182
|
+
driver.destroy(state)
|
183
|
+
expect(state.key?(:server_name)).to eq(false)
|
184
|
+
expect(state.key?(:hostname)).to eq(false)
|
185
|
+
expect(state.key?(:zone)).to eq(false)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
describe '#validate!' do
|
190
|
+
let(:config) do
|
191
|
+
{
|
192
|
+
project: "test_project",
|
193
|
+
zone: "test_zone",
|
194
|
+
machine_type: "test_machine_type",
|
195
|
+
disk_type: "test_disk_type",
|
196
|
+
network: "test_network",
|
123
197
|
}
|
198
|
+
end
|
124
199
|
|
125
|
-
|
200
|
+
before do
|
201
|
+
allow(driver).to receive(:valid_project?).and_return(true)
|
202
|
+
allow(driver).to receive(:valid_zone?).and_return(true)
|
203
|
+
allow(driver).to receive(:valid_region?).and_return(true)
|
204
|
+
allow(driver).to receive(:valid_machine_type?).and_return(true)
|
205
|
+
allow(driver).to receive(:valid_disk_type?).and_return(true)
|
206
|
+
allow(driver).to receive(:valid_network?).and_return(true)
|
207
|
+
allow(driver).to receive(:winrm_transport?).and_return(false)
|
208
|
+
allow(driver).to receive(:config).and_return(config)
|
209
|
+
end
|
126
210
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
211
|
+
it "does not raise an exception when all validations are successful" do
|
212
|
+
expect { driver.validate! }.not_to raise_error
|
213
|
+
end
|
214
|
+
|
215
|
+
context "when neither zone nor region are specified" do
|
216
|
+
let(:config) { {} }
|
217
|
+
it "raises an exception" do
|
218
|
+
expect { driver.validate! }.to raise_error(RuntimeError, "Either zone or region must be specified")
|
131
219
|
end
|
132
220
|
end
|
133
|
-
end
|
134
221
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
222
|
+
context "when zone and region are both set" do
|
223
|
+
let(:config) { { zone: "test_zone", region: "test_region" } }
|
224
|
+
|
225
|
+
it "warns the user that the region will be ignored" do
|
226
|
+
expect(driver).to receive(:warn).with("Both zone and region specified - region will be ignored.")
|
227
|
+
driver.validate!
|
139
228
|
end
|
229
|
+
end
|
140
230
|
|
141
|
-
|
142
|
-
|
143
|
-
|
231
|
+
context "when region is set to 'any'" do
|
232
|
+
let(:config) { { region: "any" } }
|
233
|
+
it "raises an exception" do
|
234
|
+
expect { driver.validate! }.to raise_error(RuntimeError, "'any' is no longer a valid region")
|
144
235
|
end
|
145
236
|
end
|
146
237
|
|
147
|
-
context
|
148
|
-
let(:config) {
|
238
|
+
context "when zone is set" do
|
239
|
+
let(:config) { { zone: "test_zone" } }
|
149
240
|
|
150
|
-
it
|
151
|
-
expect
|
241
|
+
it "raises an exception if the zone is not valid" do
|
242
|
+
expect(driver).to receive(:valid_zone?).and_return(false)
|
243
|
+
expect { driver.validate! }.to raise_error(RuntimeError, "Zone test_zone is not a valid zone")
|
152
244
|
end
|
153
245
|
end
|
154
|
-
end
|
155
246
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
247
|
+
context "when region is set" do
|
248
|
+
let(:config) { { region: "test_region" } }
|
249
|
+
|
250
|
+
it "raises an exception if the region is not valid" do
|
251
|
+
expect(driver).to receive(:valid_region?).and_return(false)
|
252
|
+
expect { driver.validate! }.to raise_error(RuntimeError, "Region test_region is not a valid region")
|
162
253
|
end
|
254
|
+
end
|
255
|
+
|
256
|
+
it "raises an exception if the project is invalid" do
|
257
|
+
expect(driver).to receive(:valid_project?).and_return(false)
|
258
|
+
expect { driver.validate! }.to raise_error(RuntimeError, "Project test_project is not a valid project")
|
259
|
+
end
|
260
|
+
|
261
|
+
it "raises an exception if the machine_type is invalid" do
|
262
|
+
expect(driver).to receive(:valid_machine_type?).and_return(false)
|
263
|
+
expect { driver.validate! }.to raise_error(RuntimeError, "Machine type test_machine_type is not valid")
|
264
|
+
end
|
163
265
|
|
164
|
-
|
165
|
-
|
266
|
+
it "raises an exception if the disk_type is invalid" do
|
267
|
+
expect(driver).to receive(:valid_disk_type?).and_return(false)
|
268
|
+
expect { driver.validate! }.to raise_error(RuntimeError, "Disk type test_disk_type is not valid")
|
269
|
+
end
|
270
|
+
|
271
|
+
it "raises an exception if the network is invalid" do
|
272
|
+
expect(driver).to receive(:valid_network?).and_return(false)
|
273
|
+
expect { driver.validate! }.to raise_error(RuntimeError, "Network test_network is not valid")
|
274
|
+
end
|
275
|
+
|
276
|
+
it "raises an exception if WinRM transport is used but no email is set" do
|
277
|
+
expect(driver).to receive(:winrm_transport?).and_return(true)
|
278
|
+
expect { driver.validate! }.to raise_error(RuntimeError, "Email address of GCE user is not set")
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
describe '#connection' do
|
283
|
+
it "returns a properly configured ComputeService" do
|
284
|
+
compute_service = double("compute_service")
|
285
|
+
client_options = double("client_options")
|
286
|
+
|
287
|
+
expect(Google::Apis::ClientOptions).to receive(:new).and_return(client_options)
|
288
|
+
expect(client_options).to receive(:application_name=).with("kitchen-google")
|
289
|
+
expect(client_options).to receive(:application_version=).with(Kitchen::Driver::GCE_VERSION)
|
290
|
+
|
291
|
+
expect(Google::Apis::ComputeV1::ComputeService).to receive(:new).and_return(compute_service)
|
292
|
+
expect(driver).to receive(:authorization).and_return("authorization_object")
|
293
|
+
expect(compute_service).to receive(:authorization=).with("authorization_object")
|
294
|
+
expect(compute_service).to receive(:client_options=).with(client_options)
|
295
|
+
|
296
|
+
expect(driver.connection).to eq(compute_service)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
describe '#authorization' do
|
301
|
+
it "returns a Google::Auth authorization object" do
|
302
|
+
auth_object = double("auth_object")
|
303
|
+
expect(Google::Auth).to receive(:get_application_default).and_return(auth_object)
|
304
|
+
expect(driver.authorization).to eq(auth_object)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
describe '#winrm_transport?' do
|
309
|
+
it "returns true if the transport name is Winrm" do
|
310
|
+
expect(transport).to receive(:name).and_return("Winrm")
|
311
|
+
expect(driver.winrm_transport?).to eq(true)
|
312
|
+
end
|
313
|
+
|
314
|
+
it "returns false if the transport name is not Winrm" do
|
315
|
+
expect(transport).to receive(:name).and_return("Ssh")
|
316
|
+
expect(driver.winrm_transport?).to eq(false)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
describe '#update_windows_password' do
|
321
|
+
it "does not attempt to reset the password if the transport is not WinRM" do
|
322
|
+
expect(driver).to receive(:winrm_transport?).and_return(false)
|
323
|
+
expect(GoogleComputeWindowsPassword).not_to receive(:new)
|
324
|
+
|
325
|
+
driver.update_windows_password("server_1")
|
326
|
+
end
|
327
|
+
|
328
|
+
it "resets the password and puts it in the state object if the transport is WinRM" do
|
329
|
+
state = {}
|
330
|
+
winpass = double("winpass")
|
331
|
+
winpass_config = {
|
332
|
+
project: "test_project",
|
333
|
+
zone: "test_zone",
|
334
|
+
instance_name: "server_1",
|
335
|
+
email: "test_email",
|
336
|
+
username: "test_username",
|
337
|
+
}
|
338
|
+
|
339
|
+
allow(driver).to receive(:state).and_return(state)
|
340
|
+
expect(transport).to receive(:config).and_return(username: "test_username")
|
341
|
+
expect(driver).to receive(:config).and_return(email: "test_email")
|
342
|
+
expect(driver).to receive(:winrm_transport?).and_return(true)
|
343
|
+
expect(GoogleComputeWindowsPassword).to receive(:new).with(winpass_config).and_return(winpass)
|
344
|
+
expect(winpass).to receive(:new_password).and_return("password123")
|
345
|
+
driver.update_windows_password("server_1")
|
346
|
+
expect(state[:password]).to eq("password123")
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
describe '#check_api_call' do
|
351
|
+
it "returns false and logs a debug message if the block raises a ClientError" do
|
352
|
+
expect(driver).to receive(:debug).with("API error: whoops")
|
353
|
+
expect(driver.check_api_call { raise Google::Apis::ClientError.new("whoops") }).to eq(false)
|
354
|
+
end
|
355
|
+
|
356
|
+
it "raises an exception if the block raises something other than a ClientError" do
|
357
|
+
expect { driver.check_api_call { raise RuntimeError.new("whoops") } }.to raise_error(RuntimeError)
|
358
|
+
end
|
359
|
+
|
360
|
+
it "returns true if the block does not raise an exception" do
|
361
|
+
expect(driver.check_api_call { true }).to eq(true)
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
describe '#valid_machine_type?' do
|
366
|
+
subject { driver.valid_machine_type? }
|
367
|
+
it_behaves_like "a validity checker", :machine_type, :get_machine_type, "test_project", "test_zone"
|
368
|
+
end
|
369
|
+
|
370
|
+
describe '#valid_network?' do
|
371
|
+
subject { driver.valid_network? }
|
372
|
+
it_behaves_like "a validity checker", :network, :get_network, "test_project"
|
373
|
+
end
|
374
|
+
|
375
|
+
describe '#valid_zone?' do
|
376
|
+
subject { driver.valid_zone? }
|
377
|
+
it_behaves_like "a validity checker", :zone, :get_zone, "test_project"
|
378
|
+
end
|
379
|
+
|
380
|
+
describe '#valid_region?' do
|
381
|
+
subject { driver.valid_region? }
|
382
|
+
it_behaves_like "a validity checker", :region, :get_region, "test_project"
|
383
|
+
end
|
384
|
+
|
385
|
+
describe '#valid_disk_type?' do
|
386
|
+
subject { driver.valid_disk_type? }
|
387
|
+
it_behaves_like "a validity checker", :disk_type, :get_disk_type, "test_project", "test_zone"
|
388
|
+
end
|
389
|
+
|
390
|
+
describe '#image_exist?' do
|
391
|
+
it "checks the outcome of the API call" do
|
392
|
+
connection = double("connection")
|
393
|
+
expect(driver).to receive(:connection).and_return(connection)
|
394
|
+
expect(connection).to receive(:get_image).with("image_project", "image_name")
|
395
|
+
expect(driver).to receive(:check_api_call).and_call_original
|
396
|
+
expect(driver.image_exist?("image_project", "image_name")).to eq(true)
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
describe '#server_exist?' do
|
401
|
+
it "checks the outcome of the API call" do
|
402
|
+
expect(driver).to receive(:server_instance).with("server_1")
|
403
|
+
expect(driver).to receive(:check_api_call).and_call_original
|
404
|
+
expect(driver.server_exist?("server_1")).to eq(true)
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
describe '#project' do
|
409
|
+
it "returns the project from the config" do
|
410
|
+
allow(driver).to receive(:project).and_call_original
|
411
|
+
expect(driver).to receive(:config).and_return(project: "my_project")
|
412
|
+
expect(driver.project).to eq("my_project")
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
describe '#region' do
|
417
|
+
it "returns the region from the config" do
|
418
|
+
expect(driver).to receive(:config).and_return(region: "my_region")
|
419
|
+
expect(driver.region).to eq("my_region")
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
describe '#zone' do
|
424
|
+
before do
|
425
|
+
allow(driver).to receive(:zone).and_call_original
|
426
|
+
end
|
427
|
+
|
428
|
+
context "when a zone exists in the state" do
|
429
|
+
let(:state) { { zone: "state_zone" } }
|
430
|
+
|
431
|
+
it "returns the zone from the state" do
|
432
|
+
expect(driver).to receive(:state).and_return(state)
|
433
|
+
expect(driver.zone).to eq("state_zone")
|
166
434
|
end
|
167
435
|
end
|
168
436
|
|
169
|
-
context
|
437
|
+
context "when a zone does not exist in the state" do
|
438
|
+
let(:state) { {} }
|
170
439
|
|
171
|
-
|
172
|
-
|
173
|
-
allow(d).to receive(:create_instance) { server }
|
174
|
-
allow(d).to receive(:wait_for_up_instance) { nil }
|
175
|
-
d
|
440
|
+
before do
|
441
|
+
allow(driver).to receive(:state).and_return(state)
|
176
442
|
end
|
177
443
|
|
178
|
-
it
|
179
|
-
driver.
|
180
|
-
expect(
|
444
|
+
it "returns the zone from the config if it exists" do
|
445
|
+
expect(driver).to receive(:config).and_return(zone: "config_zone")
|
446
|
+
expect(driver.zone).to eq("config_zone")
|
181
447
|
end
|
182
448
|
|
183
|
-
it
|
184
|
-
expect(driver.
|
449
|
+
it "returns the zone from find_zone if it does not exist in the config" do
|
450
|
+
expect(driver).to receive(:config).and_return({})
|
451
|
+
expect(driver).to receive(:find_zone).and_return("found_zone")
|
452
|
+
expect(driver.zone).to eq("found_zone")
|
185
453
|
end
|
186
454
|
end
|
187
455
|
end
|
188
456
|
|
189
|
-
describe '#
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
config[:zone_name] = 'us-central1-a'
|
195
|
-
expect(driver.send(:create_disk)).to be_a(Fog::Compute::Google::Disk)
|
196
|
-
end
|
457
|
+
describe '#find_zone' do
|
458
|
+
let(:zones_in_region) { double("zones_in_region") }
|
459
|
+
|
460
|
+
before do
|
461
|
+
expect(driver).to receive(:zones_in_region).and_return(zones_in_region)
|
197
462
|
end
|
198
463
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
464
|
+
it "returns a random zone from the list of zones in the region" do
|
465
|
+
zone = double("zone", name: "random_zone")
|
466
|
+
expect(zones_in_region).to receive(:sample).and_return(zone)
|
467
|
+
expect(driver.find_zone).to eq("random_zone")
|
468
|
+
end
|
469
|
+
|
470
|
+
it "raises an exception if no zones are found" do
|
471
|
+
expect(zones_in_region).to receive(:sample).and_return(nil)
|
472
|
+
expect(driver).to receive(:region).and_return("test_region")
|
473
|
+
expect { driver.find_zone }.to raise_error(RuntimeError, "Unable to find a suitable zone in test_region")
|
204
474
|
end
|
205
475
|
end
|
206
476
|
|
207
|
-
describe '#
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
477
|
+
describe '#zones_in_region' do
|
478
|
+
it "returns a correct list of available zones" do
|
479
|
+
zone1 = double("zone1", status: "UP", region: "a/b/c/test_region")
|
480
|
+
zone2 = double("zone2", status: "UP", region: "a/b/c/test_region")
|
481
|
+
zone3 = double("zone3", status: "DOWN", region: "a/b/c/test_region")
|
482
|
+
zone4 = double("zone4", status: "UP", region: "a/b/c/wrong_region")
|
483
|
+
zone5 = double("zone5", status: "UP", region: "a/b/c/test_region")
|
484
|
+
connection = double("connection")
|
485
|
+
response = double("response", items: [zone1, zone2, zone3, zone4, zone5])
|
486
|
+
|
487
|
+
allow(driver).to receive(:region).and_return("test_region")
|
488
|
+
expect(driver).to receive(:connection).and_return(connection)
|
489
|
+
expect(connection).to receive(:list_zones).and_return(response)
|
490
|
+
expect(driver.zones_in_region).to eq([zone1, zone2, zone5])
|
491
|
+
end
|
492
|
+
end
|
213
493
|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
494
|
+
describe '#server_instance' do
|
495
|
+
it "returns the instance from the API" do
|
496
|
+
connection = double("connection")
|
497
|
+
expect(driver).to receive(:connection).and_return(connection)
|
498
|
+
expect(connection).to receive(:get_instance).with("test_project", "test_zone", "server_1").and_return("instance")
|
499
|
+
expect(driver.server_instance("server_1")).to eq("instance")
|
218
500
|
end
|
501
|
+
end
|
219
502
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
}
|
227
|
-
end
|
503
|
+
describe '#ip_address_for' do
|
504
|
+
it "returns the private IP if use_private_ip is true" do
|
505
|
+
expect(driver).to receive(:config).and_return(use_private_ip: true)
|
506
|
+
expect(driver).to receive(:private_ip_for).with("server").and_return("1.2.3.4")
|
507
|
+
expect(driver.ip_address_for("server")).to eq("1.2.3.4")
|
508
|
+
end
|
228
509
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
510
|
+
it "returns the public IP if use_private_ip is false" do
|
511
|
+
expect(driver).to receive(:config).and_return(use_private_ip: false)
|
512
|
+
expect(driver).to receive(:public_ip_for).with("server").and_return("4.3.2.1")
|
513
|
+
expect(driver.ip_address_for("server")).to eq("4.3.2.1")
|
233
514
|
end
|
515
|
+
end
|
234
516
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
google_key_location: '/home/user/gce/123456-privatekey.p12',
|
240
|
-
google_project: 'alpha-bravo-123',
|
241
|
-
region: 'europe-west1'
|
242
|
-
}
|
243
|
-
end
|
517
|
+
describe '#private_ip_for' do
|
518
|
+
it "returns the IP address if it exists" do
|
519
|
+
network_interface = double("network_interface", network_ip: "1.2.3.4")
|
520
|
+
server = double("server", network_interfaces: [network_interface])
|
244
521
|
|
245
|
-
|
246
|
-
|
247
|
-
expect(config[:region]).to eq('europe-west1')
|
248
|
-
end
|
522
|
+
expect(driver.private_ip_for(server)).to eq("1.2.3.4")
|
523
|
+
end
|
249
524
|
|
525
|
+
it "raises an exception if the IP cannot be found" do
|
526
|
+
server = double("server")
|
527
|
+
|
528
|
+
expect(server).to receive(:network_interfaces).and_raise(NoMethodError)
|
529
|
+
expect { driver.private_ip_for(server) }.to raise_error(RuntimeError, "Unable to determine private IP for instance")
|
250
530
|
end
|
251
531
|
end
|
252
532
|
|
253
|
-
describe '#
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
533
|
+
describe '#public_ip_for' do
|
534
|
+
it "returns the IP address if it exists" do
|
535
|
+
access_config = double("access_config", nat_ip: "4.3.2.1")
|
536
|
+
network_interface = double("network_interface", access_configs: [access_config])
|
537
|
+
server = double("server", network_interfaces: [network_interface])
|
538
|
+
|
539
|
+
expect(driver.public_ip_for(server)).to eq("4.3.2.1")
|
540
|
+
end
|
541
|
+
|
542
|
+
it "raises an exception if the IP cannot be found" do
|
543
|
+
network_interface = double("network_interface")
|
544
|
+
server = double("server", network_interfaces: [network_interface])
|
545
|
+
|
546
|
+
expect(network_interface).to receive(:access_configs).and_raise(NoMethodError)
|
547
|
+
expect { driver.public_ip_for(server) }.to raise_error(RuntimeError, "Unable to determine public IP for instance")
|
259
548
|
end
|
260
549
|
end
|
261
550
|
|
262
|
-
describe '#
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
s
|
551
|
+
describe '#generate_server_name' do
|
552
|
+
it "generates and returns a server name" do
|
553
|
+
expect(instance).to receive(:name).and_return("ABC123")
|
554
|
+
expect(SecureRandom).to receive(:hex).with(3).and_return("abcdef")
|
555
|
+
expect(driver.generate_server_name).to eq("tk-abc123-abcdef")
|
268
556
|
end
|
269
557
|
|
270
|
-
it
|
271
|
-
expect(
|
558
|
+
it "uses a UUID-based server name if the instance name is too long" do
|
559
|
+
expect(instance).to receive(:name).twice.and_return("123456789012345678901234567890123456789012345678901235467890")
|
560
|
+
expect(driver).to receive(:warn)
|
561
|
+
expect(SecureRandom).to receive(:hex).with(3).and_return("abcdef")
|
562
|
+
expect(SecureRandom).to receive(:uuid).and_return("lmnop")
|
563
|
+
expect(driver.generate_server_name).to eq("tk-lmnop")
|
272
564
|
end
|
565
|
+
end
|
273
566
|
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
567
|
+
describe '#boot_disk' do
|
568
|
+
it "sets up a disk object and returns it" do
|
569
|
+
disk = double("disk")
|
570
|
+
params = double("params")
|
571
|
+
|
572
|
+
config = {
|
573
|
+
autodelete_disk: "auto_delete",
|
574
|
+
disk_size: "test_size",
|
575
|
+
disk_type: "test_type",
|
576
|
+
}
|
577
|
+
|
578
|
+
allow(driver).to receive(:config).and_return(config)
|
579
|
+
expect(driver).to receive(:disk_type_url_for).with("test_type").and_return("disk_url")
|
580
|
+
expect(driver).to receive(:disk_image_url).and_return("disk_image_url")
|
581
|
+
|
582
|
+
expect(Google::Apis::ComputeV1::AttachedDisk).to receive(:new).and_return(disk)
|
583
|
+
expect(Google::Apis::ComputeV1::AttachedDiskInitializeParams).to receive(:new).and_return(params)
|
584
|
+
expect(disk).to receive(:boot=).with(true)
|
585
|
+
expect(disk).to receive(:auto_delete=).with("auto_delete")
|
586
|
+
expect(disk).to receive(:initialize_params=).with(params)
|
587
|
+
expect(params).to receive(:disk_name=).with("server_1")
|
588
|
+
expect(params).to receive(:disk_size_gb=).with("test_size")
|
589
|
+
expect(params).to receive(:disk_type=).with("disk_url")
|
590
|
+
expect(params).to receive(:source_image=).with("disk_image_url")
|
591
|
+
|
592
|
+
expect(driver.boot_disk("server_1")).to eq(disk)
|
278
593
|
end
|
279
594
|
end
|
280
595
|
|
281
|
-
describe '#
|
282
|
-
|
283
|
-
|
284
|
-
expect(driver.send(:generate_inst_name)).to match(
|
285
|
-
/^default-distro-12-[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$/)
|
286
|
-
end
|
596
|
+
describe '#disk_type_url_for' do
|
597
|
+
it "returns a disk URL" do
|
598
|
+
expect(driver.disk_type_url_for("my_type")).to eq("zones/test_zone/diskTypes/my_type")
|
287
599
|
end
|
600
|
+
end
|
288
601
|
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
602
|
+
describe '#disk_image_url' do
|
603
|
+
before do
|
604
|
+
allow(driver).to receive(:config).and_return(config)
|
605
|
+
end
|
293
606
|
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
607
|
+
context "when the user supplies an image project" do
|
608
|
+
let(:config) { { image_project: "my_image_project", image_name: "my_image" } }
|
609
|
+
|
610
|
+
it "returns the image URL based on the image project" do
|
611
|
+
expect(driver).to receive(:image_url_for).with("my_image_project", "my_image").and_return("image_url")
|
612
|
+
expect(driver.disk_image_url).to eq("image_url")
|
299
613
|
end
|
300
614
|
end
|
301
615
|
|
302
|
-
context
|
303
|
-
let(:
|
304
|
-
|
616
|
+
context "when the user does not supply an image project" do
|
617
|
+
let(:config) { { image_name: "my_image" } }
|
618
|
+
|
619
|
+
context "when the image exists in the user's project" do
|
620
|
+
it "returns the image URL" do
|
621
|
+
expect(driver).to receive(:image_url_for).with(project, "my_image").and_return("image_url")
|
622
|
+
expect(driver.disk_image_url).to eq("image_url")
|
623
|
+
end
|
305
624
|
end
|
306
625
|
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
626
|
+
context "when the image does not exist in the user's project" do
|
627
|
+
before do
|
628
|
+
expect(driver).to receive(:image_url_for).with(project, "my_image").and_return(nil)
|
629
|
+
end
|
630
|
+
|
631
|
+
context "when the image matches a known public project" do
|
632
|
+
it "returns the image URL from the public project" do
|
633
|
+
expect(driver).to receive(:public_project_for_image).with("my_image").and_return("public_project")
|
634
|
+
expect(driver).to receive(:image_url_for).with("public_project", "my_image").and_return("image_url")
|
635
|
+
expect(driver.disk_image_url).to eq("image_url")
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
context "when the image does not match a known project" do
|
640
|
+
it "returns nil" do
|
641
|
+
expect(driver).to receive(:public_project_for_image).with("my_image").and_return(nil)
|
642
|
+
expect(driver).not_to receive(:image_url_for)
|
643
|
+
expect(driver.disk_image_url).to eq(nil)
|
644
|
+
end
|
645
|
+
end
|
311
646
|
end
|
312
647
|
end
|
648
|
+
end
|
313
649
|
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
650
|
+
describe '#image_url_for' do
|
651
|
+
it "returns nil if the image does not exist" do
|
652
|
+
expect(driver).to receive(:image_exist?).with("image_project", "image_name").and_return(false)
|
653
|
+
expect(driver.image_url_for("image_project", "image_name")).to eq(nil)
|
654
|
+
end
|
318
655
|
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
-[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$/x)
|
323
|
-
end
|
656
|
+
it "returns a properly formatted image URL if the image exists" do
|
657
|
+
expect(driver).to receive(:image_exist?).with("image_project", "image_name").and_return(true)
|
658
|
+
expect(driver.image_url_for("image_project", "image_name")).to eq("projects/image_project/global/images/image_name")
|
324
659
|
end
|
660
|
+
end
|
325
661
|
|
326
|
-
|
327
|
-
|
328
|
-
|
662
|
+
describe '#public_project_for_image' do
|
663
|
+
{
|
664
|
+
"centos" => "centos-cloud",
|
665
|
+
"container-vm" => "google-containers",
|
666
|
+
"coreos" => "coreos-cloud",
|
667
|
+
"debian" => "debian-cloud",
|
668
|
+
"opensuse-cloud" => "opensuse-cloud",
|
669
|
+
"rhel" => "rhel-cloud",
|
670
|
+
"sles" => "suse-cloud",
|
671
|
+
"ubuntu" => "ubuntu-os-cloud",
|
672
|
+
"windows" => "windows-cloud",
|
673
|
+
}.each do |image_name, project_name|
|
674
|
+
it "returns project #{project_name} for an image named #{image_name}" do
|
675
|
+
expect(driver.public_project_for_image(image_name)).to eq(project_name)
|
329
676
|
end
|
677
|
+
end
|
678
|
+
end
|
330
679
|
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
end
|
680
|
+
describe '#machine_type_url' do
|
681
|
+
it "returns a machine type URL" do
|
682
|
+
expect(driver).to receive(:config).and_return(machine_type: "machine_type")
|
683
|
+
expect(driver.machine_type_url).to eq("zones/test_zone/machineTypes/machine_type")
|
336
684
|
end
|
337
685
|
end
|
338
686
|
|
339
|
-
describe '#
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
687
|
+
describe '#instance_metadata' do
|
688
|
+
it "returns a properly-configured metadata object" do
|
689
|
+
item1 = double("item1")
|
690
|
+
item2 = double("item2")
|
691
|
+
item3 = double("item3")
|
692
|
+
metadata = double("metadata")
|
693
|
+
|
694
|
+
expect(instance).to receive(:name).and_return("instance_name")
|
695
|
+
expect(driver).to receive(:env_user).and_return("env_user")
|
696
|
+
expect(Google::Apis::ComputeV1::Metadata).to receive(:new).and_return(metadata)
|
697
|
+
expect(Google::Apis::ComputeV1::Metadata::Item).to receive(:new).and_return(item1)
|
698
|
+
expect(Google::Apis::ComputeV1::Metadata::Item).to receive(:new).and_return(item2)
|
699
|
+
expect(Google::Apis::ComputeV1::Metadata::Item).to receive(:new).and_return(item3)
|
700
|
+
expect(item1).to receive(:key=).with("created-by")
|
701
|
+
expect(item1).to receive(:value=).with("test-kitchen")
|
702
|
+
expect(item2).to receive(:key=).with("test-kitchen-instance")
|
703
|
+
expect(item2).to receive(:value=).with("instance_name")
|
704
|
+
expect(item3).to receive(:key=).with("test-kitchen-user")
|
705
|
+
expect(item3).to receive(:value=).with("env_user")
|
706
|
+
expect(metadata).to receive(:items=).with([item1, item2, item3])
|
707
|
+
|
708
|
+
expect(driver.instance_metadata).to eq(metadata)
|
709
|
+
end
|
710
|
+
end
|
348
711
|
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
712
|
+
describe '#env_user' do
|
713
|
+
it "returns the current user from the environment" do
|
714
|
+
expect(ENV).to receive(:[]).with("USER").and_return("test_user")
|
715
|
+
expect(driver.env_user).to eq("test_user")
|
716
|
+
end
|
717
|
+
|
718
|
+
it "returns 'unknown' if there is no USER present" do
|
719
|
+
expect(ENV).to receive(:[]).with("USER").and_return(nil)
|
720
|
+
expect(driver.env_user).to eq("unknown")
|
721
|
+
end
|
722
|
+
end
|
723
|
+
|
724
|
+
describe '#instance_network_interfaces' do
|
725
|
+
it "returns an array containing a properly-formatted interface" do
|
726
|
+
interface = double("interface")
|
727
|
+
|
728
|
+
expect(driver).to receive(:network_url).and_return("network_url")
|
729
|
+
expect(driver).to receive(:interface_access_configs).and_return("access_configs")
|
730
|
+
|
731
|
+
expect(Google::Apis::ComputeV1::NetworkInterface).to receive(:new).and_return(interface)
|
732
|
+
expect(interface).to receive(:network=).with("network_url")
|
733
|
+
expect(interface).to receive(:access_configs=).with("access_configs")
|
734
|
+
|
735
|
+
expect(driver.instance_network_interfaces).to eq([interface])
|
736
|
+
end
|
737
|
+
end
|
738
|
+
|
739
|
+
describe '#network_url' do
|
740
|
+
it "returns a network URL" do
|
741
|
+
expect(driver).to receive(:config).and_return(network: "test_network")
|
742
|
+
expect(driver.network_url).to eq("projects/test_project/global/networks/test_network")
|
743
|
+
end
|
744
|
+
end
|
745
|
+
|
746
|
+
describe '#interface_access_configs' do
|
747
|
+
it "returns a properly-configured access config object" do
|
748
|
+
access_config = double("access_config")
|
749
|
+
|
750
|
+
expect(driver).to receive(:config).and_return({})
|
751
|
+
expect(Google::Apis::ComputeV1::AccessConfig).to receive(:new).and_return(access_config)
|
752
|
+
expect(access_config).to receive(:name=).with("External NAT")
|
753
|
+
expect(access_config).to receive(:type=).with("ONE_TO_ONE_NAT")
|
754
|
+
|
755
|
+
expect(driver.interface_access_configs).to eq([access_config])
|
756
|
+
end
|
757
|
+
|
758
|
+
it "returns an empty array if use_private_ip is true" do
|
759
|
+
expect(driver).to receive(:config).and_return(use_private_ip: true)
|
760
|
+
expect(driver.interface_access_configs).to eq([])
|
761
|
+
end
|
762
|
+
end
|
763
|
+
|
764
|
+
describe '#instance_scheduling' do
|
765
|
+
it "returns a properly-configured scheduling object" do
|
766
|
+
scheduling = double("scheduling")
|
767
|
+
|
768
|
+
allow(driver).to receive(:config).and_return(auto_restart: true, preemptible: false)
|
769
|
+
expect(driver).to receive(:migrate_setting).and_return("host_maintenance")
|
770
|
+
expect(Google::Apis::ComputeV1::Scheduling).to receive(:new).and_return(scheduling)
|
771
|
+
expect(scheduling).to receive(:automatic_restart=).with("true")
|
772
|
+
expect(scheduling).to receive(:preemptible=).with("false")
|
773
|
+
expect(scheduling).to receive(:on_host_maintenance=).with("host_maintenance")
|
774
|
+
expect(driver.instance_scheduling).to eq(scheduling)
|
775
|
+
end
|
776
|
+
end
|
777
|
+
|
778
|
+
describe '#migrate_setting' do
|
779
|
+
it "returns MIGRATE if auto_migrate is true" do
|
780
|
+
expect(driver).to receive(:config).and_return(auto_migrate: true)
|
781
|
+
expect(driver.migrate_setting).to eq("MIGRATE")
|
782
|
+
end
|
783
|
+
|
784
|
+
it "returns TERMINATE if auto_migrate is false" do
|
785
|
+
expect(driver).to receive(:config).and_return(auto_migrate: false)
|
786
|
+
expect(driver.migrate_setting).to eq("TERMINATE")
|
787
|
+
end
|
788
|
+
end
|
789
|
+
|
790
|
+
describe '#instance_service_accounts' do
|
791
|
+
it "returns nil if service_account_scopes is nil" do
|
792
|
+
allow(driver).to receive(:config).and_return({})
|
793
|
+
expect(driver.instance_service_accounts).to eq(nil)
|
794
|
+
end
|
795
|
+
|
796
|
+
it "returns nil if service_account_scopes is empty" do
|
797
|
+
allow(driver).to receive(:config).and_return(service_account_scopes: [])
|
798
|
+
expect(driver.instance_service_accounts).to eq(nil)
|
799
|
+
end
|
800
|
+
|
801
|
+
it "returns an array containing a properly-formatted service account" do
|
802
|
+
service_account = double("service_account")
|
803
|
+
|
804
|
+
allow(driver).to receive(:config).and_return(service_account_name: "account_name", service_account_scopes: %w{scope1 scope2})
|
805
|
+
expect(Google::Apis::ComputeV1::ServiceAccount).to receive(:new).and_return(service_account)
|
806
|
+
expect(service_account).to receive(:email=).with("account_name")
|
807
|
+
expect(driver).to receive(:service_account_scope_url).with("scope1").and_return("https://www.googleapis.com/auth/scope1")
|
808
|
+
expect(driver).to receive(:service_account_scope_url).with("scope2").and_return("https://www.googleapis.com/auth/scope2")
|
809
|
+
expect(service_account).to receive(:scopes=).with([
|
810
|
+
"https://www.googleapis.com/auth/scope1",
|
811
|
+
"https://www.googleapis.com/auth/scope2",
|
812
|
+
])
|
813
|
+
|
814
|
+
expect(driver.instance_service_accounts).to eq([service_account])
|
815
|
+
end
|
816
|
+
end
|
817
|
+
|
818
|
+
describe '#service_account_scope_url' do
|
819
|
+
it "returns the passed-in scope if it already looks like a scope URL" do
|
820
|
+
scope = "https://www.googleapis.com/auth/fake_scope"
|
821
|
+
expect(driver.service_account_scope_url(scope)).to eq(scope)
|
822
|
+
end
|
823
|
+
|
824
|
+
it "returns a properly-formatted scope URL if a short-name or alias is provided" do
|
825
|
+
expect(driver).to receive(:translate_scope_alias).with("scope_alias").and_return("real_scope")
|
826
|
+
expect(driver.service_account_scope_url("scope_alias")).to eq("https://www.googleapis.com/auth/real_scope")
|
827
|
+
end
|
828
|
+
end
|
829
|
+
|
830
|
+
describe '#translate_scope_alias' do
|
831
|
+
it "returns a scope for a given alias" do
|
832
|
+
expect(driver.translate_scope_alias("storage-rw")).to eq("devstorage.read_write")
|
833
|
+
end
|
834
|
+
|
835
|
+
it "returns the passed-in scope alias if nothing matches in the alias map" do
|
836
|
+
expect(driver.translate_scope_alias("fake_scope")).to eq("fake_scope")
|
837
|
+
end
|
838
|
+
end
|
839
|
+
|
840
|
+
describe '#instance_tags' do
|
841
|
+
it "returns a properly-formatted tags object" do
|
842
|
+
tags_obj = double("tags_obj")
|
843
|
+
|
844
|
+
expect(driver).to receive(:config).and_return(tags: "test_tags")
|
845
|
+
expect(Google::Apis::ComputeV1::Tags).to receive(:new).and_return(tags_obj)
|
846
|
+
expect(tags_obj).to receive(:items=).with("test_tags")
|
847
|
+
|
848
|
+
expect(driver.instance_tags).to eq(tags_obj)
|
849
|
+
end
|
850
|
+
end
|
851
|
+
|
852
|
+
describe '#wait_time' do
|
853
|
+
it "returns the configured wait time" do
|
854
|
+
expect(driver).to receive(:config).and_return(wait_time: 123)
|
855
|
+
expect(driver.wait_time).to eq(123)
|
856
|
+
end
|
857
|
+
end
|
858
|
+
|
859
|
+
describe '#refresh_rate' do
|
860
|
+
it "returns the configured refresh rate" do
|
861
|
+
expect(driver).to receive(:config).and_return(refresh_rate: 321)
|
862
|
+
expect(driver.refresh_rate).to eq(321)
|
863
|
+
end
|
864
|
+
end
|
865
|
+
|
866
|
+
describe '#wait_for_status' do
|
867
|
+
let(:item) { double("item") }
|
868
|
+
|
869
|
+
before do
|
870
|
+
allow(driver).to receive(:wait_time).and_return(600)
|
871
|
+
allow(driver).to receive(:refresh_rate).and_return(2)
|
872
|
+
|
873
|
+
# don"t actually sleep
|
874
|
+
allow(driver).to receive(:sleep)
|
875
|
+
end
|
876
|
+
|
877
|
+
context "when the items completes normally, 3 loops" do
|
878
|
+
it "only refreshes the item 3 times" do
|
879
|
+
allow(item).to receive(:status).exactly(3).times.and_return("PENDING", "RUNNING", "DONE")
|
880
|
+
|
881
|
+
driver.wait_for_status("DONE") { item }
|
354
882
|
end
|
355
883
|
end
|
356
884
|
|
357
|
-
context
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
google_project: 'alpha-bravo-123'
|
363
|
-
}
|
885
|
+
context "when the item is completed on the first loop" do
|
886
|
+
it "only refreshes the item 1 time" do
|
887
|
+
allow(item).to receive(:status).once.and_return("DONE")
|
888
|
+
|
889
|
+
driver.wait_for_status("DONE") { item }
|
364
890
|
end
|
891
|
+
end
|
365
892
|
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
893
|
+
context "when the timeout is exceeded" do
|
894
|
+
it "prints a warning and exits" do
|
895
|
+
allow(Timeout).to receive(:timeout).and_raise(Timeout::Error)
|
896
|
+
expect(driver).to receive(:error)
|
897
|
+
.with("Request did not complete in 600 seconds. Check the Google Cloud Console for more info.")
|
898
|
+
expect { driver.wait_for_status("DONE") { item } }.to raise_error(RuntimeError)
|
370
899
|
end
|
371
900
|
end
|
372
901
|
|
373
|
-
context
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
google_key_location: '/home/user/gce/123456-privatekey.p12',
|
378
|
-
google_project: 'alpha-bravo-123'
|
379
|
-
}
|
902
|
+
context "when a non-timeout exception is raised" do
|
903
|
+
it "raises the original exception" do
|
904
|
+
allow(item).to receive(:status).and_raise(NoMethodError)
|
905
|
+
expect { driver.wait_for_status("DONE") { item } }.to raise_error(NoMethodError)
|
380
906
|
end
|
907
|
+
end
|
908
|
+
end
|
381
909
|
|
382
|
-
|
383
|
-
|
384
|
-
%w(us-central1-a us-central1-b us-central2-a).include?(zone)
|
385
|
-
end
|
910
|
+
describe '#wait_for_operation' do
|
911
|
+
let(:operation) { double("operation", name: "operation-123") }
|
386
912
|
|
387
|
-
|
913
|
+
it "raises a properly-formatted exception when errors exist" do
|
914
|
+
error1 = double("error1", code: "ERROR1", message: "error 1")
|
915
|
+
error2 = double("error2", code: "ERROR2", message: "error 2")
|
916
|
+
expect(driver).to receive(:wait_for_status).with("DONE")
|
917
|
+
expect(driver).to receive(:operation_errors).with("operation-123").and_return([error1, error2])
|
918
|
+
expect(driver).to receive(:error).with("ERROR1: error 1")
|
919
|
+
expect(driver).to receive(:error).with("ERROR2: error 2")
|
920
|
+
|
921
|
+
expect { driver.wait_for_operation(operation) }.to raise_error(RuntimeError, "Operation operation-123 failed.")
|
922
|
+
end
|
923
|
+
|
924
|
+
it "does not raise an exception if no errors are encountered" do
|
925
|
+
expect(driver).to receive(:wait_for_status).with("DONE")
|
926
|
+
expect(driver).to receive(:operation_errors).with("operation-123").and_return([])
|
927
|
+
expect(driver).not_to receive(:error)
|
928
|
+
|
929
|
+
expect { driver.wait_for_operation(operation) }.not_to raise_error
|
388
930
|
end
|
389
931
|
end
|
390
932
|
|
391
|
-
describe '#
|
392
|
-
it
|
393
|
-
|
394
|
-
|
395
|
-
expect(
|
933
|
+
describe '#zone_operation' do
|
934
|
+
it "fetches the operation from the API and returns it" do
|
935
|
+
connection = double("connection")
|
936
|
+
expect(driver).to receive(:connection).and_return(connection)
|
937
|
+
expect(connection).to receive(:get_zone_operation).with(project, zone, "operation-123").and_return("operation")
|
938
|
+
expect(driver.zone_operation("operation-123")).to eq("operation")
|
939
|
+
end
|
940
|
+
end
|
941
|
+
|
942
|
+
describe '#operation_errors' do
|
943
|
+
let(:operation) { double("operation") }
|
944
|
+
let(:error_obj) { double("error_obj") }
|
945
|
+
|
946
|
+
before do
|
947
|
+
expect(driver).to receive(:zone_operation).with("operation-123").and_return(operation)
|
948
|
+
end
|
949
|
+
|
950
|
+
it "returns an empty array if there are no errors" do
|
951
|
+
expect(operation).to receive(:error).and_return(nil)
|
952
|
+
expect(driver.operation_errors("operation-123")).to eq([])
|
953
|
+
end
|
954
|
+
|
955
|
+
it "returns the errors from the operation if they exist" do
|
956
|
+
expect(operation).to receive(:error).twice.and_return(error_obj)
|
957
|
+
expect(error_obj).to receive(:errors).and_return("some errors")
|
958
|
+
expect(driver.operation_errors("operation-123")).to eq("some errors")
|
396
959
|
end
|
397
960
|
end
|
398
961
|
end
|