Exemplo n.º 1
0
 def declare_precedence(cls) -> int:
     """
         This API is called so that the IntegrationCoupling can declare an ordinal precedence that should be
         utilized for bringing up its integration state.
     """
     cls.logger = getAutomatonKitLogger()
     return
Exemplo n.º 2
0
class LandscapeBaseLayer:

    context = Context()

    logger = getAutomatonKitLogger()
    landscape_lock = threading.RLock()

    landscape_description = LandscapeDescription
    landscape_device = LandscapeDevice
    landscape_device_extension = LandscapeDeviceExtension

    topology_description = TopologyDescription
Exemplo n.º 3
0
    def __init__(self, manufacturer: str, modelNumber: str, modelDescription):
        """
            Creates a root device object.
        """
        super(UpnpRootDevice, self).__init__()

        if self.MANUFACTURER != manufacturer:
            self.MANUFACTURER = manufacturer
        if self.MODEL_NUMBER != modelNumber:
            self.MODEL_NUMBER = modelNumber
        if self.MODEL_DESCRIPTION != modelDescription:
            self.MODEL_DESCRIPTION = modelDescription

        self._extra = {}
        self._cachecontrol = None
        self._ext = None
        self._location = None
        self._server = None
        self._st = None
        self._usn = None
        self._usn_dev = None
        self._usn_cls = None

        self._specVersion = None

        self._host = None
        self._ip_address = None
        self._routes = None
        self._primary_route = None

        self._devices = {}
        self._device_descriptions = {}

        self._auto_subscribe = False
        self._mode = None

        self._logger = getAutomatonKitLogger()

        self._coord_ref = None

        self._root_device_lock = threading.RLock()

        self._subscriptions = {}
        self._sid_to_service_lookup = {}
        self._variables = {}

        self._last_alive = datetime.min
        self._last_byebye = datetime.min
        self._available = False
        return
Exemplo n.º 4
0
    def __init__(self,
                 action_pattern: ActionPattern = ActionPattern.SINGLE_CALL,
                 completion_timeout: float = DEFAULT_COMPLETION_TIMEOUT,
                 completion_interval: float = DEFAULT_COMPLETION_INTERVAL,
                 inactivity_timeout: float = DEFAULT_INACTIVITY_TIMEOUT,
                 inactivity_interval: float = DEFAULT_INACTIVITY_INTERVAL,
                 monitor_delay: float = DEFAULT_MONITOR_DELAY,
                 logging_pattern: LoggingPattern = DEFAULT_LOGGING_PATTERN,
                 retry_logging_interval: int = DEFAULT_RETRY_LOGGING_INTERVAL,
                 allowed_error_codes: List[int] = DEFAULT_ALLOWED_ERROR_CODES,
                 must_connect: bool = DEFAULT_MUST_CONNECT,
                 logger: Optional[Logger] = None):
        """
            Creates an :class:`Aspects` instance package.

            :param action_pattern: The :class:`ActionPattern` that the API should exhibit such as
                                   SINGULAR, DO_UNTIL_SUCCESS, DO_WHILE_SUCCESS
            :param completion_timeout: The time in seconds as a float that is the max time before
                                       timeout for the activity to complete.
            :param completion_interval: The time in seconds as a float that is waited before reattempting
                                        an activity.
            :param inactivity_timeout: The time in seconds as a float that is the max time before timeout
                                       that is waited before a :class:`TimeoutError` is raised due to inactivity.
            :param inactivity_interval: The time in seconds as a float that is waited before reattempting an activity.
            :param retry_logging_interval: The logging interval for retry loops.
            :param allowed_error_codes: A list of error codes that allow retry
            :param must_connect: Used to indicate that connection failures should be raised.
        """
        self.action_pattern = action_pattern
        self.completion_timeout = completion_timeout
        self.completion_interval = completion_interval
        self.inactivity_timeout = inactivity_timeout
        self.inactivity_interval = inactivity_interval
        self.monitor_delay = monitor_delay
        self.logging_pattern = logging_pattern
        self.retry_logging_interval = retry_logging_interval
        self.allowed_error_codes = allowed_error_codes
        self.must_connect = must_connect

        if logger is None:
            self.logger = getAutomatonKitLogger()
        else:
            self.logger = logger

        return
Exemplo n.º 5
0
    def load(self, topology_file: str, log_to_directory: Optional[str]=None):
        """
            Loads and validates the topology description file.
        """
        logger = getAutomatonKitLogger()

        topology_info = None


        with open(topology_file, 'r') as tf:
            tfcontent = tf.read()
            topology_info = yaml.safe_load(tfcontent)

        if log_to_directory is not None:
            try:
                topology_file_basename = os.path.basename(topology_file)
                topology_file_basename, topology_file_ext = os.path.splitext(topology_file_basename)

                topology_file_copy = os.path.join(log_to_directory, "topology-declared{}".format(topology_file_ext))
                shutil.copy2(topology_file, topology_file_copy)
            except Exception as xcpt:
                err_msg = "Error while logging the topology file (%s)%s%s" % (
                    topology_file, os.linesep, traceback.format_exc())
                raise AKitRuntimeError(err_msg) from xcpt

        errors, warnings = self.validate_topology(topology_info)

        if len(errors) > 0:
            errmsg_lines = [
                "ERROR Topology validation failures:"
            ]
            for err in errors:
                errmsg_lines.append("    %s" % err)

            errmsg = os.linesep.join(errmsg_lines)
            raise AKitConfigurationError(errmsg) from None

        if len(warnings) > 0:
            for wrn in warnings:
                logger.warn("Topology Configuration Warning: (%s)" % wrn)

        return topology_info
Exemplo n.º 6
0
    def __init__(self,
                 lscape: "Landscape",
                 *args,
                 coord_config=None,
                 **kwargs):
        """
            Constructs an instance of a derived :class:`CoordinatorBase` object.

            :param lscape: The :class:`Landscape` singleton instance.
            :param *args: A pass through for other positional args.
            :param **kwargs: A pass through for the other keyword args.
        """
        this_type = type(self)
        if not this_type.initialized:
            this_type.initialized = True

            self._logger = getAutomatonKitLogger()

            # If the landscape is in interactive mode, then all the coordinators should
            # default to using interactive mode
            self._interactive_mode = lscape.interactive_mode

            self._lscape_ref = weakref.ref(lscape)

            self._coord_lock = threading.RLock()

            self._cl_children = {}

            self._found_devices = []
            self._available_devices = []
            self._unavailable_devices = []

            self._coord_config = coord_config

            self._initialize(*args, **kwargs)
        return
