Move 'launchpad' directory to 'RIFT_VAR_ROOT' from 'RIFT_ARTIFACTS'
[osm/SO.git] / rwcm / plugins / rwconman / rift / tasklets / rwconmantasklet / rwconman_config.py
index 4848e9e..c25cab9 100644 (file)
@@ -30,11 +30,14 @@ from gi.repository import (
 )
 
 import rift.tasklets
+import rift.package.script
+import rift.package.store
 
 from . import rwconman_conagent as conagent
 from . import RiftCM_rpc
 from . import riftcm_config_plugin
 
+
 if sys.version_info < (3, 4, 4):
     asyncio.ensure_future = asyncio.async
 
@@ -296,27 +299,21 @@ class ConfigManagerConfig(object):
     def process_nsd_vnf_configuration(self, nsr_obj, vnfr):
 
         def get_config_method(vnf_config):
-            cfg_types = ['netconf', 'juju', 'script']
+            cfg_types = ['juju', 'script']
             for method in cfg_types:
                 if method in vnf_config:
                     return method
             return None
             
-        def get_cfg_file_extension(method,  configuration_options):
+        def get_cfg_file_extension(method):
             ext_dict = {
-                "netconf" : "xml",
-                "script" : {
-                    "bash" : "sh",
-                    "expect" : "exp",
-                },
-                "juju" : "yml"
+                "juju" : "yml",
+                "script" : "py"
             }
 
-            if method == "netconf":
+            if method == "juju":
                 return ext_dict[method]
             elif method == "script":
-                return ext_dict[method][configuration_options['script_type']]
-            elif method == "juju":
                 return ext_dict[method]
             else:
                 return "cfg"
@@ -405,12 +402,6 @@ class ConfigManagerConfig(object):
                     configuration_options[cfg_opt] = vnf_config[method][cfg_opt]
                     vnf_cfg[cfg_opt] = configuration_options[cfg_opt]
 
-            cfg_opt_list = ['mgmt_ip_address', 'username', 'password']
-            for cfg_opt in cfg_opt_list:
-                if cfg_opt in vnf_config['config_access']:
-                    configuration_options[cfg_opt] = vnf_config['config_access'][cfg_opt]
-                    vnf_cfg[cfg_opt] = configuration_options[cfg_opt]
-
             # Add to the cp_dict
             vnf_cp_dict = nsr_obj._cp_dict[vnfr['member_vnf_index_ref']]
             vnf_cp_dict['rw_mgmt_ip'] = vnf_cfg['mgmt_ip_address']
@@ -419,22 +410,9 @@ class ConfigManagerConfig(object):
             
 
             # TBD - see if we can neatly include the config in "config_attributes" file, no need though
-            #config_priority['config_template'] = vnf_config['config_template']
             # Create config file
             vnf_cfg['juju_script'] = os.path.join(self._parent.cfg_dir, 'juju_if.py')
-
-            if 'config_template' in vnf_config:
-                vnf_cfg['cfg_template'] = '{}_{}_template.cfg'.format(nsr_obj.cfg_path_prefix, config_priority['configuration_type'])
-                vnf_cfg['cfg_file'] = '{}.{}'.format(nsr_obj.cfg_path_prefix, get_cfg_file_extension(method, configuration_options))
-                vnf_cfg['xlate_script'] = os.path.join(self._parent.cfg_dir, 'xlate_cfg.py')
-                try:
-                    # Now write this template into file
-                    with open(vnf_cfg['cfg_template'], "w") as cf:
-                        cf.write(vnf_config['config_template'])
-                except Exception as e:
-                    self._log.error("Processing NSD, failed to generate configuration template : %s (Error : %s)",
-                                    vnf_config['config_template'], str(e))
-                    raise
+            vnf_cfg['cfg_file'] = '{}.{}'.format(nsr_obj.cfg_path_prefix, get_cfg_file_extension(method))
 
             self._log.debug("VNF endpoint so far: %s", vnf_cfg)
 
@@ -467,10 +445,6 @@ class ConfigManagerConfig(object):
         nsr_dict = self._nsr_dict
         self._log.info("Configure NSR, id = %s", id)
 
-        #####################TBD###########################
-        # yield from self._config_agent_mgr.invoke_config_agent_plugins('notify_create_nsr', self.id, self._nsd)
-        # yield from self._config_agent_mgr.invoke_config_agent_plugins('notify_nsr_active', self.id, self._vnfrs)
-        
         try:
             if id not in nsr_dict:
                 nsr_obj = ConfigManagerNSR(self._log, self._loop, self, id)
