blob: 01b0a9e4d9c5d43dbe0c14eb70696f16049b5d33 [file] [log] [blame]
Tri Vo29ac1822016-10-01 17:06:29 -07001#!/usr/bin/env python
2#
3# Copyright 2016 - The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""Tests for acloud.internal.lib.gcompute_client."""
18
19import os
20
21import apiclient.http
22import mock
Kevin Chengb5963882018-05-09 00:06:27 -070023from absl.testing import parameterized
Tri Vo29ac1822016-10-01 17:06:29 -070024
25import unittest
26from acloud.internal.lib import driver_test_lib
27from acloud.internal.lib import gcompute_client
28from acloud.internal.lib import utils
29from acloud.public import errors
30
Kevin Chengb5963882018-05-09 00:06:27 -070031GS_IMAGE_SOURCE_URI = "https://storage.googleapis.com/fake-bucket/fake.tar.gz"
32GS_IMAGE_SOURCE_DISK = (
33 "https://www.googleapis.com/compute/v1/projects/fake-project/zones/"
34 "us-east1-d/disks/fake-disk")
35PROJECT = "fake-project"
Tri Vo29ac1822016-10-01 17:06:29 -070036
Kevin Chengb5963882018-05-09 00:06:27 -070037
38class ComputeClientTest(driver_test_lib.BaseDriverTest, parameterized.TestCase):
Tri Vo29ac1822016-10-01 17:06:29 -070039 """Test ComputeClient."""
40
Kevin Chengb5963882018-05-09 00:06:27 -070041 PROJECT_OTHER = "fake-project-other"
Tri Vo29ac1822016-10-01 17:06:29 -070042 INSTANCE = "fake-instance"
43 IMAGE = "fake-image"
44 IMAGE_URL = "http://fake-image-url"
Kevin Chengb5963882018-05-09 00:06:27 -070045 IMAGE_OTHER = "fake-image-other"
Tri Vo29ac1822016-10-01 17:06:29 -070046 MACHINE_TYPE = "fake-machine-type"
47 MACHINE_TYPE_URL = "http://fake-machine-type-url"
48 METADATA = ("metadata_key", "metadata_value")
Kevin Chengb5963882018-05-09 00:06:27 -070049 ACCELERATOR_URL = "http://speedy-gpu"
Tri Vo29ac1822016-10-01 17:06:29 -070050 NETWORK = "fake-network"
51 NETWORK_URL = "http://fake-network-url"
52 ZONE = "fake-zone"
53 REGION = "fake-region"
54 OPERATION_NAME = "fake-op"
Kevin Chengb5963882018-05-09 00:06:27 -070055 IMAGE_FINGERPRINT = "L_NWHuz7wTY="
56 GPU = "fancy-graphics"
Tri Vo29ac1822016-10-01 17:06:29 -070057
58 def setUp(self):
59 """Set up test."""
60 super(ComputeClientTest, self).setUp()
61 self.Patch(gcompute_client.ComputeClient, "InitResourceHandle")
62 fake_cfg = mock.MagicMock()
Kevin Chengb5963882018-05-09 00:06:27 -070063 fake_cfg.project = PROJECT
64 self.compute_client = gcompute_client.ComputeClient(
65 fake_cfg, mock.MagicMock())
Tri Vo29ac1822016-10-01 17:06:29 -070066 self.compute_client._service = mock.MagicMock()
67
68 def _SetupMocksForGetOperationStatus(self, mock_result, operation_scope):
69 """A helper class for setting up mocks for testGetOperationStatus*.
70
Kevin Chengb5963882018-05-09 00:06:27 -070071 Args:
72 mock_result: The result to return by _GetOperationStatus.
73 operation_scope: A value of OperationScope.
Tri Vo29ac1822016-10-01 17:06:29 -070074
Kevin Chengb5963882018-05-09 00:06:27 -070075 Returns:
76 A mock for Resource object.
77 """
Tri Vo29ac1822016-10-01 17:06:29 -070078 resource_mock = mock.MagicMock()
79 mock_api = mock.MagicMock()
80 if operation_scope == gcompute_client.OperationScope.GLOBAL:
81 self.compute_client._service.globalOperations = mock.MagicMock(
82 return_value=resource_mock)
83 elif operation_scope == gcompute_client.OperationScope.ZONE:
84 self.compute_client._service.zoneOperations = mock.MagicMock(
85 return_value=resource_mock)
86 elif operation_scope == gcompute_client.OperationScope.REGION:
87 self.compute_client._service.regionOperations = mock.MagicMock(
88 return_value=resource_mock)
89 resource_mock.get = mock.MagicMock(return_value=mock_api)
90 mock_api.execute = mock.MagicMock(return_value=mock_result)
91 return resource_mock
92
93 def testGetOperationStatusGlobal(self):
94 """Test _GetOperationStatus for global."""
95 resource_mock = self._SetupMocksForGetOperationStatus(
96 {"status": "GOOD"}, gcompute_client.OperationScope.GLOBAL)
97 status = self.compute_client._GetOperationStatus(
98 {"name": self.OPERATION_NAME},
99 gcompute_client.OperationScope.GLOBAL)
100 self.assertEqual(status, "GOOD")
101 resource_mock.get.assert_called_with(
Kevin Chengb5963882018-05-09 00:06:27 -0700102 project=PROJECT, operation=self.OPERATION_NAME)
Tri Vo29ac1822016-10-01 17:06:29 -0700103
104 def testGetOperationStatusZone(self):
105 """Test _GetOperationStatus for zone."""
106 resource_mock = self._SetupMocksForGetOperationStatus(
107 {"status": "GOOD"}, gcompute_client.OperationScope.ZONE)
108 status = self.compute_client._GetOperationStatus(
109 {"name": self.OPERATION_NAME}, gcompute_client.OperationScope.ZONE,
110 self.ZONE)
111 self.assertEqual(status, "GOOD")
112 resource_mock.get.assert_called_with(
Kevin Chengb5963882018-05-09 00:06:27 -0700113 project=PROJECT,
Tri Vo29ac1822016-10-01 17:06:29 -0700114 operation=self.OPERATION_NAME,
115 zone=self.ZONE)
116
117 def testGetOperationStatusRegion(self):
118 """Test _GetOperationStatus for region."""
119 resource_mock = self._SetupMocksForGetOperationStatus(
120 {"status": "GOOD"}, gcompute_client.OperationScope.REGION)
121 self.compute_client._GetOperationStatus(
122 {"name": self.OPERATION_NAME},
123 gcompute_client.OperationScope.REGION, self.REGION)
124 resource_mock.get.assert_called_with(
Kevin Chengb5963882018-05-09 00:06:27 -0700125 project=PROJECT, operation=self.OPERATION_NAME, region=self.REGION)
Tri Vo29ac1822016-10-01 17:06:29 -0700126
127 def testGetOperationStatusError(self):
128 """Test _GetOperationStatus failed."""
129 self._SetupMocksForGetOperationStatus(
130 {"error": {"errors": ["error1", "error2"]}},
131 gcompute_client.OperationScope.GLOBAL)
132 self.assertRaisesRegexp(errors.DriverError,
133 "Get operation state failed.*error1.*error2",
134 self.compute_client._GetOperationStatus,
135 {"name": self.OPERATION_NAME},
136 gcompute_client.OperationScope.GLOBAL)
137
138 def testWaitOnOperation(self):
139 """Test WaitOnOperation."""
140 mock_error = mock.MagicMock()
141 self.Patch(utils, "PollAndWait")
142 self.Patch(errors, "GceOperationTimeoutError", return_value=mock_error)
143 self.compute_client.WaitOnOperation(
144 operation={"name": self.OPERATION_NAME},
145 operation_scope=gcompute_client.OperationScope.REGION,
146 scope_name=self.REGION)
147 utils.PollAndWait.assert_called_with(
148 func=self.compute_client._GetOperationStatus,
149 expected_return="DONE",
150 timeout_exception=mock_error,
151 timeout_secs=self.compute_client.OPERATION_TIMEOUT_SECS,
Kevin Chengb5963882018-05-09 00:06:27 -0700152 sleep_interval_secs=self.compute_client.OPERATION_POLL_INTERVAL_SECS,
Tri Vo29ac1822016-10-01 17:06:29 -0700153 operation={"name": self.OPERATION_NAME},
154 operation_scope=gcompute_client.OperationScope.REGION,
155 scope_name=self.REGION)
156
Kevin Chengb5963882018-05-09 00:06:27 -0700157 def testGetImage(self):
158 """Test GetImage."""
159 resource_mock = mock.MagicMock()
160 mock_api = mock.MagicMock()
161 self.compute_client._service.images = mock.MagicMock(
162 return_value=resource_mock)
163 resource_mock.get = mock.MagicMock(return_value=mock_api)
164 mock_api.execute = mock.MagicMock(return_value={"name": self.IMAGE})
165 result = self.compute_client.GetImage(self.IMAGE)
166 self.assertEqual(result, {"name": self.IMAGE})
167 resource_mock.get.assert_called_with(project=PROJECT, image=self.IMAGE)
168
169 def testGetImageOther(self):
170 """Test GetImage with other project."""
171 resource_mock = mock.MagicMock()
172 mock_api = mock.MagicMock()
173 self.compute_client._service.images = mock.MagicMock(
174 return_value=resource_mock)
175 resource_mock.get = mock.MagicMock(return_value=mock_api)
176 mock_api.execute = mock.MagicMock(return_value={"name": self.IMAGE_OTHER})
177 result = self.compute_client.GetImage(
178 image_name=self.IMAGE_OTHER,
179 image_project=self.PROJECT_OTHER)
180 self.assertEqual(result, {"name": self.IMAGE_OTHER})
181 resource_mock.get.assert_called_with(
182 project=self.PROJECT_OTHER, image=self.IMAGE_OTHER)
183
184 # pyformat: disable
185 @parameterized.parameters(
186 (GS_IMAGE_SOURCE_URI, None, None,
187 {"name": IMAGE,
188 "rawDisk": {"source": GS_IMAGE_SOURCE_URI}}),
189 (None, GS_IMAGE_SOURCE_DISK, None,
190 {"name": IMAGE,
191 "sourceDisk": GS_IMAGE_SOURCE_DISK}),
192 (None, GS_IMAGE_SOURCE_DISK, {"label1": "xxx"},
193 {"name": IMAGE,
194 "sourceDisk": GS_IMAGE_SOURCE_DISK,
195 "labels": {"label1": "xxx"}}))
196 # pyformat: enable
197 def testCreateImage(self, source_uri, source_disk, labels, expected_body):
Tri Vo29ac1822016-10-01 17:06:29 -0700198 """Test CreateImage."""
199 self.Patch(gcompute_client.ComputeClient, "WaitOnOperation")
200 resource_mock = mock.MagicMock()
201 self.compute_client._service.images = mock.MagicMock(
202 return_value=resource_mock)
203 resource_mock.insert = mock.MagicMock()
Tri Vo29ac1822016-10-01 17:06:29 -0700204 self.compute_client.CreateImage(
Kevin Chengb5963882018-05-09 00:06:27 -0700205 image_name=self.IMAGE, source_uri=source_uri,
206 source_disk=source_disk, labels=labels)
Tri Vo29ac1822016-10-01 17:06:29 -0700207 resource_mock.insert.assert_called_with(
Kevin Chengb5963882018-05-09 00:06:27 -0700208 project=PROJECT, body=expected_body)
Tri Vo29ac1822016-10-01 17:06:29 -0700209 self.compute_client.WaitOnOperation.assert_called_with(
210 operation=mock.ANY,
211 operation_scope=gcompute_client.OperationScope.GLOBAL)
212
Kevin Chengb5963882018-05-09 00:06:27 -0700213 @unittest.skip("Needs to use mock lib for proper mocking.")
214 def testSetImageLabel(self):
215 # TODO: AddMockObject() needs to be converted to use mock.
216 # self.AddMockObject(self.compute_client._service, "images",
217 # return_value=mock.MagicMock(setLabels=mock.MagicMock()))
218 image = {"name": self.IMAGE,
219 "sourceDisk": GS_IMAGE_SOURCE_DISK,
220 "labelFingerprint": self.IMAGE_FINGERPRINT,
221 "labels": {"a": "aaa", "b": "bbb"}}
222 # self.AddMockObject(self.compute_client, "GetImage", return_value=image)
223 new_labels = {"a": "xxx", "c": "ccc"}
224 # Test
225 self.compute_client.SetImageLabels(
226 self.IMAGE, new_labels)
227 # Check result
228 expected_labels = {"a": "xxx", "b": "bbb", "c": "ccc"}
229 self.compute_client._service.images().setLabels.assert_called_with(
230 project=PROJECT,
231 resource=self.IMAGE,
232 body={
233 "labels": expected_labels,
234 "labelFingerprint": self.IMAGE_FINGERPRINT
235 })
236
237 @parameterized.parameters(
238 (GS_IMAGE_SOURCE_URI, GS_IMAGE_SOURCE_DISK),
239 (None, None))
240 def testCreateImageRaiseDriverError(self, source_uri, source_disk):
241 """Test CreateImage."""
242 self.assertRaises(errors.DriverError, self.compute_client.CreateImage,
243 image_name=self.IMAGE, source_uri=source_uri,
244 source_disk=source_disk)
245
Tri Vo29ac1822016-10-01 17:06:29 -0700246 def testCreateImageFail(self):
247 """Test CreateImage fails."""
248 self.Patch(
249 gcompute_client.ComputeClient,
250 "WaitOnOperation",
251 side_effect=errors.DriverError("Expected fake error"))
252 self.Patch(
253 gcompute_client.ComputeClient,
254 "CheckImageExists",
255 return_value=True)
256 self.Patch(gcompute_client.ComputeClient, "DeleteImage")
257
258 resource_mock = mock.MagicMock()
259 self.compute_client._service.images = mock.MagicMock(
260 return_value=resource_mock)
261 resource_mock.insert = mock.MagicMock()
262
263 expected_body = {
264 "name": self.IMAGE,
265 "rawDisk": {
Kevin Chengb5963882018-05-09 00:06:27 -0700266 "source": GS_IMAGE_SOURCE_URI,
Tri Vo29ac1822016-10-01 17:06:29 -0700267 },
268 }
269 self.assertRaisesRegexp(
270 errors.DriverError,
271 "Expected fake error",
272 self.compute_client.CreateImage,
273 image_name=self.IMAGE,
Kevin Chengb5963882018-05-09 00:06:27 -0700274 source_uri=GS_IMAGE_SOURCE_URI)
Tri Vo29ac1822016-10-01 17:06:29 -0700275 resource_mock.insert.assert_called_with(
Kevin Chengb5963882018-05-09 00:06:27 -0700276 project=PROJECT, body=expected_body)
Tri Vo29ac1822016-10-01 17:06:29 -0700277 self.compute_client.WaitOnOperation.assert_called_with(
278 operation=mock.ANY,
279 operation_scope=gcompute_client.OperationScope.GLOBAL)
280 self.compute_client.CheckImageExists.assert_called_with(self.IMAGE)
281 self.compute_client.DeleteImage.assert_called_with(self.IMAGE)
282
283 def testCheckImageExistsTrue(self):
284 """Test CheckImageExists return True."""
285 resource_mock = mock.MagicMock()
286 mock_api = mock.MagicMock()
287 self.compute_client._service.images = mock.MagicMock(
288 return_value=resource_mock)
289 resource_mock.get = mock.MagicMock(return_value=mock_api)
290 mock_api.execute = mock.MagicMock(return_value={"name": self.IMAGE})
291 self.assertTrue(self.compute_client.CheckImageExists(self.IMAGE))
292
293 def testCheckImageExistsFalse(self):
294 """Test CheckImageExists return False."""
295 resource_mock = mock.MagicMock()
296 mock_api = mock.MagicMock()
297 self.compute_client._service.images = mock.MagicMock(
298 return_value=resource_mock)
299 resource_mock.get = mock.MagicMock(return_value=mock_api)
300 mock_api.execute = mock.MagicMock(
301 side_effect=errors.ResourceNotFoundError(404, "no image"))
302 self.assertFalse(self.compute_client.CheckImageExists(self.IMAGE))
303
304 def testDeleteImage(self):
305 """Test DeleteImage."""
306 self.Patch(gcompute_client.ComputeClient, "WaitOnOperation")
307 resource_mock = mock.MagicMock()
308 self.compute_client._service.images = mock.MagicMock(
309 return_value=resource_mock)
310 resource_mock.delete = mock.MagicMock()
311 self.compute_client.DeleteImage(self.IMAGE)
312 resource_mock.delete.assert_called_with(
Kevin Chengb5963882018-05-09 00:06:27 -0700313 project=PROJECT, image=self.IMAGE)
Tri Vo29ac1822016-10-01 17:06:29 -0700314 self.assertTrue(self.compute_client.WaitOnOperation.called)
315
316 def _SetupBatchHttpRequestMock(self):
317 """Setup BatchHttpRequest mock."""
318 requests = {}
319
320 def _Add(request, callback, request_id):
321 requests[request_id] = (request, callback)
322
323 def _Execute():
324 for rid in requests:
325 _, callback = requests[rid]
326 callback(
327 request_id=rid, response=mock.MagicMock(), exception=None)
Tri Vo29ac1822016-10-01 17:06:29 -0700328 mock_batch = mock.MagicMock()
329 mock_batch.add = _Add
330 mock_batch.execute = _Execute
331 self.Patch(apiclient.http, "BatchHttpRequest", return_value=mock_batch)
332
333 def testDeleteImages(self):
334 """Test DeleteImages."""
335 self._SetupBatchHttpRequestMock()
336 self.Patch(gcompute_client.ComputeClient, "WaitOnOperation")
337 fake_images = ["fake_image_1", "fake_image_2"]
338 mock_api = mock.MagicMock()
339 resource_mock = mock.MagicMock()
340 self.compute_client._service.images = mock.MagicMock(
341 return_value=resource_mock)
342 resource_mock.delete = mock.MagicMock(return_value=mock_api)
343 # Call the API.
344 deleted, failed, error_msgs = self.compute_client.DeleteImages(
345 fake_images)
346 # Verify
Kevin Chengb5963882018-05-09 00:06:27 -0700347 calls = [
348 mock.call(project=PROJECT, image="fake_image_1"),
349 mock.call(project=PROJECT, image="fake_image_2")
350 ]
Tri Vo29ac1822016-10-01 17:06:29 -0700351 resource_mock.delete.assert_has_calls(calls, any_order=True)
352 self.assertEqual(
353 gcompute_client.ComputeClient.WaitOnOperation.call_count, 2)
354 self.assertEqual(error_msgs, [])
355 self.assertEqual(failed, [])
356 self.assertEqual(set(deleted), set(fake_images))
357
358 def testListImages(self):
359 """Test ListImages."""
360 fake_token = "fake_next_page_token"
361 image_1 = "image_1"
362 image_2 = "image_2"
363 response_1 = {"items": [image_1], "nextPageToken": fake_token}
364 response_2 = {"items": [image_2]}
365 self.Patch(
366 gcompute_client.ComputeClient,
367 "Execute",
368 side_effect=[response_1, response_2])
369 resource_mock = mock.MagicMock()
370 self.compute_client._service.images = mock.MagicMock(
371 return_value=resource_mock)
372 resource_mock.list = mock.MagicMock()
373 images = self.compute_client.ListImages()
374 calls = [
Kevin Chengb5963882018-05-09 00:06:27 -0700375 mock.call(project=PROJECT, filter=None, pageToken=None),
376 mock.call(project=PROJECT, filter=None, pageToken=fake_token)
Tri Vo29ac1822016-10-01 17:06:29 -0700377 ]
378 resource_mock.list.assert_has_calls(calls)
379 self.assertEqual(images, [image_1, image_2])
380
Kevin Chengb5963882018-05-09 00:06:27 -0700381 def testListImagesFromExternalProject(self):
382 """Test ListImages which accepts different project."""
383 image = "image_1"
384 response = {"items": [image]}
385 self.Patch(gcompute_client.ComputeClient, "Execute", side_effect=[response])
386 resource_mock = mock.MagicMock()
387 self.compute_client._service.images = mock.MagicMock(
388 return_value=resource_mock)
389 resource_mock.list = mock.MagicMock()
390 images = self.compute_client.ListImages(
391 image_project="fake-project-2")
392 calls = [
393 mock.call(project="fake-project-2", filter=None, pageToken=None)]
394 resource_mock.list.assert_has_calls(calls)
395 self.assertEqual(images, [image])
396
Tri Vo29ac1822016-10-01 17:06:29 -0700397 def testGetInstance(self):
398 """Test GetInstance."""
399 resource_mock = mock.MagicMock()
400 mock_api = mock.MagicMock()
401 self.compute_client._service.instances = mock.MagicMock(
402 return_value=resource_mock)
403 resource_mock.get = mock.MagicMock(return_value=mock_api)
404 mock_api.execute = mock.MagicMock(return_value={"name": self.INSTANCE})
405 result = self.compute_client.GetInstance(self.INSTANCE, self.ZONE)
406 self.assertEqual(result, {"name": self.INSTANCE})
407 resource_mock.get.assert_called_with(
Kevin Chengb5963882018-05-09 00:06:27 -0700408 project=PROJECT, zone=self.ZONE, instance=self.INSTANCE)
Tri Vo29ac1822016-10-01 17:06:29 -0700409
410 def testListInstances(self):
411 """Test ListInstances."""
412 fake_token = "fake_next_page_token"
413 instance_1 = "instance_1"
414 instance_2 = "instance_2"
415 response_1 = {"items": [instance_1], "nextPageToken": fake_token}
416 response_2 = {"items": [instance_2]}
417 self.Patch(
418 gcompute_client.ComputeClient,
419 "Execute",
420 side_effect=[response_1, response_2])
421 resource_mock = mock.MagicMock()
422 self.compute_client._service.instances = mock.MagicMock(
423 return_value=resource_mock)
424 resource_mock.list = mock.MagicMock()
425 instances = self.compute_client.ListInstances(self.ZONE)
426 calls = [
427 mock.call(
Kevin Chengb5963882018-05-09 00:06:27 -0700428 project=PROJECT,
Tri Vo29ac1822016-10-01 17:06:29 -0700429 zone=self.ZONE,
430 filter=None,
431 pageToken=None),
432 mock.call(
Kevin Chengb5963882018-05-09 00:06:27 -0700433 project=PROJECT,
Tri Vo29ac1822016-10-01 17:06:29 -0700434 zone=self.ZONE,
435 filter=None,
436 pageToken=fake_token),
437 ]
438 resource_mock.list.assert_has_calls(calls)
439 self.assertEqual(instances, [instance_1, instance_2])
440
441 def testCreateInstance(self):
442 """Test CreateInstance."""
443 self.Patch(gcompute_client.ComputeClient, "WaitOnOperation")
444 self.Patch(
445 gcompute_client.ComputeClient,
446 "GetMachineType",
447 return_value={"selfLink": self.MACHINE_TYPE_URL})
448 self.Patch(
449 gcompute_client.ComputeClient,
450 "GetNetworkUrl",
451 return_value=self.NETWORK_URL)
452 self.Patch(
453 gcompute_client.ComputeClient,
454 "GetImage",
455 return_value={"selfLink": self.IMAGE_URL})
456
457 resource_mock = mock.MagicMock()
458 self.compute_client._service.instances = mock.MagicMock(
459 return_value=resource_mock)
460 resource_mock.insert = mock.MagicMock()
461
462 expected_body = {
463 "machineType": self.MACHINE_TYPE_URL,
464 "name": self.INSTANCE,
465 "networkInterfaces": [
466 {
467 "network": self.NETWORK_URL,
468 "accessConfigs": [
469 {"name": "External NAT",
470 "type": "ONE_TO_ONE_NAT"}
471 ],
472 }
473 ],
474 "disks": [
475 {
476 "type": "PERSISTENT",
477 "boot": True,
478 "mode": "READ_WRITE",
479 "autoDelete": True,
480 "initializeParams": {
481 "diskName": self.INSTANCE,
482 "sourceImage": self.IMAGE_URL,
483 },
484 }
485 ],
486 "serviceAccounts": [
487 {"email": "default",
488 "scopes": self.compute_client.DEFAULT_INSTANCE_SCOPE}
489 ],
490 "metadata": {
491 "items": [{"key": self.METADATA[0],
492 "value": self.METADATA[1]}],
493 },
494 }
495
496 self.compute_client.CreateInstance(
497 instance=self.INSTANCE,
498 image_name=self.IMAGE,
499 machine_type=self.MACHINE_TYPE,
500 metadata={self.METADATA[0]: self.METADATA[1]},
501 network=self.NETWORK,
502 zone=self.ZONE)
503
504 resource_mock.insert.assert_called_with(
Kevin Chengb5963882018-05-09 00:06:27 -0700505 project=PROJECT, zone=self.ZONE, body=expected_body)
Tri Vo29ac1822016-10-01 17:06:29 -0700506 self.compute_client.WaitOnOperation.assert_called_with(
507 mock.ANY,
508 operation_scope=gcompute_client.OperationScope.ZONE,
509 scope_name=self.ZONE)
510
Kevin Chengb5963882018-05-09 00:06:27 -0700511 def testCreateInstanceWithGpu(self):
512 """Test CreateInstance with a GPU parameter not set to None."""
513 self.Patch(gcompute_client.ComputeClient, "WaitOnOperation")
514 self.Patch(
515 gcompute_client.ComputeClient,
516 "GetMachineType",
517 return_value={"selfLink": self.MACHINE_TYPE_URL})
518 self.Patch(
519 gcompute_client.ComputeClient,
520 "GetNetworkUrl",
521 return_value=self.NETWORK_URL)
522 self.Patch(
523 gcompute_client.ComputeClient,
524 "GetAcceleratorUrl",
525 return_value=self.ACCELERATOR_URL)
526 self.Patch(
527 gcompute_client.ComputeClient,
528 "GetImage",
529 return_value={"selfLink": self.IMAGE_URL})
530
531 resource_mock = mock.MagicMock()
532 self.compute_client._service.instances = mock.MagicMock(
533 return_value=resource_mock)
534 resource_mock.insert = mock.MagicMock()
535
536 expected_body = {
537 "machineType":
538 self.MACHINE_TYPE_URL,
539 "name":
540 self.INSTANCE,
541 "networkInterfaces": [{
542 "network":
543 self.NETWORK_URL,
544 "accessConfigs": [{
545 "name": "External NAT",
546 "type": "ONE_TO_ONE_NAT"
547 }],
548 }],
549 "disks": [{
550 "type": "PERSISTENT",
551 "boot": True,
552 "mode": "READ_WRITE",
553 "autoDelete": True,
554 "initializeParams": {
555 "diskName": self.INSTANCE,
556 "sourceImage": self.IMAGE_URL,
557 },
558 }],
559 "serviceAccounts": [{
560 "email": "default",
561 "scopes": self.compute_client.DEFAULT_INSTANCE_SCOPE
562 }],
563 "scheduling": {
564 "onHostMaintenance": "terminate"
565 },
566 "guestAccelerators": [{
567 "acceleratorCount": 1,
568 "acceleratorType": "http://speedy-gpu"
569 }],
570 "metadata": {
571 "items": [{
572 "key": self.METADATA[0],
573 "value": self.METADATA[1]
574 }],
575 },
576 }
577
578 self.compute_client.CreateInstance(
579 instance=self.INSTANCE,
580 image_name=self.IMAGE,
581 machine_type=self.MACHINE_TYPE,
582 metadata={self.METADATA[0]: self.METADATA[1]},
583 network=self.NETWORK,
584 zone=self.ZONE,
585 gpu=self.GPU)
586
587 resource_mock.insert.assert_called_with(
588 project=PROJECT, zone=self.ZONE, body=expected_body)
589 self.compute_client.WaitOnOperation.assert_called_with(
590 mock.ANY, operation_scope=gcompute_client.OperationScope.ZONE,
591 scope_name=self.ZONE)
592
Tri Vo29ac1822016-10-01 17:06:29 -0700593 def testDeleteInstance(self):
594 """Test DeleteInstance."""
595 self.Patch(gcompute_client.ComputeClient, "WaitOnOperation")
596 resource_mock = mock.MagicMock()
597 self.compute_client._service.instances = mock.MagicMock(
598 return_value=resource_mock)
599 resource_mock.delete = mock.MagicMock()
600 self.compute_client.DeleteInstance(
601 instance=self.INSTANCE, zone=self.ZONE)
602 resource_mock.delete.assert_called_with(
Kevin Chengb5963882018-05-09 00:06:27 -0700603 project=PROJECT, zone=self.ZONE, instance=self.INSTANCE)
Tri Vo29ac1822016-10-01 17:06:29 -0700604 self.compute_client.WaitOnOperation.assert_called_with(
605 mock.ANY,
606 operation_scope=gcompute_client.OperationScope.ZONE,
607 scope_name=self.ZONE)
608
609 def testDeleteInstances(self):
610 """Test DeleteInstances."""
611 self._SetupBatchHttpRequestMock()
612 self.Patch(gcompute_client.ComputeClient, "WaitOnOperation")
613 fake_instances = ["fake_instance_1", "fake_instance_2"]
614 mock_api = mock.MagicMock()
615 resource_mock = mock.MagicMock()
616 self.compute_client._service.instances = mock.MagicMock(
617 return_value=resource_mock)
618 resource_mock.delete = mock.MagicMock(return_value=mock_api)
619 deleted, failed, error_msgs = self.compute_client.DeleteInstances(
620 fake_instances, self.ZONE)
621 calls = [
622 mock.call(
Kevin Chengb5963882018-05-09 00:06:27 -0700623 project=PROJECT,
Tri Vo29ac1822016-10-01 17:06:29 -0700624 instance="fake_instance_1",
Kevin Chengb5963882018-05-09 00:06:27 -0700625 zone=self.ZONE),
626 mock.call(
627 project=PROJECT,
628 instance="fake_instance_2",
629 zone=self.ZONE)
Tri Vo29ac1822016-10-01 17:06:29 -0700630 ]
631 resource_mock.delete.assert_has_calls(calls, any_order=True)
632 self.assertEqual(
633 gcompute_client.ComputeClient.WaitOnOperation.call_count, 2)
634 self.assertEqual(error_msgs, [])
635 self.assertEqual(failed, [])
636 self.assertEqual(set(deleted), set(fake_instances))
637
Kevin Chengb5963882018-05-09 00:06:27 -0700638 @parameterized.parameters(("fake-image-project", "fake-image-project"),
639 (None, PROJECT))
640 def testCreateDisk(self, source_project, expected_project_to_use):
641 """Test CreateDisk with images."""
642 self.Patch(gcompute_client.ComputeClient, "WaitOnOperation")
643 resource_mock = mock.MagicMock()
644 self.compute_client._service.disks = mock.MagicMock(
645 return_value=resource_mock)
646 resource_mock.insert = mock.MagicMock()
647 self.compute_client.CreateDisk(
648 "fake_disk", "fake_image", 10, self.ZONE, source_project=source_project)
649 resource_mock.insert.assert_called_with(
650 project=PROJECT,
651 zone=self.ZONE,
652 sourceImage="projects/%s/global/images/fake_image" %
653 expected_project_to_use,
654 body={
655 "name":
656 "fake_disk",
657 "sizeGb":
658 10,
659 "type":
660 "projects/%s/zones/%s/diskTypes/pd-standard" % (PROJECT,
661 self.ZONE)
662 })
663 self.assertTrue(self.compute_client.WaitOnOperation.called)
664
665 @parameterized.parameters(
666 (gcompute_client.PersistentDiskType.STANDARD, "pd-standard"),
667 (gcompute_client.PersistentDiskType.SSD, "pd-ssd"))
668 def testCreateDiskWithType(self, disk_type, expected_disk_type_string):
669 """Test CreateDisk with images."""
670 self.Patch(gcompute_client.ComputeClient, "WaitOnOperation")
671 resource_mock = mock.MagicMock()
672 self.compute_client._service.disks = mock.MagicMock(
673 return_value=resource_mock)
674 resource_mock.insert = mock.MagicMock()
675 self.compute_client.CreateDisk(
676 "fake_disk",
677 "fake_image",
678 10,
679 self.ZONE,
680 source_project="fake-project",
681 disk_type=disk_type)
682 resource_mock.insert.assert_called_with(
683 project=PROJECT,
684 zone=self.ZONE,
685 sourceImage="projects/%s/global/images/fake_image" % "fake-project",
686 body={
687 "name":
688 "fake_disk",
689 "sizeGb":
690 10,
691 "type":
692 "projects/%s/zones/%s/diskTypes/%s" %
693 (PROJECT, self.ZONE, expected_disk_type_string)
694 })
695 self.assertTrue(self.compute_client.WaitOnOperation.called)
696
697 def testAttachDisk(self):
698 """Test AttachDisk."""
699 self.Patch(gcompute_client.ComputeClient, "WaitOnOperation")
700 resource_mock = mock.MagicMock()
701 self.compute_client._service.instances = mock.MagicMock(
702 return_value=resource_mock)
703 resource_mock.attachDisk = mock.MagicMock()
704 self.compute_client.AttachDisk(
705 "fake_instance_1", self.ZONE, deviceName="fake_disk",
706 source="fake-selfLink")
707 resource_mock.attachDisk.assert_called_with(
708 project=PROJECT,
709 zone=self.ZONE,
710 instance="fake_instance_1",
711 body={
712 "deviceName": "fake_disk",
713 "source": "fake-selfLink"
714 })
715 self.assertTrue(self.compute_client.WaitOnOperation.called)
716
717 def testDetachDisk(self):
718 """Test DetachDisk."""
719 self.Patch(gcompute_client.ComputeClient, "WaitOnOperation")
720 resource_mock = mock.MagicMock()
721 self.compute_client._service.instances = mock.MagicMock(
722 return_value=resource_mock)
723 resource_mock.detachDisk = mock.MagicMock()
724 self.compute_client.DetachDisk("fake_instance_1", self.ZONE, "fake_disk")
725 resource_mock.detachDisk.assert_called_with(
726 project=PROJECT,
727 zone=self.ZONE,
728 instance="fake_instance_1",
729 deviceName="fake_disk")
730 self.assertTrue(self.compute_client.WaitOnOperation.called)
731
732 def testAttachAccelerator(self):
733 """Test AttachAccelerator."""
734 self.Patch(gcompute_client.ComputeClient, "WaitOnOperation")
735 self.Patch(
736 gcompute_client.ComputeClient,
737 "GetAcceleratorUrl",
738 return_value=self.ACCELERATOR_URL)
739 resource_mock = mock.MagicMock()
740 self.compute_client._service.instances = mock.MagicMock(
741 return_value=resource_mock)
742 resource_mock.attachAccelerator = mock.MagicMock()
743 self.compute_client.AttachAccelerator("fake_instance_1", self.ZONE, 1,
744 "nvidia-tesla-k80")
745 resource_mock.setMachineResources.assert_called_with(
746 project=PROJECT,
747 zone=self.ZONE,
748 instance="fake_instance_1",
749 body={
750 "guestAccelerators": [{
751 "acceleratorType": self.ACCELERATOR_URL,
752 "acceleratorCount": 1
753 }]
754 })
755 self.assertTrue(self.compute_client.WaitOnOperation.called)
756
Tri Vo29ac1822016-10-01 17:06:29 -0700757 def testBatchExecuteOnInstances(self):
758 self._SetupBatchHttpRequestMock()
759 self.Patch(gcompute_client.ComputeClient, "WaitOnOperation")
760 action = mock.MagicMock(return_value=mock.MagicMock())
761 fake_instances = ["fake_instance_1", "fake_instance_2"]
762 done, failed, error_msgs = self.compute_client._BatchExecuteOnInstances(
763 fake_instances, self.ZONE, action)
764 calls = [mock.call(instance="fake_instance_1"),
765 mock.call(instance="fake_instance_2")]
766 action.assert_has_calls(calls, any_order=True)
767 self.assertEqual(
768 gcompute_client.ComputeClient.WaitOnOperation.call_count, 2)
769 self.assertEqual(set(done), set(fake_instances))
770 self.assertEqual(error_msgs, [])
771 self.assertEqual(failed, [])
772
773 def testResetInstance(self):
774 """Test ResetInstance."""
775 self.Patch(gcompute_client.ComputeClient, "WaitOnOperation")
776 resource_mock = mock.MagicMock()
777 self.compute_client._service.instances = mock.MagicMock(
778 return_value=resource_mock)
779 resource_mock.reset = mock.MagicMock()
780 self.compute_client.ResetInstance(
781 instance=self.INSTANCE, zone=self.ZONE)
782 resource_mock.reset.assert_called_with(
Kevin Chengb5963882018-05-09 00:06:27 -0700783 project=PROJECT, zone=self.ZONE, instance=self.INSTANCE)
Tri Vo29ac1822016-10-01 17:06:29 -0700784 self.compute_client.WaitOnOperation.assert_called_with(
785 mock.ANY,
786 operation_scope=gcompute_client.OperationScope.ZONE,
787 scope_name=self.ZONE)
788
789 def _CompareMachineSizeTestHelper(self,
790 machine_info_1,
791 machine_info_2,
792 expected_result=None,
793 expected_error_type=None):
794 """Helper class for testing CompareMachineSize.
795
Kevin Chengb5963882018-05-09 00:06:27 -0700796 Args:
797 machine_info_1: A dictionary representing the first machine size.
798 machine_info_2: A dictionary representing the second machine size.
799 expected_result: An integer, 0, 1 or -1, or None if not set.
800 expected_error_type: An exception type, if set will check for exception.
801 """
Tri Vo29ac1822016-10-01 17:06:29 -0700802 self.Patch(
803 gcompute_client.ComputeClient,
804 "GetMachineType",
805 side_effect=[machine_info_1, machine_info_2])
806 if expected_error_type:
807 self.assertRaises(expected_error_type,
808 self.compute_client.CompareMachineSize, "name1",
809 "name2", self.ZONE)
810 else:
811 result = self.compute_client.CompareMachineSize("name1", "name2",
812 self.ZONE)
813 self.assertEqual(result, expected_result)
814
815 self.compute_client.GetMachineType.assert_has_calls(
816 [mock.call("name1", self.ZONE), mock.call("name2", self.ZONE)])
817
818 def testCompareMachineSizeSmall(self):
819 """Test CompareMachineSize where the first one is smaller."""
820 machine_info_1 = {"guestCpus": 10, "memoryMb": 100}
821 machine_info_2 = {"guestCpus": 10, "memoryMb": 200}
822 self._CompareMachineSizeTestHelper(machine_info_1, machine_info_2, -1)
823
824 def testCompareMachineSizeLarge(self):
825 """Test CompareMachineSize where the first one is larger."""
826 machine_info_1 = {"guestCpus": 10, "memoryMb": 200}
827 machine_info_2 = {"guestCpus": 10, "memoryMb": 100}
828 self._CompareMachineSizeTestHelper(machine_info_1, machine_info_2, 1)
829
830 def testCompareMachineSizeEqual(self):
831 """Test CompareMachineSize where two machine sizes are equal."""
832 machine_info = {"guestCpus": 10, "memoryMb": 100}
833 self._CompareMachineSizeTestHelper(machine_info, machine_info, 0)
834
835 def testCompareMachineSizeBadMetric(self):
836 """Test CompareMachineSize with bad metric."""
Kevin Chengb5963882018-05-09 00:06:27 -0700837 machine_info = {"unknown_metric": 10, "memoryMb": 100}
Tri Vo29ac1822016-10-01 17:06:29 -0700838 self._CompareMachineSizeTestHelper(
839 machine_info, machine_info, expected_error_type=errors.DriverError)
840
841 def testGetMachineType(self):
842 """Test GetMachineType."""
843 resource_mock = mock.MagicMock()
844 mock_api = mock.MagicMock()
845 self.compute_client._service.machineTypes = mock.MagicMock(
846 return_value=resource_mock)
847 resource_mock.get = mock.MagicMock(return_value=mock_api)
848 mock_api.execute = mock.MagicMock(
849 return_value={"name": self.MACHINE_TYPE})
850 result = self.compute_client.GetMachineType(self.MACHINE_TYPE,
851 self.ZONE)
852 self.assertEqual(result, {"name": self.MACHINE_TYPE})
853 resource_mock.get.assert_called_with(
Kevin Chengb5963882018-05-09 00:06:27 -0700854 project=PROJECT,
Tri Vo29ac1822016-10-01 17:06:29 -0700855 zone=self.ZONE,
856 machineType=self.MACHINE_TYPE)
857
858 def _GetSerialPortOutputTestHelper(self, response):
859 """Helper function for testing GetSerialPortOutput.
860
Kevin Chengb5963882018-05-09 00:06:27 -0700861 Args:
862 response: A dictionary representing a fake response.
863 """
Tri Vo29ac1822016-10-01 17:06:29 -0700864 resource_mock = mock.MagicMock()
865 mock_api = mock.MagicMock()
866 self.compute_client._service.instances = mock.MagicMock(
867 return_value=resource_mock)
868 resource_mock.getSerialPortOutput = mock.MagicMock(
869 return_value=mock_api)
870 mock_api.execute = mock.MagicMock(return_value=response)
871
872 if "contents" in response:
873 result = self.compute_client.GetSerialPortOutput(
874 instance=self.INSTANCE, zone=self.ZONE)
875 self.assertEqual(result, "fake contents")
876 else:
877 self.assertRaisesRegexp(
878 errors.DriverError,
879 "Malformed response.*",
880 self.compute_client.GetSerialPortOutput,
881 instance=self.INSTANCE,
882 zone=self.ZONE)
883 resource_mock.getSerialPortOutput.assert_called_with(
Kevin Chengb5963882018-05-09 00:06:27 -0700884 project=PROJECT,
Tri Vo29ac1822016-10-01 17:06:29 -0700885 zone=self.ZONE,
886 instance=self.INSTANCE,
887 port=1)
888
889 def testGetSerialPortOutput(self):
890 response = {"contents": "fake contents"}
891 self._GetSerialPortOutputTestHelper(response)
892
893 def testGetSerialPortOutputFail(self):
894 response = {"malformed": "fake contents"}
895 self._GetSerialPortOutputTestHelper(response)
896
897 def testGetInstanceNamesByIPs(self):
898 """Test GetInstanceNamesByIPs."""
899 good_instance = {
900 "name": "instance_1",
901 "networkInterfaces": [
902 {
903 "accessConfigs": [
904 {"natIP": "172.22.22.22"},
905 ],
906 },
907 ],
908 }
909 bad_instance = {"name": "instance_2"}
910 self.Patch(
911 gcompute_client.ComputeClient,
912 "ListInstances",
913 return_value=[good_instance, bad_instance])
914 ip_name_map = self.compute_client.GetInstanceNamesByIPs(
915 ips=["172.22.22.22", "172.22.22.23"], zone=self.ZONE)
916 self.assertEqual(ip_name_map, {"172.22.22.22": "instance_1",
917 "172.22.22.23": None})
918
919 def testAddSshRsa(self):
920 """Test AddSshRsa.."""
921 fake_user = "fake_user"
922 sshkey = (
923 "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBkTOTRze9v2VOqkkf7RG"
924 "jSkg6Z2kb9Q9UHsDGatvend3fmjIw1Tugg0O7nnjlPkskmlgyd4a/j99WOeLL"
925 "CPk6xPyoVjrPUVBU/pAk09ORTC4Zqk6YjlW7LOfzvqmXhmIZfYu6Q4Yt50pZzhl"
926 "lllfu26nYjY7Tg12D019nJi/kqPX5+NKgt0LGXTu8T1r2Gav/q4V7QRWQrB8Eiu"
927 "pxXR7I2YhynqovkEt/OXG4qWgvLEXGsWtSQs0CtCzqEVxz0Y9ECr7er4VdjSQxV"
928 "AaeLAsQsK9ROae8hMBFZ3//8zLVapBwpuffCu+fUoql9qeV9xagZcc9zj8XOUOW"
929 "ApiihqNL1111 test@test1.org")
930 project = {
931 "commonInstanceMetadata": {
932 "kind": "compute#metadata",
933 "fingerprint": "a-23icsyx4E=",
934 "items": [
935 {
936 "key": "sshKeys",
937 "value": "user:key"
938 }
939 ]
940 }
941 }
942 expected = {
943 "kind": "compute#metadata",
944 "fingerprint": "a-23icsyx4E=",
945 "items": [
946 {
947 "key": "sshKeys",
948 "value": "user:key\n%s:%s" % (fake_user, sshkey)
949 }
950 ]
951 }
952
953 self.Patch(os.path, "exists", return_value=True)
954 m = mock.mock_open(read_data=sshkey)
955 self.Patch(__builtins__, "open", m, create=True)
956 self.Patch(gcompute_client.ComputeClient, "WaitOnOperation")
957 self.Patch(
958 gcompute_client.ComputeClient, "GetProject", return_value=project)
959 resource_mock = mock.MagicMock()
960 self.compute_client._service.projects = mock.MagicMock(
961 return_value=resource_mock)
962 resource_mock.setCommonInstanceMetadata = mock.MagicMock()
963
964 self.compute_client.AddSshRsa(fake_user, "/path/to/test_rsa.pub")
965 resource_mock.setCommonInstanceMetadata.assert_called_with(
Kevin Chengb5963882018-05-09 00:06:27 -0700966 project=PROJECT, body=expected)
Tri Vo29ac1822016-10-01 17:06:29 -0700967
968 def testAddSshRsaInvalidKey(self):
969 """Test AddSshRsa.."""
970 fake_user = "fake_user"
971 sshkey = "ssh-rsa v2VOqkkf7RGL1111 test@test1.org"
972 project = {
973 "commonInstanceMetadata": {
974 "kind": "compute#metadata",
975 "fingerprint": "a-23icsyx4E=",
976 "items": [
977 {
978 "key": "sshKeys",
979 "value": "user:key"
980 }
981 ]
982 }
983 }
984 self.Patch(os.path, "exists", return_value=True)
985 m = mock.mock_open(read_data=sshkey)
986 self.Patch(__builtins__, "open", m, create=True)
987 self.Patch(gcompute_client.ComputeClient, "WaitOnOperation")
988 self.Patch(
989 gcompute_client.ComputeClient, "GetProject", return_value=project)
990 self.assertRaisesRegexp(errors.DriverError, "rsa key is invalid:*",
991 self.compute_client.AddSshRsa, fake_user,
992 "/path/to/test_rsa.pub")
993
994 def testDeleteDisks(self):
995 """Test DeleteDisks."""
996 self._SetupBatchHttpRequestMock()
997 self.Patch(gcompute_client.ComputeClient, "WaitOnOperation")
998 fake_disks = ["fake_disk_1", "fake_disk_2"]
999 mock_api = mock.MagicMock()
1000 resource_mock = mock.MagicMock()
1001 self.compute_client._service.disks = mock.MagicMock(
1002 return_value=resource_mock)
1003 resource_mock.delete = mock.MagicMock(return_value=mock_api)
1004 # Call the API.
1005 deleted, failed, error_msgs = self.compute_client.DeleteDisks(
1006 fake_disks, zone=self.ZONE)
1007 # Verify
Kevin Chengb5963882018-05-09 00:06:27 -07001008 calls = [
1009 mock.call(project=PROJECT, disk="fake_disk_1", zone=self.ZONE),
1010 mock.call(project=PROJECT, disk="fake_disk_2", zone=self.ZONE)
1011 ]
Tri Vo29ac1822016-10-01 17:06:29 -07001012 resource_mock.delete.assert_has_calls(calls, any_order=True)
1013 self.assertEqual(
1014 gcompute_client.ComputeClient.WaitOnOperation.call_count, 2)
1015 self.assertEqual(error_msgs, [])
1016 self.assertEqual(failed, [])
1017 self.assertEqual(set(deleted), set(fake_disks))
1018
Kevin Chengb5963882018-05-09 00:06:27 -07001019 def testRetryOnFingerPrintError(self):
1020 @utils.RetryOnException(gcompute_client._IsFingerPrintError, 10)
1021 def Raise412(sentinel):
1022 if not sentinel.hitFingerPrintConflict.called:
1023 sentinel.hitFingerPrintConflict()
1024 raise errors.HttpError(412, "resource labels have changed")
1025 return "Passed"
1026
1027 sentinel = mock.MagicMock()
1028 result = Raise412(sentinel)
1029 self.assertEqual(1, sentinel.hitFingerPrintConflict.call_count)
1030 self.assertEqual("Passed", result)
1031
Tri Vo29ac1822016-10-01 17:06:29 -07001032
1033if __name__ == "__main__":
1034 unittest.main()