BUG 402 : Raising Exception if the charm directory is not found
[osm/SO.git] / rwcm / plugins / rwconman / rift / tasklets / rwconmantasklet / jujuconf.py
index dce31c3..b003df8 100644 (file)
@@ -1,4 +1,4 @@
-# 
+#
 #   Copyright 2016 RIFT.IO Inc
 #
 #   Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,9 +25,10 @@ import rift.mano.utils.juju_api as juju
 from . import riftcm_config_plugin
 
 
-# Charm service name accepts only a to z and -.
-def get_vnf_unique_name(nsr_name, vnfr_short_name, member_vnf_index):
-    name = "{}-{}-{}".format(nsr_name, vnfr_short_name, member_vnf_index)
+def get_vnf_unique_name(nsr_name, vnfr_name, member_vnf_index):
+    """Get the unique VNF name.
+    Charm names accepts only a to z and non-consecutive - characters."""
+    name = "{}-{}-{}".format(nsr_name, vnfr_name, member_vnf_index)
     new_name = ''
     for c in name:
         if c.isdigit():
@@ -35,15 +36,16 @@ def get_vnf_unique_name(nsr_name, vnfr_short_name, member_vnf_index):
         elif not c.isalpha():
             c = "-"
         new_name += c
-    return new_name.lower()
+    return re.sub('\-+', '-', new_name.lower())
 
 
 class JujuConfigPlugin(riftcm_config_plugin.RiftCMConfigPluginBase):
     """
         Juju implementation of the riftcm_config_plugin.RiftCMConfigPluginBase
     """
-    def __init__(self, dts, log, loop, account):
-        riftcm_config_plugin.RiftCMConfigPluginBase.__init__(self, dts, log, loop, account)
+    def __init__(self, dts, log, loop, project, account):
+        riftcm_config_plugin.RiftCMConfigPluginBase.__init__(self, dts, log, loop,
+                                                             project, account)
         self._name = account.name
         self._type = 'juju'
         self._ip_address = account.juju.ip_address
@@ -51,7 +53,7 @@ class JujuConfigPlugin(riftcm_config_plugin.RiftCMConfigPluginBase):
         self._user = account.juju.user
         self._secret = account.juju.secret
         self._rift_install_dir = os.environ['RIFT_INSTALL']
-        self._rift_artif_dir = os.environ['RIFT_ARTIFACTS']
+        self._rift_var_root_dir = os.environ['RIFT_VAR_ROOT']
 
         ############################################################
         # This is wrongfully overloaded with 'juju' private data.  #
@@ -174,22 +176,25 @@ class JujuConfigPlugin(riftcm_config_plugin.RiftCMConfigPluginBase):
                           'tags': {},
                           'active': False,
                           'config': vnf_config,
-                          'vnfr_name' : agent_vnfr.name})
+                          'vnfr_name': agent_vnfr.name})
         self._log.debug("jujuCA: Charm %s for vnf %s to be deployed as %s",
                         charm, agent_vnfr.name, vnf_unique_name)
 
         # Find the charm directory
         try:
-            path = os.path.join(self._rift_artif_dir,
-                                'launchpad/libs',
-                                agent_vnfr.vnfr_msg.vnfd_ref,
-                                'charms/trusty',
+            path = os.path.join(self._rift_var_root_dir,
+                                'launchpad/packages/vnfd',
+                                self._project.name,
+                                agent_vnfr.vnfr_msg.vnfd.id,
+                                'charms',
                                 charm)
             self._log.debug("jujuCA: Charm dir is {}".format(path))
             if not os.path.isdir(path):
-                self._log.error("jujuCA: Did not find the charm directory at {}".
-                                format(path))
+                msg = "jujuCA: Did not find the charm directory at {}".format(path)
+                self._log.error(msg)
                 path = None
+                # Return from here instead of forwarding the config request to juju_api
+                raise Exception(msg)
         except Exception as e:
             self.log.exception(e)
             return False
@@ -197,12 +202,13 @@ class JujuConfigPlugin(riftcm_config_plugin.RiftCMConfigPluginBase):
         if vnf_unique_name not in self._tasks:
             self._tasks[vnf_unique_name] = {}
 
-        self._tasks[vnf_unique_name]['deploy'] = self.loop.create_task(
-            self.api.deploy_service(charm, vnf_unique_name, path=path))
-
         self._log.debug("jujuCA: Deploying service %s",
                         vnf_unique_name)
-
+        yield from self.api.deploy_application(
+            charm,
+            vnf_unique_name,
+            path=path,
+        )
         return True
 
     @asyncio.coroutine
@@ -237,23 +243,22 @@ class JujuConfigPlugin(riftcm_config_plugin.RiftCMConfigPluginBase):
             vnfr = agent_vnfr.vnfr
             service = vnfr['vnf_juju_name']
 
-            self._log.debug ("jujuCA: Terminating VNFr %s, %s",
-                             agent_vnfr.name, service)
-            self._tasks[service]['destroy'] = self.loop.create_task(
-                    self.api.destroy_service(service)
-                )
+            self._log.debug("jujuCA: Terminating VNFr {}, {}".format(
+                agent_vnfr.name,
+                service,
+            ))
+            yield from self.api.remove_application(service)
 
             del self._juju_vnfs[agent_vnfr.id]
-            self._log.debug ("jujuCA: current vnfrs={}".
-                             format(self._juju_vnfs))
+            self._log.debug("jujuCA: current vnfrs={}".
+                            format(self._juju_vnfs))
             if service in self._tasks:
                 tasks = []
                 for action in self._tasks[service].keys():
-                    #if self.check_task_status(service, action):
                     tasks.append(action)
                 del tasks
         except KeyError as e:
-            self._log.debug ("jujuCA: Termiating charm service for VNFr {}, e={}".
+            self._log.debug ("jujuCA: Terminating charm service for VNFr {}, e={}".
                              format(agent_vnfr.name, e))
         except Exception as e:
             self._log.error("jujuCA: Exception terminating charm service for VNFR {}: {}".
@@ -268,129 +273,156 @@ class JujuConfigPlugin(riftcm_config_plugin.RiftCMConfigPluginBase):
         """
         return True
 
-    def check_task_status(self, service, action):
-        #self.log.debug("jujuCA: check task status for %s, %s" % (service, action))
-        try:
-            task = self._tasks[service][action]
-            if task.done():
-                self.log.debug("jujuCA: Task for %s, %s done" % (service, action))
-                e = task.exception()
-                if e:
-                    self.log.error("jujuCA: Error in task for {} and {} : {}".
-                                   format(service, action, e))
-                    raise Exception(e)
-                r= task.result()
-                if r:
-                    self.log.debug("jujuCA: Task for {} and {}, returned {}".
-                                   format(service, action,r))
-                return True
-            else:
-                self.log.debug("jujuCA: task {}, {} not done".
-                               format(service, action))
-                return False
-        except KeyError as e:
-            self.log.error("jujuCA: KeyError for task for {} and {}: {}".
-                           format(service, action, e))
-        except Exception as e:
-            self.log.error("jujuCA: Error for task for {} and {}: {}".
-                           format(service, action, e))
-            raise
-        return True
-
     @asyncio.coroutine
-    def vnf_config_primitive(self, nsr_id, vnfr_id, primitive, output):
-        self._log.debug("jujuCA: VNF config primititve {} for nsr {}, vnfr_id {}".
+    def _vnf_config_primitive(self, nsr_id, vnfr_id, primitive,
+                              vnf_config=None, wait=False):
+        self._log.debug("jujuCA: VNF config primitive {} for nsr {}, "
+                        "vnfr_id {}".
                         format(primitive, nsr_id, vnfr_id))
-        try:
-            vnfr = self._juju_vnfs[vnfr_id].vnfr
-        except KeyError:
-            self._log.error("jujuCA: Did not find VNFR %s in juju plugin",
-                            vnfr_id)
-            return
 
-        output.execution_status = "failed"
-        output.execution_id = ''
-        output.execution_error_details = ''
+        if vnf_config is None:
+            vnfr_msg = yield from self.get_vnfr(vnfr_id)
+            if vnfr_msg is None:
+                msg = "Unable to get VNFR {} through DTS".format(vnfr_id)
+                self._log.error(msg)
+                return 3, msg
+
+            vnf_config = vnfr_msg.vnf_configuration
+        self._log.debug("VNF config= %s", vnf_config.as_dict())
 
         try:
             service = vnfr['vnf_juju_name']
-            vnf_config = vnfr['config']
             self._log.debug("VNF config %s", vnf_config)
-            configs = vnf_config.service_primitive
+            configs = vnf_config.config_primitive
             for config in configs:
                 if config.name == primitive.name:
                     self._log.debug("jujuCA: Found the config primitive %s",
                                     config.name)
                     params = {}
-                    for parameter in primitive.parameter:
-                        if parameter.value:
-                            val = self.xlate(parameter.value, vnfr['tags'])
-                            # TBD do validation of the parameters
-                            data_type = 'string'
-                            found = False
-                            for ca_param in config.parameter:
-                                if ca_param.name == parameter.name:
-                                    data_type = ca_param.data_type
-                                    found = True
-                                    break
-                                if data_type == 'integer':
-                                    val = int(parameter.value)
-                            if not found:
-                                self._log.warn("jujuCA: Did not find parameter {} for {}".
-                                               format(parameter, config.name))
+                    for parameter in config.parameter:
+                        val = None
+                        for p in primitive.parameter:
+                            if p.name == parameter.name:
+                                if p.value:
+                                    val = self.xlate(p.value, vnfr['tags'])
+                                break
+
+                        if val is None:
+                            val = parameter.default_value
+
+                        if val is None:
+                            # Check if mandatory parameter
+                            if parameter.mandatory:
+                                msg = "VNFR {}: Primitive {} called " \
+                                      "without mandatory parameter {}". \
+                                      format(vnfr_msg.name, config.name,
+                                             parameter.name)
+                                self._log.error(msg)
+                                return 'failed', '', msg
+
+                        if val:
+                            val = self.convert_value(val, parameter.data_type)
                             params.update({parameter.name: val})
 
+                    rc = ''
+                    exec_id = ''
+                    details = ''
                     if config.name == 'config':
-                        output.execution_id = 'config'
+                        exec_id = 'config'
                         if len(params):
-                            self._log.debug("jujuCA: applying config with params {} for service {}".
+                            self._log.debug("jujuCA: applying config with "
+                                            "params {} for service {}".
                                             format(params, service))
 
-                            rc = yield from self.api.apply_config(params, service=service, wait=False)
+                            rc = yield from self.api.apply_config(params, application=service)
 
                             if rc:
-                                # Mark as pending and check later for the status
-                                output.execution_status = "pending"
-                                self._log.debug("jujuCA: applied config {} on {}".
-                                                format(params, service))
+                                rc = "completed"
+                                self._log.debug("jujuCA: applied config {} "
+                                                "on {}".format(params, service))
                             else:
-                                output.execution_status = 'failed'
-                                output.execution_error_details = \
+                                rc = 'failed'
+                                details = \
                                     'Failed to apply config: {}'.format(params)
-                                self._log.error("jujuCA: Error applying config {} on service {}".
+                                self._log.error("jujuCA: Error applying "
+                                                "config {} on service {}".
                                                 format(params, service))
                         else:
-                            self._log.warn("jujuCA: Did not find valid parameters for config : {}".
+                            self._log.warn("jujuCA: Did not find valid "
+                                           "parameters for config : {}".
                                            format(primitive.parameter))
-                            output.execution_status = "completed"
+                            rc = "completed"
                     else:
-                        self._log.debug("jujuCA: Execute action {} on service {} with params {}".
+                        self._log.debug("jujuCA: Execute action {} on "
+                                        "service {} with params {}".
                                         format(config.name, service, params))
 
-                        resp = yield from self.api.execute_action(config.name,
-                                                                  params,
-                                                                  service=service)
+                        resp = yield from self.api.execute_action(
+                            service,
+                            config.name,
+                            **params,
+                        )
 
                         if resp:
                             if 'error' in resp:
-                                output.execution_error_details = resp['error']['Message']
+                                details = resp['error']['message']
                             else:
-                                output.execution_id = resp['action']['tag']
-                                output.execution_status = resp['status']
-                                if output.execution_status == 'failed':
-                                    output.execution_error_details = resp['message']
-                            self._log.debug("jujuCA: execute action {} on service {} returned {}".
-                                            format(config.name, service, output.execution_status))
+                                exec_id = resp['action']['tag']
+                                rc = resp['status']
+                                if rc == 'failed':
+                                    details = resp['message']
+
+                            self._log.debug("jujuCA: execute action {} on "
+                                            "service {} returned {}".
+                                            format(config.name, service, rc))
                         else:
-                            self._log.error("jujuCA: error executing action {} for {} with {}".
-                                            format(config.name, service, params))
-                            output.execution_id = ''
-                            output.execution_status = 'failed'
-                            output.execution_error_details = "Failed to queue the action"
+                            self._log.error("jujuCA: error executing action "
+                                            "{} for {} with {}".
+                                            format(config.name, service,
+                                                   params))
+                            exec_id = ''
+                            rc = 'failed'
+                            details = "Failed to queue the action"
                     break
 
         except KeyError as e:
-            self._log.info("VNF %s does not have config primititves, e=%s", vnfr_id, e)
+            msg = "VNF %s does not have config primitives, e=%s", \
+                  vnfr_id, e
+            self._log.exception(msg)
+            raise ValueError(msg)
+
+        while wait and (rc in ['pending', 'running']):
+            self._log.debug("JujuCA: action {}, rc {}".
+                            format(exec_id, rc))
+            yield from asyncio.sleep(0.2, loop=self._loop)
+            status = yield from self.api.get_action_status(exec_id)
+            rc = status['status']
+
+        return rc, exec_id, details
+
+    @asyncio.coroutine
+    def vnf_config_primitive(self, nsr_id, vnfr_id, primitive, output):
+        try:
+            vnfr = self._juju_vnfs[vnfr_id].vnfr
+        except KeyError:
+            msg = "Did not find VNFR {} in Juju plugin".format(vnfr_id)
+            self._log.debug(msg)
+            return
+
+        output.execution_status = "failed"
+        output.execution_id = ''
+        output.execution_error_details = ''
+
+        rc, exec_id, err = yield from self._vnf_config_primitive(
+            nsr_id,
+            vnfr_id,
+            primitive)
+
+        self._log.debug("VNFR {} primitive {} exec status: {}".
+                        format(vnfr.name, primitive.name, rc))
+        output.execution_status = rc
+        output.execution_id = exec_id
+        output.execution_error_details = err
 
     @asyncio.coroutine
     def apply_config(self, agent_nsr, agent_vnfr, config, rpc_ip):
@@ -472,7 +504,9 @@ class JujuConfigPlugin(riftcm_config_plugin.RiftCMConfigPluginBase):
             # The script has full path, use as is
             script = rpc_ip.user_defined_script
         else:
-            script = os.path.join(self._rift_artif_dir, 'launchpad/libs', agent_nsr.id, 'scripts',
+            script = os.path.join(self._rift_var_root_dir, 'launchpad/nsd',
+                                  self._project.name,
+                                  agent_nsr.id, 'scripts',
                                   rpc_ip.user_defined_script)
             self.log.debug("jujuCA: Checking for script in %s", script)
             if not os.path.exists(script):
@@ -497,95 +531,125 @@ class JujuConfigPlugin(riftcm_config_plugin.RiftCMConfigPluginBase):
         Actions in initial config may not work based on charm design
         """
 
-        vnfr = agent_vnfr.vnfr
-        service = vnfr['vnf_juju_name']
+        try:
+            vnfr = self._juju_vnfs[agent_vnfr.id].vnfr
+            service = vnfr['vnf_juju_name']
+        except KeyError:
+            self._log.debug("Did not find VNFR %s in Juju plugin",
+                            agent_vnfr.name)
+            return False
 
-        rc = yield from self.api.is_service_up(service=service)
+        vnfr_msg = yield from self.get_vnfr(agent_vnfr.id)
+        if vnfr_msg is None:
+            msg = "Unable to get VNFR {} ({}) through DTS". \
+                  format(agent_vnfr.id, agent_vnfr.name)
+            self._log.error(msg)
+            raise RuntimeError(msg)
+
+        vnf_config = vnfr_msg.vnf_configuration
+        self._log.debug("VNFR %s config: %s", vnfr_msg.name,
+                        vnf_config.as_dict())
+
+        # Sort the primitive based on the sequence number
+        primitives = sorted(vnf_config.initial_config_primitive,
+                            key=lambda k: k.seq)
+        if not primitives:
+            self._log.debug("VNFR {}: No initial-config-primitive specified".
+                            format(vnfr_msg.name))
+            return True
+
+        rc = yield from self.api.is_application_up(application=service)
         if not rc:
             return False
 
-        action_ids = []
         try:
-            vnf_cat = agent_vnfr.vnfr_msg
-            if vnf_cat and vnf_cat.mgmt_interface.ip_address:
-                vnfr['tags'].update({'rw_mgmt_ip': vnf_cat.mgmt_interface.ip_address})
+            if vnfr_msg.mgmt_interface.ip_address:
+                vnfr['tags'].update({'rw_mgmt_ip': vnfr_msg.mgmt_interface.ip_address})
                 self._log.debug("jujuCA:(%s) tags: %s", vnfr['vnf_juju_name'], vnfr['tags'])
 
-            config = {}
-            try:
-                for primitive in vnfr['config'].initial_config_primitive:
-                    self._log.debug("jujuCA:(%s) Initial config primitive %s", vnfr['vnf_juju_name'], primitive)
+            for primitive in primitives:
+                self._log.debug("(%s) Initial config primitive %s",
+                                vnfr['vnf_juju_name'], primitive.as_dict())
+                if primitive.config_primitive_ref:
+                    # Reference to a primitive in config primitive
+                    class Primitive:
+                        def __init__(self, name):
+                            self.name = name
+                            self.value = None
+                            self.parameter = []
+
+                    prim = Primitive(primitive.config_primitive_ref)
+                    rc, eid, err = yield from self._vnf_config_primitive(
+                        agent_nsr.id,
+                        agent_vnfr.id,
+                        prim,
+                        vnf_config,
+                        wait=True)
+
+                    if rc == "failed":
+                        msg = "Error executing initial config primitive" \
+                              " {} in VNFR {}: rc={}, stderr={}". \
+                              format(prim.name, vnfr_msg.name, rc, err)
+                        self._log.error(msg)
+                        return False
+
+                elif primitive.name:
+                    config = {}
                     if primitive.name == 'config':
                         for param in primitive.parameter:
                             if vnfr['tags']:
-                                val = self.xlate(param.value, vnfr['tags'])
+                                val = self.xlate(param.value,
+                                                 vnfr['tags'])
                                 config.update({param.name: val})
-            except KeyError as e:
-                self._log.exception("jujuCA:(%s) Initial config error(%s): config=%s",
-                                    vnfr['vnf_juju_name'], str(e), config)
-                config = None
-                return False
-
-            if config:
-                self.juju_log('info', vnfr['vnf_juju_name'],
-                              "Applying Initial config:%s",
-                              config)
-
-                rc = yield from self.api.apply_config(config, service=service)
-                if rc is False:
-                    self.log.error("Service {} is in error state".format(service))
-                    return False
-
-
-            # Apply any actions specified as part of initial config
-            for primitive in vnfr['config'].initial_config_primitive:
-                if primitive.name != 'config':
-                    self._log.debug("jujuCA:(%s) Initial config action primitive %s",
-                                    vnfr['vnf_juju_name'], primitive)
-                    action = primitive.name
-                    params = {}
-                    for param in primitive.parameter:
-                        val = self.xlate(param.value, vnfr['tags'])
-                        params.update({param.name: val})
-
-                    self._log.info("jujuCA:(%s) Action %s with params %s",
-                                   vnfr['vnf_juju_name'], action, params)
-
-                    resp = yield from self.api.execute_action(action, params,
-                                                              service=service)
-                    if 'error' in resp:
-                        self._log.error("Applying initial config on {} failed for {} with {}: {}".
-                                        format(vnfr['vnf_juju_name'], action, params, resp))
-                        return False
-
-                    action_ids.append(resp['action']['tag'])
 
+                        if config:
+                            self.juju_log('info', vnfr['vnf_juju_name'],
+                                          "Applying Initial config:%s",
+                                          config)
+
+                            rc = yield from self.api.apply_config(
+                                config,
+                                application=service,
+                            )
+                            if rc is False:
+                                self.log.error("Service {} is in error state".format(service))
+                                return False
+                    else:
+                        # Apply any actions specified as part of initial config
+                        for primitive in vnfr['config'].initial_config_primitive:
+                            if primitive.name != 'config':
+                                self._log.debug("jujuCA:(%s) Initial config action primitive %s",
+                                                vnfr['vnf_juju_name'], primitive)
+                                action = primitive.name
+                                params = {}
+                                for param in primitive.parameter:
+                                    val = self.xlate(param.value, vnfr['tags'])
+                                    params.update({param.name: val})
+
+                                self._log.info("jujuCA:(%s) Action %s with params %s",
+                                               vnfr['vnf_juju_name'], action, params)
+                                self._log.debug("executing action")
+                                resp = yield from self.api.execute_action(
+                                    service,
+                                    action,
+                                    **params,
+                                )
+                                self._log.debug("executed action")
+                                if 'error' in resp:
+                                    self._log.error("Applying initial config on {} failed for {} with {}: {}".
+                                                    format(vnfr['vnf_juju_name'], action, params, resp))
+                                    return False
         except KeyError as e:
             self._log.info("Juju config agent(%s): VNFR %s not managed by Juju",
                            vnfr['vnf_juju_name'], agent_vnfr.id)
             return False
         except Exception as e:
-            self._log.exception("jujuCA:(%s) Exception juju apply_initial_config for VNFR {}: {}".
-                                format(vnfr['vnf_juju_name'], agent_vnfr.id, e))
+            self._log.exception("jujuCA:(%s) Exception juju "
+                                "apply_initial_config for VNFR {}: {}".
+                                format(vnfr['vnf_juju_name'],
+                                       agent_vnfr.id, e))
             return False
 
-        # Check if all actions completed
-        pending = True
-        while pending:
-            pending = False
-            for act in action_ids:
-                resp = yield from self.api.get_action_status(act)
-                if 'error' in resp:
-                    self._log.error("Initial config failed: {}".format(resp))
-                    return False
-
-                if resp['status'] == 'failed':
-                    self._log.error("Initial config action failed: {}".format(resp))
-                    return False
-
-                if resp['status'] == 'pending':
-                    pending = True
-
         return True
 
     def add_vnfr_managed(self, agent_vnfr):
@@ -613,7 +677,7 @@ class JujuConfigPlugin(riftcm_config_plugin.RiftCMConfigPluginBase):
 
             vnfr = self._juju_vnfs[vnfr_id].vnfr
             service = vnfr['vnf_juju_name']
-            resp = self.api.is_service_active(service=service)
+            resp = self.api.is_application_active(application=service)
             self._juju_vnfs[vnfr_id]['active'] = resp
             self._log.debug("jujuCA: Service state for {} is {}".
                             format(service, resp))
@@ -642,12 +706,13 @@ class JujuConfigPlugin(riftcm_config_plugin.RiftCMConfigPluginBase):
 
         rc = 'configuring'
 
-        if not self.check_task_status(service, 'deploy'):
-            return rc
-
         try:
-            resp = yield from self.api.get_service_status(service=service)
-            self._log.debug("jujuCA: Get service %s status? %s", service, resp)
+            # Get the status of the application
+            resp = yield from self.api.get_application_status(service)
+
+            # No status means the application is still pending deployment
+            if resp is None:
+                return rc
 
             if resp == 'error':
                 return 'error'