@@ -509,14 +483,11 @@ class ConfigManagerConfig(object):
 
                 # Parse NSR
                 if nsr is not None:
-                    nsr_obj.set_nsr_name(nsr['nsd_name_ref'])
+                    nsr_obj.set_nsr_name(nsr['name_ref'])
                     nsr_dir = os.path.join(self._parent.cfg_dir, nsr_obj.nsr_name)
                     self._log.info("Checking NS config directory: %s", nsr_dir)
                     if not os.path.isdir(nsr_dir):
                         os.makedirs(nsr_dir)
-                        # self._log.critical("NS %s is not to be configured by Service Orchestrator!", nsr_obj.nsr_name)
-                        # yield from nsr_obj.update_ns_cm_state(conmanY.RecordState.READY_NO_CFG)
-                        # return
 
                     nsr_obj.set_config_dir(self)
                     
@@ -536,12 +507,6 @@ class ConfigManagerConfig(object):
                                 nsr_obj.agent_nsr,
                                 agent_vnfr)
 
-                        #####################TBD###########################
-                        # self._log.debug("VNF active. Apply initial config for vnfr {}".format(vnfr.name))
-                        # yield from self._config_agent_mgr.invoke_config_agent_plugins('apply_initial_config',
-                        #                                             vnfr.id, vnfr)
-                        # yield from self._config_agent_mgr.invoke_config_agent_plugins('notify_terminate_vnf', self.id, vnfr)
-
             except Exception as e:
                 self._log.error("Failed processing NSR (%s) as (%s)", nsr_obj.nsr_name, str(e))
                 self._log.exception(e)
@@ -555,13 +520,6 @@ class ConfigManagerConfig(object):
             except Exception as e:
                 self._log.error("NS:(%s) failed to write config attributes file as (%s)", nsr_obj.nsr_name, str(e))
 
-            try:
-                # Generate nsr_xlate_dict.yaml (For debug reference)
-                with open(nsr_obj.xlate_dict_file, "w") as yf:
-                    yf.write(yaml.dump(nsr_obj._cp_dict, default_flow_style=False))
-            except Exception as e:
-                self._log.error("NS:(%s) failed to write nsr xlate tags file as (%s)", nsr_obj.nsr_name, str(e))
-
             self._log.debug("Starting to configure each VNF")
 
             # Check if this NS has input parametrs
@@ -573,19 +531,11 @@ class ConfigManagerConfig(object):
                     # Go in loop to configure by specified order
                     self._log.info("Using Dynamic configuration input parametrs for NS: %s", nsr_obj.nsr_name)
 
-                    # cfg_delay = nsr_obj.nsr_cfg_config_attributes_dict['configuration_delay']
-                    # if cfg_delay:
-                    #     self._log.info("Applying configuration delay for NS (%s) ; %d seconds",
-                    #                    nsr_obj.nsr_name, cfg_delay)
-                    #     yield from asyncio.sleep(cfg_delay, loop=self._loop)
-
                     for config_attributes_dict in nsr_obj.nsr_cfg_config_attributes_dict.values():
                         # Iterate through each priority level
                         for vnf_config_attributes_dict in config_attributes_dict:
                             # Iterate through each vnfr at this priority level
                                 