Exemplo n.º 7
0
__credits__ = []
__version__ = "1.0.0"
__maintainer__ = "Myron Walker"
__email__ = "*****@*****.**"
__status__ = "Development"  # Prototype, Development or Production
__license__ = "MIT"

from typing import List
from akit.exceptions import AKitSemanticError

from akit.testing.testplus.registration.resourceregistry import resource_registry
from akit.testing.testplus.testref import TestRef

from akit.xlogging.foundations import getAutomatonKitLogger

logger = getAutomatonKitLogger()


class TestGroup:
    """
              -------------
              |  Group A  |
        ---------------------------
        |  Group AA  |  Scope AB  |
        -------------------------------
        |         Scope AAA/ABA       |
        -------------------------------
    """
    def __init__(self, name, package=None):
        self._name = name
        self._package = package
Exemplo n.º 8
0
def command_akit_jobs_run(root, job, output, start, branch, build, flavor, console_level, logfile_level):

    # pylint: disable=unused-import,import-outside-toplevel

    # We do the imports of the automation framework code inside the action functions because
    # we don't want to startup loggin and the processing of inputs and environment variables
    # until we have entered an action method.  Thats way we know how to setup the environment.

    # IMPORTANT: We need to load the context first because it will trigger the loading
    # of the default user configuration
    from akit.environment.context import Context

    from akit.compat import import_by_name
    from akit.environment.variables import extend_path

    try:
        ctx = Context()
        ctx.insert("/environment/jobtype", 'testrun')

        test_root = os.path.abspath(os.path.expandvars(os.path.expanduser(root)))
        if not os.path.isdir(test_root):
            errmsg = "The specified root folder does not exist. root=%s" % root
            if test_root != root:
                errmsg += " expanded=%s" % test_root
            raise argparse.ArgumentError("--root", errmsg)

        # Make sure we extend PATH to include the test root
        extend_path(test_root)

        # We perform activation a little later in the testrunner.py file so we can
        # handle exceptions in the context of testrunner_main function
        import akit.activation.testrun
        from akit.xlogging.foundations import logging_initialize, getAutomatonKitLogger

        # Initialize logging
        logging_initialize()
        logger = getAutomatonKitLogger()

        from akit.extensionpoints import AKitExtensionPoints
        akep = AKitExtensionPoints()

        # At this point in the code, we either lookup an existing test job or we create a test job
        # from the includes, excludes or test_module
        TestJobType = akep.get_testplus_default_job_type()

        if job is not None:
            job_parts = job.split("@")
            if len(job_parts) != 2:
                errmsg = "A --job parameter must be of the form 'package.module@JobClass'"
                raise click.UsageError(errmsg)

            job_package, job_class = job_parts

            try:
                job_mod = import_by_name(job_package)
            except ImportError as imperr:
                errmsg = "Failure while importing job package %r"  % job_package
                raise argparse.ArgumentError("--job", errmsg) from imperr

            if not hasattr(job_mod, job_class):
                errmsg = "The job package %r does not have a job type %r." % (job_package, job_class)
                raise argparse.ArgumentError("--job", errmsg)

            TestJobType = getattr(job_mod, job_class)

        result_code = 0
        with TestJobType(logger, test_root) as tjob:
            result_code = tjob.execute()

        sys.exit(result_code)

    finally:
        pass

    return
