[VVP] Support pluggable data sources for preload data
[vvp/validation-scripts.git] / ice_validator / preload / data.py
diff --git a/ice_validator/preload/data.py b/ice_validator/preload/data.py
new file mode 100644 (file)
index 0000000..721608f
--- /dev/null
@@ -0,0 +1,372 @@
+from abc import ABC, abstractmethod
+from pathlib import Path
+from typing import Iterable, Any, Optional, Mapping
+
+from preload.model import VnfModule
+
+
+class AbstractPreloadInstance(ABC):
+    """
+    Represents the data source for a single instance of a preload for
+    any format.  The implementation of AbstractPreloadGenerator will
+    call the methods of this class to retrieve the necessary data
+    to populate the preload.  If a data element is not available,
+    then simply return ``None`` and a suitable placeholder will be
+    placed in the preload.
+    """
+
+    @property
+    @abstractmethod
+    def output_dir(self) -> Path:
+        """
+        Base output directory where the preload will be generated.  Please
+        note, that the generator may create nested directories under this
+        directory for the preload.
+
+        :return: Path to the desired output directory.  This directory
+                 and its parents will be created by the generator if
+                 it is not already present.
+        """
+        raise NotImplementedError()
+
+    @property
+    @abstractmethod
+    def module_label(self) -> str:
+        """
+        Identifier of the module.  This must match the base name of the
+        heat module (ex: if the Heat file name is base.yaml, then the label
+        is 'base'.
+
+        :return: string name of the module
+        """
+        raise NotImplementedError()
+
+    @property
+    @abstractmethod
+    def vf_module_name(self) -> Optional[str]:
+        """
+        :return: module name to populate in the preload if available
+        """
+        raise NotImplementedError()
+
+    @property
+    @abstractmethod
+    def flag_incompletes(self) -> bool:
+        """
+        If True, then the generator will modify the file name of any
+        generated preload to end with _incomplete.<ext> if any preload
+        value was not satisfied by the data source.  If False, then
+        the file name will be the same regardless of the completeness
+        of the preload.
+
+        :return: True if file names should denote preload incompleteness
+        """
+        raise NotImplementedError()
+
+    @property
+    @abstractmethod
+    def preload_basename(self) -> str:
+        """
+        Base name of the preload that will be used by the generator to create
+        the file name.
+        """
+        raise NotImplementedError()
+
+    @property
+    @abstractmethod
+    def vnf_name(self) -> Optional[str]:
+        """
+        :return: the VNF name to populate in the prelad if available
+        """
+        raise NotImplementedError()
+
+    @property
+    @abstractmethod
+    def vnf_type(self) -> Optional[str]:
+        """
+        The VNF Type must be match the values in SDC.  It is a concatenation
+        of <Service Instance Name>/<Resource Instance Name>.
+
+        :return: VNF Type to populate in the preload if available
+        """
+        raise NotImplementedError()
+
+    @property
+    @abstractmethod
+    def vf_module_model_name(self) -> Optional[str]:
+        """
+        :return: Module model name if available
+        """
+        raise NotImplementedError()
+
+    @abstractmethod
+    def get_availability_zone(self, index: int, param_name: str) -> Optional[str]:
+        """
+        Retrieve the value for the availability zone at requested zero-based
+        index (i.e. 0, 1, 2, etc.)
+
+        :param index:       index of availability zone (0, 1, etc.)
+        :param param_name:  Name of the parameter from Heat
+        :return:            value for the AZ if available
+        """
+        raise NotImplementedError()
+
+    @abstractmethod
+    def get_network_name(self, network_role: str, name_param: str) -> Optional[str]:
+        """
+        Retrieve the OpenStack name of the network for the given network role.
+
+        :param network_role:    Network role from Heat template
+        :param name_param:      Network name parameter from Heat
+        :return:                Name of the network if available
+        """
+        raise NotImplementedError()
+
+    @abstractmethod
+    def get_subnet_id(
+        self, network_role: str, ip_version: int, param_name: str
+    ) -> Optional[str]:
+        """
+        Retrieve the subnet's UUID for the given network and IP version (4 or 6).
+
+        :param network_role:    Network role from Heat template
+        :param ip_version:      IP Version (4 or 6)
+        :param param_name:      Parameter name from Heat
+        :return:                UUID of the subnet if available
+        """
+        raise NotImplementedError()
+
+    @abstractmethod
+    def get_subnet_name(
+        self, network_role: str, ip_version: int, param_name: str
+    ) -> Optional[str]:
+        """
+        Retrieve the OpenStack Subnet name for the given network role and IP version
+
+        :param network_role:    Network role from Heat template
+        :param ip_version:      IP Version (4 or 6)
+        :param param_name:      Parameter name from Heat
+        :return:                Name of the subnet if available
+        """
+        raise NotImplementedError()
+
+    @abstractmethod
+    def get_vm_name(self, vm_type: str, index: int, param_name: str) -> Optional[str]:
+        """
+        Retrieve the vm name for the given VM type and index.
+
+        :param vm_type:         VM Type from Heat template
+        :param index:           Zero-based index of the VM for the vm-type
+        :param param_name:      Parameter name from Heat
+        :return:                VM Name if available
+        """
+        raise NotImplementedError()
+
+    @abstractmethod
+    def get_floating_ip(
+        self, vm_type: str, network_role: str, ip_version: int, param_name: str
+    ) -> Optional[str]:
+        """
+        Retreive the floating IP for the VM and Port identified by VM Type,
+        Network Role, and IP Version.
+
+        :param vm_type:         VM Type from Heat template
+        :param network_role:    Network Role from Heat template
+        :param ip_version:      IP Version (4 or 6)
+        :param param_name:      Parameter name from Heat
+        :return: floating IP address if available
+        """
+        raise NotImplementedError()
+
+    @abstractmethod
+    def get_fixed_ip(
+        self, vm_type: str, network_role: str, ip_version: int, index: int, param: str
+    ) -> Optional[str]:
+        """
+        Retreive the fixed IP for the VM and Port identified by VM Type,
+        Network Role, IP Version, and index.
+
+        :param vm_type:         VM Type from Heat template
+        :param network_role:    Network Role from Heat template
+        :param ip_version:      IP Version (4 or 6)
+        :param index:           zero-based index for the IP for the given
+                                VM Type, Network Role, IP Version combo
+        :param param_name:      Parameter name from Heat
+        :return: floating IP address if available
+        """
+        raise NotImplementedError()
+
+    @abstractmethod
+    def get_vnf_parameter(self, key: str, value: Any) -> Optional[Any]:
+        """
+        Retrieve the value for the given key.  These will be placed in the
+        tag-values/vnf parameters in the preload.  If a value was specified in
+        the environment packaged in the Heat for for the VNF module, then
+        that value will be  passed in ``value``.  This class can return
+        the value or ``None`` if it does not have a value for the given key.
+
+        :param key:     parameter name from Heat
+        :param value:   Value from Heat env file if it was assigned there;
+                        None otherwise
+        :return:        Returns the value for the object.  This should
+                        be a str, dict, or list.  The generator will
+                        format it properly based on the selected output format
+        """
+        raise NotImplementedError()
+
+    @abstractmethod
+    def get_additional_parameters(self) -> Mapping[str, Any]:
+        """
+        Return any additional parameters that should be added to the VNF parameters.
+
+        This can be useful if you want to duplicate paramters in tag values that are
+        also in the other sections (ex: VM names).
+
+        :return: dict of str to object mappings that the generator must add to
+                 the vnf_parameters/tag values
+        """
+        raise NotImplementedError()
+
+
+class AbstractPreloadDataSource(ABC):
+    """
+    Represents a data source for a VNF preload data.  Implementations of this
+    class can be dynamically discovered if they are in a preload plugin module.
+    A module is considered a preload plugin module if it starts with
+    prelaod_ and is available as a top level module on Python's sys.path.
+
+    The ``get_module_preloads`` will be invoked for each module in
+    the VNF.  An instance of AbstractPreloadInstance must be returned for
+    each instance of the preload module that is to be created.
+
+    Parameters:
+        :param path:    The path to the configuration source selected
+                        in either the VVP GUI or command-line.  This
+                        may be a file or directory depending upon
+                        the source_type defined by this data source
+    """
+
+    def __init__(self, path: Path):
+        self.path = path
+
+    @classmethod
+    @abstractmethod
+    def get_source_type(cls) -> str:
+        """
+        If 'FILE' returned, then the config source will be a specific
+        file; If 'DIR', then the config source will be a directory
+        :return:
+        """
+        raise NotImplementedError()
+
+    @classmethod
+    @abstractmethod
+    def get_identifier(cls) -> str:
+        """
+        Identifier for the given data source. This is the value that
+        can be passed via --preload-source-type.
+
+        :return: short identifier for this data source type
+        """
+
+    @classmethod
+    @abstractmethod
+    def get_name(self) -> str:
+        """
+        Human readable name to describe the preload data source. It is
+        recommended not to exceed 50 characters.
+
+        :return: human readable name of the preload data source (ex: Environment Files)
+        """
+        raise NotImplementedError()
+
+    @abstractmethod
+    def get_module_preloads(
+        self, module: VnfModule
+    ) -> Iterable[AbstractPreloadInstance]:
+        """
+        For the  requested module, return an instance of AbstractPreloadInstance
+        for every preload module you wish to be created.
+
+        :param module:  Module of the VNF
+        :return:        iterable of preloads to create for the given module
+        """
+        raise NotImplementedError()
+
+
+class BlankPreloadInstance(AbstractPreloadInstance):
+    """
+    Used to create blank preload templates.  VVP will always create
+    a template of a preload in the requested format with no data provided.
+    """
+
+    def __init__(self, output_dir: Path, module_name: str):
+        self._output_dir = output_dir
+        self._module_name = module_name
+
+    @property
+    def flag_incompletes(self) -> bool:
+        return False
+
+    @property
+    def preload_basename(self) -> str:
+        return self._module_name
+
+    @property
+    def vf_module_name(self) -> Optional[str]:
+        return None
+
+    def get_vm_name(self, vm_type: str, index: int, param_name: str) -> Optional[str]:
+        return None
+
+    def get_availability_zone(self, index: int, param_name: str) -> Optional[str]:
+        return None
+
+    @property
+    def output_dir(self) -> Path:
+        return self._output_dir
+
+    @property
+    def module_label(self) -> str:
+        return self._module_name
+
+    @property
+    def vnf_name(self) -> Optional[str]:
+        return None
+
+    @property
+    def vnf_type(self) -> Optional[str]:
+        return None
+
+    @property
+    def vf_module_model_name(self) -> Optional[str]:
+        return None
+
+    def get_network_name(self, network_role: str, name_param: str) -> Optional[str]:
+        return None
+
+    def get_subnet_id(
+        self, network_role: str, ip_version: int, param_name: str
+    ) -> Optional[str]:
+        return None
+
+    def get_subnet_name(
+        self, network_role: str, ip_version: int, param_name: str
+    ) -> Optional[str]:
+        return None
+
+    def get_floating_ip(
+        self, vm_type: str, network_role: str, ip_version: int, param_name: str
+    ) -> Optional[str]:
+        return None
+
+    def get_fixed_ip(
+        self, vm_type: str, network_role: str, ip_version: int, index: int, param: str
+    ) -> Optional[str]:
+        return None
+
+    def get_vnf_parameter(self, key: str, value: Any) -> Optional[Any]:
+        return None
+
+    def get_additional_parameters(self) -> Mapping[str, Any]:
+        return {}