-                            # Make up vnf_unique_name with vnfd name and member index
-                            #vnfr_name = "{}.{}".format(nsr_obj.nsr_name, vnf_config_attributes_dict['name'])
                             vnf_unique_name = get_vnf_unique_name(
                                 nsr_obj.nsr_name,
                                 vnf_config_attributes_dict['name'],
@@ -659,14 +609,11 @@ class ConfigManagerConfig(object):
             # publish delete cm-state (cm-nsr)
             yield from nsr_obj.delete_cm_nsr()
 
-            #####################TBD###########################
-            # yield from self._config_agent_mgr.invoke_config_agent_plugins('notify_terminate_ns', self.id)
-
             self._log.info("NSR(%s/%s) is deleted", nsr_obj.nsr_name, id)
 
     @asyncio.coroutine
-    def process_ns_initial_config(self, nsr_obj):
-        '''Apply the initial-config-primitives specified in NSD'''
+    def process_initial_config(self, nsr_obj, conf, script, vnfr_name=None):
+        '''Apply the initial-config-primitives specified in NSD or VNFD'''
 
         def get_input_file(parameters):
             inp = {}
@@ -674,9 +621,9 @@ class ConfigManagerConfig(object):
             # Add NSR name to file
             inp['nsr_name'] = nsr_obj.nsr_name
 
-            # TODO (pjoseph): Add config agents, we need to identify which all
-            # config agents are required from this NS and provide only those
-            inp['config-agent'] = {}
+            # Add VNFR name if available
+            if vnfr_name:
+                inp['vnfr_name'] = vnfr_name
 
             # Add parameters for initial config
             inp['parameter'] = {}
@@ -684,10 +631,28 @@ class ConfigManagerConfig(object):
                 try:
                     inp['parameter'][parameter['name']] = parameter['value']
                 except KeyError as e:
-                    self._log.info("NSR {} initial config parameter {} with no value: {}".
-                                    format(nsr_obj.nsr_name, parameter, e))
+                    if vnfr_name:
+                        self._log.info("VNFR {} initial config parameter {} with no value: {}".
+                                       format(vnfr_name, parameter, e))
+                    else:
+                        self._log.info("NSR {} initial config parameter {} with no value: {}".
+                                       format(nsr_obj.nsr_name, parameter, e))
 
 
+            # Add config agents specific to each VNFR
+            inp['config-agent'] = {}
+            for vnfr in nsr_obj.agent_nsr.vnfrs:
+                # Get the config agent for the VNFR
+                # If vnfr name is specified, add only CA specific to that
+                if (vnfr_name is None) or \
+                   (vnfr_name == vnfr.name):
+                    agent = self._config_agent_mgr.get_vnfr_config_agent(vnfr.vnfr_msg)
+                    if agent:
+                        if agent.agent_type != riftcm_config_plugin.DEFAULT_CAP_TYPE:
+                            inp['config-agent'][vnfr.member_vnf_index] = agent.agent_data
+                            inp['config-agent'][vnfr.member_vnf_index] \
+                                ['service-name'] = agent.get_service_name(vnfr.id)
+
             # Add vnfrs specific data
             inp['vnfr'] = {}
             for vnfr in nsr_obj.vnfrs:
@@ -711,17 +676,21 @@ class ConfigManagerConfig(object):
                         )
 
                 v['vdur'] = []
-                vdu_data = [(vdu['name'], vdu['management_ip'], vdu['vm_management_ip'], vdu['id'])
-                        for vdu in vnfr['vdur']]
-
-                for data in vdu_data:
-                    data = dict(zip(['name', 'management_ip', 'vm_management_ip', 'id'] , data))
-                    v['vdur'].append(data)
+                vdu_data = []
+                for vdu in vnfr['vdur']:
+                    d = {}
+                    for k in ['name','management_ip', 'vm_management_ip', 'id', 'vdu_id_ref']:
+                        if k in vdu:
+                            d[k] = vdu[k]
+                    vdu_data.append(d)
+                v['vdur'] = vdu_data
 
                 inp['vnfr'][vnfr['member_vnf_index_ref']] = v
 
-            self._log.debug("Input data for NSR {}: {}".
-                            format(nsr_obj.nsr_name, inp))
+
+            self._log.debug("Input data for {}: {}".
+                            format((vnfr_name if vnfr_name else nsr_obj.nsr_name),
+                                   inp))
 
             # Convert to YAML string
             yaml_string = yaml.dump(inp, default_flow_style=False)
@@ -730,91 +699,120 @@ class ConfigManagerConfig(object):
             tmp_file = None
             with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
                 tmp_file.write(yaml_string.encode("UTF-8"))
-            self._log.debug("Input file created for NSR {}: {}".
-                            format(nsr_obj.nsr_name, tmp_file.name))
+            self._log.debug("Input file created for {}: {}".
+                            format((vnfr_name if vnfr_name \
+                                    else nsr_obj.nsr_name),
+                                   tmp_file.name))
 
             return tmp_file.name
 
