blob: bd4fd1bc50ed54c6bedf63794ac972deeed0fa5b [file] [log] [blame]
# -*- coding: utf-8 -*-
# Copyright 2019 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Unit tests for the MoblabService"""
import sys
import unittest
from collections import namedtuple
from unittest import mock
from unittest.mock import MagicMock, PropertyMock
from moblab_common.result_upload_status_connector import UploadResultState
class MockNotFoundError(Exception):
pass
class MockClientError(Exception):
pass
sys.modules["google.cloud"] = MagicMock()
sys.modules["google.api_core"] = MagicMock()
sys.modules["moblab_common"] = MagicMock()
from moblab_service import MoblabService, MoblabRpcError
class MoblabRPCServiceTest(unittest.TestCase):
"""Testing the MoblabService code."""
def setUp(self):
self.mock_afe_connector = MagicMock()
self.service = MoblabService()
self.service.afe_connector = self.mock_afe_connector
def test_get_dut_wifi_info(self):
self.service.get_dut_wifi_info()
self.mock_afe_connector.get_dut_wifi_info.assert_called_once()
def test_set_dut_wifi_info(self):
result_msg = self.service.set_dut_wifi_info(
"Fake Wifi Name", "Fake Wifi Pass"
)
self.mock_afe_connector.set_configuration_info.assert_called_once()
assert isinstance(result_msg, str)
def _create_mock_container(self, tags=[]):
mock_container = MagicMock()
mock_container.image.__str__.return_value = (
"gcr.io/chromeos-partner-moblab/mock-moblab-image"
)
mock_container.image.tags = tags
return mock_container
def test_get_usable_build_targets(self):
self.mock_afe_connector.get_connected_devices.return_value = [
{
"labels": [
"hostname:mock_dut_1",
"model:mock_model_1",
"board:mock_build_target_1",
]
},
{
"labels": [
"hostname:mock_dut_2",
"model:mock_model_2",
"board:mock_build_target_2",
]
},
{
"labels": [
"hostname:mock_dut_3",
"model:mock_model_1",
"board:mock_build_target_1",
]
},
]
build_targets = self.service.list_usable_build_targets("mock_model_1")
self.assertEqual(build_targets, ["mock_build_target_1"])
@mock.patch("moblab_service.docker.from_env")
def test_get_is_update_available(self, mock_docker_from_env):
mock_docker_client = MagicMock()
# A single tagless container should cause update to show as available.
mock_docker_client.containers.list.return_value = [
self._create_mock_container(["tag"]) for _ in range(3)
] + [self._create_mock_container()]
mock_docker_from_env.return_value = mock_docker_client
is_update_available = self.service.get_is_update_available()
assert is_update_available
@mock.patch("moblab_service.docker.from_env")
def test_get_is_update_not_available(self, mock_docker_from_env):
mock_docker_client = MagicMock()
mock_docker_client.containers.list.return_value = [
self._create_mock_container(["tag"]) for _ in range(3)
]
mock_docker_from_env.return_value = mock_docker_client
is_update_available = self.service.get_is_update_available()
assert not is_update_available
def test_update_docker(self):
mock_docker = MagicMock()
mock_docker_client = MagicMock()
mock_container_run = MagicMock()
mock_docker_client.containers.get.side_effect = [MockNotFoundError]
mock_docker_client.containers.run = mock_container_run
mock_docker.from_env.return_value = mock_docker_client
mock_docker.errors.NotFound = MockNotFoundError
with mock.patch("moblab_service.docker", new=mock_docker):
result_msg = self.service._update_docker()
mock_container_run.assert_called_once_with(
"gcr.io/chromeos-partner-moblab/watchtower:release",
command="--run-once --include-restarting",
detach=True,
name="update",
volumes=["/var/run/docker.sock:/var/run/docker.sock"],
)
assert result_msg == "Update finished."
@mock.patch("moblab_service.docker.from_env")
def test_update_docker_already_running(self, mock_docker_from_env):
mock_docker_from_env.return_value = MagicMock()
with self.assertRaises(MoblabRpcError):
_ = self.service._update_docker()
def _mock_afe_connector_methods(
self,
mock_afe_connector,
succeed_validate,
succeed_cloud_configuration_set,
succeed_remote_agent_config_commit,
):
if succeed_validate:
mock_afe_connector.validate_cloud_storage_info.return_value = (
True,
"Cloud values validated.",
)
else:
mock_afe_connector.validate_cloud_storage_info.return_value = (
False,
"Cloud validation failed.",
)
mock_afe_connector.set_configuration_info.return_value = (
succeed_cloud_configuration_set
)
mock_afe_connector.set_is_remote_agent_enabled.return_value = (
succeed_remote_agent_config_commit
)
def test_set_cloud_configuration(self):
SetCloudConfigurationTestParams = namedtuple(
"SetCloudConfigurationTestParams",
[
# Test inputs for MoblabService.set_cloud_configuration call.
"is_cloud_enabled",
"boto_key_id",
"boto_key_secret",
"gcs_bucket_url",
"is_remote_agent_enabled",
"is_remote_command_enabled",
# The follow are bools to set whether mock afe_connector
# succeeds or fails for the
# validate_cloud_storage_info, set_configuration_info,
# and set_is_remote_agent_enabled.
"succeed_validate",
"succeed_cloud_configuration_set",
"succeed_remote_agent_config_commit",
# Expected message to be returned by
# MoblabService.set_cloud_configuration
"expected_message_output",
],
)
# below is a list of different permutations of potential inputs to
# set_cloud_configuration + different behaviors for
# the mocked afe_connector. Each item includes the expected message
# output of the set_cloud_configuration that will be assert'ed against.
parameterized_set_cloud_configuration_tests = [
# test completely successful set cloud config.
SetCloudConfigurationTestParams(
is_cloud_enabled=True,
boto_key_id="fake-boto-id",
boto_key_secret="fake-boto-secret",
gcs_bucket_url="gs://bucket/",
is_remote_agent_enabled=True,
is_remote_command_enabled=False,
succeed_validate=True,
succeed_cloud_configuration_set=True,
succeed_remote_agent_config_commit=True,
expected_message_output="Successfully applied "
+ "cloud configuration values. "
+ "Remote agent has been enabled. "
+ "Remote command has been disabled.",
),
# test set only cloud disabled.
SetCloudConfigurationTestParams(
is_cloud_enabled=False,
boto_key_id=None,
boto_key_secret=None,
gcs_bucket_url=None,
is_remote_agent_enabled=None,
is_remote_command_enabled=False,
succeed_validate=True,
succeed_cloud_configuration_set=True,
succeed_remote_agent_config_commit=True,
expected_message_output="Successfully disabled "
+ "cloud configuration.",
),
# test validation failed.
SetCloudConfigurationTestParams(
is_cloud_enabled=True,
boto_key_id="fake-boto-id",
boto_key_secret="fake-boto-secret",
gcs_bucket_url="gs://bucket/",
is_remote_agent_enabled=True,
is_remote_command_enabled=False,
succeed_validate=False,
succeed_cloud_configuration_set=True,
succeed_remote_agent_config_commit=True,
expected_message_output="Cloud validation failed.",
),
# test cloud configuration successfully committed but remote
# agent toggle set failed
SetCloudConfigurationTestParams(
is_cloud_enabled=True,
boto_key_id="fake-boto-id",
boto_key_secret="fake-boto-secret",
gcs_bucket_url="gs://bucket/",
is_remote_agent_enabled=True,
is_remote_command_enabled=False,
succeed_validate=True,
succeed_cloud_configuration_set=True,
succeed_remote_agent_config_commit=False,
expected_message_output="Successfully applied cloud "
+ "configuration values. "
+ "But failed to set remote agent as enabled. "
+ "Remote command has been disabled.",
),
]
for params in parameterized_set_cloud_configuration_tests:
with self.subTest():
self._mock_afe_connector_methods(
self.mock_afe_connector,
params.succeed_validate,
params.succeed_cloud_configuration_set,
params.succeed_remote_agent_config_commit,
)
self.mock_afe_connector.get_num_jobs.return_value = 0
message = self.service.set_cloud_configuration(
params.is_cloud_enabled,
params.boto_key_id,
params.boto_key_secret,
params.gcs_bucket_url,
params.is_remote_agent_enabled,
params.is_remote_command_enabled,
)
self.assertEqual(message, params.expected_message_output)
@mock.patch("moblab_service.build_connector")
@mock.patch("moblab_service.moblab_build_connector")
def test_set_cloud_configuration_updates_build_connectors(
self,
mock_moblab_build_connector,
mock_build_connector,
):
# Mocking setup.
self._mock_afe_connector_methods(
self.mock_afe_connector, True, True, True
)
# Mock and track call-throughs to re-initialize connectors.
MockMoblabBuildConnector = (
mock_moblab_build_connector.MoblabBuildConnector
)
MockBuildConnector = mock_build_connector.MoblabBuildConnector
self.mock_afe_connector.get_num_jobs.return_value = 0
_ = self.service.set_cloud_configuration(
True,
"mock-boto-key-id",
"mock-boto-key-secret",
"gs:/mock-gcs-url",
True,
True,
)
self.service.moblab_build_connector
self.service.build_connector
# Verifies that, after an invocation of set_cloud_configuration,
# references to the two build connectors results in
# a reinitialization of each. This ensures that changes in target
# bucket or service_account creds gets ingested.
self.assertEqual(MockMoblabBuildConnector.call_count, 1)
self.assertEqual(MockBuildConnector.call_count, 1)
def test_get_is_remote_agent_enabled(self):
mock_config_connector = MagicMock()
self.service.config_connector = mock_config_connector
self.service.get_is_remote_agent_enabled()
mock_config_connector.get_is_remote_agent_enabled.assert_called_once()
def test_set_is_remote_agent_enabled(self):
self.service.set_is_remote_agent_enabled(True)
self.mock_afe_connector.set_is_remote_agent_enabled.assert_called_once()
@mock.patch.object(
MoblabService, "moblab_build_connector", new_callable=PropertyMock
)
def test_run_suite(
self,
mock_moblab_build_connector,
):
self.mock_afe_connector.get_dut_wifi_info.return_value = {
"wifi_dut_ap_name": "mock_ssid",
"wifi_dut_ap_pass": "mock_wifi_pass",
}
self.mock_afe_connector.run_suite.return_value = (
1,
None,
) # (job_id, error_msg)
mock_moblab_build_connector.return_value.check_build_stage_status.side_effect = [
False,
True,
]
self.service.run_suite(
"mock_build_target", "mock_model", "99", "00000.00.0", "mock_suite"
)
self.mock_afe_connector.run_suite.assert_called_with(
"mock_build_target",
"mock_build_target-release/R99-00000.00.0",
{"cros-version": "mock_build_target-release/R99-00000.00.0"},
"mock_suite",
1440,
None,
"mock_model",
suite_args={},
test_args={"ssid": "mock_ssid", "wifipass": "mock_wifi_pass"},
)
mock_moblab_build_connector.return_value.stage_build.assert_called_once()
@mock.patch.object(
MoblabService, "moblab_build_connector", new_callable=PropertyMock
)
def test_run_suite_afe_failure(
self,
mock_moblab_build_connector,
):
self.mock_afe_connector.get_dut_wifi_info.return_value = {
"wifi_dut_ap_name": "mock_ssid",
"wifi_dut_ap_pass": "mock_wifi_pass",
}
self.mock_afe_connector.run_suite.return_value = (
None,
"Create suite job attempt failed, blah blah blah.",
) # (job_id, error_msg)
mock_moblab_build_connector.return_value.check_build_stage_status.return_value = (
True
)
with self.assertRaises(MoblabRpcError):
self.service.run_suite(
"mock_build_target",
"mock_model",
"99",
"00000.00.0",
"mock_suite",
)
@mock.patch.object(
MoblabService, "build_connector", new_callable=PropertyMock
)
@mock.patch.object(
MoblabService, "moblab_build_connector", new_callable=PropertyMock
)
def test_run_suite_api_failure(
self,
mock_moblab_build_connector,
mock_build_connector,
):
self.mock_afe_connector.get_dut_wifi_info.return_value = {
"wifi_dut_ap_name": "mock_ssid",
"wifi_dut_ap_pass": "mock_wifi_pass",
}
self.mock_afe_connector.run_suite.return_value = (
1,
None,
) # (job_id, error_msg)
mock_moblab_build_connector.return_value.check_build_stage_status.side_effect = (
MockClientError
)
mock_build_connector.return_value.get_builds_for_milestone.return_value = [
"00000.00.0"
]
mock_google = MagicMock()
mock_google.api_core.exceptions.GoogleAPIError = MockClientError
with mock.patch("moblab_service.google", new=mock_google):
self.service.run_suite(
"mock_build_target",
"mock_model",
"99",
"00000.00.0",
"mock_suite",
)
mock_build_connector.return_value.get_builds_for_milestone.assert_called_once()
def test_get_jobs(self):
mock_upload_status_connector = MagicMock()
self.service.upload_status_connector = mock_upload_status_connector
self.mock_afe_connector.get_jobs.return_value = [
{"id": 1, "key1": "value1"},
{"id": 3, "key3": "value3"},
]
mock_upload_status_connector.get_jobs_upload_status.return_value = {
2: UploadResultState(status="ABC", attempt_number=2),
3: UploadResultState(status="DEF", attempt_number=5),
}
result = self.service.get_jobs(0, 100)
expected_result1 = {"id": 1, "key1": "value1"}
expected_result2 = {
"id": 3,
"key3": "value3",
"upload_status": "DEF",
"last_falure_reason": "",
"attempt_number": 5,
}
self.assertEqual(len(result), 2)
self.assertDictEqual(result[0], expected_result1)
self.assertDictEqual(result[1], expected_result2)
if __name__ == "__main__":
unittest.main()