Exemplo n.º 9
0
class LandscapeDevice(FeatureAttachedObject):
    """
        The base class for all landscape devices.  The :class:`LandscapeDevice' represents attributes that are common
        to all connected devices and provides attachements points and mechanisms for adding DeviceExtentions to
        the :class:`LandscapeDevice` device.
    """

    # Base landscape devices don't have any feature tags, but we want all devices to have feature
    # tag capability so we can filter all devices based on feature tags, whether they have
    # tags or not.
    FEATURE_TAGS = []

    logger = getAutomatonKitLogger()

    def __init__(self, lscape: "Landscape", keyid: str, device_type: str, device_config: dict):
        super().__init__()

        self._lscape_ref = weakref.ref(lscape)
        self._keyid = keyid
        self._device_type = device_type
        self._device_config = device_config
        self._device_lock = threading.RLock()

        self._contacted_first = None
        self._contacted_last = None
        self._is_watched = None
        self._is_isolated = None

        self._upnp: "UpnpRootDevice" = None
        self._ssh: "SshAgent" = None
        self._power = None
        self._serial = None

        self._preferred_command_interface = "ssh"

        self._match_functions = {}

        self._credentials = {}
        self._ssh_credentials = []
        if "credentials" in device_config:
            lscape_credentials = lscape.credentials
            for cred_key in device_config["credentials"]:
                if cred_key in lscape_credentials:
                    cred_info = lscape_credentials[cred_key]
                    self._credentials[cred_key] = cred_info
                    if cred_info.category == "ssh":
                        self._ssh_credentials.append(cred_info)

        return

    @property
    def contacted_first(self) -> datetime:
        """
            A datetime stamp of when this device was first contacted
        """
        return self._contacted_first

    @property
    def contacted_last(self) -> datetime:
        """
            A datetime stamp of when this device was last contacted
        """
        return self._contacted_last

    @property
    def device_config(self) -> dict:
        """
            A dictionary of the configuration information for this device.
        """
        return self._device_config

    @property
    def device_lock(self) -> threading.RLock:
        """
            Returns the lock for the device.
            
            ..note: Use with caution
        """
        return self._device_lock

    @property
    def device_type(self) -> str:
        """
            A string representing the type of device.
        """
        return self._device_type

    @property
    def has_ssh_credential(self) -> bool:
        """
            A boolean value indicating whether this device has an SSH credential.
        """
        has_creds = len(self._ssh_credentials) > 0
        return has_creds

    @property
    def identity(self) -> str:
        """
            Returns a string that identifies a device in logs. This property can
            be overridden by custom landscape devices to customize identities in
            logs.
        """
        return self.keyid

    @property
    def ipaddr(self):
        """
            The device IP of the device, if available.
        """
        ipaddr = self._resolve_ipaddress()
        return ipaddr

    @property
    def is_watched(self) -> bool:
        """
            A boolean indicating if this device is a watched device.
        """
        return self._is_watched

    @property
    def keyid(self) -> bool:
        """
            The key identifier for this device, this is generally the identifier provided
            by the coordinator that created the device instance.
        """
        return self._keyid

    @property
    def landscape(self) -> "Landscape":
        """
            Returns a strong reference to the the landscape object
        """
        return self._lscape_ref()

    @property
    def power(self) -> "LandscapeDeviceExtension":
        """
            The power agent associated with this device.
        """
        return self._power

    @property
    def serial(self) -> "LandscapeDeviceExtension":
        """
            The serial agent associated with this device.
        """
        return self._serial

    @property
    def ssh(self) -> "SshAgent":
        """
            The 'SSH' :class:`SshAgent` attached to this device or None.
        """
        return self._ssh

    @property
    def ssh_credentials(self):
        """
            The list of SSH credentials associated with this device
        """
        return self._ssh_credentials

    @property
    def upnp(self) -> "UpnpRootDevice":
        """
            The 'UPnP' :class:`UpnpRootDevice` attached to this device or None.
        """
        return self._upnp

    def attach_extension(self, ext_type, extension):
        """
            Method called by device coordinators to attach a device extension to a :class:`LandscapeDevice`.
        """
        setattr(self, "_" + ext_type, extension)
        return

    def checkout(self):
        """
            Method that makes it convenient to checkout device.
        """
        self.landscape.checkout_device(self)
        return

    def checkin(self):
        """
            Method that makes it convenient to checkin a device.
        """
        self.landscape.checkin_device(self)
        return

    def get_preferred_command_runner(self) -> ICommandRunner:
        """
            Gets the preferred command runner interface for this device.
        """
        cmd_runner = None

        if self._preferred_command_interface == "ssh":
            if hasattr(self, "ssh") and self.ssh is not None:
                cmd_runner = self.ssh
            elif hasattr(self, "serial") and self.serial is not None:
                cmd_runner = self.serial
        elif self._preferred_command_interface == "serial":
            if hasattr(self, "serial") and self.serial is not None:
                cmd_runner = self.serial
            elif hasattr(self, "ssh") and self.ssh is not None:
                cmd_runner = self.ssh
        else:
            errmsg = "Unknown command runner interface '{}'".format(self._preferred_command_interface)
            raise AKitRuntimeError(errmsg)

        return cmd_runner

    def initialize_features(self):
        """
            Initializes the features of the device based on the feature declarations and the information
            found in the feature config.
        """
        if "features" in self._device_config:
            feature_info = self._device_config["features"]
            for fkey, fval in feature_info.items():
                if fkey == "isolation":
                    self._is_isolated = fval
                elif fkey == "power":
                    self._intitialize_power(fval)
                elif fkey == "serial":
                    self._intitialize_serial(fval)
                elif fkey == "":
                    pass
        return

    def match_using_params(self, match_type, *match_params) -> bool:
        """
            Method that allows you to match :class:`LandscapeDevice` objects by providing a match_type and
            parameters.  The match type is mapped to functions that are registered by device coordinators
            and then the function is called with the match parameters to determine if a device is a match.
        """
        matches = False
        match_func = None
        match_self = None

        self._device_lock.acquire()
        try:
            if match_type in self._match_functions:
                dext_attr, match_func = self._match_functions[match_type]
                match_self = None
                if hasattr(self, dext_attr):
                    match_self = getattr(self, dext_attr)
        finally:
            self._device_lock.release()

        if match_self is not None and match_func is not None:
            matches = match_func(match_self, *match_params)

        return matches

    def set_preferred_command_interface(self, ifname: str):
        """
            Sets the name of the interface that is used as the preferred command runner.

            :param ifname: The name of the preferred commandline interface. (ssh, serial, etc.)
        """
        self._preferred_command_interface = ifname
        return

    def update_match_table(self, match_table: dict):
        """
            Method called  to update the match functions.
        """
        self._device_lock.acquire()
        try:
            self._match_functions.update(match_table)
        finally:
            self._device_lock.release()

        return

    def verify_status(self):
        """
            Verify the status of the specified device.
        """

        status = "Up"
        if self._upnp is not None:
            desc = self._upnp.query_device_description()
            if desc is None:
                status = "Down"

        elif self._ssh:
            cmd_status, _, _ = self._ssh.run_cmd("echo Hello")
            if cmd_status != 0:
                status = "Down"

        return status

    def _intitialize_power(self, power_mapping): # pylint: disable=no-self-use
        """
            Initializes the serial port connectivity for this device.
        """
        self._power = self.landscape.lookup_power_agent(power_mapping)
        return

    def _intitialize_serial(self, serial_mapping): # pylint: disable=no-self-use
        """
            Initializes the serial port connectivity for this device.
        """
        self._serial = self.landscape.lookup_serial_agent(serial_mapping)
        return

    def _resolve_ipaddress(self):
        ipaddr = None
        if self._device_type == "network/upnp" and self.upnp is not None:
            ipaddr = self.upnp.IPAddress
        elif self._device_type == "network/ssh" and self.ssh is not None:
            ipaddr = self.ssh.ipaddr
        return ipaddr

    def _repr_html_(self):
        html_repr_lines = [
            "<h1>LandscapeDevice</h1>",
            "<h2>     type: {}</h2>".format(self._device_type),
            "<h2>    keyid: {}</h2>".format(self._keyid),
            "<h2>       ip: {}</h2>".format(self.ipaddr)
        ]

        html_repr = os.linesep.join(html_repr_lines)

        return html_repr

    def __repr__(self):

        thisType = type(self)

        ipaddr = self.ipaddr
        devstr = "<{} type={} keyid={} ip={} >".format(thisType.__name__, self._device_type, self._keyid, ipaddr)

        return devstr

    def __str__(self):
        devstr = repr(self)
        return devstr