-        def get_script_file(script_name, nsd_name, nsd_id):
-            # Get the full path to the script
-            script = ''
-            # If script name starts with /, assume it is full path
-            if script_name[0] == '/':
-                # The script has full path, use as is
-                script = script_name
-            else:
-                script = os.path.join(os.environ['RIFT_ARTIFACTS'],
-                                      'launchpad/packages/nsd',
-                                      nsd_id,
-                                      nsd_name,
-                                      'scripts',
-                                      script_name)
-                self._log.debug("Checking for script at %s", script)
-                if not os.path.exists(script):
-                    self._log.debug("Did not find script %s", script)
-                    script = os.path.join(os.environ['RIFT_INSTALL'],
-                                          'usr/bin',
-                                          script_name)
-
-                # Seen cases in jenkins, where the script execution fails
-                # with permission denied. Setting the permission on script
-                # to make sure it has execute permission
-                perm = os.stat(script).st_mode
-                if not (perm  &  stat.S_IXUSR):
-                    self._log.warn("NSR {} initial config script {} " \
-                                  "without execute permission: {}".
-                                  format(nsr_id, script, perm))
-                    os.chmod(script, perm | stat.S_IXUSR)
-                return script
-
-        nsr_id = nsr_obj.nsr_id
-        nsr_name = nsr_obj.nsr_name
-        self._log.debug("Apply initial config for NSR {}({})".
-                        format(nsr_name, nsr_id))
-
-        # Fetch NSR
-        nsr = yield from self.cmdts_obj.get_nsr(nsr_id)
+        parameters = []
+        try:
+            parameters = conf['parameter']
+        except Exception as e:
+            self._log.debug("Parameter conf: {}, e: {}".
+                            format(conf, e))
+
+        inp_file = get_input_file(parameters)
+
+        cmd = "{0} {1}".format(script, inp_file)
+        self._log.debug("Running the CMD: {}".format(cmd))
+
+        process = yield from asyncio.create_subprocess_shell(cmd,
+                                                             loop=self._loop,
+                                                             stdout=subprocess.PIPE,
+                                                             stderr=subprocess.PIPE)
+        stdout, stderr = yield from process.communicate()
+        rc = yield from process.wait()
+
+        if rc:
+            msg = "NSR/VNFR {} initial config using {} failed with {}: {}". \
+                  format(vnfr_name if vnfr_name else nsr_obj.nsr_name,
+                         script, rc, stderr)
+            self._log.error(msg)
+            raise InitialConfigError(msg)
+
+        try:
+            os.remove(inp_file)
+        except Exception as e:
+            self._log.debug("Error removing input file {}: {}".
+                            format(inp_file, e))
+
+    def get_script_file(self, script_name, d_name, d_id, d_type):
+        if d_type == "vnfd":
+            package_store = rift.package.store.VnfdPackageFilesystemStore(self._log)
+            package_store.refresh()
+        elif d_type == "nsd":
+            package_store = rift.package.store.NsdPackageFilesystemStore(self._log)
+            package_store.refresh()
+        else:
+            raise
+        script_extractor = rift.package.script.PackageScriptExtractor(self._log)
+        script = script_extractor.get_extracted_script_path(d_id, script_name)
+
+        self._log.debug("Checking for script at %s", script)
+        if not os.path.exists(script):
+            self._log.warning("Did not find script %s", script)
+            return
+
+        # Seen cases in jenkins, where the script execution fails
+        # with permission denied. Setting the permission on script
+        # to make sure it has execute permission
+        perm = os.stat(script).st_mode
+        if not (perm  &  stat.S_IXUSR):
+            self._log.warning("NSR/VNFR {} script {} " \
+                                    "without execute permission: {}".
+                                    format(d_name, script, perm))
+            os.chmod(script, perm | stat.S_IXUSR)
+        return script
+
+    @asyncio.coroutine
+    def process_ns_initial_config(self, nsr_obj):
+        '''Apply the initial-config-primitives specified in NSD'''
+
+        nsr = yield from self.cmdts_obj.get_nsr(nsr_obj.nsr_id)
+        if 'initial_config_primitive' not in nsr:
+            return
+
         if nsr is not None:
-            nsd = yield from self.cmdts_obj.get_nsd(nsr_id)
+            nsd = yield from self.cmdts_obj.get_nsd(nsr_obj.nsr_id)
+            for conf in nsr['initial_config_primitive']:
+                self._log.debug("NSR {} initial config: {}".
+                                format(nsr_obj.nsr_name, conf))
+                script = self.get_script_file(conf['user_defined_script'],
+                                              nsd.name,
+                                              nsd.id,
+                                              'nsd')
 
-            try:
-                # Check if initial config is present
-                # TODO (pjoseph): Sort based on seq
-                for conf in nsr['initial_config_primitive']:
-                    self._log.debug("Parameter conf: {}".
-                                    format(conf))
+                yield from self.process_initial_config(nsr_obj, conf, script)
 
