diff options
-rw-r--r-- | sfc/lib/cleanup.py | 44 | ||||
-rw-r--r-- | sfc/lib/config.py | 2 | ||||
-rw-r--r-- | sfc/lib/openstack_tacker.py | 169 | ||||
-rw-r--r-- | sfc/lib/utils.py | 44 | ||||
-rw-r--r-- | sfc/tests/functest/config.yaml | 3 | ||||
-rw-r--r-- | sfc/tests/functest/register-vim.json | 18 | ||||
-rw-r--r-- | sfc/tests/functest/vnfd-templates/test-vnfd-default-params.json | 3 | ||||
-rw-r--r-- | sfc/tests/functest/vnfd-templates/test-vnfd-default-params.yaml | 5 | ||||
-rw-r--r-- | sfc/tests/functest/vnfd-templates/test-vnfd1.yaml | 2 | ||||
-rw-r--r-- | sfc/tests/functest/vnfd-templates/test-vnfd2.yaml | 2 | ||||
-rw-r--r-- | sfc/tests/functest/vnfd-templates/test2-vnfd1.yaml | 2 | ||||
-rw-r--r-- | sfc/tests/functest/vnfd-templates/test2-vnfd2.yaml | 2 | ||||
-rw-r--r-- | sfc/tests/functest/vnfd-templates/test3-vnfd1.yaml | 2 | ||||
-rw-r--r-- | sfc/tests/functest/vnffgd-templates/test-vnffgd.yaml | 14 | ||||
-rw-r--r-- | sfc/tests/functest/vnffgd-templates/test2-vnffgd1.yaml | 22 | ||||
-rw-r--r-- | sfc/tests/functest/vnffgd-templates/test2-vnffgd2.yaml | 22 |
16 files changed, 271 insertions, 85 deletions
diff --git a/sfc/lib/cleanup.py b/sfc/lib/cleanup.py index 6259736a..7936600b 100644 --- a/sfc/lib/cleanup.py +++ b/sfc/lib/cleanup.py @@ -1,8 +1,8 @@ import sys - +import time import logging import functest.utils.openstack_utils as os_utils -import functest.utils.openstack_tacker as os_tacker +import sfc.lib.openstack_tacker as os_tacker import sfc.lib.utils as utils @@ -44,24 +44,34 @@ def delete_vnfs(): os_tacker.delete_vnf(t, vnf_id=vnf) -def delete_sfcs(): +def delete_vnffgs(): + t = os_tacker.get_tacker_client() + vnffgs = os_tacker.list_vnffgs(t) + if vnffgs is None: + return + for vnffg in vnffgs: + logger.info("Removing vnffg: {0}".format(vnffg)) + os_tacker.delete_vnffg(t, vnffg_id=vnffg) + + +def delete_vnffgds(): t = os_tacker.get_tacker_client() - sfcs = os_tacker.list_sfcs(t) - if sfcs is None: + vnffgds = os_tacker.list_vnffgds(t) + if vnffgds is None: return - for sfc in sfcs: - logger.info("Removing sfc: {0}".format(sfc)) - os_tacker.delete_sfc(t, sfc_id=sfc) + for vnffgd in vnffgds: + logger.info("Removing vnffgd: {0}".format(vnffgd)) + os_tacker.delete_vnffgd(t, vnffgd_id=vnffgd) -def delete_sfc_clfs(): +def delete_vims(): t = os_tacker.get_tacker_client() - sfc_clfs = os_tacker.list_sfc_classifiers(t) - if sfc_clfs is None: + vims = os_tacker.list_vims(t) + if vims is None: return - for sfc_clf in sfc_clfs: - logger.info("Removing sfc classifier: {0}".format(sfc_clf)) - os_tacker.delete_sfc_classifier(t, sfc_clf_id=sfc_clf) + for vim in vims: + logger.info("Removing vim: {0}".format(vim)) + os_tacker.delete_vim(t, vim_id=vim) def delete_floating_ips(): @@ -102,10 +112,12 @@ def cleanup_odl(odl_ip, odl_port): def cleanup(odl_ip=None, odl_port=None): - delete_sfc_clfs() - delete_sfcs() + delete_vnffgs() + delete_vnffgds() delete_vnfs() + time.sleep(20) delete_vnfds() + delete_vims() delete_stacks() delete_floating_ips() delete_instances() diff --git a/sfc/lib/config.py b/sfc/lib/config.py index 16613eab..fe95e983 100644 --- a/sfc/lib/config.py +++ b/sfc/lib/config.py @@ -39,9 +39,11 @@ class CommonConfig(object): self.vnfd_dir = os.path.join(self.sfc_test_dir, "vnfd-templates") self.vnfd_default_params_file = os.path.join( self.sfc_test_dir, "vnfd-default-params-file") + self.vnffgd_dir = os.path.join(self.sfc_test_dir, "vnffgd-templates") self.functest_results_dir = os.path.join( CONST.dir_results, "odl-sfc") self.config_file = os.path.join(self.sfc_test_dir, "config.yaml") + self.vim_file = os.path.join(self.sfc_test_dir, "register-vim.json") self.installer_type = CONST.__getattribute__('INSTALLER_TYPE') diff --git a/sfc/lib/openstack_tacker.py b/sfc/lib/openstack_tacker.py index 7b0f79a2..df112c39 100644 --- a/sfc/lib/openstack_tacker.py +++ b/sfc/lib/openstack_tacker.py @@ -1,6 +1,8 @@ import logging import os import time +import json +import yaml from tackerclient.tacker import client as tackerclient from functest.utils import openstack_utils as os_utils @@ -41,6 +43,10 @@ def get_vnfd_id(tacker_client, vnfd_name): return get_id_from_name(tacker_client, 'vnfd', vnfd_name) +def get_vim_id(tacker_client, vim_name): + return get_id_from_name(tacker_client, 'vim', vim_name) + + def get_vnf_id(tacker_client, vnf_name, timeout=5): vnf_id = None while vnf_id is None and timeout >= 0: @@ -53,6 +59,22 @@ def get_vnf_id(tacker_client, vnf_name, timeout=5): return vnf_id +def get_vnffg_id(tacker_client, vnffg_name, timeout=5): + vnffg_id = None + while vnffg_id is None and timeout >= 0: + vnffg_id = get_id_from_name(tacker_client, 'vnffg', vnffg_name) + if vnffg_id is None: + logger.info("Could not retrieve ID for vnffg with name [%s]." + " Retrying." % vnffg_name) + time.sleep(1) + timeout -= 1 + return vnffg_id + + +def get_vnffgd_id(tacker_client, vnffgd_name): + return get_id_from_name(tacker_client, 'vnffgd', vnffgd_name) + + def list_vnfds(tacker_client, verbose=False): try: vnfds = tacker_client.list_vnfds(retrieve_all=True) @@ -64,7 +86,7 @@ def list_vnfds(tacker_client, verbose=False): return None -def create_vnfd(tacker_client, tosca_file=None): +def create_vnfd(tacker_client, tosca_file=None, vnfd_name=None): try: vnfd_body = {} if tosca_file is not None: @@ -72,7 +94,8 @@ def create_vnfd(tacker_client, tosca_file=None): vnfd_body = tosca_fd.read() logger.info('VNFD template:\n{0}'.format(vnfd_body)) return tacker_client.create_vnfd( - body={"vnfd": {"attributes": {"vnfd": vnfd_body}}}) + body={"vnfd": {"attributes": {"vnfd": vnfd_body}, + "name": vnfd_name}}) except Exception, e: logger.error("Error [create_vnfd(tacker_client, '%s')]: %s" % (tosca_file, e)) @@ -105,7 +128,7 @@ def list_vnfs(tacker_client, verbose=False): def create_vnf(tacker_client, vnf_name, vnfd_id=None, - vnfd_name=None, param_file=None): + vnfd_name=None, vim_id=None, vim_name=None, param_file=None): try: vnf_body = { 'vnf': { @@ -120,10 +143,15 @@ def create_vnf(tacker_client, vnf_name, vnfd_id=None, vnf_body['vnf']['attributes']['param_values'] = params if vnfd_id is not None: vnf_body['vnf']['vnfd_id'] = vnfd_id + if vim_id is not None: + vnf_body['vnf']['vim_id'] = vim_id else: if vnfd_name is None: raise Exception('vnfd id or vnfd name is required') vnf_body['vnf']['vnfd_id'] = get_vnfd_id(tacker_client, vnfd_name) + if vim_name is None: + raise Exception('vim id or vim name is required') + vnf_body['vnf']['vim_id'] = get_vim_id(tacker_client, vim_name) return tacker_client.create_vnf(body=vnf_body) except Exception, e: logger.error("error [create_vnf(tacker_client," @@ -151,7 +179,7 @@ def get_vnf(tacker_client, vnf_id=None, vnf_name=None): return None -def wait_for_vnf(tacker_client, vnf_id=None, vnf_name=None, timeout=60): +def wait_for_vnf(tacker_client, vnf_id=None, vnf_name=None, timeout=100): try: vnf = get_vnf(tacker_client, vnf_id, vnf_name) if vnf is None: @@ -188,3 +216,136 @@ def delete_vnf(tacker_client, vnf_id=None, vnf_name=None): logger.error("Error [delete_vnf(tacker_client, '%s', '%s')]: %s" % (vnf_id, vnf_name, e)) return None + + +def create_vim(tacker_client, vim_file=None): + try: + vim_body = {} + if vim_file is not None: + with open(vim_file) as vim_fd: + vim_body = json.load(vim_fd) + logger.info('VIM template:\n{0}'.format(vim_body)) + return tacker_client.create_vim(body=vim_body) + except Exception, e: + logger.error("Error [create_vim(tacker_client, '%s')]: %s" + % (vim_file, e)) + return None + + +def create_vnffgd(tacker_client, tosca_file=None, vnffgd_name=None): + try: + vnffgd_body = {} + if tosca_file is not None: + with open(tosca_file) as tosca_fd: + vnffgd_body = yaml.load(tosca_fd) + logger.info('VNFFGD template:\n{0}'.format(vnffgd_body)) + return tacker_client.create_vnffgd( + body={'vnffgd': {'name': vnffgd_name, + 'template': {'vnffgd': vnffgd_body}}}) + except Exception, e: + logger.error("Error [create_vnffgd(tacker_client, '%s')]: %s" + % (tosca_file, e)) + return None + + +def create_vnffg(tacker_client, vnffg_name=None, vnffgd_id=None, + vnffgd_name=None): + ''' + Tacker doesn't support Symmetrical chain and parameter file + in Openstack/Ocata + ''' + try: + vnffg_body = { + 'vnffg': { + 'attributes': {}, + 'name': vnffg_name + } + } + if vnffgd_id is not None: + vnffg_body['vnffg']['vnffgd_id'] = vnffgd_id + else: + if vnffgd_name is None: + raise Exception('vnffgd id or vnffgd name is required') + vnffg_body['vnffg']['vnffgd_id'] = get_vnffgd_id(tacker_client, + vnffgd_name) + return tacker_client.create_vnffg(body=vnffg_body) + except Exception, e: + logger.error("error [create_vnffg(tacker_client," + " '%s', '%s', '%s')]: %s" + % (vnffg_name, vnffgd_id, vnffgd_name, e)) + return None + + +def list_vnffgds(tacker_client, verbose=False): + try: + vnffgds = tacker_client.list_vnffgds(retrieve_all=True) + if not verbose: + vnffgds = [vnffgd['id'] for vnffgd in vnffgds['vnffgds']] + return vnffgds + except Exception, e: + logger.error("Error [list_vnffgds(tacker_client)]: %s" % e) + return None + + +def list_vnffgs(tacker_client, verbose=False): + try: + vnffgs = tacker_client.list_vnffgs(retrieve_all=True) + if not verbose: + vnffgs = [vnffg['id'] for vnffg in vnffgs['vnffgs']] + return vnffgs + except Exception, e: + logger.error("Error [list_vnffgs(tacker_client)]: %s" % e) + return None + + +def delete_vnffg(tacker_client, vnffg_id=None, vnffg_name=None): + try: + vnffg = vnffg_id + if vnffg is None: + if vnffg_name is None: + raise Exception('You need to provide a VNFFG id or name') + vnffg = get_vnffg_id(tacker_client, vnffg_name) + return tacker_client.delete_vnffg(vnffg) + except Exception, e: + logger.error("Error [delete_vnffg(tacker_client, '%s', '%s')]: %s" + % (vnffg_id, vnffg_name, e)) + return None + + +def delete_vnffgd(tacker_client, vnffgd_id=None, vnffgd_name=None): + try: + vnffgd = vnffgd_id + if vnffgd is None: + if vnffgd_name is None: + raise Exception('You need to provide VNFFGD id or VNFFGD name') + vnffgd = get_vnffgd_id(tacker_client, vnffgd_name) + return tacker_client.delete_vnffgd(vnffgd) + except Exception, e: + logger.error("Error [delete_vnffgd(tacker_client, '%s', '%s')]: %s" + % (vnffgd_id, vnffgd_name, e)) + return None + + +def list_vims(tacker_client, verbose=False): + try: + vims = tacker_client.list_vims(retrieve_all=True) + if not verbose: + vims = [vim['id'] for vim in vims['vims']] + return vims + except Exception, e: + logger.error("Error [list_vims(tacker_client)]: %s" % e) + return None + + +def delete_vim(tacker_client, vim_id=None, vim_name=None): + try: + vim = vim_id + if vim is None: + if vim_name is None: + raise Exception('You need to provide VIM id or VIM name') + vim = get_vim_id(tacker_client, vim_name) + return tacker_client.delete_vim(vim) + except Exception, e: + logger.error("Error [delete_vim(tacker_client, '%s', '%s')]: %s" + % (vim_id, vim_name, e)) + return None diff --git a/sfc/lib/utils.py b/sfc/lib/utils.py index cb831969..aacce25f 100644 --- a/sfc/lib/utils.py +++ b/sfc/lib/utils.py @@ -13,13 +13,13 @@ import re import subprocess import requests import time -import yaml - +import json import logging +from functest.utils.constants import CONST import functest.utils.functest_utils as ft_utils import functest.utils.openstack_utils as os_utils -import functest.utils.openstack_tacker as os_tacker +import sfc.lib.openstack_tacker as os_tacker logger = logging.getLogger(__name__) @@ -79,28 +79,27 @@ def get_av_zones(): def create_vnf_in_av_zone( - tacker_client, vnf_name, vnfd_name, default_param_file, av_zone=None): + tacker_client, + vnf_name, + vnfd_name, + vim_name, + default_param_file, + av_zone=None): param_file = default_param_file if av_zone is not None or av_zone != 'nova': param_file = os.path.join( '/tmp', - 'param_{0}.yaml'.format(av_zone.replace('::', '_'))) + 'param_{0}.json'.format(av_zone.replace('::', '_'))) data = { - 'vdus': { - 'vdu1': { - 'param': { - 'zone': av_zone - } - } - } - } + 'zone': av_zone + } with open(param_file, 'w+') as f: - yaml.dump(data, f, default_flow_style=False) - + json.dump(data, f) os_tacker.create_vnf(tacker_client, vnf_name, vnfd_name=vnfd_name, + vim_name=vim_name, param_file=param_file) @@ -639,3 +638,18 @@ def fill_installer_dict(installer_type): "pkey_file": default_string+"pkey_file" } return installer_yaml_fields + + +def register_vim(tacker_client, vim_file=None): + tmp_file = '/tmp/register-vim.json' + if vim_file is not None: + with open(vim_file) as f: + json_dict = json.load(f) + + json_dict['vim']['auth_url'] = CONST.__getattribute__('OS_AUTH_URL') + json_dict['vim']['auth_cred']['password'] = CONST.__getattribute__( + 'OS_PASSWORD') + + json.dump(json_dict, open(tmp_file, 'w')) + + os_tacker.create_vim(tacker_client, vim_file=tmp_file) diff --git a/sfc/tests/functest/config.yaml b/sfc/tests/functest/config.yaml index fdb94caf..a37642d3 100644 --- a/sfc/tests/functest/config.yaml +++ b/sfc/tests/functest/config.yaml @@ -36,6 +36,8 @@ testcases: secgroup_descr: "Example Security group" test_vnfd_red: "test-vnfd1.yaml" test_vnfd_blue: "test-vnfd2.yaml" + test_vnffgd_red: "test2-vnffgd1.yaml" + test_vnffgd_blue: "test2-vnffgd2.yaml" sfc_one_chain_two_service_functions: enabled: true @@ -49,6 +51,7 @@ testcases: secgroup_descr: "Example Security group" test_vnfd_red: "test2-vnfd1.yaml" test_vnfd_blue: "test2-vnfd2.yaml" + test_vnffgd_red: "test-vnffgd.yaml" sfc_symmetric_chain: enabled: false diff --git a/sfc/tests/functest/register-vim.json b/sfc/tests/functest/register-vim.json new file mode 100644 index 00000000..00719449 --- /dev/null +++ b/sfc/tests/functest/register-vim.json @@ -0,0 +1,18 @@ +{ + "vim": { + "vim_project": { + "project_domain_name": "Default", + "id": "", + "name": "admin" + }, + "auth_cred": { + "username": "admin", + "user_domain_name": "Default", + "password": "", + "user_id": "" + }, + "auth_url": "", + "type": "openstack", + "name": "test-vim" + } +} diff --git a/sfc/tests/functest/vnfd-templates/test-vnfd-default-params.json b/sfc/tests/functest/vnfd-templates/test-vnfd-default-params.json new file mode 100644 index 00000000..4715461b --- /dev/null +++ b/sfc/tests/functest/vnfd-templates/test-vnfd-default-params.json @@ -0,0 +1,3 @@ +{ +zone: 'nova' +} diff --git a/sfc/tests/functest/vnfd-templates/test-vnfd-default-params.yaml b/sfc/tests/functest/vnfd-templates/test-vnfd-default-params.yaml deleted file mode 100644 index d87e6e68..00000000 --- a/sfc/tests/functest/vnfd-templates/test-vnfd-default-params.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -vdus: - vdu1: - param: - zone: nova diff --git a/sfc/tests/functest/vnfd-templates/test-vnfd1.yaml b/sfc/tests/functest/vnfd-templates/test-vnfd1.yaml index 277c1880..3fbc22ae 100644 --- a/sfc/tests/functest/vnfd-templates/test-vnfd1.yaml +++ b/sfc/tests/functest/vnfd-templates/test-vnfd1.yaml @@ -42,7 +42,7 @@ topology_template: properties: management: true order: 0 - anti_spoofing_protection: false + anti_spoofing_protection: true requirements: - virtualLink: node: VL1 diff --git a/sfc/tests/functest/vnfd-templates/test-vnfd2.yaml b/sfc/tests/functest/vnfd-templates/test-vnfd2.yaml index 337e9412..8f442a90 100644 --- a/sfc/tests/functest/vnfd-templates/test-vnfd2.yaml +++ b/sfc/tests/functest/vnfd-templates/test-vnfd2.yaml @@ -43,7 +43,7 @@ topology_template: properties: management: true order: 0 - anti_spoofing_protection: false + anti_spoofing_protection: true requirements: - virtualLink: node: VL1 diff --git a/sfc/tests/functest/vnfd-templates/test2-vnfd1.yaml b/sfc/tests/functest/vnfd-templates/test2-vnfd1.yaml index cc5fed6f..aad1e672 100644 --- a/sfc/tests/functest/vnfd-templates/test2-vnfd1.yaml +++ b/sfc/tests/functest/vnfd-templates/test2-vnfd1.yaml @@ -42,7 +42,7 @@ topology_template: properties: management: true order: 0 - anti_spoofing_protection: false + anti_spoofing_protection: true requirements: - virtualLink: node: VL1 diff --git a/sfc/tests/functest/vnfd-templates/test2-vnfd2.yaml b/sfc/tests/functest/vnfd-templates/test2-vnfd2.yaml index f8150f25..b5502f12 100644 --- a/sfc/tests/functest/vnfd-templates/test2-vnfd2.yaml +++ b/sfc/tests/functest/vnfd-templates/test2-vnfd2.yaml @@ -42,7 +42,7 @@ topology_template: properties: management: true order: 0 - anti_spoofing_protection: false + anti_spoofing_protection: true requirements: - virtualLink: node: VL1 diff --git a/sfc/tests/functest/vnfd-templates/test3-vnfd1.yaml b/sfc/tests/functest/vnfd-templates/test3-vnfd1.yaml index 26491b5e..7f8f1787 100644 --- a/sfc/tests/functest/vnfd-templates/test3-vnfd1.yaml +++ b/sfc/tests/functest/vnfd-templates/test3-vnfd1.yaml @@ -39,7 +39,7 @@ topology_template: properties: management: true order: 0 - anti_spoofing_protection: false + anti_spoofing_protection: true requirements: - virtualLink: node: VL1 diff --git a/sfc/tests/functest/vnffgd-templates/test-vnffgd.yaml b/sfc/tests/functest/vnffgd-templates/test-vnffgd.yaml index 9c696d2e..b1fd574c 100644 --- a/sfc/tests/functest/vnffgd-templates/test-vnffgd.yaml +++ b/sfc/tests/functest/vnffgd-templates/test-vnffgd.yaml @@ -4,11 +4,6 @@ description: test-case1 topology_template: description: topology-template-test1 - inputs: - net_src_id: - type: string - ip_dst: - type: string node_templates: Forwarding_path1: @@ -19,14 +14,13 @@ topology_template: policy: type: ACL criteria: - - network_src_port_id: {get_input: net_src_id} + - source_port_range: 0-0 - destination_port_range: 80-80 - ip_proto: 6 - - ip_dst_prefix: {get_input: ip_dst} path: - - forwarder: VNFD1 + - forwarder: test-vnfd1 capability: CP1 - - forwarder: VNFD2 + - forwarder: test-vnfd2 capability: CP1 groups: @@ -39,5 +33,5 @@ topology_template: number_of_endpoints: 2 dependent_virtual_link: [VL1, VL1] connection_point: [CP1, CP1] - constituent_vnfs: [VNFD1, VNFD2] + constituent_vnfs: [test-vnfd1, test-vnfd2] members: [Forwarding_path1] diff --git a/sfc/tests/functest/vnffgd-templates/test2-vnffgd1.yaml b/sfc/tests/functest/vnffgd-templates/test2-vnffgd1.yaml index a15e94cc..24a32db4 100644 --- a/sfc/tests/functest/vnffgd-templates/test2-vnffgd1.yaml +++ b/sfc/tests/functest/vnffgd-templates/test2-vnffgd1.yaml @@ -4,11 +4,6 @@ description: test-case2_HTTP Test topology_template: description: topology-template-test2 - inputs: - net_src_id: - type: string - ip_dst: - type: string node_templates: Forwarding_path1: @@ -19,14 +14,11 @@ topology_template: policy: type: ACL criteria: - - network_src_port_id: {get_input: net_src_id} - - destination_port_range: 80-80 + - source_port_range: 0-0 + - destination_port_range: 22-80 - ip_proto: 6 - - ip_dst_prefix: {get_input: ip_dst} path: - - forwarder: VNFD1 - capability: CP1 - - forwarder: VNFD2 + - forwarder: test-vnfd1 capability: CP1 groups: @@ -36,8 +28,8 @@ topology_template: properties: vendor: tacker version: 1.0 - number_of_endpoints: 2 - dependent_virtual_link: [VL1, VL1] - connection_point: [CP1, CP1] - constituent_vnfs: [VNFD1, VNFD2] + number_of_endpoints: 1 + dependent_virtual_link: [VL1] + connection_point: [CP1] + constituent_vnfs: [test-vnfd1] members: [Forwarding_path1] diff --git a/sfc/tests/functest/vnffgd-templates/test2-vnffgd2.yaml b/sfc/tests/functest/vnffgd-templates/test2-vnffgd2.yaml index 80a41edd..401a4272 100644 --- a/sfc/tests/functest/vnffgd-templates/test2-vnffgd2.yaml +++ b/sfc/tests/functest/vnffgd-templates/test2-vnffgd2.yaml @@ -4,11 +4,6 @@ description: test-case2_SSH Test topology_template: description: topology-template-test2 - inputs: - net_src_id: - type: string - ip_dst: - type: string node_templates: Forwarding_path1: @@ -19,14 +14,11 @@ topology_template: policy: type: ACL criteria: - - network_src_port_id: {get_input: net_src_id} - - destination_port_range: 22-22 + - source_port_range: 0-0 + - destination_port_range: 22-80 - ip_proto: 6 - - ip_dst_prefix: {get_input: ip_dst} path: - - forwarder: VNFD1 - capability: CP1 - - forwarder: VNFD2 + - forwarder: test-vnfd2 capability: CP1 groups: @@ -36,8 +28,8 @@ topology_template: properties: vendor: tacker version: 1.0 - number_of_endpoints: 2 - dependent_virtual_link: [VL1, VL1] - connection_point: [CP1, CP1] - constituent_vnfs: [VNFD1, VNFD2] + number_of_endpoints: 1 + dependent_virtual_link: [VL1] + connection_point: [CP1] + constituent_vnfs: [test-vnfd2] members: [Forwarding_path1] |