зеркало из https://github.com/microsoft/lisa.git
Implement load environment and platform
1. Add Config class to hold config data and helper methods. 2. Add RuntimeObject, hold running objects. 3. Improve schema of platform and environment. 4. Rename decorators 5. Load node, environment, platform from config 6. Implement Ready env. 7. Add connection class to manage ssh session of node. 8. other minor improvements.
This commit is contained in:
Родитель
c0e8def98c
Коммит
4ba1f0c100
|
@ -27,9 +27,9 @@ Make sure below settings are in root level of `.vscode/settings.json`
|
|||
"python.linting.enabled": true,
|
||||
"python.formatting.provider": "black",
|
||||
"python.linting.mypyEnabled": true,
|
||||
"python.formatting.blackArgs": [
|
||||
"--line-length",
|
||||
"80"
|
||||
"python.linting.flake8Args": [
|
||||
"--max-line-length",
|
||||
"88"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
# run name prefix to help grouping results and put it in title.
|
||||
name: full example
|
||||
# share configurations for similar runs.
|
||||
parent:
|
||||
parent:
|
||||
- path: parentconfig.yaml
|
||||
# for simple merge, this part is optional.
|
||||
# for simple merge, this part is optional.
|
||||
# operations include:
|
||||
# overwrite: default behavior. add non-exist items and replace exist.
|
||||
# remove: remove specified path totally
|
||||
# add: add non-exist, not replace exist.
|
||||
strategy:
|
||||
strategy:
|
||||
- path: platform.template
|
||||
operation: remove
|
||||
# add extended classes can be put in folders and include here.
|
||||
# it doesn't matter how those files are organized, lisa loads by their inherits relationship.
|
||||
# if there is any conflict on type name, there should be an error message.
|
||||
extends:
|
||||
paths:
|
||||
extensions:
|
||||
paths:
|
||||
- "./myextends"
|
||||
# it uses to support variables in other fields.
|
||||
# duplicate items will be overwritten one by one.
|
||||
|
@ -36,36 +36,57 @@ variables:
|
|||
# it's not recommended highly to put secret in configurations directly.
|
||||
isSecret: true
|
||||
# supports multiple artifacts in future.
|
||||
artifacts:
|
||||
artifact:
|
||||
# name is optional. artifacts can be referred by name or index.
|
||||
- name: default
|
||||
type: vhd
|
||||
locations:
|
||||
http: https://path-to-azure-storage/aaa.vhd
|
||||
- type: http
|
||||
path: https://path-to-azure-storage/aaa.vhd
|
||||
- name: dsvm
|
||||
type: vhd
|
||||
locations:
|
||||
http: https://path-to-azure-storage/bbb.vhd
|
||||
- type: http
|
||||
path: https://path-to-azure-storage/bbb.vhd
|
||||
# it's spec of environment and node.
|
||||
# for ready type, it's optional
|
||||
# for ready type, it's optional.
|
||||
environment:
|
||||
topology: subnet
|
||||
# template and nodes conflicts, they should have only one.
|
||||
# it uses to prevent duplicate content for big amount nodes.
|
||||
template:
|
||||
- vcpu: 4
|
||||
# optional, if there is only one artifact
|
||||
nodeCount: 2
|
||||
# optional, if there is only one artifact.
|
||||
artifact: default
|
||||
# sometimes, it may need special configuration for a platform
|
||||
# we should avoid this kind of setting, it limits configuration to other platforms.
|
||||
azure:
|
||||
vmSize: testSize
|
||||
flag: "test=true"
|
||||
nodeCount:
|
||||
nodes:
|
||||
- memoryGB: 4
|
||||
isDefault: true
|
||||
- memoryGB: 8
|
||||
- type: spec
|
||||
azure:
|
||||
vmSize: testSize
|
||||
- type: spec
|
||||
memoryGB: 8
|
||||
gpuCount: 1
|
||||
artifact: dsvm
|
||||
# local and remote are node type, and don't need platform to handle
|
||||
- type: local
|
||||
# If test suite doesn't specify where to run a case,
|
||||
# it should be run on default node.
|
||||
isDefault: true
|
||||
# it's optional
|
||||
- type: remote
|
||||
name: secondary
|
||||
address: 10.0.0.2
|
||||
port: 22
|
||||
publicAddress: 23.36.36.36
|
||||
publicPort: 1112
|
||||
username: $$linuxTestUsername$$
|
||||
password: $$linuxTestPassword$$
|
||||
privateKeyFile: $$sshPrivateKey$$
|
||||
# the environment spec may not be fully supported by each platform.
|
||||
# If so, there is a warning message.
|
||||
# Environment spec can be forced to apply, as error is loud.
|
||||
|
@ -84,22 +105,9 @@ platform:
|
|||
subscription: $$SubscriptionID$$
|
||||
subscriptionKey: $$SubscriptionServicePrincipalKey$$
|
||||
datacenter: wu
|
||||
# for test case development, run test cases on current machine.
|
||||
# It uses to pure existing environment.
|
||||
# run test cases on existing machine.
|
||||
- type: ready
|
||||
nodes:
|
||||
- type: local
|
||||
# If test suite doesn't specify where to run a case,
|
||||
# it should be run on default node.
|
||||
isDefault: true
|
||||
# it's optional
|
||||
- type: ssh
|
||||
name: secondary
|
||||
ip: 10.0.0.2
|
||||
port: 22
|
||||
publicIp: 23.36.36.36
|
||||
publicPort: 1112
|
||||
password: $$linuxTestPassword$$
|
||||
privateKey: $$sshPrivateKey$$
|
||||
# rules apply ordered on previous selection.
|
||||
# The order of test cases running is not guaranteed, until it set dependencies.
|
||||
tests:
|
||||
|
|
|
@ -2,13 +2,18 @@
|
|||
name: local try
|
||||
# share configurations for similar runs.
|
||||
# it's examples for all platforms, but currently should leave single one only.
|
||||
extends:
|
||||
extensions:
|
||||
paths:
|
||||
- "examples/testsuites"
|
||||
environment:
|
||||
nodes:
|
||||
- type: local
|
||||
# If test suite doesn't specify where to run a case,
|
||||
# it should be run on default node.
|
||||
isDefault: true
|
||||
# it's optional
|
||||
platform:
|
||||
- type: ready
|
||||
nodes:
|
||||
- type: local
|
||||
tests:
|
||||
- criteria:
|
||||
# compatible with current test cases.
|
||||
|
|
|
@ -4,11 +4,16 @@ name: local try
|
|||
notifier:
|
||||
- type: junit
|
||||
- type: html
|
||||
environment:
|
||||
nodes:
|
||||
- type: local
|
||||
# If test suite doesn't specify where to run a case,
|
||||
# it should be run on default node.
|
||||
isDefault: true
|
||||
# it's optional
|
||||
# it's examples for all platforms, but currently should leave single one only.
|
||||
platform:
|
||||
- type: ready
|
||||
nodes:
|
||||
- type: local
|
||||
tests:
|
||||
- criteria:
|
||||
# all rules in same criteria are AND condition.
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
from lisa.core.decorator.testclass import TestClass
|
||||
from lisa.core.testsuite import TestSuite
|
||||
from lisa import TestMethod, log
|
||||
from lisa import SuiteMetadata, CaseMetadata, log
|
||||
|
||||
|
||||
@TestClass(area="demo", category="simple", tags=["demo"])
|
||||
@SuiteMetadata(area="demo", category="simple", tags=["demo"])
|
||||
class SimpleTestSuite(TestSuite):
|
||||
@TestMethod(priority=1)
|
||||
@CaseMetadata(priority=1)
|
||||
def hello(self):
|
||||
log.info("hello world")
|
||||
|
||||
@TestMethod(priority=1)
|
||||
@CaseMetadata(priority=1)
|
||||
def bye(self):
|
||||
log.info("bye!")
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from .util import constants
|
||||
from .common.logger import log
|
||||
from .core.action import Action, ActionStatus
|
||||
from .core.decorator.testmethod import TestMethod
|
||||
from .core.decorator.testclass import TestClass
|
||||
from .core.decorator.caseMetadata import CaseMetadata
|
||||
from .core.decorator.suiteMetadata import SuiteMetadata
|
||||
from .core.testrunner import TestRunner
|
||||
from .core.testsuite import TestSuite
|
||||
from .core.environment import Environment
|
||||
|
@ -14,9 +15,10 @@ __all__ = [
|
|||
"Environment",
|
||||
"Node",
|
||||
"TestRunner",
|
||||
"TestClass",
|
||||
"TestMethod",
|
||||
"SuiteMetadata",
|
||||
"CaseMetadata",
|
||||
"TestSuite",
|
||||
"Platform",
|
||||
"log",
|
||||
"constants",
|
||||
]
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import asyncio
|
||||
from lisa.core.platform_factory import platform_factory
|
||||
from lisa.core.runtimeObject import RuntimeObject
|
||||
from lisa.core.environment import Environment
|
||||
import os
|
||||
from lisa.core.package import import_module, packages
|
||||
from lisa import log, Platform
|
||||
from lisa.core.package import import_module
|
||||
from lisa import log
|
||||
from lisa.test_runner.lisarunner import LISARunner
|
||||
from lisa.parameter_parser.parser import parse
|
||||
from lisa.core.platform_factory import platformFactory
|
||||
|
||||
|
||||
def load_extends(extends_config):
|
||||
def _load_extends(extends_config):
|
||||
if extends_config is not None:
|
||||
paths = extends_config.get("paths")
|
||||
if paths is not None:
|
||||
|
@ -15,31 +17,46 @@ def load_extends(extends_config):
|
|||
import_module(path)
|
||||
|
||||
|
||||
def build_factories(package_name):
|
||||
# platform factories
|
||||
for sub_class in Platform.__subclasses__():
|
||||
platformFactory.registerPlatform(sub_class)
|
||||
|
||||
|
||||
def _initialize(args):
|
||||
|
||||
# make sure extensions in lisa is loaded
|
||||
base_module_path = os.path.dirname(__file__)
|
||||
import_module(base_module_path, logDetails=False)
|
||||
config = parse(args)
|
||||
extends_config = config.get("extends")
|
||||
load_extends(extends_config)
|
||||
for package_name in packages:
|
||||
build_factories(package_name)
|
||||
|
||||
return config
|
||||
# merge all parameters
|
||||
config = parse(args)
|
||||
runtime_object = RuntimeObject(config)
|
||||
|
||||
# load external extensions
|
||||
extends_config = config.getExtensions()
|
||||
_load_extends(extends_config)
|
||||
|
||||
# initialize environment
|
||||
environment_config = config.getEnvironment()
|
||||
environment = Environment.loadEnvironment(environment_config)
|
||||
runtime_object.environment = environment
|
||||
|
||||
# initialize platform
|
||||
platform_config = config.getPlatform()
|
||||
runtime_object.platform = platform_factory.initializePlatform(platform_config)
|
||||
|
||||
runtime_object.validate()
|
||||
|
||||
return runtime_object
|
||||
|
||||
|
||||
def run(args):
|
||||
config = _initialize(args)
|
||||
runtime_object = _initialize(args)
|
||||
runner = LISARunner()
|
||||
awaitable = runner.start()
|
||||
asyncio.run(awaitable)
|
||||
|
||||
|
||||
# check configs
|
||||
def check(args):
|
||||
_initialize(args)
|
||||
|
||||
|
||||
def list_start(args):
|
||||
config = _initialize(args)
|
||||
runtime_object = _initialize(args)
|
||||
log.info("list information here")
|
||||
|
|
|
@ -3,7 +3,7 @@ from lisa import log
|
|||
from lisa.core.testfactory import testFactory
|
||||
|
||||
|
||||
class TestMethod(object):
|
||||
class CaseMetadata(object):
|
||||
def __init__(self, priority):
|
||||
self.priority = priority
|
||||
|
|
@ -2,7 +2,7 @@ from lisa.core.testfactory import testFactory
|
|||
from typing import List
|
||||
|
||||
|
||||
class TestClass:
|
||||
class SuiteMetadata:
|
||||
def __init__(self, area: str, category: str, tags: List[str], name=None):
|
||||
self.area = area
|
||||
self.category = category
|
|
@ -1,10 +1,71 @@
|
|||
import copy
|
||||
from lisa import log
|
||||
from lisa.core.nodeFactory import NodeFactory
|
||||
from .node import Node
|
||||
from lisa import constants
|
||||
|
||||
|
||||
class Environment:
|
||||
class Environment(object):
|
||||
CONFIG_NODES = "nodes"
|
||||
CONFIG_TEMPLATE = "template"
|
||||
CONFIG_TEMPLATE_NODE_COUNT = "nodeCount"
|
||||
|
||||
def __init__(self):
|
||||
self.nodes: list[Node] = None
|
||||
self.nodes: list[Node] = []
|
||||
self.platform = None
|
||||
self.spec = None
|
||||
|
||||
@staticmethod
|
||||
def loadEnvironment(config):
|
||||
environment = Environment()
|
||||
spec = copy.deepcopy(config)
|
||||
|
||||
has_default_node = False
|
||||
nodes_spec = []
|
||||
node_config = spec.get(Environment.CONFIG_NODES)
|
||||
if node_config is not None:
|
||||
for node_config in config.get(Environment.CONFIG_NODES):
|
||||
node = NodeFactory.createNodeFromConfig(node_config)
|
||||
if node is not None:
|
||||
environment.nodes.append(node)
|
||||
else:
|
||||
nodes_spec.append(node_config)
|
||||
|
||||
has_default_node = environment._validateSingleDefault(
|
||||
has_default_node, node_config.get(constants.IS_DEFAULT)
|
||||
)
|
||||
|
||||
# validate template and node not appear together
|
||||
nodes_template = spec.get(Environment.CONFIG_TEMPLATE)
|
||||
if nodes_template is not None:
|
||||
for item in nodes_template:
|
||||
|
||||
node_count = item.get(Environment.CONFIG_TEMPLATE_NODE_COUNT)
|
||||
if node_count is None:
|
||||
node_count = 1
|
||||
else:
|
||||
del item[Environment.CONFIG_TEMPLATE_NODE_COUNT]
|
||||
|
||||
is_default = item.get(constants.IS_DEFAULT)
|
||||
has_default_node = environment._validateSingleDefault(
|
||||
has_default_node, is_default
|
||||
)
|
||||
for index in range(node_count):
|
||||
copied_item = copy.deepcopy(item)
|
||||
# only one default node for template also
|
||||
if is_default is True and index > 0:
|
||||
del copied_item[constants.IS_DEFAULT]
|
||||
nodes_spec.append(copied_item)
|
||||
del spec[Environment.CONFIG_TEMPLATE]
|
||||
|
||||
if len(nodes_spec) == 0 and len(environment.nodes) == 0:
|
||||
raise Exception("not found any node in environment")
|
||||
|
||||
spec[Environment.CONFIG_NODES] = nodes_spec
|
||||
|
||||
environment.spec = spec
|
||||
log.debug("environment spec is %s", environment.spec)
|
||||
return environment
|
||||
|
||||
@property
|
||||
def defaultNode(self):
|
||||
|
@ -46,3 +107,10 @@ class Environment:
|
|||
|
||||
def setNodes(self, nodes):
|
||||
self.nodes = nodes
|
||||
|
||||
def _validateSingleDefault(self, has_default, is_default):
|
||||
if is_default is True:
|
||||
if has_default is True:
|
||||
raise Exception("only one node can set isDefault to True")
|
||||
has_default = True
|
||||
return has_default
|
||||
|
|
|
@ -1,8 +1,41 @@
|
|||
from lisa.core.sshConnection import SshConnection
|
||||
from lisa.util import constants
|
||||
|
||||
|
||||
class Node:
|
||||
def __init__(self, isRemote=True, isDefault=False):
|
||||
def __init__(self, isRemote=True, spec=None, isDefault=False):
|
||||
self.isDefault: bool = isDefault
|
||||
self.isRemote: bool = isRemote
|
||||
self.sshSession = None
|
||||
self.spec = spec
|
||||
self.connection = None
|
||||
self.publicSshSession = None
|
||||
|
||||
@staticmethod
|
||||
def createNode(
|
||||
spec=None, node_type=constants.ENVIRONMENT_NODES_REMOTE, isDefault=False
|
||||
):
|
||||
if node_type == constants.ENVIRONMENT_NODES_REMOTE:
|
||||
isRemote = True
|
||||
elif node_type == constants.ENVIRONMENT_NODES_LOCAL:
|
||||
isRemote = False
|
||||
else:
|
||||
raise Exception("unsupported node_type '%s'", node_type)
|
||||
node = Node(spec=spec, isRemote=isRemote, isDefault=isDefault)
|
||||
return node
|
||||
|
||||
def setConnectionInfo(
|
||||
self,
|
||||
address: str = "",
|
||||
port: int = 22,
|
||||
publicAddress: str = "",
|
||||
publicPort: int = 22,
|
||||
username: str = "root",
|
||||
password: str = "",
|
||||
privateKeyFile: str = "",
|
||||
):
|
||||
self.connection = SshConnection(
|
||||
address, port, publicAddress, publicPort, username, password, privateKeyFile
|
||||
)
|
||||
|
||||
def connect(self):
|
||||
if self.sshSession is None:
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
from lisa import log, constants
|
||||
from .node import Node
|
||||
|
||||
|
||||
class NodeFactory:
|
||||
@staticmethod
|
||||
def createNodeFromConfig(config):
|
||||
node_type = config.get(constants.TYPE)
|
||||
node = None
|
||||
if node_type is None:
|
||||
raise Exception("type of node shouldn't be None")
|
||||
if node_type in [
|
||||
constants.ENVIRONMENT_NODES_LOCAL,
|
||||
constants.ENVIRONMENT_NODES_REMOTE,
|
||||
]:
|
||||
is_default = NodeFactory._isDefault(config)
|
||||
node = Node.createNode(node_type=node_type, isDefault=is_default)
|
||||
if node is not None:
|
||||
log.debug("created node '%s'", node_type)
|
||||
return node
|
||||
|
||||
@staticmethod
|
||||
def createNodeFromSpec(spec, node_type=constants.ENVIRONMENT_NODES_REMOTE):
|
||||
if node_type == Node.TYPE_REMOTE:
|
||||
isRemote = True
|
||||
elif node_type == Node.TYPE_LOCAL:
|
||||
isRemote = False
|
||||
else:
|
||||
raise Exception("unsupported node_type '%s'", node_type)
|
||||
node = Node.createNode(spec=spec, node_type=node_type, isRemote=isRemote)
|
||||
return node
|
||||
|
||||
@staticmethod
|
||||
def _isDefault(config) -> bool:
|
||||
default = config.get(constants.IS_DEFAULT)
|
||||
if default is not None and default is True:
|
||||
default = True
|
||||
else:
|
||||
default = False
|
||||
return default
|
|
@ -17,7 +17,9 @@ def import_module(path, logDetails=True):
|
|||
packages.append(package_name)
|
||||
package_dir = os.path.dirname(path)
|
||||
sys.path.append(package_dir)
|
||||
log.info("loading extensions from %s", path)
|
||||
if logDetails:
|
||||
log.info("loading extensions from %s", path)
|
||||
|
||||
for file in glob(os.path.join(path, "**", "*.py"), recursive=True):
|
||||
file_name = os.path.basename(file)
|
||||
dir_name = os.path.dirname(file)
|
||||
|
@ -40,9 +42,7 @@ def import_module(path, logDetails=True):
|
|||
full_module_name,
|
||||
local_module_name,
|
||||
)
|
||||
importlib.import_module(
|
||||
local_module_name, package=local_package_name
|
||||
)
|
||||
importlib.import_module(local_module_name, package=local_package_name)
|
||||
|
||||
|
||||
packages = ["lisa"]
|
||||
|
|
|
@ -7,11 +7,14 @@ class Platform(ABC):
|
|||
def platformType(cls) -> str:
|
||||
pass
|
||||
|
||||
@abstractclassmethod
|
||||
def config(self, key: str, value: object):
|
||||
pass
|
||||
|
||||
@abstractclassmethod
|
||||
def requestEnvironment(self, environmentSpec):
|
||||
pass
|
||||
|
||||
@abstractclassmethod
|
||||
def deleteEnvironment(self, environment: Environment):
|
||||
pass
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from lisa.util import constants
|
||||
from lisa.common.logger import log
|
||||
from .platform import Platform
|
||||
from typing import Dict
|
||||
|
@ -8,22 +9,39 @@ class PlatformFactory:
|
|||
self.platforms: Dict[str, Platform] = dict()
|
||||
|
||||
def registerPlatform(self, platform):
|
||||
platform_type = platform.platformType(platform)
|
||||
platform_type = platform.platformType(platform).lower()
|
||||
if self.platforms.get(platform_type) is None:
|
||||
log.info(
|
||||
"registered platform '%s'", platform_type,
|
||||
)
|
||||
self.platforms[platform_type] = platform()
|
||||
else:
|
||||
# not sure what happens, subclass returns multiple items for
|
||||
# same class
|
||||
# so just log debug level here.
|
||||
log.debug(
|
||||
"platform type '%s' already registered", platform_type,
|
||||
)
|
||||
raise Exception("platform '%s' exists, cannot be registered again")
|
||||
|
||||
def loadPlatform(self, type_name, config):
|
||||
pass
|
||||
def initializePlatform(self, config):
|
||||
# we may extend it later to support multiple platforms
|
||||
platform_count = len(config)
|
||||
if platform_count != 1:
|
||||
raise Exception("There must be 1 and only 1 platform")
|
||||
|
||||
platform_type = config[0].get("type")
|
||||
if platform_type is None:
|
||||
raise Exception("type of platfrom shouldn't be None")
|
||||
|
||||
self._buildFactory()
|
||||
log.debug(
|
||||
"registered platforms [%s]",
|
||||
", ".join([name for name in self.platforms.keys()]),
|
||||
)
|
||||
|
||||
platform = self.platforms.get(platform_type.lower())
|
||||
if platform is None:
|
||||
raise Exception("cannot find platform type '%s'" % platform_type)
|
||||
log.info("activated platform '%s'", platform_type)
|
||||
|
||||
platform.config(constants.CONFIG_CONFIG, config[0])
|
||||
return platform
|
||||
|
||||
def _buildFactory(self):
|
||||
for sub_class in Platform.__subclasses__():
|
||||
self.registerPlatform(sub_class)
|
||||
|
||||
|
||||
platformFactory = PlatformFactory()
|
||||
platform_factory = PlatformFactory()
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
from lisa.parameter_parser.config import Config
|
||||
from lisa.common.logger import log
|
||||
from lisa import Platform, Environment
|
||||
from lisa.sut_orchestrator.ready import ReadyPlatform
|
||||
from lisa import constants
|
||||
|
||||
|
||||
class RuntimeObject:
|
||||
def __init__(self, config: Config):
|
||||
# global config
|
||||
self.config: Config = config
|
||||
self.environment: Environment = None
|
||||
self.platform: Platform = None
|
||||
|
||||
# do some cross object validation
|
||||
def validate(self):
|
||||
environment_config = self.config.getEnvironment()
|
||||
warn_as_error = None
|
||||
if environment_config is not None:
|
||||
warn_as_error = environment_config[constants.WARN_AS_ERROR]
|
||||
if self.environment.spec is not None and isinstance(
|
||||
self.platform, ReadyPlatform
|
||||
):
|
||||
self._validateMessage(
|
||||
warn_as_error, "environment spec won't be processed by ready platform."
|
||||
)
|
||||
|
||||
def _validateMessage(self, warn_as_error: bool, message, *args):
|
||||
if warn_as_error:
|
||||
raise Exception(message % args)
|
||||
else:
|
||||
log.warn(message, *args)
|
|
@ -0,0 +1,61 @@
|
|||
import os
|
||||
|
||||
|
||||
class SshConnection:
|
||||
def __init__(
|
||||
self,
|
||||
address: str = "",
|
||||
port: int = 22,
|
||||
publicAddress: str = "",
|
||||
publicPort: int = 22,
|
||||
username: str = "root",
|
||||
password: str = "",
|
||||
privateKeyFile: str = "",
|
||||
):
|
||||
self.address = address
|
||||
self.port = port
|
||||
self.publicAddress = publicAddress
|
||||
self.publicPort = publicPort
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.privateKeyFile = privateKeyFile
|
||||
|
||||
if (self.address is None or self.address == "") and (
|
||||
self.publicAddress is None or self.publicAddress == ""
|
||||
):
|
||||
raise Exception("at least one of address and publicAddress need to be set")
|
||||
elif self.address is None or self.address == "":
|
||||
self.address = self.publicAddress
|
||||
elif self.publicAddress is None or self.publicAddress == "":
|
||||
self.publicAddress = self.address
|
||||
|
||||
if (self.port is None or self.port <= 0) and (
|
||||
self.publicPort is None or self.publicPort <= 0
|
||||
):
|
||||
raise Exception("at least one of port and publicPort need to be set")
|
||||
elif self.port is None or self.port <= 0:
|
||||
self.port = self.publicPort
|
||||
elif self.publicPort is None or self.publicPort <= 0:
|
||||
self.publicPort = self.port
|
||||
|
||||
if (self.password is None or self.password == "") and (
|
||||
self.privateKeyFile is None or self.privateKeyFile == ""
|
||||
):
|
||||
raise Exception(
|
||||
"at least one of password and privateKeyFile need to be set"
|
||||
)
|
||||
elif self.password is not None and self.password != "":
|
||||
self.usePassword = True
|
||||
else:
|
||||
if not os.path.exists(self.privateKeyFile):
|
||||
raise FileNotFoundError(self.privateKeyFile)
|
||||
self.usePassword = False
|
||||
|
||||
if self.username is None or self.username == "":
|
||||
raise Exception("username must be set")
|
||||
|
||||
def getInternalConnection(self):
|
||||
pass
|
||||
|
||||
def getPublicConnection(self):
|
||||
pass
|
|
@ -53,7 +53,7 @@ class TestFactory:
|
|||
test_suite = TestSuiteMetadata(test_class, area, category, tags)
|
||||
self.suites[key] = test_suite
|
||||
else:
|
||||
raise Exception("TestFactory duplicate test class name: %s!" % key)
|
||||
raise Exception("TestFactory duplicate test class name: %s" % key)
|
||||
|
||||
class_prefix = "%s." % key
|
||||
for test_case in self.cases.values():
|
||||
|
@ -72,7 +72,7 @@ class TestFactory:
|
|||
if self.cases.get(full_name) is None:
|
||||
self.cases[full_name] = test_case
|
||||
else:
|
||||
raise Exception("duplicate test class name: %s!" % full_name)
|
||||
raise Exception("duplicate test class name: %s" % full_name)
|
||||
|
||||
# this should be None in current observation.
|
||||
# the methods are loadded prior to test class
|
||||
|
|
|
@ -8,11 +8,10 @@ from lisa.common import env
|
|||
from lisa.common.logger import init_log, log
|
||||
from retry import retry
|
||||
|
||||
path_template = "runtime/results/{0}/{0}-{1}"
|
||||
|
||||
|
||||
@retry(FileExistsError, tries=10, delay=0)
|
||||
def create_result_path():
|
||||
path_template = "runtime/results/{0}/{0}-{1}"
|
||||
date = datetime.utcnow().strftime("%Y%m%d")
|
||||
time = datetime.utcnow().strftime("%H%M%S-%f")[:-3]
|
||||
current_path = path_template.format(date, time)
|
||||
|
|
|
@ -50,6 +50,11 @@ def parse_args():
|
|||
support_config_file(list_parser)
|
||||
support_variable(list_parser)
|
||||
|
||||
check_parser = subparsers.add_parser("check")
|
||||
check_parser.set_defaults(func=command_entries.check)
|
||||
support_config_file(check_parser)
|
||||
support_variable(check_parser)
|
||||
|
||||
parser.set_defaults(func=command_entries.run)
|
||||
|
||||
for sub_parser in subparsers.choices.values():
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
from lisa import constants
|
||||
|
||||
|
||||
class Config(dict):
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
|
||||
def validate(self):
|
||||
# TODO implement config validation
|
||||
pass
|
||||
|
||||
def getExtensions(self):
|
||||
return self.config.get(constants.EXTENSIONS)
|
||||
|
||||
def getEnvironment(self):
|
||||
return self.config.get(constants.ENVIRONMENT)
|
||||
|
||||
def getPlatform(self):
|
||||
return self.config.get(constants.PLATFORM)
|
||||
|
||||
def getTests(self):
|
||||
return self.config.get(constants.TESTS)
|
|
@ -1,3 +1,4 @@
|
|||
from lisa.parameter_parser.config import Config
|
||||
import os
|
||||
import yaml
|
||||
from lisa import log
|
||||
|
@ -12,6 +13,7 @@ def parse(args):
|
|||
|
||||
with open(path, "r") as file:
|
||||
data = yaml.safe_load(file)
|
||||
log.debug("yaml content: %s", data)
|
||||
|
||||
return data
|
||||
log.debug("final config data: %s", data)
|
||||
config = Config(data)
|
||||
return config
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from lisa import Platform
|
||||
from lisa import Platform, Environment
|
||||
|
||||
|
||||
class ReadyPlatform(Platform):
|
||||
|
@ -6,4 +6,12 @@ class ReadyPlatform(Platform):
|
|||
return "ready"
|
||||
|
||||
def config(self, key: str, value: object):
|
||||
# ready platform has no config
|
||||
pass
|
||||
|
||||
def requestEnvironment(self, environment: Environment):
|
||||
return environment
|
||||
|
||||
def deleteEnvironment(self, environment: Environment):
|
||||
# ready platform doesn't support delete environment
|
||||
pass
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
# config types
|
||||
CONFIG_CONFIG = "config"
|
||||
|
||||
# common
|
||||
NAME = "name"
|
||||
VALUE = "value"
|
||||
TYPE = "type"
|
||||
PATH = "path"
|
||||
PATHS = "paths"
|
||||
STRATEGY = "strategy"
|
||||
FILE = "file"
|
||||
HTTP = "http"
|
||||
IS_DEFAULT = "isDefault"
|
||||
WARN_AS_ERROR = "warnAsError"
|
||||
|
||||
|
||||
# by level
|
||||
PARENT = "parent"
|
||||
OPERATION = "operation"
|
||||
OPERATION_REMOVE = "remove"
|
||||
OPERATION_ADD = "add"
|
||||
OPERATION_OVERWRITE = "overwrite"
|
||||
|
||||
EXTENSIONS = "extensions"
|
||||
|
||||
VARIABLES = "variables"
|
||||
VARIABLES_ISSECRET = "isSecret"
|
||||
|
||||
ARTIFACT = "artifact"
|
||||
ARTIFACT_TYPE_VHD = "vhd"
|
||||
ARTIFACT_LOCATIONS = "locations"
|
||||
ARTIFACT_LOCATIONS_TYPE_HTTP = "http"
|
||||
|
||||
ENVIRONMENT = "environment"
|
||||
ENVIRONMENT_TOPOLOGY = "topology"
|
||||
ENVIRONMENT_TEMPLATE = "template"
|
||||
ENVIRONMENT_TEMPLATE_NODECOUNT = "nodeCount"
|
||||
ENVIRONMENT_NODES = "nodes"
|
||||
ENVIRONMENT_NODES_SPEC = "spec"
|
||||
ENVIRONMENT_NODES_REMOTE = "remote"
|
||||
ENVIRONMENT_NODES_LOCAL = "local"
|
||||
ENVIRONMENT_NODES_REMOTE_ADDRESS = "address"
|
||||
ENVIRONMENT_NODES_REMOTE_PORT = "port"
|
||||
ENVIRONMENT_NODES_REMOTE_PUBLIC_ADDRESS = "publicAddress"
|
||||
ENVIRONMENT_NODES_REMOTE_PUBLIC_PORT = "publicPort"
|
||||
ENVIRONMENT_NODES_REMOTE_USERNAME = "username"
|
||||
ENVIRONMENT_NODES_REMOTE_PASSWORD = "password"
|
||||
ENVIRONMENT_NODES_REMOTE_PRIVATEKEYFILE = "privateKeyFile"
|
||||
|
||||
NOTIFIER = "notifier"
|
||||
|
||||
PLATFORM = "platform"
|
||||
PLATFORM_AZURE = "azure"
|
||||
PLATFORM_READY = "ready"
|
||||
|
||||
TESTS = "tests"
|
||||
TESTS_CRITERIA = "criteria"
|
||||
TESTS_CRITERIA_AREA = "area"
|
||||
TESTS_CRITERIA_CATEGORY = "category"
|
||||
TESTS_CRITERIA_PRIORITY = "priority"
|
||||
TESTS_CRITERIA_TAG = "tag"
|
||||
TESTS_FORCEINCLUDE = "forceInclude"
|
||||
TESTS_ITERATION = "iteration"
|
||||
TESTS_IGNOREFAILURE = "ignoreFailure"
|
||||
TESTS_INCLUDE = "include"
|
||||
TESTS_RETRY = "retry"
|
Загрузка…
Ссылка в новой задаче