-                    parameters = []
-                    try:
-                        parameters = conf['parameter']
-                    except Exception as e:
-                        self._log.debug("Parameter conf: {}, e: {}".
-                                        format(conf, e))
-                        pass
-
-                    inp_file = get_input_file(parameters)
-
-                    script = get_script_file(conf['user_defined_script'],
-                                             nsd.name,
-                                             nsd.id)
-
-                    cmd = "{0} {1}".format(script, inp_file)
-                    self._log.debug("Running the CMD: {}".format(cmd))
-
-                    process = yield from asyncio. \
-                              create_subprocess_shell(cmd, loop=self._loop)
-                    yield from process.wait()
-                    if process.returncode:
-                        msg = "NSR {} initial config using {} failed with {}". \
-                              format(nsr_name, script, process.returncode)
-                        self._log.error(msg)
-                        raise InitialConfigError(msg)
-                    else:
-                        os.remove(inp_file)
+    @asyncio.coroutine
+    def process_vnf_initial_config(self, nsr_obj, vnfr):
+        '''Apply the initial-config-primitives specified in VNFD'''
+
+        vnfr_name = vnfr.name
+
+        vnfd = vnfr.vnfd
+        vnf_cfg = vnfd.vnf_configuration
+
+        for conf in vnf_cfg.initial_config_primitive:
+                self._log.debug("VNFR {} initial config: {} for vnfd id {}".
+                                format(vnfr_name, conf, vnfd.id))
 
-            except KeyError as e:
-                self._log.debug("Did not find initial config {}".
-                                format(e))
+                if not conf.user_defined_script:
+                    self._log.debug("VNFR {} did not find user defined script: {}".
+                                    format(vnfr_name, conf))
+                    continue
+
+                script = self.get_script_file(conf.user_defined_script,
+                                              vnfd.name,
+                                              vnfd.id,
+                                              'vnfd')
+
+                yield from self.process_initial_config(nsr_obj,
+                                                       conf.as_dict(),
+                                                       script,
+                                                       vnfr_name=vnfr_name)
 
 
 class ConfigManagerNSR(object):
@@ -904,46 +902,7 @@ class ConfigManagerNSR(object):
             self._log.debug("NSR:(%s), Created configuration directory(%s)",
                             caller._nsr['name_ref'], self.this_nsr_dir)
         self.config_attributes_file = os.path.join(self.this_nsr_dir, "configuration_config_attributes.yml")
-        self.xlate_dict_file = os.path.join(self.this_nsr_dir, "nsr_xlate_dict.yml")
         
-    def xlate_conf(self, vnfr, vnf_cfg):
-
-        # If configuration type is not already set, try to read from attributes
-        if vnf_cfg['interface_type'] is None:
-            # Prepare unique name for this VNF
-            vnf_unique_name = get_vnf_unique_name(
-                    vnf_cfg['nsr_name'],
-                    vnfr['short_name'],
-                    vnfr['member_vnf_index_ref'],
-                    )
-
-            # Find this particular (unique) VNF's config attributes
-            if (vnf_unique_name in self.vnf_config_attributes_dict):
-                vnf_cfg_config_attributes_dict = self.vnf_config_attributes_dict[vnf_unique_name]
-                vnf_cfg['interface_type'] = vnf_cfg_config_attributes_dict['configuration_type']
-                if 'configuration_options' in vnf_cfg_config_attributes_dict:
-                    cfg_opts = vnf_cfg_config_attributes_dict['configuration_options']
-                    for key, value in cfg_opts.items():
-                        vnf_cfg[key] = value
-
-        cfg_path_prefix = '{}/{}/{}_{}'.format(
-                self._parent._parent.cfg_dir,
-                vnf_cfg['nsr_name'],
-                vnfr['short_name'],
-                vnfr['member_vnf_index_ref'],
-                )
-
-        vnf_cfg['cfg_template'] = '{}_{}_template.cfg'.format(cfg_path_prefix, vnf_cfg['interface_type'])
-        vnf_cfg['cfg_file'] = '{}.cfg'.format(cfg_path_prefix)
-        vnf_cfg['xlate_script'] = self._parent._parent.cfg_dir + '/xlate_cfg.py'
-
-        self._log.debug("VNF endpoint so far: %s", vnf_cfg)
-
-        self._log.info("Checking cfg_template %s", vnf_cfg['cfg_template'])
-        if os.path.exists(vnf_cfg['cfg_template']):
-            return True
-        return False
-
     def ConfigVNF(self, vnfr):
 
         vnf_cfg = vnfr['vnf_cfg']