Exemplo n.º 10
0
class IntegrationCoupling(BaseCoupling):
    """
        The :class:`IntegrationCoupling` object serves as the base object for the declaration of an
        automation integration requirement.  The :class:`akit.testing.unittest.testsequencer.Sequencer`
        queries the class hierarchies of the tests that are included in an automation run.
    """

    landscape = None

    logger = getAutomatonKitLogger()
    pathbase = None

    CONSTRAINTS = None

    def __init__(self, *args, role=None, **kwargs): # pylint: disable=unused-argument
        """
            The default contructor for an :class:`IntegrationCoupling`.
        """
        super(IntegrationCoupling, self).__init__()

        self._role = role
        self._connectivity_establish = False
        self._presence_establish = False

        if self.pathbase is None:
            err_msg = "The 'pathbase' class member variable must be set to a unique name for each integration class type."
            raise ValueError(err_msg)

        self.context.insert(self.pathbase, self)
        return

    @property
    def connectivity_establish(self):
        return self._connectivity_establish

    @property
    def mode(self):
        """
            Returns the current mode any associated resource is operating in.
        """
        return self._mode

    @mode.setter
    def mode(self, value):
        """
            Sets the current mode any associated resource is operating in.
        """
        old_value = self._mode
        self._mode = value
        self.on_mode_changed(old_value, value)
        return

    @property
    def presence_establish(self):
        return self._presence_establish

    @property
    def role(self):
        """
            Returns the current automation role assigned to the integration coupling.
        """
        return self._role

    def on_mode_changed(self, prev_mode, new_mode): # pylint: disable=no-self-use,unused-argument
        """
            Implemented by derived classes in order to perform the changeover of modes.
        """
        return

    @classmethod
    def declare_precedence(cls) -> int:
        """
            This API is called so that the IntegrationCoupling can declare an ordinal precedence that should be
            utilized for bringing up its integration state.
        """
        cls.logger = getAutomatonKitLogger()
        return

    @classmethod
    def attach_to_environment(cls, constraints: Dict={}):
        """
            This API is called so that the IntegrationCoupling can process configuration information.  The :class:`IntegrationCoupling`
            will verify that it has a valid environment and configuration to run in.

            :raises :class:`akit.exceptions.AKitMissingConfigError`, :class:`akit.exceptions.AKitInvalidConfigError`:
        """
        raise AKitNotOverloadedError("The 'attach_to_environment' method must be overloaded by derived integration coupling types.") from None
    
    @classmethod
    def attach_to_framework(cls, landscape: "Landscape"):
        """
            This API is called so that the IntegrationCoupling can attach to the test framework and participate with
            registration processes.  This allows the framework to ignore the bring-up of couplings that are not being
            included by a test.
        """
        cls.landscape = landscape
        return

    @classmethod
    def collect_resources(cls):
        """
            This API is called so the `IntegrationCoupling` can connect with a resource management
            system and gain access to the resources required for the automation run.

            :raises :class:`akit.exceptions.AKitResourceError`:
        """
        raise AKitNotOverloadedError("The 'collect_resources' method must be overloaded by derived integration coupling types.") from None

    @classmethod
    def diagnostic(cls, label: str, level: int, diag_folder: str): # pylint: disable=unused-argument
        """
            The API is called by the :class:`akit.sequencer.Sequencer` object when the automation sequencer is
            building out a diagnostic package at a diagnostic point in the automation sequence.  Example diagnostic
            points are:

            * pre-run
            * post-run

            Each diagnostic package has its own storage location so derived :class:`akit.scope.ScopeCoupling` objects
            can simply write to their specified output folder.

            :param label: The label associated with this diagnostic.
            :param level: The maximum diagnostic level to run dianostics for.
            :param diag_folder: The output folder path where the diagnostic information should be written.
        """
        return

    @classmethod
    def establish_connectivity(cls, allow_missing_devices: bool=False) -> Tuple[List[str], Dict]:
        """
            This API is called so the `IntegrationCoupling` can establish connectivity with any compute or storage
            resources.

            :returns: A tuple with a list of error messages for failed connections and dict of connectivity
                      reports for devices devices based on the coordinator.
        """
        raise AKitNotOverloadedError("The 'diagnostic' method must be overloaded by derived integration coupling types.") from None

    @classmethod
    def establish_presence(cls) -> Tuple[List[str], Dict]:
        """
            This API is called so the `IntegrationCoupling` can establish presence with any compute or storage
            resources.

            :returns: A tuple with a list of error messages for failed connections and dict of connectivity
                      reports for devices devices based on the coordinator.
        """
        raise AKitNotOverloadedError("The 'diagnostic' method must be overloaded by derived integration coupling types.") from None
Exemplo n.º 11
0
def command_akit_landscape_verify(credentials_file, landscape_file,
                                  runtime_file):

    # pylint: disable=unused-import,import-outside-toplevel

    # We do the imports of the automation framework code inside the action functions because
    # we don't want to startup loggin and the processing of inputs and environment variables
    # until we have entered an action method.  Thats way we know how to setup the environment.

    # IMPORTANT: We need to load the context first because it will trigger the loading
    # of the default user configuration
    from akit.environment.context import Context
    from akit.environment.contextpaths import ContextPaths

    from akit.environment.variables import JOB_TYPES, AKIT_VARIABLES

    from akit.environment.optionoverrides import (override_config_credentials,
                                                  override_config_landscape,
                                                  override_config_runtime)

    ctx = Context()
    env = ctx.lookup("/environment")

    # We need to set the job type before we trigger activation.
    env["jobtype"] = JOB_TYPES.COMMAND

    # Activate the AutomationKit environment with characteristics of a console application
    import akit.activation.console

    from akit.xlogging.foundations import logging_initialize, getAutomatonKitLogger

    # Initialize logging
    logging_initialize()
    logger = getAutomatonKitLogger()

    if credentials_file is not None:
        override_config_credentials(credentials_file)

    if landscape_file is not None:
        override_config_landscape(landscape_file)

    if runtime_file is not None:
        override_config_runtime(runtime_file)

    from akit.interop.landscaping.landscape import Landscape, startup_landscape
    from akit.interop.landscaping.landscapedevice import LandscapeDevice

    lscape: Landscape = startup_landscape(include_ssh=True, include_upnp=True)

    upnp_device_configs = lscape.get_upnp_device_configs()
    if len(upnp_device_configs):
        print("======================= UPNP DEVICES =======================")

        for dev in upnp_device_configs:
            skip_dev = True if "skip" in dev and dev["skip"] else False

            upnp_info = dev["upnp"]
            usn = upnp_info["USN"]
            modelName = upnp_info["modelName"]
            modelNumber = upnp_info["modelNumber"]

            status = "Down"
            lscape_dev: LandscapeDevice = lscape.lookup_device_by_keyid(usn)
            if lscape_dev is not None:
                status = lscape_dev.verify_status()

            dev_info_lines = [
                "    Model: {} - {}".format(modelName, modelNumber),
                "      USN: {}".format(usn), "     Skip: {}".format(skip_dev),
                "   Status: {}".format(status)
            ]
            dev_info = os.linesep.join(dev_info_lines)

            print(dev_info)
            print("")

    ssh_devices_configs = lscape.get_ssh_device_configs(exclude_upnp=True)
    if len(ssh_devices_configs):
        print("======================= SSH DEVICES =======================")

        for dev in ssh_devices_configs:
            skip_dev = True if "skip" in dev and dev["skip"] else False

            host = dev["host"]

            status = "Down"
            lscape_dev: LandscapeDevice = lscape.lookup_device_by_keyid(host)
            if lscape_dev is not None:
                status = lscape_dev.verify_status()

            dev_info_lines = [
                "    HOST: {}".format(host), "   Status: {}".format(status)
            ]
            dev_info = os.linesep.join(dev_info_lines)

            print(dev_info)
            print("")

    return
