diff options
author | Tri Vo <trong@google.com> | 2016-10-01 17:06:29 -0700 |
---|---|---|
committer | Tri Vo <trong@google.com> | 2016-10-06 09:03:26 -0700 |
commit | 29ac182d42965ac86ba181b9d29899da8e802afa (patch) | |
tree | 863efe19a7bd0ce0473743a3954947f277e01818 | |
parent | f6a7217f80d4e43c7479e411056e162c28999376 (diff) | |
download | acloud-29ac182d42965ac86ba181b9d29899da8e802afa.tar.gz |
Add unittests.
Change-Id: I55e7397c44605719bb01aa1b6d81eed2fa743cf4
-rw-r--r-- | internal/lib/android_compute_client_test.py | 153 | ||||
-rw-r--r-- | internal/lib/base_cloud_client_test.py | 159 | ||||
-rw-r--r-- | internal/lib/gcompute_client_test.py | 730 | ||||
-rw-r--r-- | internal/lib/gstorage_client_test.py | 148 | ||||
-rw-r--r-- | public/acloud_kernel/kernel_swapper_test.py | 97 | ||||
-rw-r--r-- | public/config_test.py | 160 | ||||
-rw-r--r-- | public/device_driver_test.py | 249 | ||||
-rw-r--r-- | public/report_test.py | 64 |
8 files changed, 1760 insertions, 0 deletions
diff --git a/internal/lib/android_compute_client_test.py b/internal/lib/android_compute_client_test.py new file mode 100644 index 00000000..88a2d600 --- /dev/null +++ b/internal/lib/android_compute_client_test.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python +# +# Copyright 2016 - The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for android_compute_client.""" + +import uuid + +import mock + +import unittest +from acloud.internal.lib import android_compute_client +from acloud.internal.lib import driver_test_lib +from acloud.internal.lib import gcompute_client +from acloud.public import errors + + +class AndroidComputeClientTest(driver_test_lib.BaseDriverTest): + """Test AndroidComputeClient.""" + + PROJECT = "fake-project" + SERVICE_ACCOUNT_NAME = "fake@fake.com" + PRIVATE_KEY_PATH = "/fake/key/path" + IMAGE = "fake-image" + GS_IMAGE_SOURCE_URI = "https://storage.googleapis.com/fake-bucket/fake.tar.gz" + MACHINE_TYPE = "fake-machine-type" + MIN_MACHINE_SIZE = "fake-min-machine-size" + METADATA = ("metadata_key", "metadata_value") + NETWORK = "fake-network" + ZONE = "fake-zone" + ORIENTATION = "portrait" + DEVICE_RESOLUTION = "1200x1200x1200x1200" + METADATA = ("metadata_key", "metadata_value") + TARGET = "gce_x86-userdebug" + BUILD_ID = "2263051" + + def _GetFakeConfig(self): + """Create a fake configuration object. + + Returns: + A fake configuration mock object. + """ + fake_cfg = mock.MagicMock() + fake_cfg.project = self.PROJECT + fake_cfg.service_account_name = self.SERVICE_ACCOUNT_NAME + fake_cfg.service_account_private_key_path = self.PRIVATE_KEY_PATH + fake_cfg.zone = self.ZONE + fake_cfg.machine_type = self.MACHINE_TYPE + fake_cfg.min_machine_size = self.MIN_MACHINE_SIZE + fake_cfg.network = self.NETWORK + fake_cfg.orientation = self.ORIENTATION + fake_cfg.resolution = self.DEVICE_RESOLUTION + fake_cfg.metadata_variable = {self.METADATA[0]: self.METADATA[1]} + return fake_cfg + + def setUp(self): + """Set up the test.""" + super(AndroidComputeClientTest, self).setUp() + self.Patch(android_compute_client.AndroidComputeClient, + "InitResourceHandle") + self.android_compute_client = android_compute_client.AndroidComputeClient( + self._GetFakeConfig(), mock.MagicMock()) + + def testCreateImage(self): + """Test CreateImage.""" + self.Patch(gcompute_client.ComputeClient, "CreateImage") + self.Patch( + gcompute_client.ComputeClient, + "CheckImageExists", + return_value=False) + unique_id = uuid.uuid4() + image_name = "image-gce-x86-userdebug-2345-abcd" + self.android_compute_client.CreateImage(image_name, + self.GS_IMAGE_SOURCE_URI) + super(android_compute_client.AndroidComputeClient, + self.android_compute_client).CreateImage.assert_called_with( + image_name, self.GS_IMAGE_SOURCE_URI) + self.android_compute_client.CheckImageExists.assert_called_with( + image_name) + + def testCreateInstance(self): + """Test CreateInstance.""" + self.Patch( + gcompute_client.ComputeClient, + "CompareMachineSize", + return_value=1) + self.Patch(gcompute_client.ComputeClient, "CreateInstance") + self.Patch( + gcompute_client.ComputeClient, + "_GetDiskArgs", + return_value=[{"fake_arg": "fake_value"}]) + self.Patch( + self.android_compute_client, + "_GetExtraDiskArgs", + return_value=[{"fake_extra_arg": "fake_extra_value"}]) + instance_name = "gce-x86-userdebug-2345-abcd" + extra_disk_name = "gce-x86-userdebug-2345-abcd-data" + expected_metadata = { + self.METADATA[0]: self.METADATA[1], + "cfg_sta_display_resolution": self.DEVICE_RESOLUTION, + "t_force_orientation": self.ORIENTATION, + } + + expected_disk_args = [ + {"fake_arg": "fake_value"}, {"fake_extra_arg": "fake_extra_value"} + ] + + self.android_compute_client.CreateInstance(instance_name, self.IMAGE, + extra_disk_name) + super(android_compute_client.AndroidComputeClient, + self.android_compute_client).CreateInstance.assert_called_with( + instance_name, self.IMAGE, self.MACHINE_TYPE, + expected_metadata, self.NETWORK, self.ZONE, + expected_disk_args) + + def testCheckMachineSizeMeetsRequirement(self): + """Test CheckMachineSize when machine size meets requirement.""" + self.Patch( + gcompute_client.ComputeClient, + "CompareMachineSize", + return_value=1) + self.android_compute_client._CheckMachineSize() + self.android_compute_client.CompareMachineSize.assert_called_with( + self.MACHINE_TYPE, self.MIN_MACHINE_SIZE, self.ZONE) + + def testCheckMachineSizeDoesNotMeetRequirement(self): + """Test CheckMachineSize when machine size does not meet requirement.""" + self.Patch( + gcompute_client.ComputeClient, + "CompareMachineSize", + return_value=-1) + self.assertRaisesRegexp( + errors.DriverError, + ".*does not meet the minimum required machine size.*", + self.android_compute_client._CheckMachineSize) + self.android_compute_client.CompareMachineSize.assert_called_with( + self.MACHINE_TYPE, self.MIN_MACHINE_SIZE, self.ZONE) + + +if __name__ == "__main__": + unittest.main() diff --git a/internal/lib/base_cloud_client_test.py b/internal/lib/base_cloud_client_test.py new file mode 100644 index 00000000..96eb3c32 --- /dev/null +++ b/internal/lib/base_cloud_client_test.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python +# +# Copyright 2016 - The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for acloud.internal.lib.base_cloud_client.""" + +import time +import apiclient +import mock + +import unittest +from acloud.internal.lib import base_cloud_client +from acloud.internal.lib import driver_test_lib +from acloud.public import errors + + +class FakeError(Exception): + """Fake Error for testing retries.""" + + +class BaseCloudApiClientTest(driver_test_lib.BaseDriverTest): + """Test BaseCloudApiClient.""" + + def setUp(self): + """Set up test.""" + super(BaseCloudApiClientTest, self).setUp() + + def testInitResourceHandle(self): + """Test InitResourceHandle.""" + # Setup mocks + mock_credentials = mock.MagicMock() + self.Patch(base_cloud_client, "build") + # Call the method + base_cloud_client.BaseCloudApiClient(mock.MagicMock()) + base_cloud_client.build.assert_called_once_with( + serviceName=base_cloud_client.BaseCloudApiClient.API_NAME, + version=base_cloud_client.BaseCloudApiClient.API_VERSION, + http=mock.ANY) + + def _SetupInitMocks(self): + """Setup mocks required to initialize a base cloud client. + + Returns: + A base_cloud_client.BaseCloudApiClient mock. + """ + self.Patch( + base_cloud_client.BaseCloudApiClient, + "InitResourceHandle", + return_value=mock.MagicMock()) + return base_cloud_client.BaseCloudApiClient(mock.MagicMock()) + + def _SetupBatchHttpRequestMock(self, rid_to_responses, rid_to_exceptions): + """Setup BatchHttpRequest mock.""" + + rid_to_exceptions = rid_to_exceptions or {} + rid_to_responses = rid_to_responses or {} + + def _CreatMockBatchHttpRequest(): + """Create a mock BatchHttpRequest object.""" + requests = {} + + def _Add(request, callback, request_id): + requests[request_id] = (request, callback) + + def _Execute(): + for rid in requests: + requests[rid][0].execute() + _, callback = requests[rid] + callback( + request_id=rid, + response=rid_to_responses.get(rid), + exception=rid_to_exceptions.get(rid)) + + mock_batch = mock.MagicMock() + mock_batch.add = _Add + mock_batch.execute = _Execute + return mock_batch + + self.Patch( + apiclient.http, + "BatchHttpRequest", + side_effect=_CreatMockBatchHttpRequest) + + def testBatchExecute(self): + """Test BatchExecute.""" + self.Patch(time, "sleep") + client = self._SetupInitMocks() + requests = {"r1": mock.MagicMock(), + "r2": mock.MagicMock(), + "r3": mock.MagicMock()} + response = {"name": "fake_response"} + error_1 = errors.HttpError(503, "fake retriable error.") + error_2 = FakeError("fake retriable error.") + responses = {"r1": response, "r2": None, "r3": None} + exceptions = {"r1": None, "r2": error_1, "r3": error_2} + self._SetupBatchHttpRequestMock(responses, exceptions) + results = client.BatchExecute( + requests, other_retriable_errors=(FakeError, )) + expected_results = { + "r1": (response, None), + "r2": (None, error_1), + "r3": (None, error_2) + } + self.assertEqual(results, expected_results) + self.assertEqual(requests["r1"].execute.call_count, 1) + self.assertEqual(requests["r2"].execute.call_count, + client.RETRY_COUNT + 1) + self.assertEqual(requests["r3"].execute.call_count, + client.RETRY_COUNT + 1) + + def testListWithMultiPages(self): + """Test ListWithMultiPages.""" + fake_token = "fake_next_page_token" + item_1 = "item_1" + item_2 = "item_2" + response_1 = {"items": [item_1], "nextPageToken": fake_token} + response_2 = {"items": [item_2]} + + api_mock = mock.MagicMock() + api_mock.execute.side_effect = [response_1, response_2] + resource_mock = mock.MagicMock(return_value=api_mock) + client = self._SetupInitMocks() + items = client.ListWithMultiPages( + api_resource=resource_mock, fake_arg="fake_arg") + self.assertEqual(items, [item_1, item_2]) + + def testExecuteWithRetry(self): + """Test Execute is called and retries are triggered.""" + self.Patch(time, "sleep") + client = self._SetupInitMocks() + api_mock = mock.MagicMock() + error = errors.HttpError(503, "fake retriable error.") + api_mock.execute.side_effect = error + self.assertRaises(errors.HttpError, client.Execute, api_mock) + + api_mock = mock.MagicMock() + api_mock.execute.side_effect = FakeError("fake retriable error.") + self.assertRaises( + FakeError, + client.Execute, + api_mock, + other_retriable_errors=(FakeError, )) + self.assertEqual(api_mock.execute.call_count, client.RETRY_COUNT + 1) + + +if __name__ == "__main__": + unittest.main() diff --git a/internal/lib/gcompute_client_test.py b/internal/lib/gcompute_client_test.py new file mode 100644 index 00000000..2382eb1e --- /dev/null +++ b/internal/lib/gcompute_client_test.py @@ -0,0 +1,730 @@ +#!/usr/bin/env python +# +# Copyright 2016 - The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for acloud.internal.lib.gcompute_client.""" + +import os + +import apiclient.http +import mock + +import unittest +from acloud.internal.lib import driver_test_lib +from acloud.internal.lib import gcompute_client +from acloud.internal.lib import utils +from acloud.public import errors + + +class ComputeClientTest(driver_test_lib.BaseDriverTest): + """Test ComputeClient.""" + + PROJECT = "fake-project" + INSTANCE = "fake-instance" + IMAGE = "fake-image" + IMAGE_URL = "http://fake-image-url" + GS_IMAGE_SOURCE_URI = "https://storage.googleapis.com/fake-bucket/fake.tar.gz" + MACHINE_TYPE = "fake-machine-type" + MACHINE_TYPE_URL = "http://fake-machine-type-url" + METADATA = ("metadata_key", "metadata_value") + NETWORK = "fake-network" + NETWORK_URL = "http://fake-network-url" + ZONE = "fake-zone" + REGION = "fake-region" + OPERATION_NAME = "fake-op" + + def setUp(self): + """Set up test.""" + super(ComputeClientTest, self).setUp() + self.Patch(gcompute_client.ComputeClient, "InitResourceHandle") + fake_cfg = mock.MagicMock() + fake_cfg.project = self.PROJECT + self.compute_client = gcompute_client.ComputeClient(fake_cfg, + mock.MagicMock()) + self.compute_client._service = mock.MagicMock() + + def _SetupMocksForGetOperationStatus(self, mock_result, operation_scope): + """A helper class for setting up mocks for testGetOperationStatus*. + + Args: + mock_result: The result to return by _GetOperationStatus. + operation_scope: A value of OperationScope. + + Returns: + A mock for Resource object. + """ + resource_mock = mock.MagicMock() + mock_api = mock.MagicMock() + if operation_scope == gcompute_client.OperationScope.GLOBAL: + self.compute_client._service.globalOperations = mock.MagicMock( + return_value=resource_mock) + elif operation_scope == gcompute_client.OperationScope.ZONE: + self.compute_client._service.zoneOperations = mock.MagicMock( + return_value=resource_mock) + elif operation_scope == gcompute_client.OperationScope.REGION: + self.compute_client._service.regionOperations = mock.MagicMock( + return_value=resource_mock) + resource_mock.get = mock.MagicMock(return_value=mock_api) + mock_api.execute = mock.MagicMock(return_value=mock_result) + return resource_mock + + def testGetOperationStatusGlobal(self): + """Test _GetOperationStatus for global.""" + resource_mock = self._SetupMocksForGetOperationStatus( + {"status": "GOOD"}, gcompute_client.OperationScope.GLOBAL) + status = self.compute_client._GetOperationStatus( + {"name": self.OPERATION_NAME}, + gcompute_client.OperationScope.GLOBAL) + self.assertEqual(status, "GOOD") + resource_mock.get.assert_called_with( + project=self.PROJECT, operation=self.OPERATION_NAME) + + def testGetOperationStatusZone(self): + """Test _GetOperationStatus for zone.""" + resource_mock = self._SetupMocksForGetOperationStatus( + {"status": "GOOD"}, gcompute_client.OperationScope.ZONE) + status = self.compute_client._GetOperationStatus( + {"name": self.OPERATION_NAME}, gcompute_client.OperationScope.ZONE, + self.ZONE) + self.assertEqual(status, "GOOD") + resource_mock.get.assert_called_with( + project=self.PROJECT, + operation=self.OPERATION_NAME, + zone=self.ZONE) + + def testGetOperationStatusRegion(self): + """Test _GetOperationStatus for region.""" + resource_mock = self._SetupMocksForGetOperationStatus( + {"status": "GOOD"}, gcompute_client.OperationScope.REGION) + self.compute_client._GetOperationStatus( + {"name": self.OPERATION_NAME}, + gcompute_client.OperationScope.REGION, self.REGION) + resource_mock.get.assert_called_with( + project=self.PROJECT, + operation=self.OPERATION_NAME, + region=self.REGION) + + def testGetOperationStatusError(self): + """Test _GetOperationStatus failed.""" + self._SetupMocksForGetOperationStatus( + {"error": {"errors": ["error1", "error2"]}}, + gcompute_client.OperationScope.GLOBAL) + self.assertRaisesRegexp(errors.DriverError, + "Get operation state failed.*error1.*error2", + self.compute_client._GetOperationStatus, + {"name": self.OPERATION_NAME}, + gcompute_client.OperationScope.GLOBAL) + + def testWaitOnOperation(self): + """Test WaitOnOperation.""" + mock_error = mock.MagicMock() + self.Patch(utils, "PollAndWait") + self.Patch(errors, "GceOperationTimeoutError", return_value=mock_error) + self.compute_client.WaitOnOperation( + operation={"name": self.OPERATION_NAME}, + operation_scope=gcompute_client.OperationScope.REGION, + scope_name=self.REGION) + utils.PollAndWait.assert_called_with( + func=self.compute_client._GetOperationStatus, + expected_return="DONE", + timeout_exception=mock_error, + timeout_secs=self.compute_client.OPERATION_TIMEOUT_SECS, + sleep_interval_secs=self.compute_client. + OPERATION_POLL_INTERVAL_SECS, + operation={"name": self.OPERATION_NAME}, + operation_scope=gcompute_client.OperationScope.REGION, + scope_name=self.REGION) + + def testCreateImage(self): + """Test CreateImage.""" + self.Patch(gcompute_client.ComputeClient, "WaitOnOperation") + resource_mock = mock.MagicMock() + self.compute_client._service.images = mock.MagicMock( + return_value=resource_mock) + resource_mock.insert = mock.MagicMock() + + expected_body = { + "name": self.IMAGE, + "rawDisk": { + "source": self.GS_IMAGE_SOURCE_URI, + }, + } + self.compute_client.CreateImage( + image_name=self.IMAGE, source_uri=self.GS_IMAGE_SOURCE_URI) + resource_mock.insert.assert_called_with( + project=self.PROJECT, body=expected_body) + self.compute_client.WaitOnOperation.assert_called_with( + operation=mock.ANY, + operation_scope=gcompute_client.OperationScope.GLOBAL) + + def testCreateImageFail(self): + """Test CreateImage fails.""" + self.Patch( + gcompute_client.ComputeClient, + "WaitOnOperation", + side_effect=errors.DriverError("Expected fake error")) + self.Patch( + gcompute_client.ComputeClient, + "CheckImageExists", + return_value=True) + self.Patch(gcompute_client.ComputeClient, "DeleteImage") + + resource_mock = mock.MagicMock() + self.compute_client._service.images = mock.MagicMock( + return_value=resource_mock) + resource_mock.insert = mock.MagicMock() + + expected_body = { + "name": self.IMAGE, + "rawDisk": { + "source": self.GS_IMAGE_SOURCE_URI, + }, + } + self.assertRaisesRegexp( + errors.DriverError, + "Expected fake error", + self.compute_client.CreateImage, + image_name=self.IMAGE, + source_uri=self.GS_IMAGE_SOURCE_URI) + resource_mock.insert.assert_called_with( + project=self.PROJECT, body=expected_body) + self.compute_client.WaitOnOperation.assert_called_with( + operation=mock.ANY, + operation_scope=gcompute_client.OperationScope.GLOBAL) + self.compute_client.CheckImageExists.assert_called_with(self.IMAGE) + self.compute_client.DeleteImage.assert_called_with(self.IMAGE) + + def testCheckImageExistsTrue(self): + """Test CheckImageExists return True.""" + resource_mock = mock.MagicMock() + mock_api = mock.MagicMock() + self.compute_client._service.images = mock.MagicMock( + return_value=resource_mock) + resource_mock.get = mock.MagicMock(return_value=mock_api) + mock_api.execute = mock.MagicMock(return_value={"name": self.IMAGE}) + self.assertTrue(self.compute_client.CheckImageExists(self.IMAGE)) + + def testCheckImageExistsFalse(self): + """Test CheckImageExists return False.""" + resource_mock = mock.MagicMock() + mock_api = mock.MagicMock() + self.compute_client._service.images = mock.MagicMock( + return_value=resource_mock) + resource_mock.get = mock.MagicMock(return_value=mock_api) + mock_api.execute = mock.MagicMock( + side_effect=errors.ResourceNotFoundError(404, "no image")) + self.assertFalse(self.compute_client.CheckImageExists(self.IMAGE)) + + def testDeleteImage(self): + """Test DeleteImage.""" + self.Patch(gcompute_client.ComputeClient, "WaitOnOperation") + resource_mock = mock.MagicMock() + self.compute_client._service.images = mock.MagicMock( + return_value=resource_mock) + resource_mock.delete = mock.MagicMock() + self.compute_client.DeleteImage(self.IMAGE) + resource_mock.delete.assert_called_with( + project=self.PROJECT, image=self.IMAGE) + self.assertTrue(self.compute_client.WaitOnOperation.called) + + def _SetupBatchHttpRequestMock(self): + """Setup BatchHttpRequest mock.""" + requests = {} + + def _Add(request, callback, request_id): + requests[request_id] = (request, callback) + + def _Execute(): + for rid in requests: + _, callback = requests[rid] + callback( + request_id=rid, response=mock.MagicMock(), exception=None) + + mock_batch = mock.MagicMock() + mock_batch.add = _Add + mock_batch.execute = _Execute + self.Patch(apiclient.http, "BatchHttpRequest", return_value=mock_batch) + + def testDeleteImages(self): + """Test DeleteImages.""" + self._SetupBatchHttpRequestMock() + self.Patch(gcompute_client.ComputeClient, "WaitOnOperation") + fake_images = ["fake_image_1", "fake_image_2"] + mock_api = mock.MagicMock() + resource_mock = mock.MagicMock() + self.compute_client._service.images = mock.MagicMock( + return_value=resource_mock) + resource_mock.delete = mock.MagicMock(return_value=mock_api) + # Call the API. + deleted, failed, error_msgs = self.compute_client.DeleteImages( + fake_images) + # Verify + calls = [mock.call( + project=self.PROJECT, image="fake_image_1"), mock.call( + project=self.PROJECT, image="fake_image_2")] + resource_mock.delete.assert_has_calls(calls, any_order=True) + self.assertEqual( + gcompute_client.ComputeClient.WaitOnOperation.call_count, 2) + self.assertEqual(error_msgs, []) + self.assertEqual(failed, []) + self.assertEqual(set(deleted), set(fake_images)) + + def testListImages(self): + """Test ListImages.""" + fake_token = "fake_next_page_token" + image_1 = "image_1" + image_2 = "image_2" + response_1 = {"items": [image_1], "nextPageToken": fake_token} + response_2 = {"items": [image_2]} + self.Patch( + gcompute_client.ComputeClient, + "Execute", + side_effect=[response_1, response_2]) + resource_mock = mock.MagicMock() + self.compute_client._service.images = mock.MagicMock( + return_value=resource_mock) + resource_mock.list = mock.MagicMock() + images = self.compute_client.ListImages() + calls = [ + mock.call( + project=self.PROJECT, filter=None, pageToken=None), mock.call( + project=self.PROJECT, filter=None, pageToken=fake_token) + ] + resource_mock.list.assert_has_calls(calls) + self.assertEqual(images, [image_1, image_2]) + + def testGetInstance(self): + """Test GetInstance.""" + resource_mock = mock.MagicMock() + mock_api = mock.MagicMock() + self.compute_client._service.instances = mock.MagicMock( + return_value=resource_mock) + resource_mock.get = mock.MagicMock(return_value=mock_api) + mock_api.execute = mock.MagicMock(return_value={"name": self.INSTANCE}) + result = self.compute_client.GetInstance(self.INSTANCE, self.ZONE) + self.assertEqual(result, {"name": self.INSTANCE}) + resource_mock.get.assert_called_with( + project=self.PROJECT, zone=self.ZONE, instance=self.INSTANCE) + + def testListInstances(self): + """Test ListInstances.""" + fake_token = "fake_next_page_token" + instance_1 = "instance_1" + instance_2 = "instance_2" + response_1 = {"items": [instance_1], "nextPageToken": fake_token} + response_2 = {"items": [instance_2]} + self.Patch( + gcompute_client.ComputeClient, + "Execute", + side_effect=[response_1, response_2]) + resource_mock = mock.MagicMock() + self.compute_client._service.instances = mock.MagicMock( + return_value=resource_mock) + resource_mock.list = mock.MagicMock() + instances = self.compute_client.ListInstances(self.ZONE) + calls = [ + mock.call( + project=self.PROJECT, + zone=self.ZONE, + filter=None, + pageToken=None), + mock.call( + project=self.PROJECT, + zone=self.ZONE, + filter=None, + pageToken=fake_token), + ] + resource_mock.list.assert_has_calls(calls) + self.assertEqual(instances, [instance_1, instance_2]) + + def testCreateInstance(self): + """Test CreateInstance.""" + self.Patch(gcompute_client.ComputeClient, "WaitOnOperation") + self.Patch( + gcompute_client.ComputeClient, + "GetMachineType", + return_value={"selfLink": self.MACHINE_TYPE_URL}) + self.Patch( + gcompute_client.ComputeClient, + "GetNetworkUrl", + return_value=self.NETWORK_URL) + self.Patch( + gcompute_client.ComputeClient, + "GetImage", + return_value={"selfLink": self.IMAGE_URL}) + + resource_mock = mock.MagicMock() + self.compute_client._service.instances = mock.MagicMock( + return_value=resource_mock) + resource_mock.insert = mock.MagicMock() + + expected_body = { + "machineType": self.MACHINE_TYPE_URL, + "name": self.INSTANCE, + "networkInterfaces": [ + { + "network": self.NETWORK_URL, + "accessConfigs": [ + {"name": "External NAT", + "type": "ONE_TO_ONE_NAT"} + ], + } + ], + "disks": [ + { + "type": "PERSISTENT", + "boot": True, + "mode": "READ_WRITE", + "autoDelete": True, + "initializeParams": { + "diskName": self.INSTANCE, + "sourceImage": self.IMAGE_URL, + }, + } + ], + "serviceAccounts": [ + {"email": "default", + "scopes": self.compute_client.DEFAULT_INSTANCE_SCOPE} + ], + "metadata": { + "items": [{"key": self.METADATA[0], + "value": self.METADATA[1]}], + }, + } + + self.compute_client.CreateInstance( + instance=self.INSTANCE, + image_name=self.IMAGE, + machine_type=self.MACHINE_TYPE, + metadata={self.METADATA[0]: self.METADATA[1]}, + network=self.NETWORK, + zone=self.ZONE) + + resource_mock.insert.assert_called_with( + project=self.PROJECT, zone=self.ZONE, body=expected_body) + self.compute_client.WaitOnOperation.assert_called_with( + mock.ANY, + operation_scope=gcompute_client.OperationScope.ZONE, + scope_name=self.ZONE) + + def testDeleteInstance(self): + """Test DeleteInstance.""" + self.Patch(gcompute_client.ComputeClient, "WaitOnOperation") + resource_mock = mock.MagicMock() + self.compute_client._service.instances = mock.MagicMock( + return_value=resource_mock) + resource_mock.delete = mock.MagicMock() + self.compute_client.DeleteInstance( + instance=self.INSTANCE, zone=self.ZONE) + resource_mock.delete.assert_called_with( + project=self.PROJECT, zone=self.ZONE, instance=self.INSTANCE) + self.compute_client.WaitOnOperation.assert_called_with( + mock.ANY, + operation_scope=gcompute_client.OperationScope.ZONE, + scope_name=self.ZONE) + + def testDeleteInstances(self): + """Test DeleteInstances.""" + self._SetupBatchHttpRequestMock() + self.Patch(gcompute_client.ComputeClient, "WaitOnOperation") + fake_instances = ["fake_instance_1", "fake_instance_2"] + mock_api = mock.MagicMock() + resource_mock = mock.MagicMock() + self.compute_client._service.instances = mock.MagicMock( + return_value=resource_mock) + resource_mock.delete = mock.MagicMock(return_value=mock_api) + deleted, failed, error_msgs = self.compute_client.DeleteInstances( + fake_instances, self.ZONE) + calls = [ + mock.call( + project=self.PROJECT, + instance="fake_instance_1", + zone=self.ZONE), mock.call( + project=self.PROJECT, + instance="fake_instance_2", + zone=self.ZONE) + ] + resource_mock.delete.assert_has_calls(calls, any_order=True) + self.assertEqual( + gcompute_client.ComputeClient.WaitOnOperation.call_count, 2) + self.assertEqual(error_msgs, []) + self.assertEqual(failed, []) + self.assertEqual(set(deleted), set(fake_instances)) + + def testBatchExecuteOnInstances(self): + self._SetupBatchHttpRequestMock() + self.Patch(gcompute_client.ComputeClient, "WaitOnOperation") + action = mock.MagicMock(return_value=mock.MagicMock()) + fake_instances = ["fake_instance_1", "fake_instance_2"] + done, failed, error_msgs = self.compute_client._BatchExecuteOnInstances( + fake_instances, self.ZONE, action) + calls = [mock.call(instance="fake_instance_1"), + mock.call(instance="fake_instance_2")] + action.assert_has_calls(calls, any_order=True) + self.assertEqual( + gcompute_client.ComputeClient.WaitOnOperation.call_count, 2) + self.assertEqual(set(done), set(fake_instances)) + self.assertEqual(error_msgs, []) + self.assertEqual(failed, []) + + def testResetInstance(self): + """Test ResetInstance.""" + self.Patch(gcompute_client.ComputeClient, "WaitOnOperation") + resource_mock = mock.MagicMock() + self.compute_client._service.instances = mock.MagicMock( + return_value=resource_mock) + resource_mock.reset = mock.MagicMock() + self.compute_client.ResetInstance( + instance=self.INSTANCE, zone=self.ZONE) + resource_mock.reset.assert_called_with( + project=self.PROJECT, zone=self.ZONE, instance=self.INSTANCE) + self.compute_client.WaitOnOperation.assert_called_with( + mock.ANY, + operation_scope=gcompute_client.OperationScope.ZONE, + scope_name=self.ZONE) + + def _CompareMachineSizeTestHelper(self, + machine_info_1, + machine_info_2, + expected_result=None, + expected_error_type=None): + """Helper class for testing CompareMachineSize. + + Args: + machine_info_1: A dictionary representing the first machine size. + machine_info_2: A dictionary representing the second machine size. + expected_result: An integer, 0, 1 or -1, or None if not set. + expected_error_type: An exception type, if set will check for exception. + """ + self.Patch( + gcompute_client.ComputeClient, + "GetMachineType", + side_effect=[machine_info_1, machine_info_2]) + if expected_error_type: + self.assertRaises(expected_error_type, + self.compute_client.CompareMachineSize, "name1", + "name2", self.ZONE) + else: + result = self.compute_client.CompareMachineSize("name1", "name2", + self.ZONE) + self.assertEqual(result, expected_result) + + self.compute_client.GetMachineType.assert_has_calls( + [mock.call("name1", self.ZONE), mock.call("name2", self.ZONE)]) + + def testCompareMachineSizeSmall(self): + """Test CompareMachineSize where the first one is smaller.""" + machine_info_1 = {"guestCpus": 10, "memoryMb": 100} + machine_info_2 = {"guestCpus": 10, "memoryMb": 200} + self._CompareMachineSizeTestHelper(machine_info_1, machine_info_2, -1) + + def testCompareMachineSizeLarge(self): + """Test CompareMachineSize where the first one is larger.""" + machine_info_1 = {"guestCpus": 10, "memoryMb": 200} + machine_info_2 = {"guestCpus": 10, "memoryMb": 100} + self._CompareMachineSizeTestHelper(machine_info_1, machine_info_2, 1) + + def testCompareMachineSizeEqual(self): + """Test CompareMachineSize where two machine sizes are equal.""" + machine_info = {"guestCpus": 10, "memoryMb": 100} + self._CompareMachineSizeTestHelper(machine_info, machine_info, 0) + + def testCompareMachineSizeBadMetric(self): + """Test CompareMachineSize with bad metric.""" + machine_info = {"unkown_metric": 10, "memoryMb": 100} + self._CompareMachineSizeTestHelper( + machine_info, machine_info, expected_error_type=errors.DriverError) + + def testGetMachineType(self): + """Test GetMachineType.""" + resource_mock = mock.MagicMock() + mock_api = mock.MagicMock() + self.compute_client._service.machineTypes = mock.MagicMock( + return_value=resource_mock) + resource_mock.get = mock.MagicMock(return_value=mock_api) + mock_api.execute = mock.MagicMock( + return_value={"name": self.MACHINE_TYPE}) + result = self.compute_client.GetMachineType(self.MACHINE_TYPE, + self.ZONE) + self.assertEqual(result, {"name": self.MACHINE_TYPE}) + resource_mock.get.assert_called_with( + project=self.PROJECT, + zone=self.ZONE, + machineType=self.MACHINE_TYPE) + + def _GetSerialPortOutputTestHelper(self, response): + """Helper function for testing GetSerialPortOutput. + + Args: + response: A dictionary representing a fake response. + """ + resource_mock = mock.MagicMock() + mock_api = mock.MagicMock() + self.compute_client._service.instances = mock.MagicMock( + return_value=resource_mock) + resource_mock.getSerialPortOutput = mock.MagicMock( + return_value=mock_api) + mock_api.execute = mock.MagicMock(return_value=response) + + if "contents" in response: + result = self.compute_client.GetSerialPortOutput( + instance=self.INSTANCE, zone=self.ZONE) + self.assertEqual(result, "fake contents") + else: + self.assertRaisesRegexp( + errors.DriverError, + "Malformed response.*", + self.compute_client.GetSerialPortOutput, + instance=self.INSTANCE, + zone=self.ZONE) + resource_mock.getSerialPortOutput.assert_called_with( + project=self.PROJECT, + zone=self.ZONE, + instance=self.INSTANCE, + port=1) + + def testGetSerialPortOutput(self): + response = {"contents": "fake contents"} + self._GetSerialPortOutputTestHelper(response) + + def testGetSerialPortOutputFail(self): + response = {"malformed": "fake contents"} + self._GetSerialPortOutputTestHelper(response) + + def testGetInstanceNamesByIPs(self): + """Test GetInstanceNamesByIPs.""" + good_instance = { + "name": "instance_1", + "networkInterfaces": [ + { + "accessConfigs": [ + {"natIP": "172.22.22.22"}, + ], + }, + ], + } + bad_instance = {"name": "instance_2"} + self.Patch( + gcompute_client.ComputeClient, + "ListInstances", + return_value=[good_instance, bad_instance]) + ip_name_map = self.compute_client.GetInstanceNamesByIPs( + ips=["172.22.22.22", "172.22.22.23"], zone=self.ZONE) + self.assertEqual(ip_name_map, {"172.22.22.22": "instance_1", + "172.22.22.23": None}) + + def testAddSshRsa(self): + """Test AddSshRsa..""" + fake_user = "fake_user" + sshkey = ( + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBkTOTRze9v2VOqkkf7RG" + "jSkg6Z2kb9Q9UHsDGatvend3fmjIw1Tugg0O7nnjlPkskmlgyd4a/j99WOeLL" + "CPk6xPyoVjrPUVBU/pAk09ORTC4Zqk6YjlW7LOfzvqmXhmIZfYu6Q4Yt50pZzhl" + "lllfu26nYjY7Tg12D019nJi/kqPX5+NKgt0LGXTu8T1r2Gav/q4V7QRWQrB8Eiu" + "pxXR7I2YhynqovkEt/OXG4qWgvLEXGsWtSQs0CtCzqEVxz0Y9ECr7er4VdjSQxV" + "AaeLAsQsK9ROae8hMBFZ3//8zLVapBwpuffCu+fUoql9qeV9xagZcc9zj8XOUOW" + "ApiihqNL1111 test@test1.org") + project = { + "commonInstanceMetadata": { + "kind": "compute#metadata", + "fingerprint": "a-23icsyx4E=", + "items": [ + { + "key": "sshKeys", + "value": "user:key" + } + ] + } + } + expected = { + "kind": "compute#metadata", + "fingerprint": "a-23icsyx4E=", + "items": [ + { + "key": "sshKeys", + "value": "user:key\n%s:%s" % (fake_user, sshkey) + } + ] + } + + self.Patch(os.path, "exists", return_value=True) + m = mock.mock_open(read_data=sshkey) + self.Patch(__builtins__, "open", m, create=True) + self.Patch(gcompute_client.ComputeClient, "WaitOnOperation") + self.Patch( + gcompute_client.ComputeClient, "GetProject", return_value=project) + resource_mock = mock.MagicMock() + self.compute_client._service.projects = mock.MagicMock( + return_value=resource_mock) + resource_mock.setCommonInstanceMetadata = mock.MagicMock() + + self.compute_client.AddSshRsa(fake_user, "/path/to/test_rsa.pub") + resource_mock.setCommonInstanceMetadata.assert_called_with( + project=self.PROJECT, body=expected) + + def testAddSshRsaInvalidKey(self): + """Test AddSshRsa..""" + fake_user = "fake_user" + sshkey = "ssh-rsa v2VOqkkf7RGL1111 test@test1.org" + project = { + "commonInstanceMetadata": { + "kind": "compute#metadata", + "fingerprint": "a-23icsyx4E=", + "items": [ + { + "key": "sshKeys", + "value": "user:key" + } + ] + } + } + self.Patch(os.path, "exists", return_value=True) + m = mock.mock_open(read_data=sshkey) + self.Patch(__builtins__, "open", m, create=True) + self.Patch(gcompute_client.ComputeClient, "WaitOnOperation") + self.Patch( + gcompute_client.ComputeClient, "GetProject", return_value=project) + self.assertRaisesRegexp(errors.DriverError, "rsa key is invalid:*", + self.compute_client.AddSshRsa, fake_user, + "/path/to/test_rsa.pub") + + def testDeleteDisks(self): + """Test DeleteDisks.""" + self._SetupBatchHttpRequestMock() + self.Patch(gcompute_client.ComputeClient, "WaitOnOperation") + fake_disks = ["fake_disk_1", "fake_disk_2"] + mock_api = mock.MagicMock() + resource_mock = mock.MagicMock() + self.compute_client._service.disks = mock.MagicMock( + return_value=resource_mock) + resource_mock.delete = mock.MagicMock(return_value=mock_api) + # Call the API. + deleted, failed, error_msgs = self.compute_client.DeleteDisks( + fake_disks, zone=self.ZONE) + # Verify + calls = [mock.call( + project=self.PROJECT, disk="fake_disk_1", zone=self.ZONE), + mock.call( + project=self.PROJECT, disk="fake_disk_2", zone=self.ZONE)] + resource_mock.delete.assert_has_calls(calls, any_order=True) + self.assertEqual( + gcompute_client.ComputeClient.WaitOnOperation.call_count, 2) + self.assertEqual(error_msgs, []) + self.assertEqual(failed, []) + self.assertEqual(set(deleted), set(fake_disks)) + + +if __name__ == "__main__": + unittest.main() diff --git a/internal/lib/gstorage_client_test.py b/internal/lib/gstorage_client_test.py new file mode 100644 index 00000000..8577dabd --- /dev/null +++ b/internal/lib/gstorage_client_test.py @@ -0,0 +1,148 @@ +"""Tests for acloud.internal.lib.gstorage_client.""" + +import io +import time + +import apiclient +import mock + +import unittest +from acloud.internal.lib import driver_test_lib +from acloud.internal.lib import gstorage_client +from acloud.public import errors + + +class StorageClientTest(driver_test_lib.BaseDriverTest): + """Test StorageClient.""" + + LOCAL_SRC = "/fake/local/path" + BUCKET = "fake_bucket" + OBJECT = "fake_obj" + MIME_TYPE = "fake_mimetype" + + def setUp(self): + """Set up test.""" + super(StorageClientTest, self).setUp() + self.Patch(gstorage_client.StorageClient, "InitResourceHandle") + self.client = gstorage_client.StorageClient(mock.MagicMock()) + self.client._service = mock.MagicMock() + + def testGet(self): + """Test Get.""" + mock_api = mock.MagicMock() + resource_mock = mock.MagicMock() + self.client._service.objects = mock.MagicMock( + return_value=resource_mock) + resource_mock.get = mock.MagicMock(return_value=mock_api) + self.client.Get(self.BUCKET, self.OBJECT) + resource_mock.get.assert_called_with( + bucket=self.BUCKET, object=self.OBJECT) + self.assertTrue(mock_api.execute.called) + + def testList(self): + """Test List.""" + mock_items = ["fake/return"] + self.Patch( + gstorage_client.StorageClient, + "ListWithMultiPages", + return_value=mock_items) + resource_mock = mock.MagicMock() + self.client._service.objects = mock.MagicMock( + return_value=resource_mock) + items = self.client.List(self.BUCKET, self.OBJECT) + self.client.ListWithMultiPages.assert_called_once_with( + api_resource=resource_mock.list, + bucket=self.BUCKET, + prefix=self.OBJECT) + self.assertEqual(mock_items, items) + + def testUpload(self): + """Test Upload.""" + # Create mocks + mock_file = mock.MagicMock() + mock_file_io = mock.MagicMock() + mock_file_io.__enter__.return_value = mock_file + mock_media = mock.MagicMock() + mock_api = mock.MagicMock() + mock_response = mock.MagicMock() + + self.Patch(io, "FileIO", return_value=mock_file_io) + self.Patch( + apiclient.http, "MediaIoBaseUpload", return_value=mock_media) + resource_mock = mock.MagicMock() + self.client._service.objects = mock.MagicMock( + return_value=resource_mock) + resource_mock.insert = mock.MagicMock(return_value=mock_api) + mock_api.execute = mock.MagicMock(return_value=mock_response) + + # Make the call to the api + response = self.client.Upload(self.LOCAL_SRC, self.BUCKET, self.OBJECT, + self.MIME_TYPE) + + # Verify + self.assertEqual(response, mock_response) + io.FileIO.assert_called_with(self.LOCAL_SRC, mode="rb") + apiclient.http.MediaIoBaseUpload.assert_called_with(mock_file, + self.MIME_TYPE) + resource_mock.insert.assert_called_with( + bucket=self.BUCKET, name=self.OBJECT, media_body=mock_media) + + def testUploadOSError(self): + """Test Upload when OSError is raised.""" + self.Patch(io, "FileIO", side_effect=OSError("fake OSError")) + self.assertRaises(errors.DriverError, self.client.Upload, + self.LOCAL_SRC, self.BUCKET, self.OBJECT, + self.MIME_TYPE) + + def testDelete(self): + """Test Delete.""" + mock_api = mock.MagicMock() + resource_mock = mock.MagicMock() + self.client._service.objects = mock.MagicMock( + return_value=resource_mock) + resource_mock.delete = mock.MagicMock(return_value=mock_api) + self.client.Delete(self.BUCKET, self.OBJECT) + resource_mock.delete.assert_called_with( + bucket=self.BUCKET, object=self.OBJECT) + self.assertTrue(mock_api.execute.called) + + def testDeleteMultipleFiles(self): + """Test Delete multiple files.""" + fake_objs = ["fake_obj1", "fake_obj2"] + mock_api = mock.MagicMock() + resource_mock = mock.MagicMock() + self.client._service.objects = mock.MagicMock( + return_value=resource_mock) + resource_mock.delete = mock.MagicMock(return_value=mock_api) + deleted, failed, error_msgs = self.client.DeleteFiles(self.BUCKET, + fake_objs) + self.assertEqual(deleted, fake_objs) + self.assertEqual(failed, []) + self.assertEqual(error_msgs, []) + calls = [mock.call( + bucket=self.BUCKET, object="fake_obj1"), mock.call( + bucket=self.BUCKET, object="fake_obj2")] + resource_mock.delete.assert_has_calls(calls) + self.assertEqual(mock_api.execute.call_count, 2) + + def testGetUrl(self): + """Test GetUrl.""" + fake_item = {"name": "fake-item-1", "selfLink": "link1"} + self.Patch( + gstorage_client.StorageClient, "Get", return_value=fake_item) + self.assertEqual( + self.client.GetUrl("fake_bucket", "fake-item-1"), "link1") + + def testGetUrlNotFound(self): + """Test GetUrl when object is not found.""" + self.Patch( + gstorage_client.StorageClient, + "Get", + side_effect=errors.ResourceNotFoundError(404, "expected error")) + self.Patch(time, "sleep") + self.assertRaises(errors.ResourceNotFoundError, self.client.GetUrl, + "fake_bucket", "fake-item-1") + + +if __name__ == "__main__": + unittest.main() diff --git a/public/acloud_kernel/kernel_swapper_test.py b/public/acloud_kernel/kernel_swapper_test.py new file mode 100644 index 00000000..bddb7220 --- /dev/null +++ b/public/acloud_kernel/kernel_swapper_test.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +# +# Copyright 2016 - The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Tests acloud.public.acloud_kernel.kernel_swapper.""" + +import subprocess +import mock + +import unittest +from acloud.internal.lib import android_compute_client +from acloud.internal.lib import auth +from acloud.internal.lib import driver_test_lib +from acloud.public.acloud_kernel import kernel_swapper + + +class KernelSwapperTest(driver_test_lib.BaseDriverTest): + """Test kernel_swapper.""" + + def setUp(self): + """Set up the test.""" + super(KernelSwapperTest, self).setUp() + self.cfg = mock.MagicMock() + self.credentials = mock.MagicMock() + self.Patch(auth, 'CreateCredentials', return_value=self.credentials) + self.compute_client = mock.MagicMock() + self.Patch( + android_compute_client, + 'AndroidComputeClient', + return_value=self.compute_client) + self.subprocess_call = self.Patch(subprocess, 'check_call') + + self.fake_ip = '123.456.789.000' + self.fake_instance = 'fake-instance' + self.compute_client.GetInstanceIP.return_value = self.fake_ip + + self.kswapper = kernel_swapper.KernelSwapper(self.cfg, + self.fake_instance) + self.ssh_cmd_prefix = 'ssh %s root@%s' % ( + ' '.join(kernel_swapper.SSH_FLAGS), self.fake_ip) + self.scp_cmd_prefix = 'scp %s' % ' '.join(kernel_swapper.SSH_FLAGS) + + def testPushFile(self): + """Test RebootTarget.""" + fake_src_path = 'fake-src' + fake_dest_path = 'fake-dest' + scp_cmd = ' '.join([self.scp_cmd_prefix, '%s root@%s:%s' % + (fake_src_path, self.fake_ip, fake_dest_path)]) + + self.kswapper.PushFile(fake_src_path, fake_dest_path) + self.subprocess_call.assert_called_once_with(scp_cmd, shell=True) + + def testRebootTarget(self): + """Test RebootTarget.""" + self.kswapper.RebootTarget() + reboot_cmd = ' '.join([ + self.ssh_cmd_prefix, '"%s"' % kernel_swapper.REBOOT_CMD + ]) + + self.subprocess_call.assert_called_once_with(reboot_cmd, shell=True) + self.compute_client.WaitForBoot.assert_called_once_with( + self.fake_instance) + + def testSwapKernel(self): + """Test SwapKernel.""" + fake_local_kernel_image = 'fake-kernel' + mount_cmd = ' '.join([ + self.ssh_cmd_prefix, '"%s"' % kernel_swapper.MOUNT_CMD + ]) + scp_cmd = ' '.join([self.scp_cmd_prefix, '%s root@%s:%s' % + (fake_local_kernel_image, self.fake_ip, '/boot')]) + reboot_cmd = ' '.join([ + self.ssh_cmd_prefix, '"%s"' % kernel_swapper.REBOOT_CMD + ]) + + self.kswapper.SwapKernel(fake_local_kernel_image) + self.subprocess_call.assert_has_calls([ + mock.call( + mount_cmd, shell=True), mock.call( + scp_cmd, shell=True), mock.call( + reboot_cmd, shell=True) + ]) + + +if __name__ == '__main__': + unittest.main() diff --git a/public/config_test.py b/public/config_test.py new file mode 100644 index 00000000..554e751f --- /dev/null +++ b/public/config_test.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python +# +# Copyright 2016 - The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for acloud.public.config.""" + +import mock + +import unittest +from acloud.internal.proto import internal_config_pb2 +from acloud.internal.proto import user_config_pb2 +from acloud.public import config +from acloud.public import errors + + +class AcloudConfigManagerTest(unittest.TestCase): + """Test acloud configuration manager.""" + + USER_CONFIG = """ +service_account_name: "fake@developer.gserviceaccount.com" +service_account_private_key_path: "/path/to/service/account/key" +project: "fake-project" +zone: "us-central1-f" +machine_type: "n1-standard-1" +network: "default" +ssh_private_key_path: "/path/to/ssh/key" +storage_bucket_name: "fake_bucket" +orientation: "portrait" +resolution: "1200x1200x1200x1200" +client_id: "fake_client_id" +client_secret: "fake_client_secret" +metadata_variable { + key: "metadata_1" + value: "metadata_value_1" +} +""" + + INTERNAL_CONFIG = """ +min_machine_size: "n1-standard-1" +disk_image_name: "avd-system.tar.gz" +disk_image_mime_type: "application/x-tar" +disk_image_extension: ".tar.gz" +disk_raw_image_name: "disk.raw" +disk_raw_image_extension: ".img" +creds_cache_file: ".fake_oauth2.dat" +user_agent: "fake_user_agent" + +default_usr_cfg { + machine_type: "n1-standard-1" + network: "default" + metadata_variable { + key: "metadata_1" + value: "metadata_value_1" + } + + metadata_variable { + key: "metadata_2" + value: "metadata_value_2" + } +} + +device_resolution_map { + key: "nexus5" + value: "1080x1920x32x480" +} + +device_default_orientation_map { + key: "nexus5" + value: "portrait" +} + +valid_branch_and_min_build_id { + key: "git_jb-gce-dev" + value: 0 +} +""" + + def setUp(self): + self.config_file = mock.MagicMock() + + def testLoadUserConfig(self): + """Test loading user config.""" + self.config_file.read.return_value = self.USER_CONFIG + cfg = config.AcloudConfigManager.LoadConfigFromProtocolBuffer( + self.config_file, user_config_pb2.UserConfig) + self.assertEquals(cfg.service_account_name, + "fake@developer.gserviceaccount.com") + self.assertEquals(cfg.service_account_private_key_path, + "/path/to/service/account/key") + self.assertEquals(cfg.project, "fake-project") + self.assertEquals(cfg.zone, "us-central1-f") + self.assertEquals(cfg.machine_type, "n1-standard-1") + self.assertEquals(cfg.network, "default") + self.assertEquals(cfg.ssh_private_key_path, "/path/to/ssh/key") + self.assertEquals(cfg.storage_bucket_name, "fake_bucket") + self.assertEquals(cfg.orientation, "portrait") + self.assertEquals(cfg.resolution, "1200x1200x1200x1200") + self.assertEquals(cfg.client_id, "fake_client_id") + self.assertEquals(cfg.client_secret, "fake_client_secret") + self.assertEquals({key: val + for key, val in cfg.metadata_variable.iteritems()}, + {"metadata_1": "metadata_value_1"}) + + def testLoadInternalConfig(self): + """Test loading internal config.""" + self.config_file.read.return_value = self.INTERNAL_CONFIG + cfg = config.AcloudConfigManager.LoadConfigFromProtocolBuffer( + self.config_file, internal_config_pb2.InternalConfig) + self.assertEquals(cfg.min_machine_size, "n1-standard-1") + self.assertEquals(cfg.disk_image_name, "avd-system.tar.gz") + self.assertEquals(cfg.disk_image_mime_type, "application/x-tar") + self.assertEquals(cfg.disk_image_extension, ".tar.gz") + self.assertEquals(cfg.disk_raw_image_name, "disk.raw") + self.assertEquals(cfg.disk_raw_image_extension, ".img") + self.assertEquals(cfg.creds_cache_file, ".fake_oauth2.dat") + self.assertEquals(cfg.user_agent, "fake_user_agent") + self.assertEquals(cfg.default_usr_cfg.machine_type, "n1-standard-1") + self.assertEquals(cfg.default_usr_cfg.network, "default") + self.assertEquals({ + key: val + for key, val in cfg.default_usr_cfg.metadata_variable.iteritems() + }, {"metadata_1": "metadata_value_1", + "metadata_2": "metadata_value_2"}) + self.assertEquals( + {key: val + for key, val in cfg.device_resolution_map.iteritems()}, + {"nexus5": "1080x1920x32x480"}) + device_resolution = { + key: val + for key, val in cfg.device_default_orientation_map.iteritems() + } + self.assertEquals(device_resolution, {"nexus5": "portrait"}) + valid_branch_and_min_build_id = { + key: val + for key, val in cfg.valid_branch_and_min_build_id.iteritems() + } + self.assertEquals(valid_branch_and_min_build_id, {"git_jb-gce-dev": 0}) + + def testLoadConfigFails(self): + """Test loading a bad file.""" + self.config_file.read.return_value = "malformed text" + with self.assertRaises(errors.ConfigError): + config.AcloudConfigManager.LoadConfigFromProtocolBuffer( + self.config_file, internal_config_pb2.InternalConfig) + + +if __name__ == "__main__": + unittest.main() diff --git a/public/device_driver_test.py b/public/device_driver_test.py new file mode 100644 index 00000000..eb0a64f8 --- /dev/null +++ b/public/device_driver_test.py @@ -0,0 +1,249 @@ +#!/usr/bin/env python +# +# Copyright 2016 - The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for acloud.public.device_driver.""" + +import datetime +import uuid + +import dateutil.parser +import mock + +import unittest +from acloud.internal.lib import auth +from acloud.internal.lib import andorid_build_client +from acloud.internal.lib import android_compute_client +from acloud.internal.lib import driver_test_lib +from acloud.internal.lib import gstorage_client +from acloud.public import device_driver + + +class DeviceDriverTest(driver_test_lib.BaseDriverTest): + """Test device_driver.""" + + def setUp(self): + """Set up the test.""" + super(DeviceDriverTest, self).setUp() + self.build_client = mock.MagicMock() + self.Patch(android_build_client, "AndroidBuildClient", + return_value=self.build_client) + self.storage_client = mock.MagicMock() + self.Patch( + gstorage_client, "StorageClient", return_value=self.storage_client) + self.compute_client = mock.MagicMock() + self.Patch( + android_compute_client, + "AndroidComputeClient", + return_value=self.compute_client) + self.Patch(auth, "CreateCredentials", return_value=mock.MagicMock()) + + def _CreateCfg(self): + """A helper method that creates a mock configuration object.""" + cfg = mock.MagicMock() + cfg.service_account_name = "fake@service.com" + cfg.service_account_private_key_path = "/fake/path/to/key" + cfg.zone = "fake_zone" + cfg.disk_image_name = "fake_image.tar.gz" + cfg.disk_image_mime_type = "fake/type" + cfg.storage_bucket_name = "fake_bucket" + cfg.extra_data_disk_size_gb = 4 + cfg.precreated_data_image_map = { + 4: "extradisk-image-4gb", + 10: "extradisk-image-10gb" + } + + return cfg + + def testCreateAndroidVirtualDevices(self): + """Test CreateAndroidVirtualDevices.""" + cfg = self._CreateCfg() + fake_gs_url = "fake_gs_url" + fake_ip = "140.1.1.1" + fake_instance = "fake-instance" + fake_image = "fake-image" + fake_build_target = "fake_target" + fake_build_id = "12345" + + # Mock uuid + fake_uuid = mock.MagicMock(hex="1234") + self.Patch(uuid, "uuid4", return_value=fake_uuid) + fake_gs_object = fake_uuid.hex + "-" + cfg.disk_image_name + self.storage_client.GetUrl.return_value = fake_gs_url + + # Mock compute client methods + disk_name = "extradisk-image-4gb" + self.compute_client.GetInstanceIP.return_value = fake_ip + self.compute_client.GenerateImageName.return_value = fake_image + self.compute_client.GenerateInstanceName.return_value = fake_instance + self.compute_client.GetDataDiskName.return_value = disk_name + + # Verify + r = device_driver.CreateAndroidVirtualDevices( + cfg, fake_build_target, fake_build_id) + self.build_client.CopyTo.assert_called_with( + fake_build_target, fake_build_id, artifact_name=cfg.disk_image_name, + destination_bucket=cfg.storage_bucket_name, + destination_path=fake_gs_object) + self.compute_client.CreateImage.assert_called_with( + image_name=fake_image, source_uri=fake_gs_url) + self.compute_client.CreateInstance.assert_called_with( + fake_instance, fake_image, disk_name) + self.compute_client.DeleteImage.assert_called_with(fake_image) + self.storage_client.Delete(cfg.storage_bucket_name, fake_gs_object) + + self.assertEquals( + r.data, + { + "devices": [ + { + "instance_name": fake_instance, + "ip": fake_ip, + }, + ], + } + ) + self.assertEquals(r.command, "create") + self.assertEquals(r.status, "SUCCESS") + + + def testDeleteAndroidVirtualDevices(self): + """Test DeleteAndroidVirtualDevices.""" + instance_names = ["fake-instance-1", "fake-instance-2"] + self.compute_client.DeleteInstances.return_value = (instance_names, [], + []) + cfg = self._CreateCfg() + r = device_driver.DeleteAndroidVirtualDevices(cfg, instance_names) + self.compute_client.DeleteInstances.assert_called_once_with( + instance_names, cfg.zone) + self.assertEquals(r.data, { + "deleted": [ + { + "name": instance_names[0], + "type": "instance", + }, + { + "name": instance_names[1], + "type": "instance", + }, + ], + }) + self.assertEquals(r.command, "delete") + self.assertEquals(r.status, "SUCCESS") + + def testCleanup(self): + expiration_mins = 30 + before_deadline = "2015-10-29T12:00:30.018-07:00" + after_deadline = "2015-10-29T12:45:30.018-07:00" + now = "2015-10-29T13:00:30.018-07:00" + self.Patch(device_driver, "datetime") + device_driver.datetime.datetime.now.return_value = dateutil.parser.parse( + now) + device_driver.datetime.timedelta.return_value = datetime.timedelta( + minutes=expiration_mins) + fake_instances = [ + { + "name": "fake_instance_1", + "creationTimestamp": before_deadline, + }, { + "name": "fake_instance_2", + "creationTimestamp": after_deadline, + } + ] + fake_images = [ + { + "name": "extradisk-image-4gb", + "creationTimestamp": before_deadline, + }, { + "name": "fake_image_1", + "creationTimestamp": before_deadline, + }, { + "name": "fake_image_2", + "creationTimestamp": after_deadline, + } + ] + fake_disks = [ + { + "name": "fake_disk_1", + "creationTimestamp": before_deadline, + }, { + "name": "fake_disk_2", + "creationTimestamp": before_deadline, + "users": ["some-instance-using-the-disk"] + }, { + "name": "fake_disk_3", + "creationTimestamp": after_deadline, + } + ] + fake_objects = [ + { + "name": "fake_object_1", + "timeCreated": before_deadline, + }, { + "name": "fake_object_2", + "timeCreated": after_deadline, + } + ] + self.compute_client.ListInstances.return_value = fake_instances + self.compute_client.ListImages.return_value = fake_images + self.compute_client.ListDisks.return_value = fake_disks + self.storage_client.List.return_value = fake_objects + self.compute_client.DeleteInstances.return_value = ( + ["fake_instance_1"], [], []) + self.compute_client.DeleteImages.return_value = (["fake_image_1"], [], + []) + self.compute_client.DeleteDisks.return_value = (["fake_disk_1"], [], + []) + self.storage_client.DeleteFiles.return_value = (["fake_object_1"], [], + []) + cfg = self._CreateCfg() + r = device_driver.Cleanup(cfg, expiration_mins) + self.assertEqual(r.errors, []) + expected_report_data = { + "deleted": [ + {"name": "fake_instance_1", + "type": "instance"}, + {"name": "fake_image_1", + "type": "image"}, + {"name": "fake_disk_1", + "type": "disk"}, + {"name": "fake_object_1", + "type": "cached_build_artifact"}, + ] + } + self.assertEqual(r.data, expected_report_data) + + self.compute_client.ListInstances.assert_called_once_with( + zone=cfg.zone) + self.compute_client.DeleteInstances.assert_called_once_with( + instances=["fake_instance_1"], zone=cfg.zone) + + self.compute_client.ListImages.assert_called_once_with() + self.compute_client.DeleteImages.assert_called_once_with( + image_names=["fake_image_1"]) + + self.compute_client.ListDisks.assert_called_once_with(zone=cfg.zone) + self.compute_client.DeleteDisks.assert_called_once_with( + disk_names=["fake_disk_1"], zone=cfg.zone) + + self.storage_client.List.assert_called_once_with( + bucket_name=cfg.storage_bucket_name) + self.storage_client.DeleteFiles.assert_called_once_with( + bucket_name=cfg.storage_bucket_name, + object_names=["fake_object_1"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/public/report_test.py b/public/report_test.py new file mode 100644 index 00000000..e1fdc554 --- /dev/null +++ b/public/report_test.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +# +# Copyright 2016 - The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for acloud.public.report.""" + +import unittest +from acloud.public import report + + +class ReportTest(unittest.TestCase): + """Test Report class.""" + + def testAddData(self): + """test AddData.""" + r = report.Report("create") + r.AddData("devices", {"instance_name": "instance_1"}) + r.AddData("devices", {"instance_name": "instance_2"}) + expected = { + "devices": [ + {"instance_name": "instance_1"}, + {"instance_name": "instance_2"} + ] + } + self.assertEqual(r.data, expected) + + def testAddError(self): + """test AddError.""" + r = report.Report("create") + r.errors.append("some errors") + r.errors.append("some errors") + self.assertEqual(r.errors, ["some errors", "some errors"]) + + def testSetStatus(self): + """test SetStatus.""" + r = report.Report("create") + r.SetStatus(report.Status.SUCCESS) + self.assertEqual(r.status, "SUCCESS") + + r.SetStatus(report.Status.FAIL) + self.assertEqual(r.status, "FAIL") + + r.SetStatus(report.Status.BOOT_FAIL) + self.assertEqual(r.status, "BOOT_FAIL") + + # Test that more severe status won't get overriden. + r.SetStatus(report.Status.FAIL) + self.assertEqual(r.status, "BOOT_FAIL") + + +if __name__ == "__main__": + unittest.main() |