@@ -964,30 +923,9 @@ class ConfigManagerNSR(object):
             self._cp_dict['rw_mgmt_ip'] = vnf_cfg['mgmt_ip_address']
             self._cp_dict['rw_username'] = vnf_cfg['username']
             self._cp_dict['rw_password'] = vnf_cfg['password']
-            ############################################################
-            # TBD - Need to lookup above 3 for a given VNF, not global #
-            # Once we do that no need to dump below file again before  #
-            # each VNF configuration translation.                      #
-            # This will require all existing config templates to be    #
-            # changed for above three tags to include member index     #
-            ############################################################
-            try:
-                nsr_obj = vnf_cfg['nsr_obj']
-                # Generate config_config_attributes.yaml (For debug reference)
-                with open(nsr_obj.xlate_dict_file, "w") as yf:
-                    yf.write(yaml.dump(nsr_obj._cp_dict, default_flow_style=False))
-            except Exception as e:
-                self._log.error("NS:(%s) failed to write nsr xlate tags file as (%s)", nsr_obj.nsr_name, str(e))
-            
-            if 'cfg_template' in vnf_cfg:
-                script_cmd = 'python3 {} -i {} -o {} -x "{}"'.format(vnf_cfg['xlate_script'], vnf_cfg['cfg_template'], vnf_cfg['cfg_file'], self.xlate_dict_file)
-                self._log.debug("xlate script command (%s)", script_cmd)
-                #xlate_msg = subprocess.check_output(script_cmd).decode('utf-8')
-                xlate_msg = subprocess.check_output(script_cmd, shell=True).decode('utf-8')
-                self._log.info("xlate script output (%s)", xlate_msg)
         except Exception as e:
             vnf_cm_state['state'] = self.state_to_string(conmanY.RecordState.CFG_PROCESS_FAILED)
-            self._log.error("Failed to execute translation script for VNF: %s with (%s)", log_this_vnf(vnf_cfg), str(e))
+            self._log.error("Failed to set tags for VNF: %s with (%s)", log_this_vnf(vnf_cfg), str(e))
             return
 
         self._log.info("Applying config to VNF: %s = %s!", log_this_vnf(vnf_cfg), vnf_cfg)
@@ -1292,6 +1230,11 @@ class XPaths(object):
         return ("D,/vnfr:vnfr-catalog/vnfr:vnfr" +
                 ("[vnfr:id='{}']".format(k) if k is not None else ""))
 
+    @staticmethod
+    def vnfd(k=None):
+        return ("C,/vnfd:vnfd-catalog/vnfd:vnfd" +
+                ("[vnfd:id='{}']".format(k) if k is not None else ""))
+
     @staticmethod
     def config_agent(k=None):
         return ("D,/rw-config-agent:config-agent/rw-config-agent:account" +
@@ -1299,11 +1242,13 @@ class XPaths(object):
 
     @staticmethod
     def nsr_config(k=None):
-        return ("C,/nsr:ns-instance-config/nsr:nsr[nsr:id='{}']".format(k) if k is not None else "")
+        return ("C,/nsr:ns-instance-config/nsr:nsr" +
+                ("[nsr:id='{}']".format(k) if k is not None else ""))
 
     @staticmethod
     def vlr(k=None):
-        return ("D,/vlr:vlr-catalog/vlr:vlr[vlr:id='{}']".format(k) if k is not None else "")
+        return ("D,/vlr:vlr-catalog/vlr:vlr" +
+                ("[vlr:id='{}']".format(k) if k is not None else ""))
 
 class ConfigManagerDTS(object):
     ''' This class either reads from DTS or publishes to DTS '''
@@ -1377,6 +1322,15 @@ class ConfigManagerDTS(object):
             vnfr_msg = vnfrl[0]
         return vnfr_msg
 
+    @asyncio.coroutine
+    def get_vnfd(self, vnfd_id):
+        self._log.debug("Attempting to get VNFD: %s", vnfd_id)
+        vnfdl = yield from self._read_dts(XPaths.vnfd(vnfd_id), do_trace=False)
+        vnfd_msg = None
+        if len(vnfdl) > 0:
+            vnfd_msg = vnfdl[0]
+        return vnfd_msg
+
     @asyncio.coroutine
     def get_vlr(self, id):
         self._log.debug("Attempting to get VLR subnet: %s", id)