Exemplo n.º 12
0
def command_akit_testing_run(root, includes, excludes, output, start, runid,
                             branch, build, flavor, credentials_file,
                             landscape_file, landscape_name, runtime_file,
                             runtime_name, topology_file, topology_name,
                             console_level, logfile_level, debugger,
                             breakpoints, time_travel, timeportals,
                             prerun_diagnostic, postrun_diagnostic):

    # pylint: disable=unused-import,import-outside-toplevel

    # We do the imports of the automation framework code inside the action functions because
    # we don't want to startup loggin and the processing of inputs and environment variables
    # until we have entered an action method.  Thats way we know how to setup the environment.

    # IMPORTANT: We need to load the context first because it will trigger the loading
    # of the default user configuration
    from akit.environment.context import Context
    from akit.environment.contextpaths import ContextPaths

    from akit.environment.variables import extend_path, JOB_TYPES, AKIT_VARIABLES

    from akit.environment.optionoverrides import (
        override_build_branch, override_build_flavor, override_build_name,
        override_config_credentials, override_config_landscape,
        override_config_landscape_name, override_config_runtime,
        override_config_runtime_name, override_config_topology,
        override_config_topology_name, override_loglevel_console,
        override_loglevel_file, override_output_directory, override_runid,
        override_starttime, override_testroot, override_debug_breakpoints,
        override_debug_debugger, override_timetravel, override_timeportals)

    ctx = Context()
    env = ctx.lookup("/environment")

    # We need to set the job type before we trigger activation.
    env["jobtype"] = JOB_TYPES.TESTRUN

    # We perform activation a little later in the testrunner.py file so we can
    # handle exceptions in the context of testrunner_main function
    import akit.activation.testrun
    from akit.xlogging.foundations import logging_initialize, getAutomatonKitLogger

    if branch is not None:
        override_build_branch(branch)

    if build is not None:
        override_build_name(build)

    if flavor is not None:
        override_build_flavor(flavor)

    if credentials_file is not None:
        override_config_credentials(credentials_file)

    if landscape_file is not None and landscape_name is not None:
        errmsg = "The '--landscape-file' and '--landscape-name' options should not be used together."
        raise click.BadOptionUsage("landscape-name", errmsg)

    if landscape_file is not None:
        override_config_landscape(landscape_file)

    if landscape_name is not None:
        override_config_landscape_name(landscape_name)

    if landscape_file is not None or landscape_name is not None:
        landscape_filename = AKIT_VARIABLES.AKIT_CONFIG_LANDSCAPE
        option_name = "landscape" if landscape_file is not None else "landscape-name"
        if not os.path.exists(landscape_filename):
            errmsg = "The specified landscape file does not exist. filename={}".format(
                landscape_filename)
            raise click.BadOptionUsage(option_name, errmsg)

    if runtime_file is not None and runtime_name is not None:
        errmsg = "The '--runtime-file' and '--runtime-name' options should not be used together."
        raise click.BadOptionUsage("runtime-name", errmsg)

    if runtime_file is not None:
        override_config_runtime(runtime_file)

    if runtime_name is not None:
        override_config_runtime_name(runtime_name)

    if runtime_file is not None or runtime_name is not None:
        runtime_filename = AKIT_VARIABLES.AKIT_CONFIG_RUNTIME
        option_name = "runtime" if runtime_file is not None else "runtime-name"
        if not os.path.exists(runtime_filename):
            errmsg = "The specified runtime file does not exist. filename={}".format(
                runtime_filename)
            raise click.BadOptionUsage(option_name, errmsg)

    if topology_file is not None and topology_name is not None:
        errmsg = "The '--topology-file' and '--topology-name' options should not be used together."
        raise click.BadOptionUsage("option_name", errmsg)

    if topology_file is not None:
        override_config_topology(topology_file)

    if topology_name is not None:
        override_config_topology_name(topology_name)

    if topology_file is not None or topology_name is not None:
        topology_filename = AKIT_VARIABLES.AKIT_CONFIG_TOPOLOGY
        option_name = "topology" if topology_file is not None else "topology-name"
        if not os.path.exists(topology_filename):
            errmsg = "The specified topology file does not exist. filename={}".format(
                topology_filename)
            raise click.BadOptionUsage(option_name, errmsg)

    if console_level is not None:
        override_loglevel_console(console_level)

    if logfile_level is not None:
        override_loglevel_file(logfile_level)

    if output is not None:
        override_output_directory(output)

    if start is not None:
        override_starttime(start)

    if runid is not None:
        override_runid(runid)

    # Process the commandline args here and then set the variables on the environment
    # as necessary.  We need to do this before we import activate.
    if breakpoints is not None:
        override_debug_breakpoints(breakpoints)

        # If a breakpoint was passed bug the debugger was not, use 'debugpy' for the
        # default debugger.
        if debugger is None:
            override_debug_debugger('debugpy')

    if debugger is not None:
        override_debug_debugger('debugpy')

    if time_travel is not None:
        override_timetravel(time_travel)

    if timeportals is not None:
        override_timeportals(timeportals)

    if prerun_diagnostic:
        ctx.insert("/environment/configuration/diagnostics/prerun-diagnostic",
                   {})

    if postrun_diagnostic:
        ctx.insert("/environment/configuration/diagnostics/postrun-diagnostic",
                   {})

    if root is None:
        if AKIT_VARIABLES.AKIT_TESTROOT is not None:
            root = AKIT_VARIABLES.AKIT_TESTROOT
        elif ctx.lookup(ContextPaths.TESTROOT) is not None:
            root = ctx.lookup(ContextPaths.TESTROOT)
        else:
            root = "."

    test_root = os.path.abspath(os.path.expandvars(os.path.expanduser(root)))
    if not os.path.isdir(test_root):
        errmsg = "The specified root folder does not exist. root=%s" % root
        if test_root != root:
            errmsg += " expanded=%s" % test_root
        raise click.BadParameter(errmsg)

    override_testroot(root)

    # Make sure we extend PATH to include the test root
    extend_path(test_root)

    # Initialize logging
    logging_initialize()
    logger = getAutomatonKitLogger()

    from akit.extensionpoints import AKitExtensionPoints
    akep = AKitExtensionPoints()

    # At this point in the code, we either lookup an existing test job or we create a test job
    # from the includes, excludes or test_module
    TestJobType = akep.get_testplus_default_job_type()
    result_code = 0
    with TestJobType(logger,
                     test_root,
                     includes=includes,
                     excludes=excludes,
                     branch=branch,
                     build=build,
                     flavor=flavor) as tjob:
        result_code = tjob.execute()

    sys.exit(result_code)

    return
