зеркало из https://github.com/microsoft/lisa.git
258 строки
9.9 KiB
Python
258 строки
9.9 KiB
Python
# Copyright (c) Microsoft Corporation.
|
|
# Licensed under the MIT license.
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Any, List, Optional, Type, Union
|
|
from unittest.case import TestCase
|
|
|
|
from dataclasses_json import dataclass_json
|
|
|
|
import lisa
|
|
from lisa import schema
|
|
from lisa.environment import (
|
|
Environment,
|
|
Environments,
|
|
EnvironmentStatus,
|
|
LisaException,
|
|
constants,
|
|
load_environments,
|
|
)
|
|
from lisa.feature import Feature
|
|
from lisa.platform_ import Platform, load_platform
|
|
from lisa.util import ResourceAwaitableException, plugin_manager
|
|
from lisa.util.logger import Logger
|
|
from selftests.test_environment import generate_runbook as generate_env_runbook
|
|
|
|
|
|
@dataclass
|
|
class MockPlatformTestData:
|
|
prepared_envs: List[str] = field(default_factory=list)
|
|
deployed_envs: List[str] = field(default_factory=list)
|
|
deleted_envs: List[str] = field(default_factory=list)
|
|
|
|
|
|
@dataclass_json()
|
|
@dataclass
|
|
class MockPlatformSchema:
|
|
# for other UT to set value
|
|
return_prepared: bool = True
|
|
deploy_success: bool = True
|
|
deployed_status: EnvironmentStatus = EnvironmentStatus.Deployed
|
|
wait_more_resource_error: bool = False
|
|
|
|
|
|
class MockPlatform(Platform):
|
|
def __init__(self, runbook: schema.Platform) -> None:
|
|
super().__init__(runbook=runbook)
|
|
self.test_data = MockPlatformTestData()
|
|
# prevent real calls
|
|
for plugin_name, _ in plugin_manager.list_name_plugin():
|
|
plugin_manager.unregister(name=plugin_name)
|
|
|
|
@classmethod
|
|
def type_name(cls) -> str:
|
|
return constants.PLATFORM_MOCK
|
|
|
|
@classmethod
|
|
def supported_features(cls) -> List[Type[Feature]]:
|
|
return []
|
|
|
|
def set_test_config(
|
|
self,
|
|
return_prepared: bool = True,
|
|
deploy_success: bool = True,
|
|
deployed_status: EnvironmentStatus = EnvironmentStatus.Deployed,
|
|
wait_more_resource_error: bool = False,
|
|
) -> None:
|
|
self.initialize()
|
|
self._mock_runbook.return_prepared = return_prepared
|
|
self._mock_runbook.deploy_success = deploy_success
|
|
self._mock_runbook.deployed_status = deployed_status
|
|
self._mock_runbook.wait_more_resource_error = wait_more_resource_error
|
|
|
|
def _initialize(self, *args: Any, **kwargs: Any) -> None:
|
|
self._mock_runbook: MockPlatformSchema = self.runbook.get_extended_runbook(
|
|
MockPlatformSchema, constants.PLATFORM_MOCK
|
|
)
|
|
|
|
def _prepare_environment(self, environment: Environment, log: Logger) -> bool:
|
|
self.test_data.prepared_envs.append(environment.name)
|
|
requirements = environment.runbook.nodes_requirement
|
|
if self._mock_runbook.return_prepared and requirements:
|
|
min_capabilities: List[schema.NodeSpace] = []
|
|
for node_space in requirements:
|
|
min_capabilities.append(node_space.generate_min_capability(node_space))
|
|
environment.runbook.nodes_requirement = min_capabilities
|
|
return self._mock_runbook.return_prepared
|
|
|
|
def _deploy_environment(self, environment: Environment, log: Logger) -> None:
|
|
if self._mock_runbook.wait_more_resource_error:
|
|
raise ResourceAwaitableException("any", "wait more resource")
|
|
if not self._mock_runbook.deploy_success:
|
|
raise LisaException("mock deploy failed")
|
|
if self._mock_runbook.return_prepared and environment.runbook.nodes_requirement:
|
|
requirements = environment.runbook.nodes_requirement
|
|
for node_space in requirements:
|
|
environment.create_node_from_requirement(node_requirement=node_space)
|
|
for node in environment.nodes.list():
|
|
node._is_initialized = True
|
|
self.test_data.deployed_envs.append(environment.name)
|
|
if self._mock_runbook.deployed_status not in [
|
|
EnvironmentStatus.Deployed,
|
|
EnvironmentStatus.Connected,
|
|
]:
|
|
raise LisaException(
|
|
f"expected status is {self._mock_runbook.deployed_status}, "
|
|
f"deployment should be failed"
|
|
)
|
|
|
|
def _delete_environment(self, environment: Environment, log: Logger) -> None:
|
|
self.test_data.deleted_envs.append(environment.name)
|
|
self.delete_called = True
|
|
|
|
|
|
def generate_platform(
|
|
keep_environment: Optional[Union[str, bool]] = False,
|
|
admin_password: str = "do not use for real",
|
|
admin_key_file: str = "",
|
|
) -> MockPlatform:
|
|
runbook_data = {
|
|
constants.TYPE: constants.PLATFORM_MOCK,
|
|
"keep_environment": keep_environment,
|
|
"admin_password": admin_password,
|
|
"admin_private_key_file": admin_key_file,
|
|
}
|
|
runbook = schema.load_by_type(schema.Platform, runbook_data)
|
|
platform = load_platform([runbook])
|
|
platform.initialize()
|
|
try:
|
|
assert isinstance(platform, MockPlatform), f"actual: {type(platform)}"
|
|
except AssertionError:
|
|
# as UT imported from tests package, instead of from lisa.tests package
|
|
# ignore by assign type from current package
|
|
platform = MockPlatform(runbook)
|
|
return platform
|
|
|
|
|
|
def generate_environments() -> Environments:
|
|
envs_runbook = generate_env_runbook(local=True, requirement=True)
|
|
envs = load_environments(envs_runbook)
|
|
|
|
return envs
|
|
|
|
|
|
class PlatformTestCase(TestCase):
|
|
def setUp(self) -> None:
|
|
lisa.environment._global_environment_id = 0
|
|
|
|
def test_prepared_env_not_success_with_exception(self) -> None:
|
|
platform = generate_platform()
|
|
platform.set_test_config(return_prepared=False)
|
|
envs = generate_environments()
|
|
self.assertEqual(2, len(envs))
|
|
with self.assertRaises(LisaException) as cm:
|
|
[platform.prepare_environment(env) for env in envs.values()]
|
|
self.assertEqual(
|
|
"no capability found for environment: Environment("
|
|
"name='customized_0', topology='subnet', nodes_raw=[{'type': 'local', "
|
|
"'capability': {'core_count': {'min': 4}}}], nodes_requirement=None, "
|
|
"_original_nodes_requirement=None)",
|
|
str(cm.exception),
|
|
)
|
|
|
|
def test_prepared_env_success(self) -> None:
|
|
platform = generate_platform()
|
|
platform.set_test_config(return_prepared=True)
|
|
envs = generate_environments()
|
|
self.assertEqual(2, len(envs))
|
|
prepared_environments = [
|
|
platform.prepare_environment(env) for env in envs.values()
|
|
]
|
|
self.assertEqual(2, len(prepared_environments))
|
|
|
|
def test_prepared_env_sorted_predefined_first(self) -> None:
|
|
platform = generate_platform()
|
|
platform.set_test_config()
|
|
envs = generate_environments()
|
|
|
|
# verify init as expected
|
|
self.assertListEqual(["customized_0", "customized_1"], [x for x in envs])
|
|
self.assertListEqual([True, True], [x.is_predefined for x in envs.values()])
|
|
|
|
# verify stable sort
|
|
envs["customized_1"].is_predefined = False
|
|
prepared_environments = [
|
|
platform.prepare_environment(env) for env in envs.values()
|
|
]
|
|
prepared_environments.sort(key=lambda x: (not x.is_predefined, x.cost))
|
|
self.assertListEqual(
|
|
["customized_0", "customized_1"], [x.name for x in prepared_environments]
|
|
)
|
|
self.assertListEqual(
|
|
[True, False], [x.is_predefined for x in prepared_environments]
|
|
)
|
|
|
|
# verify reverse sort
|
|
envs["customized_0"].is_predefined = False
|
|
envs["customized_1"].is_predefined = True
|
|
prepared_environments = [
|
|
platform.prepare_environment(env) for env in envs.values()
|
|
]
|
|
prepared_environments.sort(key=lambda x: (not x.is_predefined, x.cost))
|
|
self.assertListEqual(
|
|
["customized_1", "customized_0"],
|
|
[x.name for x in prepared_environments],
|
|
)
|
|
self.assertListEqual(
|
|
[True, False], [x.is_predefined for x in prepared_environments]
|
|
)
|
|
|
|
def test_prepared_env_sorted_by_cost(self) -> None:
|
|
platform = generate_platform()
|
|
envs = generate_environments()
|
|
platform.set_test_config()
|
|
|
|
self.assertListEqual(["customized_0", "customized_1"], [x for x in envs])
|
|
self.assertListEqual([0, 0], [x.cost for x in envs.values()])
|
|
|
|
envs["customized_0"].cost = 1
|
|
envs["customized_1"].cost = 2
|
|
prepared_environments = [
|
|
platform.prepare_environment(env) for env in envs.values()
|
|
]
|
|
prepared_environments.sort(key=lambda x: (not x.is_predefined, x.cost))
|
|
self.assertListEqual(
|
|
["customized_0", "customized_1"], [x.name for x in prepared_environments]
|
|
)
|
|
self.assertListEqual([1, 2], [x.cost for x in prepared_environments])
|
|
|
|
envs["customized_0"].cost = 2
|
|
envs["customized_1"].cost = 1
|
|
prepared_environments = [
|
|
platform.prepare_environment(env) for env in envs.values()
|
|
]
|
|
prepared_environments.sort(key=lambda x: (not x.is_predefined, x.cost))
|
|
self.assertListEqual(
|
|
["customized_1", "customized_0"], [x.name for x in prepared_environments]
|
|
)
|
|
self.assertListEqual([1, 2], [x.cost for x in prepared_environments])
|
|
|
|
def test_prepared_env_deploy_failed(self) -> None:
|
|
platform = generate_platform()
|
|
envs = generate_environments()
|
|
platform.set_test_config(deploy_success=False)
|
|
for env in envs.values():
|
|
with self.assertRaises(LisaException) as cm:
|
|
platform.deploy_environment(env)
|
|
self.assertEqual("mock deploy failed", str(cm.exception))
|
|
|
|
def test_prepared_env_deleted_not_ready(self) -> None:
|
|
platform = generate_platform()
|
|
envs = generate_environments()
|
|
platform.set_test_config()
|
|
for env in envs.values():
|
|
platform.deploy_environment(env)
|
|
self.assertEqual(EnvironmentStatus.Deployed, env.status)
|
|
platform.delete_environment(env)
|
|
self.assertEqual(EnvironmentStatus.Deleted, env.status)
|