RIFT OSM R1 Initial Submission
[osm/SO.git] / common / python / rift / mano / config_data / config.py
diff --git a/common/python/rift/mano/config_data/config.py b/common/python/rift/mano/config_data/config.py
new file mode 100644 (file)
index 0000000..63a2e48
--- /dev/null
@@ -0,0 +1,430 @@
+############################################################################
+# Copyright 2016 RIFT.io Inc                                               #
+#                                                                          #
+# 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 abc
+import json
+import os
+import yaml
+
+from gi.repository import NsdYang
+from gi.repository import VnfdYang
+
+
+class InitialConfigReadError(Exception):
+    pass
+
+
+class InitialConfigMethodError(Exception):
+    pass
+
+
+class InitialConfigPrimitiveReader(object):
+    """ Reader for the VNF Initial Config Input Data
+
+    This class interprets the Initial Config Primitive
+    Input data and constructs inital config primitive
+    protobuf messages.
+
+    The reason for creating a new format is to keep the structure
+    as dead-simple as possible for readability.
+
+    The structure (not serialization format) is defined as the
+    following.
+
+    [
+        {
+          "name": <primitive_name>,
+          "parameter": {
+            "hostname": "pe1"
+            "pass": "6windos"
+            ...
+          }
+        }
+        ...
+    ]
+
+    """
+    def __init__(self, primitive_input):
+        self._primitives = []
+
+        self._parse_input_data(primitive_input)
+
+    def _parse_input_data(self, input_dict):
+        for seq, cfg in enumerate(input_dict):
+            if "name" not in cfg:
+                raise InitialConfigReadError("Initial config primitive must have a name")
+
+            name = cfg["name"]
+
+            new_primitive = self. get_initial_config_primitive(seq=seq, name=name)
+            self._primitives.append(new_primitive)
+            if "parameter" in cfg:
+                for key, val in cfg["parameter"].items():
+                    new_primitive.parameter.add(name=key, value=val)
+
+    @abc.abstractmethod
+    def get_initial_config_primitive(self, seq, name):
+        '''Override in sub class to provide the correct yang model'''
+        raise InitialConfigMethodError(
+            "InitialConfigPrimitiveReader Calling abstract class method")
+
+    @property
+    def primitives(self):
+        """ Returns a copy of the read inital config primitives"""
+        return [prim.deep_copy() for prim in self._primitives]
+
+    @classmethod
+    def from_yaml_file_hdl(cls, file_hdl):
+        """ Create a instance of InitialConfigPrimitiveFileData
+        by reading a YAML file handle.
+
+        Arguments:
+            file_hdl - A file handle which contains serialized YAML which
+                       follows the documented structure.
+
+        Returns:
+            A new InitialConfigPrimitiveFileData() instance
+
+        Raises:
+            InitialConfigReadError: Input Data was malformed or could not be read
+        """
+        try:
+            input_dict = yaml.safe_load(file_hdl)
+        except yaml.YAMLError as e:
+            raise InitialConfigReadError(e)
+
+        return cls(input_dict)
+
+
+class VnfInitialConfigPrimitiveReader(InitialConfigPrimitiveReader):
+    '''Class to read the VNF initial config primitives'''
+
+    def __init__(self, primitive_input):
+        super(VnfInitialConfigPrimitiveReader, self).__init__(primitive_input)
+
+    def get_initial_config_primitive(self, seq, name):
+        return VnfdYang.InitialConfigPrimitive(seq=seq, name=name)
+
+
+class NsInitialConfigPrimitiveReader(InitialConfigPrimitiveReader):
+    '''Class to read the NS initial config primitives'''
+
+    def __init__(self, primitive_input):
+        super(NsInitialConfigPrimitiveReader, self).__init__(primitive_input)
+
+    def get_initial_config_primitive(self, seq, name):
+        return NsdYang.NsdInitialConfigPrimitive(seq=seq, name=name)
+
+
+class ConfigPrimitiveConvertor(object):
+    PARAMETER = "parameter"
+    PARAMETER_GROUP = "parameter_group"
+    CONFIG_PRIMITIVE = "service_primitive"
+    INITIAL_CONFIG_PRIMITIVE = "initial_config_primitive"
+
+    def _extract_param(self, param, field="default_value"):
+        key = param.name
+        value = getattr(param, field, None)
+
+        if value is not None:
+            setattr(param, field, None)
+
+        return key, value
+
+    def _extract_parameters(self, parameters, input_data, field="default_value"):
+        input_data[self.PARAMETER] = {}
+        for param in parameters:
+            key, value = self._extract_param(param, field)
+
+            if value is None:
+                continue
+
+            input_data[self.PARAMETER][key] = value
+
+        if not input_data[self.PARAMETER]:
+            del input_data[self.PARAMETER]
+
+    def _extract_parameter_group(self, param_groups, input_data):
+        input_data[self.PARAMETER_GROUP] = {}
+        for param_group in param_groups:
+            input_data[self.PARAMETER_GROUP][param_group.name] = {}
+            for param in param_group.parameter:
+                key, value = self._extract_param(param)
+
+                if value is None:
+                    continue
+
+                input_data[self.PARAMETER_GROUP][param_group.name][key] = value
+
+        if not input_data[self.PARAMETER_GROUP]:
+            del input_data[self.PARAMETER_GROUP]
+
+    def extract_config(self,
+                       config_primitives=None,
+                       initial_configs=None,
+                       format="yaml"):
+        input_data = {}
+
+        if config_primitives:
+            input_data[self.CONFIG_PRIMITIVE] = {}
+            for config_primitive in config_primitives:
+                input_data[self.CONFIG_PRIMITIVE][config_primitive.name] = {}
+                self._extract_parameters(
+                    config_primitive.parameter,
+                    input_data[self.CONFIG_PRIMITIVE][config_primitive.name])
+
+                try:
+                    self._extract_parameter_group(
+                        config_primitive.parameter_group,
+                        input_data[self.CONFIG_PRIMITIVE][config_primitive.name])
+                except AttributeError:
+                    pass
+
+                if not input_data[self.CONFIG_PRIMITIVE][config_primitive.name]:
+                    del input_data[self.CONFIG_PRIMITIVE][config_primitive.name]
+
+            if not input_data[self.CONFIG_PRIMITIVE]:
+                del input_data[self.CONFIG_PRIMITIVE]
+
+
+        if initial_configs:
+            input_data[self.INITIAL_CONFIG_PRIMITIVE] = []
+            for in_config_primitive in initial_configs:
+                primitive = {}
+                self._extract_parameters(
+                    in_config_primitive.parameter,
+                    primitive,
+                    field="value")
+
+                if primitive:
+                    input_data[self.INITIAL_CONFIG_PRIMITIVE].append(
+                        {
+                            "name": in_config_primitive.name,
+                            self.PARAMETER: primitive[self.PARAMETER],
+                        }
+                    )
+
+            if not input_data[self.INITIAL_CONFIG_PRIMITIVE]:
+                del input_data[self.INITIAL_CONFIG_PRIMITIVE]
+
+        if len(input_data):
+            if format == "json":
+                return json.dumps(input_data)
+            elif format == "yaml":
+                return yaml.dump(input_data, default_flow_style=False)
+        else:
+            return ''
+
+    def extract_nsd_config(self, nsd, format="yaml"):
+        config_prim = None
+        try:
+            config_prim = nsd.service_primitive
+        except AttributeError:
+            pass
+
+        initial_conf = None
+        try:
+            initial_conf = nsd.initial_config_primitive
+        except AttributeError:
+            pass
+
+        return self.extract_config(
+            config_primitives=config_prim,
+            initial_configs=initial_conf,
+            format=format)
+
+    def extract_vnfd_config(self, vnfd, format="yaml"):
+        config_prim = None
+        try:
+            config_prim = vnfd.vnf_configuration.service_primitive
+        except AttributeError:
+            pass
+
+        initial_conf = None
+        try:
+            initial_conf = vnfd.vnf_configuration.initial_config_primitive
+        except AttributeError:
+            pass
+
+        return self.extract_config(
+            config_primitives=config_prim,
+            initial_configs=initial_conf,
+            format=format)
+
+    def merge_params(self, parameters, input_config, field="default_value"):
+        for param in parameters:
+            try:
+                setattr(param, field, input_config[param.name])
+            except KeyError:
+                pass
+
+    def add_nsd_initial_config(self, nsd_init_cfg_prim_msg, input_data):
+        """ Add initial config primitives from NS Initial Config Input Data
+
+        Arguments:
+            nsd_init_cfg_prim_msg - manotypes:nsd/initial_config_primitive pb msg
+            ns_input_data - NsInitialConfigPrimitiveReader documented input data
+
+        Raises:
+           InitialConfigReadError: VNF input data was malformed
+        """
+        if self.INITIAL_CONFIG_PRIMITIVE in input_data:
+            ns_input_data = input_data[self.INITIAL_CONFIG_PRIMITIVE]
+
+            reader = NsInitialConfigPrimitiveReader(ns_input_data)
+            for prim in reader.primitives:
+                nsd_init_cfg_prim_msg.append(prim)
+
+    def merge_nsd_initial_config(self, nsd, input_data):
+        try:
+            for config_primitive in nsd.initial_config_primitive:
+                for cfg in input_data[self.INITIAL_CONFIG_PRIMITIVE]:
+                    if cfg['name'] == config_primitive.name:
+                        self.merge_params(
+                            config_primitive.parameter,
+                            cfg[self.PARAMETER],
+                            field="value")
+                        break
+
+        except AttributeError as e:
+            self._log.debug("Did not find initial-config-primitive for NSD {}: {}".
+                            format(nsd.name, e))
+
+
+    def merge_nsd_config(self, nsd, input_data):
+        for config_primitive in nsd.service_primitive:
+            try:
+                cfg = input_data[self.CONFIG_PRIMITIVE][config_primitive.name]
+            except KeyError:
+                continue
+
+            self.merge_params(
+                    config_primitive.parameter,
+                    cfg[self.PARAMETER])
+
+            for param_group in config_primitive.parameter_group:
+                self.merge_params(
+                        param_group.parameter,
+                        cfg[self.PARAMETER_GROUP][param_group.name])
+
+    def add_vnfd_initial_config(self, vnfd_init_cfg_prim_msg, input_data):
+        """ Add initial config primitives from VNF Initial Config Input Data
+
+        Arguments:
+            vnfd_init_cfg_prim_msg - manotypes:vnf-configuration/initial_config_primitive pb msg
+            vnf_input_data - VnfInitialConfigPrimitiveReader documented input data
+
+        Raises:
+           InitialConfigReadError: VNF input data was malformed
+        """
+        if self.INITIAL_CONFIG_PRIMITIVE in input_data:
+            vnf_input_data = input_data[self.INITIAL_CONFIG_PRIMITIVE]
+
+            reader = VnfInitialConfigPrimitiveReader(vnf_input_data)
+            for prim in reader.primitives:
+                vnfd_init_cfg_prim_msg.append(prim)
+
+    def merge_vnfd_config(self, vnfd, input_data):
+        for config_primitive in vnfd.vnf_configuration.service_primitive:
+            try:
+                cfg = input_data[self.CONFIG_PRIMITIVE][config_primitive.name]
+            except KeyError:
+                continue
+
+            self.merge_params(
+                config_primitive.parameter,
+                cfg[self.PARAMETER])
+
+
+class ConfigStore(object):
+    """Convenience class that fetches all the instance related data from the
+    $RIFT_ARTIFACTS/launchpad/libs directory.
+    """
+
+    def __init__(self, log):
+        """
+        Args:
+            log : Log handle.
+        """
+        self._log = log
+        self.converter = ConfigPrimitiveConvertor()
+
+    def merge_vnfd_config(self, nsd_id, vnfd, member_vnf_index):
+        """Merges the vnfd config from the config directory.
+
+        Args:
+            nsd_id (str): Id of the NSD object
+            vnfd_msg : VNFD pb message containing the VNFD id and
+                       the member index ref.
+        """
+        nsd_archive = os.path.join(
+            os.getenv('RIFT_ARTIFACTS'),
+            "launchpad/libs",
+            nsd_id,
+            "config")
+
+        self._log.info("Looking for config from the archive {}".format(nsd_archive))
+
+        if not os.path.exists(nsd_archive):
+            return
+
+        config_file = os.path.join(nsd_archive,
+                                   "{}__{}.yaml".format(vnfd.id, member_vnf_index))
+
+        if not os.path.exists(config_file):
+            self._log.info("Could not find VNF initial config in archive: %s", config_file)
+            return
+
+        input_data = self.read_from_file(config_file)
+        self._log.info("Loaded VNF config file {}: {}".format(config_file, input_data))
+
+        self.converter.merge_vnfd_config(vnfd, input_data)
+
+        self.converter.add_vnfd_initial_config(
+            vnfd.vnf_configuration.initial_config_primitive,
+            input_data,
+        )
+
+    def read_from_file(self, filename):
+        with open(filename) as fh:
+            input_data = yaml.load(fh)
+        return input_data
+
+    def merge_nsd_config(self, nsd):
+        nsd_archive = os.path.join(
+            os.getenv('RIFT_ARTIFACTS'),
+            "launchpad/libs",
+            nsd.id,
+            "config")
+
+        self._log.info("Looking for config from the archive {}".format(nsd_archive))
+
+        if not os.path.exists(nsd_archive):
+            return
+
+        config_file = os.path.join(nsd_archive,
+                                   "{}.yaml".format(nsd.id))
+        if not os.path.exists(config_file):
+            self._log.info("Could not find NS config in archive: %s", config_file)
+            return
+
+        input_data = self.read_from_file(config_file)
+        self._log.info("Loaded NS config file {}: {}".format(config_file, input_data))
+
+        self.converter.merge_nsd_config(nsd, input_data)
+
+        self.converter.merge_nsd_initial_config(nsd, input_data)