Exemplo n.º 13
0
def command_akit_testing_query(root, includes, excludes, debug):
    # pylint: disable=unused-import,import-outside-toplevel

    # We do the imports of the automation framework code inside the action functions because
    # we don't want to startup loggin and the processing of inputs and environment variables
    # until we have entered an action method.  Thats way we know how to setup the environment.

    # IMPORTANT: We need to load the context first because it will trigger the loading
    # of the default user configuration
    from akit.environment.context import Context
    from akit.environment.variables import extend_path, AKIT_VARIABLES

    ctx = Context()
    env = ctx.lookup("/environment")

    # Set the jobtype
    env["jobtype"] = "testrun"

    test_root = None
    if root is not None:
        AKIT_VARIABLES.AKIT_TESTROOT = root
    elif AKIT_VARIABLES.AKIT_TESTROOT is not None:
        root = AKIT_VARIABLES.AKIT_TESTROOT
    else:
        root = "."

    test_root = os.path.abspath(os.path.expandvars(os.path.expanduser(root)))
    if not os.path.isdir(test_root):
        errmsg = "The specified root folder does not exist. root=%s" % root
        if test_root != root:
            errmsg += " expanded=%s" % test_root
        raise click.BadParameter(errmsg)
    env["testroot"] = test_root

    # Make sure we extend PATH to include the test root
    extend_path(test_root)

    # We use console activation because all our input output is going through the terminal
    import akit.activation.console
    from akit.xlogging.foundations import logging_initialize, getAutomatonKitLogger

    # Initialize logging
    logging_initialize()
    logger = getAutomatonKitLogger()

    from akit.extensionpoints import AKitExtensionPoints
    akep = AKitExtensionPoints()

    # At this point in the code, we either lookup an existing test job or we create a test job
    # from the includes, excludes or test_module
    TestJobType = akep.get_testplus_default_job_type()
    result_code = 0
    with TestJobType(logger, test_root, includes=includes,
                     excludes=excludes) as tjob:
        query_results = tjob.query()

        test_names = [tn for tn in query_results.keys()]
        test_names.sort()
        print()
        print("Tests:")
        for tname in test_names:
            tref = query_results[tname]
            print("    " + tname)
            param_names = [pn for pn in tref.subscriptions.keys()]
            param_names.sort()
            for pname in param_names:
                pinfo = tref.subscriptions[pname]
                print("        {}: {}".format(pname, pinfo.describe_source()))

        print()

        if len(tjob.import_errors) > 0:
            print("IMPORT ERRORS:", file=sys.stderr)
            for ifilename in tjob.import_errors:
                imperr_msg = ifilename
                print("    " + imperr_msg, file=sys.stderr)
            print("", file=sys.stderr)

    return
