# Copyright 2014-2016 ARM Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os from copy import copy from collections import OrderedDict from wlauto.exceptions import ConfigError from wlauto.utils.misc import (get_article, merge_config_values) from wlauto.utils.types import (identifier, integer, boolean, list_of_strings, toggle_set) from wlauto.core.configuration.tree import SectionNode ########################## ### CONFIG POINT TYPES ### ########################## class RebootPolicy(object): """ Represents the reboot policy for the execution -- at what points the device should be rebooted. This, in turn, is controlled by the policy value that is passed in on construction and would typically be read from the user's settings. Valid policy values are: :never: The device will never be rebooted. :as_needed: Only reboot the device if it becomes unresponsive, or needs to be flashed, etc. :initial: The device will be rebooted when the execution first starts, just before executing the first workload spec. :each_spec: The device will be rebooted before running a new workload spec. :each_iteration: The device will be rebooted before each new iteration. """ valid_policies = ['never', 'as_needed', 'initial', 'each_spec', 'each_iteration'] def __init__(self, policy): policy = policy.strip().lower().replace(' ', '_') if policy not in self.valid_policies: message = 'Invalid reboot policy {}; must be one of {}'.format(policy, ', '.join(self.valid_policies)) raise ConfigError(message) self.policy = policy @property def can_reboot(self): return self.policy != 'never' @property def perform_initial_boot(self): return self.policy not in ['never', 'as_needed'] @property def reboot_on_each_spec(self): return self.policy in ['each_spec', 'each_iteration'] @property def reboot_on_each_iteration(self): return self.policy == 'each_iteration' def __str__(self): return self.policy __repr__ = __str__ def __cmp__(self, other): if isinstance(other, RebootPolicy): return cmp(self.policy, other.policy) else: return cmp(self.policy, other) def to_pod(self): return self.policy @staticmethod def from_pod(pod): return RebootPolicy(pod) class status_list(list): def append(self, item): list.append(self, str(item).upper()) class LoggingConfig(dict): defaults = { 'file_format': '%(asctime)s %(levelname)-8s %(name)s: %(message)s', 'verbose_format': '%(asctime)s %(levelname)-8s %(name)s: %(message)s', 'regular_format': '%(levelname)-8s %(message)s', 'color': True, } def __init__(self, config=None): dict.__init__(self) if isinstance(config, dict): config = {identifier(k.lower()): v for k, v in config.iteritems()} self['regular_format'] = config.pop('regular_format', self.defaults['regular_format']) self['verbose_format'] = config.pop('verbose_format', self.defaults['verbose_format']) self['file_format'] = config.pop('file_format', self.defaults['file_format']) self['color'] = config.pop('colour_enabled', self.defaults['color']) # legacy self['color'] = config.pop('color', self.defaults['color']) if config: message = 'Unexpected logging configuation parameters: {}' raise ValueError(message.format(bad_vals=', '.join(config.keys()))) elif config is None: for k, v in self.defaults.iteritems(): self[k] = v else: raise ValueError(config) class ConfigurationPoint(object): """ This defines a generic configuration point for workload automation. This is used to handle global settings, plugin parameters, etc. """ # Mapping for kind conversion; see docs for convert_types below kind_map = { int: integer, bool: boolean, dict: OrderedDict, } def __init__(self, name, kind=None, mandatory=None, default=None, override=False, allowed_values=None, description=None, constraint=None, merge=False, aliases=None, convert_types=True): """ Create a new Parameter object. :param name: The name of the parameter. This will become an instance member of the plugin object to which the parameter is applied, so it must be a valid python identifier. This is the only mandatory parameter. :param kind: The type of parameter this is. This must be a callable that takes an arbitrary object and converts it to the expected type, or raised ``ValueError`` if such conversion is not possible. Most Python standard types -- ``str``, ``int``, ``bool``, etc. -- can be used here. This defaults to ``str`` if not specified. :param mandatory: If set to ``True``, then a non-``None`` value for this parameter *must* be provided on plugin object construction, otherwise ``ConfigError`` will be raised. :param default: The default value for this parameter. If no value is specified on plugin construction, this value will be used instead. (Note: if this is specified and is not ``None``, then ``mandatory`` parameter will be ignored). :param override: A ``bool`` that specifies whether a parameter of the same name further up the hierarchy should be overridden. If this is ``False`` (the default), an exception will be raised by the ``AttributeCollection`` instead. :param allowed_values: This should be the complete list of allowed values for this parameter. Note: ``None`` value will always be allowed, even if it is not in this list. If you want to disallow ``None``, set ``mandatory`` to ``True``. :param constraint: If specified, this must be a callable that takes the parameter value as an argument and return a boolean indicating whether the constraint has been satisfied. Alternatively, can be a two-tuple with said callable as the first element and a string describing the constraint as the second. :param merge: The default behaviour when setting a value on an object that already has that attribute is to overrided with the new value. If this is set to ``True`` then the two values will be merged instead. The rules by which the values are merged will be determined by the types of the existing and new values -- see ``merge_config_values`` documentation for details. :param aliases: Alternative names for the same configuration point. These are largely for backwards compatibility. :param convert_types: If ``True`` (the default), will automatically convert ``kind`` values from native Python types to WA equivalents. This allows more ituitive interprestation of parameter values, e.g. the string ``"false"`` being interpreted as ``False`` when specifed as the value for a boolean Parameter. """ self.name = identifier(name) if kind is not None and not callable(kind): raise ValueError('Kind must be callable.') if convert_types and kind in self.kind_map: kind = self.kind_map[kind] self.kind = kind self.mandatory = mandatory self.default = default self.override = override self.allowed_values = allowed_values self.description = description if self.kind is None and not self.override: self.kind = str if constraint is not None and not callable(constraint) and not isinstance(constraint, tuple): raise ValueError('Constraint must be callable or a (callable, str) tuple.') self.constraint = constraint self.merge = merge self.aliases = aliases or [] def match(self, name): if name == self.name: return True elif name in self.aliases: return True return False def set_value(self, obj, value=None, check_mandatory=True): if value is None: if self.default is not None: value = self.default elif check_mandatory and self.mandatory: msg = 'No values specified for mandatory parameter "{}" in {}' raise ConfigError(msg.format(self.name, obj.name)) else: try: value = self.kind(value) except (ValueError, TypeError): typename = self.get_type_name() msg = 'Bad value "{}" for {}; must be {} {}' article = get_article(typename) raise ConfigError(msg.format(value, self.name, article, typename)) if value is not None: self.validate_value(obj.name, value) if self.merge and hasattr(obj, self.name): value = merge_config_values(getattr(obj, self.name), value) setattr(obj, self.name, value) def get_type_name(self): typename = str(self.kind) if '\'' in typename: typename = typename.split('\'')[1] elif typename.startswith('