mirror of
https://github.com/ARM-software/workload-automation.git
synced 2025-01-19 20:34:30 +00:00
e95ba608ec
Previously `aliases` was conflated with global_aliases. This commit fixes this. `global_alias`'s are a name that can be used in top level configuration and set the values of one or more plugin parameters that use the same global_alias. `aliases` is a list of alternative names for a configuration point. Currently this is only used for instrumentation/instruments and workload_name/name but in the future it will likely be used when parameters have to be renamed to be more meaningful but still maintain backward compatibility.
198 lines
9.0 KiB
Python
198 lines
9.0 KiB
Python
# Copyright 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.
|
|
|
|
from copy import copy
|
|
from collections import defaultdict
|
|
|
|
from wlauto.core import pluginloader
|
|
from wlauto.exceptions import ConfigError
|
|
from wlauto.utils.types import obj_dict
|
|
from devlib.utils.misc import memoized
|
|
|
|
GENERIC_CONFIGS = ["device_config", "workload_parameters",
|
|
"boot_parameters", "runtime_parameters"]
|
|
|
|
|
|
class PluginCache(object):
|
|
"""
|
|
The plugin cache is used to store configuration that cannot be processed at
|
|
this stage, whether thats because it is unknown if its needed
|
|
(in the case of disabled plug-ins) or it is not know what it belongs to (in
|
|
the case of "device-config" ect.). It also maintains where configuration came
|
|
from, and the priority order of said sources.
|
|
"""
|
|
|
|
def __init__(self, loader=pluginloader):
|
|
self.loader = loader
|
|
self.sources = []
|
|
self.plugin_configs = defaultdict(lambda: defaultdict(dict))
|
|
self.global_alias_values = defaultdict(dict)
|
|
|
|
# Generate a mapping of what global aliases belong to
|
|
self._global_alias_map = defaultdict(dict)
|
|
self._list_of_global_aliases = set()
|
|
for plugin in self.loader.list_plugins():
|
|
for param in plugin.parameters:
|
|
if param.global_alias:
|
|
self._global_alias_map[plugin.name][param.global_alias] = param
|
|
self._list_of_global_aliases.add(param.global_alias)
|
|
|
|
def add_source(self, source):
|
|
if source in self.sources:
|
|
raise Exception("Source has already been added.")
|
|
self.sources.append(source)
|
|
|
|
def add_global_alias(self, alias, value, source):
|
|
if source not in self.sources:
|
|
msg = "Source '{}' has not been added to the plugin cache."
|
|
raise RuntimeError(msg.format(source))
|
|
|
|
if not self.is_global_alias(alias):
|
|
msg = "'{} is not a valid global alias'"
|
|
raise RuntimeError(msg.format(alias))
|
|
|
|
self.global_alias_values[alias][source] = value
|
|
|
|
def add_configs(self, plugin_name, values, source):
|
|
print plugin_name, values
|
|
if self.is_global_alias(plugin_name):
|
|
self.add_global_alias(plugin_name, values, source)
|
|
return
|
|
for name, value in values.iteritems():
|
|
self.add_config(plugin_name, name, value, source)
|
|
|
|
def add_config(self, plugin_name, name, value, source):
|
|
if source not in self.sources:
|
|
msg = "Source '{}' has not been added to the plugin cache."
|
|
raise RuntimeError(msg.format(source))
|
|
|
|
if not self.loader.has_plugin(plugin_name) and plugin_name not in GENERIC_CONFIGS:
|
|
msg = 'configuration provided for unknown plugin "{}"'
|
|
raise ConfigError(msg.format(plugin_name))
|
|
|
|
if (plugin_name not in GENERIC_CONFIGS and
|
|
name not in self.get_plugin_parameters(plugin_name)):
|
|
msg = "'{}' is not a valid parameter for '{}'"
|
|
raise ConfigError(msg.format(name, plugin_name))
|
|
|
|
self.plugin_configs[plugin_name][source][name] = value
|
|
|
|
def is_global_alias(self, name):
|
|
return name in self._list_of_global_aliases
|
|
|
|
def get_plugin_config(self, plugin_name, generic_name=None):
|
|
config = obj_dict(not_in_dict=['name'])
|
|
config.name = plugin_name
|
|
|
|
# Load plugin defaults
|
|
cfg_points = self.get_plugin_parameters(plugin_name)
|
|
for cfg_point in cfg_points.itervalues():
|
|
cfg_point.set_value(config, check_mandatory=False)
|
|
|
|
# Merge global aliases
|
|
for alias, param in self._global_alias_map[plugin_name].iteritems():
|
|
if alias in self.global_alias_values:
|
|
for source in self.sources:
|
|
if source not in self.global_alias_values[alias]:
|
|
continue
|
|
param.set_value(config, value=self.global_alias_values[alias][source])
|
|
|
|
# Merge user config
|
|
# Perform a simple merge with the order of sources representing priority
|
|
if generic_name is None:
|
|
plugin_config = self.plugin_configs[plugin_name]
|
|
for source in self.sources:
|
|
if source not in plugin_config:
|
|
continue
|
|
for name, value in plugin_config[source].iteritems():
|
|
cfg_points[name].set_value(config, value=value)
|
|
# A more complicated merge that involves priority of sources and specificity
|
|
else:
|
|
self._merge_using_priority_specificity(plugin_name, generic_name, config)
|
|
|
|
return config
|
|
|
|
@memoized
|
|
def get_plugin_parameters(self, name):
|
|
params = self.loader.get_plugin_class(name).parameters
|
|
return {param.name: param for param in params}
|
|
|
|
# pylint: disable=too-many-nested-blocks, too-many-branches
|
|
def _merge_using_priority_specificity(self, specific_name, generic_name, final_config):
|
|
"""
|
|
WA configuration can come from various sources of increasing priority, as well
|
|
as being specified in a generic and specific manner (e.g. ``device_config``
|
|
and ``nexus10`` respectivly). WA has two rules for the priority of configuration:
|
|
|
|
- Configuration from higher priority sources overrides configuration from
|
|
lower priority sources.
|
|
- More specific configuration overrides less specific configuration.
|
|
|
|
There is a situation where these two rules come into conflict. When a generic
|
|
configuration is given in config source of high priority and a specific
|
|
configuration is given in a config source of lower priority. In this situation
|
|
it is not possible to know the end users intention and WA will error.
|
|
|
|
:param generic_name: The name of the generic configuration e.g ``device_config``
|
|
:param specific_name: The name of the specific configuration used, e.g ``nexus10``
|
|
:param cfg_point: A dict of ``ConfigurationPoint``s to be used when merging configuration.
|
|
keys=config point name, values=config point
|
|
|
|
:rtype: A fully merged and validated configuration in the form of a obj_dict.
|
|
"""
|
|
generic_config = copy(self.plugin_configs[generic_name])
|
|
specific_config = copy(self.plugin_configs[specific_name])
|
|
cfg_points = self.get_plugin_parameters(specific_name)
|
|
sources = self.sources
|
|
seen_specific_config = defaultdict(list)
|
|
|
|
# set_value uses the 'name' attribute of the passed object in it error
|
|
# messages, to ensure these messages make sense the name will have to be
|
|
# changed several times during this function.
|
|
final_config.name = specific_name
|
|
|
|
# pylint: disable=too-many-nested-blocks
|
|
for source in sources:
|
|
try:
|
|
if source in generic_config:
|
|
final_config.name = generic_name
|
|
for name, cfg_point in cfg_points.iteritems():
|
|
if name in generic_config[source]:
|
|
if name in seen_specific_config:
|
|
msg = ('"{generic_name}" configuration "{config_name}" has already been '
|
|
'specified more specifically for {specific_name} in:\n\t\t{sources}')
|
|
msg = msg.format(generic_name=generic_name,
|
|
config_name=name,
|
|
specific_name=specific_name,
|
|
sources=", ".join(seen_specific_config[name]))
|
|
raise ConfigError(msg)
|
|
value = generic_config[source][name]
|
|
cfg_point.set_value(final_config, value, check_mandatory=False)
|
|
|
|
if source in specific_config:
|
|
final_config.name = specific_name
|
|
for name, cfg_point in cfg_points.iteritems():
|
|
if name in specific_config[source]:
|
|
seen_specific_config[name].append(str(source))
|
|
value = specific_config[source][name]
|
|
cfg_point.set_value(final_config, value, check_mandatory=False)
|
|
|
|
except ConfigError as e:
|
|
raise ConfigError('Error in "{}":\n\t{}'.format(source, str(e)))
|
|
|
|
# Validate final configuration
|
|
final_config.name = specific_name
|
|
for cfg_point in cfg_points.itervalues():
|
|
cfg_point.validate(final_config)
|