Exemplo n.º 14
0
class Landscape(LandscapeConfigurationLayer, LandscapeIntegrationLayer,
                LandscapeOperationalLayer):
    """
        The base class for all derived :class:`Landscape` objects.  The :class:`Landscape`
        object is a singleton object that provides access to the resources and test
        environment level methods.  The functionality of the :class:`Landscape` object is setup
        so it can be transitioned through activation stages:
        
        * Configuration
        * Integration
        * Operational

        The different phases of operation of the landscape allow it to be used for a wider variety
        of purposes from commandline configuration and maintenance operations, peristent services
        and automation run functionality.

        The activation stages or levels of the :class:`Landscape` object are implemented using
        a python MixIn pattern in order to ensure that individual layers can be customized
        using object inheritance while at the same time keeping the object hierarchy simple.

        ..note: The :class:`Landscape` object constructor utilizes the `super` keyword for calling
        the mixin layer constructors using method resolution order or `mro`.  In order for `super`
        to work correctly all objects in the hierarchy should also provide a constructor and should
        also utilize `super`.  This is true also for objects that only inherit from :class:`object`.
        Should you need to create a custom layer override object, you must ensure the proper use
        of `super` in its constructor.
    """

    context = Context()

    logger = getAutomatonKitLogger()
    landscape_lock = threading.RLock()

    _landscape_type = None
    _instance = None
    _instance_initialized = False

    def __new__(cls):
        """
            Constructs new instances of the Landscape object from the :class:`Landscape`
            type or from a derived type that is found in the module specified in the
            :module:`akit.environment.variables` module or by setting the
            'AKIT_CONFIG_LANDSCAPE_MODULE' environment variable.
        """
        if cls._instance is None:
            if cls._landscape_type is None:
                cls._instance = super(Landscape, cls).__new__(cls)
            else:
                cls._instance = super(Landscape, cls._landscape_type).__new__(
                    cls._landscape_type)
            # Put any initialization here.
        return cls._instance

    def __init__(self):
        """
            Creates an instance or reference to the :class:`Landscape` singleton object.  On the first call to this
            constructor the :class:`Landscape` object is initialized and the landscape configuration is loaded.
        """
        lscapeType = type(self)

        # We are a singleton so we only want the intialization code to run once
        lscapeType.landscape_lock.acquire()
        if not lscapeType._instance_initialized:
            lscapeType._instance_initialized = True
            lscapeType.landscape_lock.release()

            self._interactive_mode = False

            super().__init__()
        else:
            lscapeType.landscape_lock.release()

        return

    @property
    def interactive_mode(self):
        """
            Returns a boolean indicating if interactive mode is on or off.
        """
        return self._interactive_mode

    @interactive_mode.setter
    def interactive_mode(self, interactive: bool) -> None:
        """
            Turn on or off interactive mode.
        """
        self._interactive_mode = interactive
        return

    def checkin_device(self, device: LandscapeDevice):
        """
            Returns a landscape device to the the available device pool.
        """
        self._ensure_activation()

        keyid = device.keyid

        if keyid not in self._checked_out_devices:
            errmsg = "Attempting to checkin a device that is not checked out. {}".format(
                device)
            raise AKitSemanticError(errmsg)

        self.landscape_lock.acquire()
        try:
            self._device_pool[keyid] = device
            del self._checked_out_devices[keyid]
        finally:
            self.landscape_lock.release()

        return

    def checkin_multiple_devices(self, devices: List[LandscapeDevice]):
        """
            Returns a landscape device to the the available device pool.
        """
        self._ensure_activation()

        checkin_errors = []

        self.landscape_lock.acquire()
        try:

            for dev in devices:
                keyid = dev.keyid

                if keyid not in self._checked_out_devices:
                    self._device_pool[keyid] = dev
                    checkin_errors.append(dev)

            if len(checkin_errors) > 0:
                err_msg_lines = [
                    "Attempting to checkin a device that is not checked out.",
                    "DEVICES:"
                ]
                for dev in checkin_errors:
                    err_msg_lines.append("    {}".format(dev))

                err_msg = os.linesep.join(err_msg_lines)
                raise AKitSemanticError(err_msg)

            for dev in devices:
                keyid = dev.keyid

                if keyid in self._checked_out_devices:
                    self._device_pool[keyid] = dev
                    del self._checked_out_devices[keyid]

        finally:
            self.landscape_lock.release()

        return

    def checkout_a_device_by_modelName(
            self, modelName: str) -> Optional[LandscapeDevice]:
        """
            Checks out a single device from the available pool using the modelName match
            criteria provided.
        """
        self._ensure_activation()

        device = None

        device_list = self.checkout_devices_by_match("modelName",
                                                     modelName,
                                                     count=1)
        if len(device_list) > 0:
            device = device_list[0]

        return device

    def checkout_a_device_by_modelNumber(
            self, modelNumber: str) -> Optional[LandscapeDevice]:
        """
            Checks out a single device from the available pool using the modelNumber match
            criteria provided.
        """
        self._ensure_activation()

        device = None

        device_list = self.checkout_devices_by_match("modelNumber",
                                                     modelNumber,
                                                     count=1)
        if len(device_list) > 0:
            device = device_list[0]

        return device

    def checkout_device(self, device: LandscapeDevice):
        """
            Checks out the specified device from the device pool.
        """
        self._ensure_activation()

        self.landscape_lock.acquire()
        try:
            self._locked_checkout_device(device)
        finally:
            self.landscape_lock.release()

        return

    def checkout_device_list(self, device_list: List[LandscapeDevice]):
        """
            Checks out the list of specified devices from the device pool.
        """
        self._ensure_activation()

        self.landscape_lock.acquire()
        try:
            for device in device_list:
                self._locked_checkout_device(device)
        finally:
            self.landscape_lock.release()

        return

    def checkout_devices_by_match(self,
                                  match_type: str,
                                  *match_params,
                                  count=None) -> List[LandscapeDevice]:
        """
            Checks out the devices that are found to correspond with the match criteria provided.  If the
            'count' parameter is passed, then the number of devices that are checked out is limited to
            count matching devices.
        """
        self._ensure_activation()

        match_list = None

        self.landscape_lock.acquire()
        try:
            match_list = self.list_available_devices_by_match(match_type,
                                                              *match_params,
                                                              count=count)

            for device in match_list:
                self._locked_checkout_device(device)
        finally:
            self.landscape_lock.release()

        return match_list

    def checkout_devices_by_modelName(self,
                                      modelName: str,
                                      count=None) -> List[LandscapeDevice]:
        """
            Checks out the devices that are found to correspond with the modelName match criteria provided.
            If the 'count' parameter is passed, the the number of devices that are checked out is limited to
            count matching devices.
        """
        self._ensure_activation()

        device_list = self.checkout_devices_by_match("modelName",
                                                     modelName,
                                                     count=count)

        return device_list

    def checkout_devices_by_modelNumber(self,
                                        modelNumber: str,
                                        count=None) -> List[LandscapeDevice]:
        """
            Checks out the devices that are found to correspond with the modelNumber match criteria provided.
            If the 'count' parameter is passed, the the number of devices that are checked out is limited to
            count matching devices.
        """
        self._ensure_activation()

        device_list = self.checkout_devices_by_match("modelNumber",
                                                     modelNumber,
                                                     count=count)

        return device_list

    def diagnostic(self, diaglabel: str, diags: dict):
        """
            Can be called in order to perform a diagnostic capture across the test landscape.

            :param diaglabel: The label to use for the diagnostic.
            :param diags: A dictionary of diagnostics to run.
        """
        self._ensure_activation()

        return

    def first_contact(self) -> List[str]:
        """
            The `first_contact` method provides a mechanism for the verification of connectivity with
            enterprise resources that is seperate from the initial call to `establish_connectivity`.

            :returns list: list of failing entities
        """
        error_list = []
        return error_list

    def list_available_devices_by_match(self,
                                        match_type,
                                        *match_params,
                                        count=None) -> List[LandscapeDevice]:
        """
            Creates and returns a list of devices from the available devices pool that are found
            to correspond to the match criteria provided.  If a 'count' parameter is passed
            then the number of devices returned is limited to count devices.

            .. note:: This API does not perform a checkout of the devices returns so the
                      caller should not consider themselves to the the owner of the devices.
        """
        matching_devices = []
        device_list = self.list_available_devices()

        for dev in device_list:
            if dev.match_using_params(match_type, *match_params):
                matching_devices.append(dev)
                if count is not None and len(matching_devices) >= count:
                    break

        return matching_devices

    def list_devices_by_match(self,
                              match_type,
                              *match_params,
                              count=None) -> List[LandscapeDevice]:
        """
            Creates and returns a list of devices that are found to correspond to the match
            criteria provided.  If a 'count' parameter is passed then the number of devices
            returned is limited to count devices.
        """
        matching_devices = []
        device_list = self.get_devices()

        for dev in device_list:
            if dev.match_using_params(match_type, *match_params):
                matching_devices.append(dev)
                if count is not None and len(matching_devices) >= count:
                    break

        return matching_devices

    def list_devices_by_modelName(self,
                                  modelName,
                                  count=None) -> List[LandscapeDevice]:
        """
            Creates and returns a list of devices that are found to correspond to the modelName
            match criteria provided.  If a 'count' parameter is passed then the number of devices
            returned is limited to count devices.
        """

        matching_devices = self.list_devices_by_match("modelName",
                                                      modelName,
                                                      count=count)

        return matching_devices

    def list_devices_by_modelNumber(self,
                                    modelNumber,
                                    count=None) -> List[LandscapeDevice]:
        """
            Creates and returns a list of devices that are found to correspond to the modelNumber
            match criteria provided.  If a 'count' parameter is passed then the number of devices
            returned is limited to count devices.
        """

        matching_devices = self.list_devices_by_match("modelNumber",
                                                      modelNumber,
                                                      count=count)

        return matching_devices

    def lookup_credential(self, credential_name) -> Union[str, None]:
        """
            Looks up a credential.
        """
        cred_info = None

        if credential_name in self._credentials:
            cred_info = self._credentials[credential_name]

        return cred_info

    def lookup_device_by_keyid(self, keyid) -> Optional[LandscapeDevice]:
        """
            Looks up a single device that is found to correspond to the keyid.
        """
        found_device = None

        device_list = self.get_devices()
        for device in device_list:
            if device.keyid == keyid:
                found_device = device
                break

        return found_device

    def lookup_device_by_modelName(self,
                                   modelName) -> Optional[LandscapeDevice]:
        """
            Looks up a single device that is found to correspond to the modelName match criteria
            provided.
        """
        found_device = None

        matching_devices = self.list_devices_by_match("modelName",
                                                      modelName,
                                                      count=1)
        if len(matching_devices) > 0:
            found_device = matching_devices[0]

        return found_device

    def lookup_device_by_modelNumber(self,
                                     modelNumber) -> Optional[LandscapeDevice]:
        """
            Looks up a single device that is found to correspond to the modelNumber match criteria
            provided.
        """
        found_device = None

        matching_devices = self.list_devices_by_match("modelNumber",
                                                      modelNumber,
                                                      count=1)
        if len(matching_devices) > 0:
            found_device = matching_devices[0]

        return found_device

    def lookup_power_agent(self, power_mapping: dict) -> Union[dict, None]:
        """
            Looks up a power agent by name.
        """
        power_agent = self._power_coord.lookup_agent(power_mapping)
        return power_agent

    def lookup_serial_agent(self, serial_mapping: str) -> Union[dict, None]:
        """
            Looks up a serial agent name.
        """
        serial_agent = self._serial_coordinator.lookup_agent(serial_mapping)
        return serial_agent
