Source code for ironic.common.trait_based_networking.config_file

#    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 ironic.common.i18n import _
import ironic.common.trait_based_networking.base as base

import yaml


[docs] class ConfigFile(object): """Provides functionality to read TBN configuration files Basic flow for use goes like: .. code-block:: python cf = ConfigFile("some_tbn_config.yaml") # File is read(). valid, reasons = cf.validate() if not valid: # Do something with reasons, like raise an exception and log. return reasons cf.parse() # If the file is valid, this *should* parse the config traits = cf.traits() # Get the parsed traits as a list. """ def __init__(self, filename): self._filename = filename self._traits = [] # TODO(clif): Do this here, or defer to clients of class calling these? self.read()
[docs] def read(self): """Read the YAML YBN configuration file""" with open(self._filename, 'r') as file: self._contents = yaml.safe_load(file)
[docs] def validate(self): """Check that contents conform to TBN expectations. :returns: (valid, reasons): valid is a boolean representing if the contents passed validation or not, and reasons is a list of strings describing why the contents failed validation if they are not valid. """ reasons = [] valid = True for trait_name, trait_members in self._contents.items(): if 'actions' not in trait_members: valid = False reasons.append( _(f"'{trait_name}' trait does not include an 'actions' " "key ")) continue if not isinstance(trait_members['actions'], list): reasons.append( _(f"'{trait_name}.actions' does not consist of a list of " "actions")) valid = False continue for trait_action in trait_members['actions']: # Check necessary keys are present. action_valid = True for n in base.TraitAction.NECESSARY_KEYS: if n not in trait_action.keys(): reasons.append( _(f"'{trait_name}' trait is missing '{n}' key")) action_valid = False valid = False # Check for errant keys. for sub_key in trait_action.keys(): if sub_key not in base.TraitAction.ALL_KEYS: reasons.append( _(f"'{trait_name}' trait action has unrecognized " f"key '{sub_key}'")) action_valid = False valid = False # Make sure action is valid if 'action' in trait_action.keys(): action = trait_action['action'] action_obj = None try: action_obj = base.Actions(action) except Exception: action_valid = False valid = False reasons.append( _(f"'{trait_name}' trait action has unrecognized " f"action '{action}'")) # Does the filter parse? if 'filter' in trait_action.keys(): try: base.FilterExpression.parse(trait_action['filter']) except Exception: action_valid = False valid = False # TODO(clif): Surface exception text in reason below? reasons.append( _(f"'{trait_name}' trait action has malformed " "filter expression: " f"'{trait_action['filter']}'")) if action_valid: min_count = trait_action.get('min_count', None) if min_count is not None: min_count = int(min_count) max_count = trait_action.get('max_count', None) if max_count is not None: max_count = int(max_count) action_obj = base.TraitAction( trait_name, base.Actions(trait_action['action']), base.FilterExpression.parse(trait_action['filter']), min_count=min_count, max_count=max_count) validated, reason = action_obj.validate() if not validated: valid = False reasons.append( _(f"'{trait_name}' has an invalid '{action}': " f"{reason}")) return valid, reasons
[docs] def parse(self): """Render contents of configuration file as TBN objects The result of this method can later be retrieved by calling traits(). """ self._traits = [] for trait_name, trait_members in self._contents.items(): parsed_actions = [] for action in trait_members['actions']: min_count = action.get('min_count', None) if min_count is not None: min_count = int(min_count) max_count = action.get('max_count', None) if max_count is not None: max_count = int(max_count) parsed_actions.append(base.TraitAction( trait_name, base.Actions(action['action']), base.FilterExpression.parse(action['filter']), min_count=min_count, max_count=max_count)) order = trait_members.get('order', 1) self._traits.append(base.NetworkTrait(trait_name, parsed_actions, order))
[docs] def traits(self): """Return the parsed traits from the configuration file.""" return self._traits