Exemplo n.º 15
0
    def load(self, landscape_file: str, log_to_directory: Optional[str]=None):
        """
            Loads and validates the landscape description file.
        """
        logger = getAutomatonKitLogger()

        landscape_info = None

        with open(landscape_file, 'r') as lf:
            lfcontent = lf.read()
            landscape_info = yaml.safe_load(lfcontent)

        if log_to_directory is not None:
            try:
                landscape_file_basename = os.path.basename(landscape_file)
                landscape_file_basename, landscape_file_ext = os.path.splitext(landscape_file_basename)

                landscape_file_copy = os.path.join(log_to_directory, "landscape-declared{}".format(landscape_file_ext))
                shutil.copy2(landscape_file, landscape_file_copy)

                # Create a json copy of the landscape file until the time when we can
                # parse yaml in the test summary javascript.
                landscape_info_copy = copy.deepcopy(landscape_info)

                landscape_file_copy = os.path.join(log_to_directory, "landscape-declared.json")
                with open(landscape_file_copy, 'w') as lsf:
                    json.dump(landscape_info_copy, lsf, indent=4)
            except Exception as xcpt:
                err_msg = "Error while logging the landscape file (%s)%s%s" % (
                    landscape_file, os.linesep, traceback.format_exc())
                raise AKitRuntimeError(err_msg) from xcpt

        errors, warnings = self.validate_landscape(landscape_info)

        if len(errors) > 0:
            errmsg_lines = [
                "ERROR Landscape validation failures:"
            ]
            for err in errors:
                errmsg_lines.append("    %s" % err)

            errmsg = os.linesep.join(errmsg_lines)
            raise AKitConfigurationError(errmsg) from None

        if len(warnings) > 0:
            for wrn in warnings:
                logger.warn("Landscape Configuration Warning: (%s)" % wrn)

        if "devices" in landscape_info["pod"]:
            devices = landscape_info["pod"]["devices"]

            device_lookup_table = {}
            for dev in devices:
                dev_type = dev["deviceType"]
                if dev_type == "network/upnp":
                    dkey = "UPNP:{}".format(dev["upnp"]["USN"]).upper()
                    device_lookup_table[dkey] = dev
                elif dev_type == "network/ssh":
                    dkey = "SSH:{}".format(dev["host"]).upper()
                    device_lookup_table[dkey] = dev

            ctx = Context()
            skipped_devices = ctx.lookup(ContextPaths.SKIPPED_DEVICES, default=[])

            for dev_key in skipped_devices:
                dev_key = dev_key.upper()
                if dev_key in device_lookup_table:
                    device = device_lookup_table[dev_key]
                    device["skip"] = True

        return landscape_info