diff --git a/.zuul.yaml b/.zuul.yaml index 685bbf716..eaa3e4331 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -273,6 +273,12 @@ Multinodes job for SOL V2 devstack-based functional tests host-vars: controller-tacker: + devstack_local_conf: + post-config: + # Skip notification endpoint testing in create subscription + $TACKER_CONF: + v2_nfvo: + test_callback_uri: False tox_envlist: dsvm-functional-sol-v2 - job: diff --git a/tacker/sol_refactored/controller/vnflcm_v2.py b/tacker/sol_refactored/controller/vnflcm_v2.py index 6dfbb4b21..bd2046678 100644 --- a/tacker/sol_refactored/controller/vnflcm_v2.py +++ b/tacker/sol_refactored/controller/vnflcm_v2.py @@ -81,19 +81,24 @@ class VnfLcmControllerV2(sol_wsgi.SolAPIController): vnfSoftwareVersion=pkg_info.vnfSoftwareVersion, vnfdVersion=pkg_info.vnfdVersion, instantiationState='NOT_INSTANTIATED', - # optional fields - # NOTE: it is OK to absent but fill empty value to make them - # handle easy. - vnfInstanceName=body.get('vnfInstanceName', ""), - vnfInstanceDescription=body.get('vnfInstanceDescription', ""), - vnfConfigurableProperties=vnfd_prop['vnfConfigurableProperties'], - metadata=metadata, - extensions=vnfd_prop['extensions'] # not set at the moment. will be set when instantiate. # vimConnectionInfo # instantiatedVnfInfo ) + # set optional fields + if body.get('vnfInstanceName'): + inst.vnfInstanceName = body['vnfInstanceName'] + if body.get('vnfInstanceDescription'): + inst.vnfInstanceDescription = body['vnfInstanceDescription'] + if vnfd_prop.get('vnfConfigurableProperties'): + inst.vnfConfigurableProperties = vnfd_prop[ + 'vnfConfigurableProperties'] + if metadata: + inst.metadata = metadata + if vnfd_prop.get('extensions'): + inst.extensions = vnfd_prop['extensions'] + inst.create(context) self.nfvo_client.send_inst_create_notification(context, inst, diff --git a/tacker/tests/functional/sol_v2/base_v2.py b/tacker/tests/functional/sol_v2/base_v2.py index d875a18fe..f7356294e 100644 --- a/tacker/tests/functional/sol_v2/base_v2.py +++ b/tacker/tests/functional/sol_v2/base_v2.py @@ -17,6 +17,7 @@ import os import shutil import tempfile import time +import urllib import yaml from oslo_config import cfg @@ -38,7 +39,7 @@ VNF_PACKAGE_UPLOAD_TIMEOUT = 300 class BaseSolV2Test(base.BaseTestCase): - """Base test case class for SOL v2 functionl tests.""" + """Base test case class for SOL v2 functional tests.""" @classmethod def setUpClass(cls): @@ -63,6 +64,10 @@ class BaseSolV2Test(base.BaseTestCase): cls.tacker_client = http_client.HttpClient(auth) cls.neutron_client = http_client.HttpClient(auth, service_type='network') + cls.glance_client = http_client.HttpClient(auth, + service_type='image') + cls.nova_client = http_client.HttpClient(auth, + service_type='compute') cls.heat_client = heat_utils.HeatClient(vim_info) @classmethod @@ -138,6 +143,11 @@ class BaseSolV2Test(base.BaseTestCase): cls.tacker_client.do_request(path, "DELETE") + def get_vnf_package(self, pkg_id): + path = "/vnfpkgm/v1/vnf_packages/{}".format(pkg_id) + resp, body = self.tacker_client.do_request(path, "GET") + return body + def get_network_ids(self, networks): path = "/v2.0/networks" resp, body = self.neutron_client.do_request(path, "GET") @@ -156,6 +166,100 @@ class BaseSolV2Test(base.BaseTestCase): subnet_ids[subnet['name']] = subnet['id'] return subnet_ids + def create_network(self, name): + path = "/v2.0/networks" + req_body = { + "network": { + # "admin_state_up": true, + "name": name + } + } + try: + resp, resp_body = self.neutron_client.do_request( + path, "POST", body=req_body) + return resp_body['network']['id'] + except Exception as e: + self.fail("Failed, create network for name=<%s>, %s" % + (name, e)) + + def delete_network(self, net_id): + path = "/v2.0/networks/{}".format(net_id) + try: + self.neutron_client.do_request(path, "DELETE") + except Exception as e: + self.fail("Failed, delete network for id=<%s>, %s" % + (net_id, e)) + + def create_subnet(self, net_id, sub_name, sub_range, ip_version): + path = "/v2.0/subnets" + req_body = { + "subnet": { + "name": sub_name, + "network_id": net_id, + "cidr": sub_range, + "ip_version": ip_version + } + } + try: + resp, resp_body = self.neutron_client.do_request( + path, "POST", body=req_body) + return resp_body['subnet']['id'] + except Exception as e: + self.fail("Failed, create subnet for name=<%s>, %s" % + (sub_name, e)) + + def delete_subnet(self, sub_id): + path = "/v2.0/subnets/{}".format(sub_id) + try: + self.neutron_client.do_request(path, "DELETE") + except Exception as e: + self.fail("Failed, delete subnet for id=<%s>, %s" % + (sub_id, e)) + + def create_port(self, network_id, name): + path = "/v2.0/ports" + req_body = { + 'port': { + 'network_id': network_id, + 'name': name + } + } + try: + resp, resp_body = self.neutron_client.do_request( + path, "POST", body=req_body) + return resp_body['port']['id'] + except Exception as e: + self.fail("Failed, create port for net_id=<%s>, %s" % + (network_id, e)) + + def delete_port(self, port_id): + path = "/v2.0/ports/{}".format(port_id) + try: + self.neutron_client.do_request(path, "DELETE") + except Exception as e: + self.fail("Failed, delete port for id=<%s>, %s" % + (port_id, e)) + + def get_image_id(self, image_name): + path = "/v2.0/images" + resp, resp_body = self.glance_client.do_request(path, "GET") + + image_id = None + for image in resp_body.get('images'): + if image_name == image['name']: + image_id = image['id'] + return image_id + + def get_server_details(self, server_name): + path = "/servers/detail" + resp, resp_body = self.nova_client.do_request(path, "GET") + + server_details = None + for server in resp_body.get('servers'): + if server_name == server['name']: + server_details = server + return server_details + def create_vnf_instance(self, req_body): path = "/vnflcm/v2/vnf_instances" return self.tacker_client.do_request( @@ -171,6 +275,13 @@ class BaseSolV2Test(base.BaseTestCase): return self.tacker_client.do_request( path, "GET", version="2.0.0") + def list_vnf_instance(self, filter_expr=None): + path = "/vnflcm/v2/vnf_instances" + if filter_expr: + path = "{}?{}".format(path, urllib.parse.urlencode(filter_expr)) + return self.tacker_client.do_request( + path, "GET", version="2.0.0") + def instantiate_vnf_instance(self, inst_id, req_body): path = "/vnflcm/v2/vnf_instances/{}/instantiate".format(inst_id) return self.tacker_client.do_request( @@ -196,3 +307,92 @@ class BaseSolV2Test(base.BaseTestCase): continue else: # FAILED_TEMP or ROLLED_BACK raise Exception("Operation failed. state: %s" % state) + + def wait_lcmocc_failed_temp(self, lcmocc_id): + # NOTE: It is not necessary to set timeout because the operation + # itself set timeout and the state will become 'FAILED_TEMP'. + path = "/vnflcm/v2/vnf_lcm_op_occs/{}".format(lcmocc_id) + while True: + time.sleep(5) + _, body = self.tacker_client.do_request( + path, "GET", expected_status=[200], version="2.0.0") + state = body['operationState'] + if state == 'FAILED_TEMP': + return + elif state in ['STARTING', 'PROCESSING']: + continue + elif state == 'COMPLETED': + raise Exception("Operation unexpected COMPLETED.") + else: # ROLLED_BACK + raise Exception("Operation failed. state: %s" % state) + + def show_lcmocc(self, lcmocc_id): + path = "/vnflcm/v2/vnf_lcm_op_occs/{}".format(lcmocc_id) + return self.tacker_client.do_request( + path, "GET", version="2.0.0") + + def list_lcmocc(self, filter_expr=None): + path = "/vnflcm/v2/vnf_lcm_op_occs" + if filter_expr: + path = "{}?{}".format(path, urllib.parse.urlencode(filter_expr)) + return self.tacker_client.do_request( + path, "GET", version="2.0.0") + + def create_subscription(self, req_body): + path = "/vnflcm/v2/subscriptions" + return self.tacker_client.do_request( + path, "POST", body=req_body, version="2.0.0") + + def delete_subscription(self, sub_id): + path = "/vnflcm/v2/subscriptions/{}".format(sub_id) + return self.tacker_client.do_request( + path, "DELETE", version="2.0.0") + + def show_subscription(self, sub_id): + path = "/vnflcm/v2/subscriptions/{}".format(sub_id) + return self.tacker_client.do_request( + path, "GET", version="2.0.0") + + def list_subscriptions(self, filter_expr=None): + path = "/vnflcm/v2/subscriptions" + if filter_expr: + path = "{}?{}".format(path, urllib.parse.urlencode(filter_expr)) + return self.tacker_client.do_request( + path, "GET", version="2.0.0") + + def _check_resp_headers(self, resp, supported_headers): + unsupported_headers = ['Link', 'Retry-After', + 'Content-Range', 'WWW-Authenticate'] + for s in supported_headers: + if s not in resp.headers: + raise Exception("Supported header doesn't exist: %s" % s) + for u in unsupported_headers: + if u in resp.headers: + raise Exception("Unsupported header exist: %s" % u) + + def check_resp_headers_in_create(self, resp): + # includes location header and response body + supported_headers = ['Version', 'Location', 'Content-Type', + 'Accept-Ranges'] + self._check_resp_headers(resp, supported_headers) + + def check_resp_headers_in_operation_task(self, resp): + # includes location header and no response body + supported_headers = ['Version', 'Location'] + self._check_resp_headers(resp, supported_headers) + + def check_resp_headers_in_get(self, resp): + # includes response body and no location header + supported_headers = ['Version', 'Content-Type', + 'Accept-Ranges'] + self._check_resp_headers(resp, supported_headers) + + def check_resp_headers_in_delete(self, resp): + # no location header and response body + supported_headers = ['Version'] + self._check_resp_headers(resp, supported_headers) + + def check_resp_body(self, body, expected_attrs): + for attr in expected_attrs: + if attr not in body: + raise Exception("Expected attribute doesn't exist: %s" % attr) diff --git a/tacker/tests/functional/sol_v2/paramgen.py b/tacker/tests/functional/sol_v2/paramgen.py index 39e22edc1..21ea69d61 100644 --- a/tacker/tests/functional/sol_v2/paramgen.py +++ b/tacker/tests/functional/sol_v2/paramgen.py @@ -16,226 +16,451 @@ from oslo_utils import uuidutils +def sub1_create(): + # All attributes are set. + # NOTE: All of the following cardinality attributes are set. + # In addition, 0..N or 1..N attributes are set to 2 or more. + # - 0..1 (1) + # - 0..N (2 or more) + # - 1 + # - 1..N (2 or more) + vnf_provider_1 = { + "vnfProvider": "dummy-vnfProvider-1", + "vnfProducts": [ + { + "vnfProductName": "dummy-vnfProductName-1-1", + "versions": [ + { + "vnfSoftwareVersion": 1.0, + "vnfdVersions": [1.0, 2.0] + }, + { + "vnfSoftwareVersion": 1.1, + "vnfdVersions": [1.1, 2.1] + }, + ] + }, + { + "vnfProductName": "dummy-vnfProductName-1-2", + "versions": [ + { + "vnfSoftwareVersion": 1.0, + "vnfdVersions": [1.0, 2.0] + }, + { + "vnfSoftwareVersion": 1.1, + "vnfdVersions": [1.1, 2.1] + }, + ] + } + ] + } + vnf_provider_2 = { + "vnfProvider": "dummy-vnfProvider-2", + "vnfProducts": [ + { + "vnfProductName": "dummy-vnfProductName-2-1", + "versions": [ + { + "vnfSoftwareVersion": 1.0, + "vnfdVersions": [1.0, 2.0] + }, + { + "vnfSoftwareVersion": 1.1, + "vnfdVersions": [1.1, 2.1] + }, + ] + }, + { + "vnfProductName": "dummy-vnfProductName-2-2", + "versions": [ + { + "vnfSoftwareVersion": 1.0, + "vnfdVersions": [1.0, 2.0] + }, + { + "vnfSoftwareVersion": 1.1, + "vnfdVersions": [1.1, 2.1] + }, + ] + } + ] + } + + # NOTE: The following is omitted because authType is BASIC in this case + # - "paramsOauth2ClientCredentials" + return { + "filter": { + "vnfInstanceSubscriptionFilter": { + "vnfdIds": [ + "dummy-vnfdId-1", + "dummy-vnfdId-2" + ], + "vnfProductsFromProviders": [ + vnf_provider_1, + vnf_provider_2 + ], + "vnfInstanceIds": [ + "dummy-vnfInstanceId-1", + "dummy-vnfInstanceId-2" + ], + "vnfInstanceNames": [ + "dummy-vnfInstanceName-1", + "dummy-vnfInstanceName-2" + ] + }, + "notificationTypes": [ + "VnfIdentifierCreationNotification", + "VnfLcmOperationOccurrenceNotification" + ], + "operationTypes": [ + "INSTANTIATE", + "TERMINATE" + ], + "operationStates": [ + "COMPLETED", + "FAILED" + ] + }, + "callbackUri": "http://127.0.0.1/", + "authentication": { + "authType": [ + "BASIC" + ], + "paramsBasic": { + "password": "test_pass", + "userName": "test_user" + }, + # "paramsOauth2ClientCredentials": omitted, + }, + "verbosity": "SHORT" + } + + +def sub2_create(): + # Omit except for required attributes + # NOTE: Only the following cardinality attributes are set. + # - 1 + # - 1..N (1) + return { + "callbackUri": "http://127.0.0.1/" + } + + def sample1_create(vnfd_id): + # All attributes are set. + # NOTE: All of the following cardinality attributes are set. + # In addition, 0..N or 1..N attributes are set to 2 or more. + # - 0..1 (1) + # - 0..N (2 or more) + # - 1 + # - 1..N (2 or more) return { "vnfdId": vnfd_id, "vnfInstanceName": "sample1", - "vnfInstanceDescription": "test sample1" + "vnfInstanceDescription": "test sample1", + "metadata": {"dummy-key": "dummy-val"} } def sample1_terminate(): + # All attributes are set. + # NOTE: All of the following cardinality attributes are set. + # In addition, 0..N or 1..N attributes are set to 2 or more. + # - 0..1 (1) + # - 0..N (2 or more) + # - 1 + # - 1..N (2 or more) + return { + "terminationType": "GRACEFUL", + "gracefulTerminationTimeout": 5, + "additionalParams": {"dummy-key": "dummy-val"} + } + + +def sample1_instantiate(net_ids, subnets, ports, auth_url): + # All attributes are set. + # NOTE: All of the following cardinality attributes are set. + # In addition, 0..N or 1..N attributes are set to 2 or more. + # - 0..1 (1) + # - 0..N (2 or more) + # - 1 + # - 1..N (2 or more) + + vim_id_1 = uuidutils.generate_uuid() + vim_id_2 = uuidutils.generate_uuid() + link_port_id_1 = uuidutils.generate_uuid() + link_port_id_2 = uuidutils.generate_uuid() + + # NOTE: The following is not supported so it is omitted + # - "segmentationId" + # - "addressRange" + # - Multiple "cpProtocolData" + # - Multiple "fixedAddresses" + ext_vl_1 = { + "id": uuidutils.generate_uuid(), + "vimConnectionId": vim_id_1, + "resourceProviderId": "Company", + "resourceId": net_ids['net0'], + "extCps": [ + { + "cpdId": "VDU1_CP1", + "cpConfig": { + "VDU1_CP1": { + "parentCpConfigId": uuidutils.generate_uuid(), + # "linkPortId": omitted, + "cpProtocolData": [{ + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + # "macAddress": omitted, + # "segmentationId": omitted, + "ipAddresses": [{ + "type": "IPV4", + # "fixedAddresses": omitted, + "numDynamicAddresses": 1, + # "addressRange": omitted, + "subnetId": subnets['subnet0']}]}}] + }, + # { "VDU1_CP1_2": omitted } + } + }, + { + "cpdId": "VDU2_CP1-1", + "cpConfig": { + "VDU2_CP1-1": { + "parentCpConfigId": uuidutils.generate_uuid(), + "linkPortId": link_port_id_1, + "cpProtocolData": [{ + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + # "macAddress": omitted, + # "segmentationId": omitted, + "ipAddresses": [{ + "type": "IPV4", + # "fixedAddresses": omitted, + "numDynamicAddresses": 1, + # "addressRange": omitted, + "subnetId": subnets['subnet0'] + }] + } + }] + }, + # { "VDU2_CP1_2": omitted } + } + }, + { + "cpdId": "VDU2_CP1-2", + "cpConfig": { + "VDU2_CP1-2": { + "parentCpConfigId": uuidutils.generate_uuid(), + "linkPortId": link_port_id_2, + "cpProtocolData": [{ + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + # "macAddress": omitted, + # "segmentationId": omitted, + "ipAddresses": [{ + "type": "IPV4", + # "fixedAddresses": omitted, + "numDynamicAddresses": 1, + # "addressRange": omitted, + "subnetId": subnets['subnet0'] + }] + } + }] + }, + # { "VDU2_CP1_2": omitted } + } + } + ], + "extLinkPorts": [ + { + "id": link_port_id_1, + "resourceHandle": { + "resourceId": ports['VDU2_CP1-1'] + } + }, + # NOTE: Set dummy value because it is set by "additionalParams" + { + "id": link_port_id_2, + "resourceHandle": { + "resourceId": "dummy-id" + } + } + ] + } + + # NOTE: The following is not supported so it is omitted + # - "segmentationId" + # - "addressRange" + # - Multiple "cpProtocolData" + # - Multiple "fixedAddresses" + ext_vl_2 = { + "id": uuidutils.generate_uuid(), + "vimConnectionId": vim_id_1, + "resourceProviderId": "Company", + "resourceId": net_ids['ft-net0'], + "extCps": [ + { + "cpdId": "VDU1_CP2", + "cpConfig": { + "VDU1_CP2": { + "parentCpConfigId": uuidutils.generate_uuid(), + # "linkPortId": omitted, + "cpProtocolData": [{ + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + # "macAddress": omitted, + # "segmentationId": omitted, + "ipAddresses": [{ + "type": "IPV4", + # "fixedAddresses": omitted, + "numDynamicAddresses": 1, + # "addressRange": omitted, + "subnetId": subnets['ft-ipv4-subnet0']} + ]} + }] + }, + # { "VDU1_CP2_2": omitted } + } + }, + { + "cpdId": "VDU2_CP2", + "cpConfig": { + "VDU2_CP2": { + "parentCpConfigId": uuidutils.generate_uuid(), + # "linkPortId": omitted, + "cpProtocolData": [{ + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + "macAddress": "fa:16:3e:fa:22:75", + # "segmentationId": omitted, + "ipAddresses": [{ + "type": "IPV4", + "fixedAddresses": [ + "100.100.100.11", + # omitted + ], + # "numDynamicAddresses": omitted, + # "addressRange": omitted, + "subnetId": subnets['ft-ipv4-subnet0'] + }, { + "type": "IPV6", + # "fixedAddresses": omitted, + # "numDynamicAddresses": omitted, + "numDynamicAddresses": 1, + # "addressRange": omitted, + "subnetId": subnets['ft-ipv6-subnet0'] + }] + } + }] + }, + # { "VDU2_CP2_2": omitted } + } + } + ] + # "extLinkPorts": omitted + } + # NOTE: "vnfLinkPort" is omitted because it is not supported + ext_mngd_vl_1 = { + "id": uuidutils.generate_uuid(), + "vnfVirtualLinkDescId": "internalVL1", + "vimConnectionId": vim_id_1, + "resourceProviderId": "Company", + "resourceId": net_ids['net_mgmt'], + # "vnfLinkPort": omitted, + "extManagedMultisiteVirtualLinkId": uuidutils.generate_uuid() + } + # NOTE: "vnfLinkPort" is omitted because it is not supported + ext_mngd_vl_2 = { + "id": uuidutils.generate_uuid(), + "vnfVirtualLinkDescId": "internalVL2", + "vimConnectionId": vim_id_1, + "resourceProviderId": "Company", + "resourceId": net_ids['net1'], + # "vnfLinkPort": omitted, + "extManagedMultisiteVirtualLinkId": uuidutils.generate_uuid() + } + vim_1 = { + "vimId": vim_id_1, + "vimType": "ETSINFV.OPENSTACK_KEYSTONE.V_3", + "interfaceInfo": {"endpoint": auth_url}, + "accessInfo": { + "username": "nfv_user", + "region": "RegionOne", + "password": "devstack", + "project": "nfv", + "projectDomain": "Default", + "userDomain": "Default" + }, + "extra": {"dummy-key": "dummy-val"} + } + vim_2 = { + "vimId": vim_id_2, + "vimType": "ETSINFV.OPENSTACK_KEYSTONE.V_3", + "interfaceInfo": {"endpoint": auth_url}, + "accessInfo": { + "username": "dummy_user", + "region": "RegionOne", + "password": "dummy_password", + "project": "dummy_project", + "projectDomain": "Default", + "userDomain": "Default" + }, + "extra": {"dummy-key": "dummy-val"} + } + addParams = { + "lcm-operation-user-data": "./UserData/userdata.py", + "lcm-operation-user-data-class": "UserData", + "nfv": {"CP": {"VDU2_CP1-2": {"port": ports['VDU2_CP1-2']}}} + } + + return { + "flavourId": "simple", + "instantiationLevelId": "instantiation_level_1", + "extVirtualLinks": [ + ext_vl_1, + ext_vl_2 + ], + "extManagedVirtualLinks": [ + ext_mngd_vl_1, + ext_mngd_vl_2 + ], + "vimConnectionInfo": { + "vim1": vim_1, + "vim2": vim_2 + }, + "localizationLanguage": "ja", + "additionalParams": addParams, + "extensions": {"dummy-key": "dummy-val"} + } + + +def sample2_create(vnfd_id): + # Omit except for required attributes + # NOTE: Only the following cardinality attributes are set. + # - 1 + # - 1..N (1) + return { + "vnfdId": vnfd_id, + } + + +def sample2_terminate(): + # Omit except for required attributes + # NOTE: Only the following cardinality attributes are set. + # - 1 + # - 1..N (1) return { "terminationType": "FORCEFUL" } -def sample1_instantiate(net_ids, subnet_ids, auth_url): - ext_vl_1 = { - "id": uuidutils.generate_uuid(), - "resourceId": net_ids['net0'], - "extCps": [ - { - "cpdId": "VDU1_CP1", - "cpConfig": { - "VDU1_CP1_1": { - "cpProtocolData": [{ - "layerProtocol": "IP_OVER_ETHERNET", - "ipOverEthernet": { - "ipAddresses": [{ - "type": "IPV4", - "numDynamicAddresses": 1}]}}]} - } - }, - { - "cpdId": "VDU2_CP1", - "cpConfig": { - "VDU2_CP1_1": { - "cpProtocolData": [{ - "layerProtocol": "IP_OVER_ETHERNET", - "ipOverEthernet": { - "ipAddresses": [{ - "type": "IPV4", - "fixedAddresses": ["10.10.0.101"]}]}}]} - } - } - ], - } - ext_vl_2 = { - "id": uuidutils.generate_uuid(), - "resourceId": net_ids['net1'], - "extCps": [ - { - "cpdId": "VDU1_CP2", - "cpConfig": { - "VDU1_CP2_1": { - "cpProtocolData": [{ - "layerProtocol": "IP_OVER_ETHERNET", - "ipOverEthernet": { - "ipAddresses": [{ - "type": "IPV4", - "numDynamicAddresses": 1, - "subnetId": subnet_ids['subnet1']}]}}]} - } - }, - { - "cpdId": "VDU2_CP2", - "cpConfig": { - "VDU2_CP2_1": { - "cpProtocolData": [{ - "layerProtocol": "IP_OVER_ETHERNET", - "ipOverEthernet": { - "ipAddresses": [{ - "type": "IPV4", - "fixedAddresses": ["10.10.1.101"], - "subnetId": subnet_ids['subnet1']}]}}]} - } - } - ] - } - +def sample2_instantiate(): + # Omit except for required attributes + # NOTE: Only the following cardinality attributes are set. + # - 1 + # - 1..N (1) return { - "flavourId": "simple", - "instantiationLevelId": "instantiation_level_1", - "extVirtualLinks": [ - ext_vl_1, - ext_vl_2 - ], - "extManagedVirtualLinks": [ - { - "id": uuidutils.generate_uuid(), - "vnfVirtualLinkDescId": "internalVL1", - "resourceId": net_ids['net_mgmt'] - }, - ], - "vimConnectionInfo": { - "vim1": { - "vimType": "ETSINFV.OPENSTACK_KEYSTONE.V_3", - "vimId": uuidutils.generate_uuid(), - "interfaceInfo": {"endpoint": auth_url}, - "accessInfo": { - "username": "nfv_user", - "region": "RegionOne", - "password": "devstack", - "project": "nfv", - "projectDomain": "Default", - "userDomain": "Default" - } - } - } - } - - -def sample2_create(vnfd_id): - return { - "vnfdId": vnfd_id, - "vnfInstanceName": "sample2", - "vnfInstanceDescription": "test sample2" - } - - -def sample2_terminate(): - return { - "terminationType": "GRACEFUL", - "gracefulTerminationTimeout": 5 - } - - -def sample2_instantiate(net_ids, subnet_ids, auth_url): - ext_vl_1 = { - "id": uuidutils.generate_uuid(), - "resourceId": net_ids['net0'], - "extCps": [ - { - "cpdId": "VDU1_CP1", - "cpConfig": { - "VDU1_CP1_1": { - "cpProtocolData": [{ - "layerProtocol": "IP_OVER_ETHERNET", - "ipOverEthernet": { - "ipAddresses": [{ - "type": "IPV4", - "numDynamicAddresses": 1}]}}]} - } - }, - { - "cpdId": "VDU2_CP1", - "cpConfig": { - "VDU2_CP1_1": { - "cpProtocolData": [{ - "layerProtocol": "IP_OVER_ETHERNET", - "ipOverEthernet": { - "ipAddresses": [{ - "type": "IPV4", - "fixedAddresses": ["10.10.0.102"]}]}}]} - } - } - ], - } - ext_vl_2 = { - "id": uuidutils.generate_uuid(), - "resourceId": net_ids['net1'], - "extCps": [ - { - "cpdId": "VDU1_CP2", - "cpConfig": { - "VDU1_CP2_1": { - "cpProtocolData": [{ - "layerProtocol": "IP_OVER_ETHERNET", - "ipOverEthernet": { - "ipAddresses": [{ - "type": "IPV4", - "numDynamicAddresses": 1, - "subnetId": subnet_ids['subnet1']}]}}]} - } - }, - { - "cpdId": "VDU2_CP2", - "cpConfig": { - "VDU2_CP2_1": { - "cpProtocolData": [{ - "layerProtocol": "IP_OVER_ETHERNET", - "ipOverEthernet": { - "ipAddresses": [{ - "type": "IPV4", - "fixedAddresses": ["10.10.1.102"], - "subnetId": subnet_ids['subnet1']}]}}]} - } - } - ] - } - - return { - "flavourId": "simple", - "instantiationLevelId": "instantiation_level_1", - "extVirtualLinks": [ - ext_vl_1, - ext_vl_2 - ], - "extManagedVirtualLinks": [ - { - "id": uuidutils.generate_uuid(), - "vnfVirtualLinkDescId": "internalVL1", - "resourceId": net_ids['net_mgmt'] - }, - ], - "vimConnectionInfo": { - "vim1": { - "vimType": "ETSINFV.OPENSTACK_KEYSTONE.V_3", - "vimId": uuidutils.generate_uuid(), - "interfaceInfo": {"endpoint": auth_url}, - "accessInfo": { - "username": "nfv_user", - "region": "RegionOne", - "password": "devstack", - "project": "nfv", - "projectDomain": "Default", - "userDomain": "Default" - } - } - }, - "additionalParams": { - "lcm-operation-user-data": "./UserData/userdata_default.py", - "lcm-operation-user-data-class": "DefaultUserData" - } + "flavourId": "simple" } diff --git a/tacker/tests/functional/sol_v2/samples/sample1/contents/BaseHOT/simple/nested/VDU1.yaml b/tacker/tests/functional/sol_v2/samples/sample1/contents/BaseHOT/simple/nested/VDU1.yaml index f6bf17c70..cad8936d0 100644 --- a/tacker/tests/functional/sol_v2/samples/sample1/contents/BaseHOT/simple/nested/VDU1.yaml +++ b/tacker/tests/functional/sol_v2/samples/sample1/contents/BaseHOT/simple/nested/VDU1.yaml @@ -18,7 +18,9 @@ parameters: type: string net5: type: string - subnet: + subnet1: + type: string + subnet2: type: string resources: @@ -27,7 +29,7 @@ resources: properties: flavor: { get_param: flavor } name: VDU1 - block_device_mapping_v2: [{"volume_id": { get_resource: VirtualStorage }}] + block_device_mapping_v2: [{"volume_id": { get_resource: VDU1-VirtualStorage }}] networks: - port: get_resource: VDU1_CP1 @@ -44,7 +46,7 @@ resources: availability_zone: { get_param: zone } - VirtualStorage: + VDU1-VirtualStorage: type: OS::Cinder::Volume properties: image: { get_param: image } @@ -61,6 +63,8 @@ resources: type: OS::Neutron::Port properties: network: { get_param: net1 } + fixed_ips: + - subnet: { get_param: subnet1} # extVL with numDynamicAddresses and subnet VDU1_CP2: @@ -68,7 +72,7 @@ resources: properties: network: { get_param: net2 } fixed_ips: - - subnet: { get_param: subnet} + - subnet: { get_param: subnet2} # delete the following line when extmanagedVLs' Ports are specified in instantiatevnfrequest VDU1_CP3: diff --git a/tacker/tests/functional/sol_v2/samples/sample1/contents/BaseHOT/simple/sample1.yaml b/tacker/tests/functional/sol_v2/samples/sample1/contents/BaseHOT/simple/sample1.yaml index 6290459d7..8d14dd503 100644 --- a/tacker/tests/functional/sol_v2/samples/sample1/contents/BaseHOT/simple/sample1.yaml +++ b/tacker/tests/functional/sol_v2/samples/sample1/contents/BaseHOT/simple/sample1.yaml @@ -16,11 +16,12 @@ resources: type: VDU1.yaml properties: flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] } - image: { get_param: [ nfv, VDU, VirtualStorage, vcImageId ] } + image: { get_param: [ nfv, VDU, VDU1-VirtualStorage, vcImageId ] } zone: { get_param: [ nfv, VDU, VDU1, locationConstraints] } net1: { get_param: [ nfv, CP, VDU1_CP1, network] } net2: { get_param: [ nfv, CP, VDU1_CP2, network ] } - subnet: { get_param: [nfv, CP, VDU1_CP2, fixed_ips, 0, subnet ]} + subnet1: { get_param: [nfv, CP, VDU1_CP1, fixed_ips, 0, subnet ]} + subnet2: { get_param: [nfv, CP, VDU1_CP2, fixed_ips, 0, subnet ]} net3: { get_resource: internalVL1 } net4: { get_resource: internalVL2 } net5: { get_resource: internalVL3 } @@ -44,11 +45,12 @@ resources: type: OS::Nova::Server properties: flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] } - image: { get_param: [ nfv, VDU, VDU2, vcImageId] } + name: VDU2 availability_zone: { get_param: [ nfv, VDU, VDU2, locationConstraints ] } + block_device_mapping_v2: [{"volume_id": { get_resource: VDU2-VirtualStorage }}] networks: - - port: - get_resource: VDU2_CP1 + - port: { get_param: [ nfv, CP, VDU2_CP1-1, port ] } + - port: { get_param: [ nfv, CP, VDU2_CP1-2, port ] } - port: get_resource: VDU2_CP2 - port: @@ -58,13 +60,17 @@ resources: - port: get_resource: VDU2_CP5 -# extVL with FixedIP - VDU2_CP1: - type: OS::Neutron::Port + VDU2-VirtualStorage: + type: OS::Cinder::Volume properties: - network: { get_param: [ nfv, CP, VDU2_CP1, network ] } - fixed_ips: - - ip_address: { get_param: [nfv, CP, VDU2_CP1, fixed_ips, 0, ip_address]} + image: { get_param: [ nfv, VDU, VDU2-VirtualStorage, vcImageId] } + size: 1 + volume_type: { get_resource: multi } + multi: + type: OS::Cinder::VolumeType + properties: + name: VDU2-multi + metadata: { multiattach: " True" } # extVL with FixedIP and Subnet VDU2_CP2: @@ -74,6 +80,7 @@ resources: fixed_ips: - ip_address: { get_param: [nfv, CP, VDU2_CP2, fixed_ips, 0, ip_address]} subnet: { get_param: [nfv, CP, VDU2_CP2, fixed_ips, 0, subnet]} + - subnet: { get_param: [nfv, CP, VDU2_CP2, fixed_ips, 1, subnet]} VDU2_CP3: type: OS::Neutron::Port diff --git a/tacker/tests/functional/sol_v2/samples/sample1/contents/Definitions/v2_sample1_df_simple.yaml b/tacker/tests/functional/sol_v2/samples/sample1/contents/Definitions/v2_sample1_df_simple.yaml index 46db3074f..f6bbbd47b 100644 --- a/tacker/tests/functional/sol_v2/samples/sample1/contents/Definitions/v2_sample1_df_simple.yaml +++ b/tacker/tests/functional/sol_v2/samples/sample1/contents/Definitions/v2_sample1_df_simple.yaml @@ -34,7 +34,8 @@ topology_template: flavour_id: simple requirements: virtual_link_external1_1: [ VDU1_CP1, virtual_link ] - virtual_link_external1_2: [ VDU2_CP1, virtual_link ] + virtual_link_external1_2: [ VDU2_CP1-1, virtual_link ] + virtual_link_external1_3: [ VDU2_CP1-2, virtual_link ] virtual_link_external2_1: [ VDU1_CP2, virtual_link ] virtual_link_external2_2: [ VDU2_CP2, virtual_link ] @@ -79,7 +80,7 @@ topology_template: virtual_local_storage: - size_of_storage: 3 GB requirements: - - virtual_storage: VirtualStorage + - virtual_storage: VDU1-VirtualStorage VDU2: type: tosca.nodes.nfv.Vdu.Compute @@ -89,17 +90,6 @@ topology_template: vdu_profile: min_number_of_instances: 1 max_number_of_instances: 1 - sw_image_data: - name: VDU2-image - version: '0.5.2' - checksum: - algorithm: sha-256 - hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464 - container_format: bare - disk_format: qcow2 - min_disk: 0 GB - min_ram: 256 MB - size: 12 GB capabilities: virtual_compute: properties: @@ -115,19 +105,17 @@ topology_template: num_virtual_cpu: 1 virtual_local_storage: - size_of_storage: 3 GB - artifacts: - sw_image: - type: tosca.artifacts.nfv.SwImage - file: ../Files/images/cirros-0.5.2-x86_64-disk.img + requirements: + - virtual_storage: VDU2-VirtualStorage - VirtualStorage: + VDU1-VirtualStorage: type: tosca.nodes.nfv.Vdu.VirtualBlockStorage properties: virtual_block_storage_data: size_of_storage: 1 GB rdma_enabled: true sw_image_data: - name: cirros-0.5.2-x86_64-disk + name: VDU1-VirtualStorage-image version: '0.5.2' checksum: algorithm: sha-256 @@ -137,6 +125,32 @@ topology_template: min_disk: 0 GB min_ram: 256 MB size: 12 GB + artifacts: + sw_image: + type: tosca.artifacts.nfv.SwImage + file: ../Files/images/cirros-0.5.2-x86_64-disk.img + + VDU2-VirtualStorage: + type: tosca.nodes.nfv.Vdu.VirtualBlockStorage + properties: + virtual_block_storage_data: + size_of_storage: 1 GB + rdma_enabled: true + sw_image_data: + name: VDU2-VirtualStorage-image + version: '0.5.2' + checksum: + algorithm: sha-256 + hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464 + container_format: bare + disk_format: qcow2 + min_disk: 0 GB + min_ram: 256 MB + size: 12 GB + artifacts: + sw_image: + type: tosca.artifacts.nfv.SwImage + file: ../Files/images/cirros-0.5.2-x86_64-disk.img VDU1_CP1: type: tosca.nodes.nfv.VduCp @@ -181,7 +195,7 @@ topology_template: - virtual_binding: VDU1 - virtual_link: internalVL3 - VDU2_CP1: + VDU2_CP1-1: type: tosca.nodes.nfv.VduCp properties: layer_protocols: [ ipv4 ] @@ -189,7 +203,7 @@ topology_template: requirements: - virtual_binding: VDU2 - VDU2_CP2: + VDU2_CP1-2: type: tosca.nodes.nfv.VduCp properties: layer_protocols: [ ipv4 ] @@ -197,20 +211,28 @@ topology_template: requirements: - virtual_binding: VDU2 - VDU2_CP3: + VDU2_CP2: type: tosca.nodes.nfv.VduCp properties: layer_protocols: [ ipv4 ] order: 2 requirements: - virtual_binding: VDU2 + + VDU2_CP3: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 3 + requirements: + - virtual_binding: VDU2 - virtual_link: internalVL1 VDU2_CP4: type: tosca.nodes.nfv.VduCp properties: layer_protocols: [ ipv4 ] - order: 3 + order: 4 requirements: - virtual_binding: VDU2 - virtual_link: internalVL2 @@ -219,7 +241,7 @@ topology_template: type: tosca.nodes.nfv.VduCp properties: layer_protocols: [ ipv4 ] - order: 4 + order: 5 requirements: - virtual_binding: VDU2 - virtual_link: internalVL3 diff --git a/tacker/tests/functional/sol_v2/samples/sample1/contents/Scripts/sample_script.py b/tacker/tests/functional/sol_v2/samples/sample1/contents/Scripts/sample_script.py new file mode 100644 index 000000000..db5260ef1 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/sample1/contents/Scripts/sample_script.py @@ -0,0 +1,67 @@ +# Copyright (C) 2021 Nippon Telegraph and Telephone Corporation +# All Rights Reserved. +# +# 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 os +import pickle +import sys + + +class SampleScript(object): + + def __init__(self, req, inst, grant_req, grant, csar_dir): + self.req = req + self.inst = inst + self.grant_req = grant_req + self.grant = grant + self.csar_dir = csar_dir + + def instantiate_start(self): + pass + + def instantiate_end(self): + pass + + def terminate_start(self): + pass + + def terminate_end(self): + pass + + +def main(): + script_dict = pickle.load(sys.stdin.buffer) + + operation = script_dict['operation'] + req = script_dict['request'] + inst = script_dict['vnf_instance'] + grant_req = script_dict['grant_request'] + grant = script_dict['grant_response'] + csar_dir = script_dict['tmp_csar_dir'] + + script = SampleScript(req, inst, grant_req, grant, csar_dir) + try: + getattr(script, operation)() + except AttributeError: + raise Exception("{} is not included in the script.".format(operation)) + + +if __name__ == "__main__": + try: + main() + os._exit(0) + except Exception as ex: + sys.stderr.write(str(ex)) + sys.stderr.flush() + os._exit(1) diff --git a/tacker/tests/functional/sol_v2/samples/sample2/contents/UserData/userdata_default.py b/tacker/tests/functional/sol_v2/samples/sample1/contents/UserData/userdata.py similarity index 66% rename from tacker/tests/functional/sol_v2/samples/sample2/contents/UserData/userdata_default.py rename to tacker/tests/functional/sol_v2/samples/sample1/contents/UserData/userdata.py index 6322aae5d..848e79bae 100644 --- a/tacker/tests/functional/sol_v2/samples/sample2/contents/UserData/userdata_default.py +++ b/tacker/tests/functional/sol_v2/samples/sample1/contents/UserData/userdata.py @@ -19,10 +19,34 @@ from tacker.sol_refactored.common import vnf_instance_utils as inst_utils from tacker.sol_refactored.infra_drivers.openstack import userdata_utils -class DefaultUserData(userdata_utils.AbstractUserData): +class UserData(userdata_utils.AbstractUserData): @staticmethod def instantiate(req, inst, grant_req, grant, tmp_csar_dir): + def _get_param_port(cp_name, grant, req): + # see grant first then instantiateVnfRequest + vls = grant.get('extVirtualLinks', []) + req.get('extVirtualLinks', + []) + port_ids = [] + for vl in vls: + link_port_ids = [] + for extcp in vl['extCps']: + if extcp['cpdId'] == cp_name: + link_port_ids = _get_link_port_ids_from_extcp(extcp) + if 'extLinkPorts' not in vl: + continue + for extlp in vl['extLinkPorts']: + if extlp['id'] in link_port_ids: + port_ids.append(extlp['resourceHandle']['resourceId']) + return port_ids + + def _get_link_port_ids_from_extcp(extcp): + link_port_ids = [] + for cp_conf in extcp['cpConfig'].values(): + if 'linkPortId' in cp_conf: + link_port_ids.append(cp_conf['linkPortId']) + return link_port_ids + vnfd = userdata_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir) flavour_id = req['flavourId'] @@ -65,6 +89,16 @@ class DefaultUserData(userdata_utils.AbstractUserData): 'ip_address') fixed_ips.append(ips_i) cp_value['fixed_ips'] = fixed_ips + # NOTE: In the case where multiple cpConfigs corresponding + # to a single cpdId are defined, always get the first element + # of cpConfig. This is because, according to the current + # SOL definitions, the key of cpConfig is the ID managed by + # the API consumer, and it is not possible to uniquely determine + # which element of cpConfig should be selected by cpdId. + # See SOL003 v3.3.1 4.4.1.10 Type: VnfExtCpData. + if 'port' in cp_value: + cp_value['port'] = _get_param_port( + cp_name, grant, req).pop() userdata_utils.apply_ext_managed_vls(top_hot, req, grant) diff --git a/tacker/tests/functional/sol_v2/samples/sample1/pkggen.py b/tacker/tests/functional/sol_v2/samples/sample1/pkggen.py index 25661663c..b4ecd8d8b 100644 --- a/tacker/tests/functional/sol_v2/samples/sample1/pkggen.py +++ b/tacker/tests/functional/sol_v2/samples/sample1/pkggen.py @@ -42,11 +42,22 @@ shutil.rmtree(tmp_dir) create_req = paramgen.sample1_create(vnfd_id) terminate_req = paramgen.sample1_terminate() -net_ids = utils.get_network_ids(['net0', 'net1', 'net_mgmt']) -subnet_ids = utils.get_subnet_ids(['subnet0', 'subnet1']) +print('#####################################################################\n' + '# Run pre.py if an error occurs #\n' + '# - If an error occurs, run the pre.py script in advance #\n' + '# to create the openstack resource required to run this script. #\n' + '# Run post.py when you finish tests #\n' + '# - When you no longer need these openstack resources #\n' + '# after testing, run post.py and delete them. #\n' + '#####################################################################') + +net_ids = utils.get_network_ids(['net0', 'net1', 'net_mgmt', 'ft-net0']) +subnet_ids = utils.get_subnet_ids( + ['subnet0', 'subnet1', 'ft-ipv4-subnet0', 'ft-ipv6-subnet0']) +port_ids = utils.get_port_ids(['VDU2_CP1-1', 'VDU2_CP1-2']) instantiate_req = paramgen.sample1_instantiate( - net_ids, subnet_ids, "http://localhost/identity/v3") + net_ids, subnet_ids, port_ids, "http://localhost/identity/v3") with open("create_req", "w") as f: f.write(json.dumps(create_req, indent=2)) diff --git a/tacker/tests/functional/sol_v2/samples/sample1/post.py b/tacker/tests/functional/sol_v2/samples/sample1/post.py new file mode 100644 index 000000000..368537db1 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/sample1/post.py @@ -0,0 +1,20 @@ +# Copyright (C) 2021 Nippon Telegraph and Telephone Corporation +# All Rights Reserved. +# +# 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. +from tacker.tests.functional.sol_v2 import utils + +utils.delete_network('ft-net0') +# NOTE: subnet is automatically deleted by network deletion +utils.delete_port('VDU2_CP1-1') +utils.delete_port('VDU2_CP1-2') diff --git a/tacker/tests/functional/sol_v2/samples/sample1/pre.py b/tacker/tests/functional/sol_v2/samples/sample1/pre.py new file mode 100644 index 000000000..61be9a5d2 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/sample1/pre.py @@ -0,0 +1,21 @@ +# Copyright (C) 2021 Nippon Telegraph and Telephone Corporation +# All Rights Reserved. +# +# 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. +from tacker.tests.functional.sol_v2 import utils + +utils.create_network('ft-net0') +utils.create_subnet('ft-ipv4-subnet0', 'ft-net0', '100.100.100.0/24', '4') +utils.create_subnet('ft-ipv6-subnet0', 'ft-net0', '1111:2222:3333::/64', '6') +utils.create_port('VDU2_CP1-1', 'net0') +utils.create_port('VDU2_CP1-2', 'net0') diff --git a/tacker/tests/functional/sol_v2/samples/sample2/contents/BaseHOT/simple/nested/VDU1.yaml b/tacker/tests/functional/sol_v2/samples/sample2/contents/BaseHOT/simple/nested/VDU1.yaml index 3ce177811..d2959d317 100644 --- a/tacker/tests/functional/sol_v2/samples/sample2/contents/BaseHOT/simple/nested/VDU1.yaml +++ b/tacker/tests/functional/sol_v2/samples/sample2/contents/BaseHOT/simple/nested/VDU1.yaml @@ -6,84 +6,25 @@ parameters: type: string image: type: string - net1: - type: string - net2: - type: string - net3: - type: string - net4: - type: string net5: type: string - subnet: + affinity: type: string -# uncomment when BUG "https://storyboard.openstack.org/#!/story/2009164" fixed -# affinity: -# type: string resources: VDU1: type: OS::Nova::Server properties: flavor: { get_param: flavor } + image: { get_param: image } name: VDU1 - block_device_mapping_v2: [{"volume_id": { get_resource: VirtualStorage }}] networks: - port: get_resource: VDU1_CP1 - - port: - get_resource: VDU1_CP2 -# replace the following line to Port ID when extmanagedVLs' Ports are specified in instantiatevnfrequest - - port: - get_resource: VDU1_CP3 - - port: - get_resource: VDU1_CP4 - - port: - get_resource: VDU1_CP5 + scheduler_hints: + group: {get_param: affinity } -# uncomment when BUG "https://storyboard.openstack.org/#!/story/2009164" fixed -# scheduler_hints: -# group: {get_param: affinity } - - VirtualStorage: - type: OS::Cinder::Volume - properties: - image: { get_param: image } - size: 1 - volume_type: { get_resource: multi } - multi: - type: OS::Cinder::VolumeType - properties: - name: { get_resource: VDU1_CP1 } - metadata: { multiattach: " True" } - -# extVL without FixedIP or with numDynamicAddresses VDU1_CP1: - type: OS::Neutron::Port - properties: - network: { get_param: net1 } - -# extVL with numDynamicAddresses and subnet - VDU1_CP2: - type: OS::Neutron::Port - properties: - network: { get_param: net2 } - fixed_ips: - - subnet: { get_param: subnet} - -# CPs of internal VLs are deleted when extmangaedVLs and port are specified in instantiatevnfrequest - VDU1_CP3: - type: OS::Neutron::Port - properties: - network: { get_param: net3 } - - VDU1_CP4: - type: OS::Neutron::Port - properties: - network: { get_param: net4 } - - VDU1_CP5: type: OS::Neutron::Port properties: network: { get_param: net5 } diff --git a/tacker/tests/functional/sol_v2/samples/sample2/contents/BaseHOT/simple/sample2.yaml b/tacker/tests/functional/sol_v2/samples/sample2/contents/BaseHOT/simple/sample2.yaml index 47b0fe8c3..6469a7bd3 100644 --- a/tacker/tests/functional/sol_v2/samples/sample2/contents/BaseHOT/simple/sample2.yaml +++ b/tacker/tests/functional/sol_v2/samples/sample2/contents/BaseHOT/simple/sample2.yaml @@ -16,15 +16,9 @@ resources: type: VDU1.yaml properties: flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] } - image: { get_param: [ nfv, VDU, VirtualStorage, vcImageId ] } - net1: { get_param: [ nfv, CP, VDU1_CP1, network ] } - net2: { get_param: [ nfv, CP, VDU1_CP2, network ] } - subnet: { get_param: [nfv, CP, VDU1_CP2, fixed_ips, 0, subnet ]} - net3: { get_resource: internalVL1 } - net4: { get_resource: internalVL2 } + image: { get_param: [ nfv, VDU, VDU1, vcImageId ] } net5: { get_resource: internalVL3 } -# uncomment when BUG "https://storyboard.openstack.org/#!/story/2009164" fixed -# affinity: { get_resource: nfvi_node_affinity } + affinity: { get_resource: nfvi_node_affinity } VDU1_scale_out: type: OS::Heat::ScalingPolicy properties: @@ -44,80 +38,23 @@ resources: type: OS::Nova::Server properties: flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] } + name: VDU2 image: { get_param: [ nfv, VDU, VDU2, vcImageId] } networks: - port: get_resource: VDU2_CP1 - - port: - get_resource: VDU2_CP2 - - port: - get_resource: VDU2_CP3 - - port: - get_resource: VDU2_CP4 - - port: - get_resource: VDU2_CP5 -# uncomment when BUG "https://storyboard.openstack.org/#!/story/2009164" fixed -# scheduler_hints: -# group: {get_resource: nfvi_node_affinity } + scheduler_hints: + group: {get_resource: nfvi_node_affinity } -# extVL with FixedIP VDU2_CP1: type: OS::Neutron::Port properties: - network: { get_param: [ nfv, CP, VDU2_CP1, network ] } - fixed_ips: - - ip_address: { get_param: [nfv, CP, VDU2_CP1, fixed_ips, 0, ip_address]} - -# extVL with FixedIP and Subnet - VDU2_CP2: - type: OS::Neutron::Port - properties: - network: { get_param: [ nfv, CP, VDU2_CP2, network ] } - fixed_ips: - - ip_address: { get_param: [nfv, CP, VDU2_CP2, fixed_ips, 0, ip_address]} - subnet: { get_param: [nfv, CP, VDU2_CP2, fixed_ips, 0, subnet]} - - VDU2_CP3: - type: OS::Neutron::Port - properties: -# replace the following line to VL's ID when extmanagedVLs are specified in instantiatevnfrequest - network: { get_resource: internalVL1 } - - VDU2_CP4: - type: OS::Neutron::Port - properties: -# replace the following line to VL's ID when extmanagedVLs are specified in instantiatevnfrequest - network: { get_resource: internalVL2 } - - VDU2_CP5: - type: OS::Neutron::Port - properties: -# replace the following line to VL's ID when extmanagedVLs are specified in instantiatevnfrequest network: { get_resource: internalVL3 } -# delete the following lines when extmanagedVLs are specified in instantiatevnfrequest - internalVL1: - type: OS::Neutron::Net - internalVL2: - type: OS::Neutron::Net internalVL3: type: OS::Neutron::Net - internalVL1_subnet: - type: OS::Neutron::Subnet - properties: - ip_version: 4 - network: - get_resource: internalVL1 - cidr: 192.168.3.0/24 - internalVL2_subnet: - type: OS::Neutron::Subnet - properties: - ip_version: 4 - network: - get_resource: internalVL2 - cidr: 192.168.4.0/24 internalVL3_subnet: type: OS::Neutron::Subnet properties: @@ -126,11 +63,10 @@ resources: get_resource: internalVL3 cidr: 192.168.5.0/24 -# uncomment when BUG "https://storyboard.openstack.org/#!/story/2009164" fixed -# nfvi_node_affinity: -# type: OS::Nova::ServerGroup -# properties: -# name: nfvi_node_affinity -# policies: [ 'affinity' ] + nfvi_node_affinity: + type: OS::Nova::ServerGroup + properties: + name: nfvi_node_affinity + policies: [ 'affinity' ] outputs: {} diff --git a/tacker/tests/functional/sol_v2/samples/sample2/contents/Definitions/v2_sample2_df_simple.yaml b/tacker/tests/functional/sol_v2/samples/sample2/contents/Definitions/v2_sample2_df_simple.yaml index 2eacaa8c9..ea3bb7c41 100644 --- a/tacker/tests/functional/sol_v2/samples/sample2/contents/Definitions/v2_sample2_df_simple.yaml +++ b/tacker/tests/functional/sol_v2/samples/sample2/contents/Definitions/v2_sample2_df_simple.yaml @@ -32,11 +32,6 @@ topology_template: node_type: company.provider.VNF properties: flavour_id: simple - requirements: - virtual_link_external1_1: [ VDU1_CP1, virtual_link ] - virtual_link_external1_2: [ VDU2_CP1, virtual_link ] - virtual_link_external2_1: [ VDU1_CP2, virtual_link ] - virtual_link_external2_2: [ VDU2_CP2, virtual_link ] node_templates: VNF: @@ -67,6 +62,17 @@ topology_template: vdu_profile: min_number_of_instances: 1 max_number_of_instances: 3 + sw_image_data: + name: cirros-0.5.2-x86_64-disk + version: '0.5.2' + checksum: + algorithm: sha-256 + hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464 + container_format: bare + disk_format: qcow2 + min_disk: 0 GB + min_ram: 256 MB + size: 12 GB capabilities: virtual_compute: properties: @@ -82,8 +88,6 @@ topology_template: num_virtual_cpu: 1 virtual_local_storage: - size_of_storage: 3 GB - requirements: - - virtual_storage: VirtualStorage VDU2: type: tosca.nodes.nfv.Vdu.Compute @@ -120,59 +124,7 @@ topology_template: virtual_local_storage: - size_of_storage: 3 GB - VirtualStorage: - type: tosca.nodes.nfv.Vdu.VirtualBlockStorage - properties: - virtual_block_storage_data: - size_of_storage: 1 GB - rdma_enabled: true - sw_image_data: - name: cirros-0.5.2-x86_64-disk - version: '0.5.2' - checksum: - algorithm: sha-256 - hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464 - container_format: bare - disk_format: qcow2 - min_disk: 0 GB - min_ram: 256 MB - size: 12 GB - VDU1_CP1: - type: tosca.nodes.nfv.VduCp - properties: - layer_protocols: [ ipv4 ] - order: 0 - requirements: - - virtual_binding: VDU1 - - VDU1_CP2: - type: tosca.nodes.nfv.VduCp - properties: - layer_protocols: [ ipv4 ] - order: 1 - requirements: - - virtual_binding: VDU1 - - VDU1_CP3: - type: tosca.nodes.nfv.VduCp - properties: - layer_protocols: [ ipv4 ] - order: 2 - requirements: - - virtual_binding: VDU1 - - virtual_link: internalVL1 - - VDU1_CP4: - type: tosca.nodes.nfv.VduCp - properties: - layer_protocols: [ ipv4 ] - order: 3 - requirements: - - virtual_binding: VDU1 - - virtual_link: internalVL2 - - VDU1_CP5: type: tosca.nodes.nfv.VduCp properties: layer_protocols: [ ipv4 ] @@ -182,40 +134,6 @@ topology_template: - virtual_link: internalVL3 VDU2_CP1: - type: tosca.nodes.nfv.VduCp - properties: - layer_protocols: [ ipv4 ] - order: 0 - requirements: - - virtual_binding: VDU2 - - VDU2_CP2: - type: tosca.nodes.nfv.VduCp - properties: - layer_protocols: [ ipv4 ] - order: 1 - requirements: - - virtual_binding: VDU2 - - VDU2_CP3: - type: tosca.nodes.nfv.VduCp - properties: - layer_protocols: [ ipv4 ] - order: 2 - requirements: - - virtual_binding: VDU2 - - virtual_link: internalVL1 - - VDU2_CP4: - type: tosca.nodes.nfv.VduCp - properties: - layer_protocols: [ ipv4 ] - order: 3 - requirements: - - virtual_binding: VDU2 - - virtual_link: internalVL2 - - VDU2_CP5: type: tosca.nodes.nfv.VduCp properties: layer_protocols: [ ipv4 ] @@ -224,44 +142,6 @@ topology_template: - virtual_binding: VDU2 - virtual_link: internalVL3 - internalVL1: - type: tosca.nodes.nfv.VnfVirtualLink - properties: - connectivity_type: - layer_protocols: [ ipv4 ] - description: External Managed Virtual link in the VNF - vl_profile: - max_bitrate_requirements: - root: 1048576 - leaf: 1048576 - min_bitrate_requirements: - root: 1048576 - leaf: 1048576 - virtual_link_protocol_data: - - associated_layer_protocol: ipv4 - l3_protocol_data: - ip_version: ipv4 - cidr: 192.168.3.0/24 - - internalVL2: - type: tosca.nodes.nfv.VnfVirtualLink - properties: - connectivity_type: - layer_protocols: [ ipv4 ] - description: External Managed Virtual link in the VNF - vl_profile: - max_bitrate_requirements: - root: 1048576 - leaf: 1048576 - min_bitrate_requirements: - root: 1048576 - leaf: 1048576 - virtual_link_protocol_data: - - associated_layer_protocol: ipv4 - l3_protocol_data: - ip_version: ipv4 - cidr: 192.168.4.0/24 - internalVL3: type: tosca.nodes.nfv.VnfVirtualLink properties: @@ -357,34 +237,6 @@ topology_template: number_of_instances: 1 targets: [ VDU2 ] - - internalVL1_instantiation_levels: - type: tosca.policies.nfv.VirtualLinkInstantiationLevels - properties: - levels: - instantiation_level_1: - bitrate_requirements: - root: 1048576 - leaf: 1048576 - instantiation_level_2: - bitrate_requirements: - root: 1048576 - leaf: 1048576 - targets: [ internalVL1 ] - - - internalVL2_instantiation_levels: - type: tosca.policies.nfv.VirtualLinkInstantiationLevels - properties: - levels: - instantiation_level_1: - bitrate_requirements: - root: 1048576 - leaf: 1048576 - instantiation_level_2: - bitrate_requirements: - root: 1048576 - leaf: 1048576 - targets: [ internalVL2 ] - - internalVL3_instantiation_levels: type: tosca.policies.nfv.VirtualLinkInstantiationLevels properties: diff --git a/tacker/tests/functional/sol_v2/samples/sample2/pkggen.py b/tacker/tests/functional/sol_v2/samples/sample2/pkggen.py index d42b63c35..cc0f0d811 100644 --- a/tacker/tests/functional/sol_v2/samples/sample2/pkggen.py +++ b/tacker/tests/functional/sol_v2/samples/sample2/pkggen.py @@ -34,12 +34,7 @@ shutil.rmtree(tmp_dir) create_req = paramgen.sample2_create(vnfd_id) terminate_req = paramgen.sample2_terminate() - -net_ids = utils.get_network_ids(['net0', 'net1', 'net_mgmt']) -subnet_ids = utils.get_subnet_ids(['subnet0', 'subnet1']) - -instantiate_req = paramgen.sample2_instantiate( - net_ids, subnet_ids, "http://localhost/identity/v3") +instantiate_req = paramgen.sample2_instantiate() with open("create_req", "w") as f: f.write(json.dumps(create_req, indent=2)) diff --git a/tacker/tests/functional/sol_v2/test_vnflcm_basic.py b/tacker/tests/functional/sol_v2/test_vnflcm_basic.py index 504d0f088..83111a254 100644 --- a/tacker/tests/functional/sol_v2/test_vnflcm_basic.py +++ b/tacker/tests/functional/sol_v2/test_vnflcm_basic.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import ddt import os import time @@ -20,6 +21,7 @@ from tacker.tests.functional.sol_v2 import base_v2 from tacker.tests.functional.sol_v2 import paramgen +@ddt.ddt class VnfLcmTest(base_v2.BaseSolV2Test): @classmethod @@ -53,10 +55,18 @@ class VnfLcmTest(base_v2.BaseSolV2Test): super(VnfLcmTest, self).setUp() def test_api_versions(self): + """Test version operations + + * About version operations: + This test includes the following operations. + - 1. List VNFLCM API versions + - 2. Show VNFLCM API versions + """ path = "/vnflcm/api_versions" resp, body = self.tacker_client.do_request( path, "GET", version="2.0.0") self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) expected_body = { "uriPrefix": "/vnflcm", "apiVersions": [ @@ -70,6 +80,7 @@ class VnfLcmTest(base_v2.BaseSolV2Test): resp, body = self.tacker_client.do_request( path, "GET", version="2.0.0") self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) expected_body = { "uriPrefix": "/vnflcm/v2", "apiVersions": [ @@ -78,29 +89,334 @@ class VnfLcmTest(base_v2.BaseSolV2Test): } self.assertEqual(body, expected_body) + @ddt.data(True, False) + def test_subscriptions(self, is_all): + """Test subscription operations + + * About attributes: + - is_all=True + All of the following cardinality attributes are set. + In addition, 0..N or 1..N attributes are set to 2 or more. + - 0..1 (1) + - 0..N (2 or more) + - 1 + - 1..N (2 or more) + - is_all=False + Omit except for required attributes. + Only the following cardinality attributes are set. + - 1 + - 1..N (1) + + * About subscription operations: + This test includes the following operations. + - 0. Pre-setting + - 1. Create a new subscription + - 2. Show subscription + - 3. List subscription with attribute-based filtering + - 4. Delete a subscription + """ + # NOTE: Skip notification endpoint testing in subscription creation + # by setting "v2_nfvo.test_callback_uri = False" to 'tacker.conf' + # in '.zuul.yaml'. + + # 0. Pre-setting + sub_req = paramgen.sub2_create() + if is_all: + sub_req = paramgen.sub1_create() + + # 1. Create a new subscription + resp, body = self.create_subscription(sub_req) + self.assertEqual(201, resp.status_code) + self.check_resp_headers_in_create(resp) + sub_id = body['id'] + + # 2. Show subscription + expected_attrs = [ + 'id', 'callbackUri', 'verbosity', '_links' + ] + if is_all: + additional_attrs = ['filter'] + expected_attrs.extend(additional_attrs) + + resp, body = self.show_subscription(sub_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.check_resp_body(body, expected_attrs) + + # 3. List subscription with attribute-based filtering + filter_expr = {'filter': '(eq,id,%s)' % sub_id} + resp, body = self.list_subscriptions(filter_expr) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + for sbsc in body: + self.check_resp_body(sbsc, expected_attrs) + + # 4. Delete a subscription + resp, body = self.delete_subscription(sub_id) + self.assertEqual(204, resp.status_code) + self.check_resp_headers_in_delete(resp) + def test_sample1(self): + """Test LCM operations with all attributes set + + * About attributes: + All of the following cardinality attributes are set. + In addition, 0..N or 1..N attributes are set to 2 or more. + - 0..1 (1) + - 0..N (2 or more) + - 1 + - 1..N (2 or more) + + * About LCM operations: + This test includes the following operations. + - 0. Pre-setting + - 1. Create a new VNF instance resource + - 2. Instantiate a VNF instance + - 3. Show VNF instance + - 4. List VNF instance with attribute-based filtering + - 5. Show VNF LCM operation occurrence + - 6. List VNF LCM operation occurrence with attribute-based filtering + - 7. Terminate a VNF instance + - 8. Delete a VNF instance + """ + # 0. Pre-setting + # Create a new network and subnet to check the IP allocation of + # IPv4 and IPv6 + ft_net0_name = 'ft-net0' + ft_net0_subs = { + 'ft-ipv4-subnet0': { + 'range': '100.100.100.0/24', + 'ip_version': 4 + }, + 'ft-ipv6-subnet0': { + 'range': '1111:2222:3333::/64', + 'ip_version': 6 + } + } + ft_net0_id = self.create_network(ft_net0_name) + self.addCleanup(self.delete_network, ft_net0_id) + for sub_name, val in ft_net0_subs.items(): + # subnet is automatically deleted with network deletion + self.create_subnet( + ft_net0_id, sub_name, val['range'], val['ip_version']) + + net_ids = self.get_network_ids( + ['net0', 'net1', 'net_mgmt', 'ft-net0']) + subnet_ids = self.get_subnet_ids( + ['subnet0', 'subnet1', 'ft-ipv4-subnet0', 'ft-ipv6-subnet0']) + + port_names = ['VDU2_CP1-1', 'VDU2_CP1-2'] + port_ids = {} + for port_name in port_names: + port_id = self.create_port(net_ids['net0'], port_name) + port_ids[port_name] = port_id + self.addCleanup(self.delete_port, port_id) + + # 1. Create a new VNF instance resource + # NOTE: extensions and vnfConfigurableProperties are omitted + # because they are commented out in etsi_nfv_sol001. + expected_inst_attrs = [ + 'id', + 'vnfInstanceName', + 'vnfInstanceDescription', + 'vnfdId', + 'vnfProvider', + 'vnfProductName', + 'vnfSoftwareVersion', + 'vnfdVersion', + # 'vnfConfigurableProperties', # omitted + # 'vimConnectionInfo', # omitted + 'instantiationState', + # 'instantiatedVnfInfo', # omitted + 'metadata', + # 'extensions', # omitted + '_links' + ] create_req = paramgen.sample1_create(self.vnfd_id_1) resp, body = self.create_vnf_instance(create_req) self.assertEqual(201, resp.status_code) + self.check_resp_headers_in_create(resp) + self.check_resp_body(body, expected_inst_attrs) inst_id = body['id'] - net_ids = self.get_network_ids(['net0', 'net1', 'net_mgmt']) - subnet_ids = self.get_subnet_ids(['subnet0', 'subnet1']) + # check usageState of VNF Package + usage_state = self.get_vnf_package(self.vnf_pkg_1).get('usageState') + self.assertEqual('IN_USE', usage_state) + + # 2. Instantiate a VNF instance instantiate_req = paramgen.sample1_instantiate( - net_ids, subnet_ids, self.auth_url) + net_ids, subnet_ids, port_ids, self.auth_url) resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req) self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) lcmocc_id = os.path.basename(resp.headers['Location']) self.wait_lcmocc_complete(lcmocc_id) + # check creation of Heat-stack + stack_name = "vnf-{}".format(inst_id) + stack_status, _ = self.heat_client.get_status(stack_name) + self.assertEqual("CREATE_COMPLETE", stack_status) + + # check creation of Glance-image + image_name_list = ['VDU1-VirtualStorage-image', + 'VDU2-VirtualStorage-image'] + for image_name in image_name_list: + image_id = self.get_image_id(image_name) + self.assertIsNotNone(image_id) + + # check that the servers set in "zone:Affinity" are + # deployed on 'nova' AZ. + # NOTE: local_nfvo returns this AZ + vdu1_details = self.get_server_details('VDU1') + vdu2_details = self.get_server_details('VDU2') + vdu1_az = vdu1_details.get('OS-EXT-AZ:availability_zone') + vdu2_az = vdu2_details.get('OS-EXT-AZ:availability_zone') + self.assertEqual('nova', vdu1_az) + self.assertEqual('nova', vdu2_az) + + # 3. Show VNF instance + additional_inst_attrs = [ + 'vimConnectionInfo', + 'instantiatedVnfInfo' + ] + expected_inst_attrs.extend(additional_inst_attrs) resp, body = self.show_vnf_instance(inst_id) self.assertEqual(200, resp.status_code) - # TODO(oda-g): check body + self.check_resp_headers_in_get(resp) + self.check_resp_body(body, expected_inst_attrs) + # 4. List VNF instance with attribute-based filtering + # check attribute-based filtering on VNF instance + # NOTE: extensions and vnfConfigurableProperties are omitted + # because they are commented out in etsi_nfv_sol001. + # * all_fields + # -> check the attribute omitted in "exclude_default" is set. + filter_expr = {'filter': '(eq,id,%s)' % inst_id, 'all_fields': ''} + resp, body = self.list_vnf_instance(filter_expr) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + for inst in body: + self.assertIsNotNone(inst.get('vnfInstanceName')) + self.assertIsNotNone(inst.get('vnfInstanceDescription')) + self.assertIsNotNone(inst.get('vimConnectionInfo')) + self.assertIsNotNone(inst.get('instantiatedVnfInfo')) + self.assertIsNotNone(inst.get('metadata')) + # * fields= + # -> check the attribute specified in "fields" is set + filter_expr = {'filter': '(eq,id,%s)' % inst_id, + 'fields': 'metadata'} + resp, body = self.list_vnf_instance(filter_expr) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + for inst in body: + self.assertIsNone(inst.get('vnfInstanceName')) + self.assertIsNone(inst.get('vnfInstanceDescription')) + self.assertIsNone(inst.get('vimConnectionInfo')) + self.assertIsNone(inst.get('instantiatedVnfInfo')) + self.assertIsNotNone(inst.get('metadata')) + # * exclude_fields= + # -> check the attribute specified in "exclude_fields" is not set + filter_expr = {'filter': '(eq,id,%s)' % inst_id, + 'exclude_fields': 'vnfInstanceName'} + resp, body = self.list_vnf_instance(filter_expr) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + for inst in body: + self.assertIsNone(inst.get('vnfInstanceName')) + self.assertIsNotNone(inst.get('vnfInstanceDescription')) + self.assertIsNotNone(inst.get('vimConnectionInfo')) + self.assertIsNotNone(inst.get('instantiatedVnfInfo')) + self.assertIsNotNone(inst.get('metadata')) + # * exclude_default + # -> check the attribute omitted in "exclude_default" is not set. + filter_expr = {'filter': '(eq,id,%s)' % inst_id, 'exclude_default': ''} + resp, body = self.list_vnf_instance(filter_expr) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + for inst in body: + self.assertIsNotNone(inst.get('vnfInstanceName')) + self.assertIsNotNone(inst.get('vnfInstanceDescription')) + self.assertIsNone(inst.get('vimConnectionInfo')) + self.assertIsNone(inst.get('instantiatedVnfInfo')) + self.assertIsNone(inst.get('metadata')) + + # 5. Show VNF LCM operation occurrence + # NOTE: omitted values are not supported at that time + expected_attrs = [ + 'id', + 'operationState', + 'stateEnteredTime', + 'startTime', + 'vnfInstanceId', + # 'grantId', # omitted + 'operation', + 'isAutomaticInvocation', + 'operationParams', + 'isCancelPending', + # 'cancelMode', # omitted + # 'error', # omitted + 'resourceChanges', + # 'changedInfo', # omitted + # 'changedExtConnectivity', # omitted + # 'modificationsTriggeredByVnfPkgChange', # omitted + # 'vnfSnapshotInfoId', # omitted + '_links' + ] + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.check_resp_body(body, expected_attrs) + + # 6. List VNF LCM operation occurrence with attribute-based filtering + # check attribute-based filtering on vnf_lcm_op_occs + # NOTE: error and changedInfo, changedExtConnectivity are omitted + # because these values are not supported at that time + # * all_fields + # -> check the attribute omitted in "exclude_default" is set. + filter_expr = {'filter': '(eq,id,%s)' % lcmocc_id, 'all_fields': ''} + resp, body = self.list_lcmocc(filter_expr) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + for lcmocc in body: + self.assertIsNotNone(lcmocc.get('operationParams')) + self.assertIsNotNone(lcmocc.get('resourceChanges')) + # * fields= + # -> check the attribute specified in "fields" is set + filter_expr = {'filter': '(eq,id,%s)' % lcmocc_id, + 'fields': 'operationParams'} + resp, body = self.list_lcmocc(filter_expr) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + for lcmocc in body: + self.assertIsNotNone(lcmocc.get('operationParams')) + self.assertIsNone(lcmocc.get('resourceChanges')) + # * exclude_fields= + # -> check the attribute specified in "exclude_fields" is not set + filter_expr = {'filter': '(eq,id,%s)' % inst_id, + 'exclude_fields': 'operationParams'} + resp, body = self.list_lcmocc(filter_expr) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + for lcmocc in body: + self.assertIsNone(lcmocc.get('operationParams')) + self.assertIsNotNone(lcmocc.get('resourceChanges')) + # * exclude_default + # -> check the attribute omitted in "exclude_default" is not set. + filter_expr = {'filter': '(eq,id,%s)' % lcmocc_id, + 'exclude_default': ''} + resp, body = self.list_lcmocc(filter_expr) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + for lcmocc in body: + self.assertIsNone(lcmocc.get('operationParams')) + self.assertIsNone(lcmocc.get('resourceChanges')) + + # 7. Terminate a VNF instance terminate_req = paramgen.sample1_terminate() resp, body = self.terminate_vnf_instance(inst_id, terminate_req) self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) lcmocc_id = os.path.basename(resp.headers['Location']) self.wait_lcmocc_complete(lcmocc_id) @@ -109,32 +425,113 @@ class VnfLcmTest(base_v2.BaseSolV2Test): # update and terminate completion. time.sleep(10) + # check deletion of Heat-stack + stack_status, _ = self.heat_client.get_status(stack_name) + self.assertIsNone(stack_status) + + # check deletion of Glance-image + for image_name in image_name_list: + image_id = self.get_image_id(image_name) + self.assertIsNone(image_id) + + # 8. Delete a VNF instance resp, body = self.delete_vnf_instance(inst_id) self.assertEqual(204, resp.status_code) + self.check_resp_headers_in_delete(resp) + + # check deletion of VNF instance + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(404, resp.status_code) + + # check usageState of VNF Package + usage_state = self.get_vnf_package(self.vnf_pkg_1).get('usageState') + self.assertEqual('NOT_IN_USE', usage_state) def test_sample2(self): + """Test LCM operations with omitting except for required attributes + + * About attributes: + Omit except for required attributes. + Only the following cardinality attributes are set. + - 1 + - 1..N (1) + + * About LCM operations: + This test includes the following operations. + - 1. Create a new VNF instance resource + - 2. Instantiate a VNF instance + - 3. Show VNF instance + - 4. Terminate a VNF instance + - 5. Delete a VNF instance + """ + # 1. Create a new VNF instance resource + expected_inst_attrs = [ + 'id', + # 'vnfInstanceName', # omitted + # 'vnfInstanceDescription', # omitted + 'vnfdId', + 'vnfProvider', + 'vnfProductName', + 'vnfSoftwareVersion', + 'vnfdVersion', + # 'vnfConfigurableProperties', # omitted + # 'vimConnectionInfo', # omitted + 'instantiationState', + # 'instantiatedVnfInfo', # omitted + # 'metadata', # omitted + # 'extensions', # omitted + '_links' + ] create_req = paramgen.sample2_create(self.vnfd_id_2) resp, body = self.create_vnf_instance(create_req) self.assertEqual(201, resp.status_code) + self.check_resp_headers_in_create(resp) + self.check_resp_body(body, expected_inst_attrs) inst_id = body['id'] - net_ids = self.get_network_ids(['net0', 'net1', 'net_mgmt']) - subnet_ids = self.get_subnet_ids(['subnet0', 'subnet1']) - instantiate_req = paramgen.sample2_instantiate( - net_ids, subnet_ids, self.auth_url) + # check usageState of VNF Package + usage_state = self.get_vnf_package(self.vnf_pkg_2).get('usageState') + self.assertEqual('IN_USE', usage_state) + + # 2. Instantiate a VNF instance + instantiate_req = paramgen.sample2_instantiate() resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req) self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) lcmocc_id = os.path.basename(resp.headers['Location']) self.wait_lcmocc_complete(lcmocc_id) + # check creation of Heat-stack + stack_name = "vnf-{}".format(inst_id) + stack_status, _ = self.heat_client.get_status(stack_name) + self.assertEqual("CREATE_COMPLETE", stack_status) + + # check that the servers set in "nfvi_node:Affinity" are + # deployed on the same host. + # NOTE: it's up to heat to decide which host to deploy to + vdu1_details = self.get_server_details('VDU1') + vdu2_details = self.get_server_details('VDU2') + vdu1_host = vdu1_details['hostId'] + vdu2_host = vdu2_details['hostId'] + self.assertEqual(vdu1_host, vdu2_host) + + # 3. Show VNF instance + additional_inst_attrs = [ + 'vimConnectionInfo', + 'instantiatedVnfInfo' + ] + expected_inst_attrs.extend(additional_inst_attrs) resp, body = self.show_vnf_instance(inst_id) self.assertEqual(200, resp.status_code) - # TODO(oda-g): check body + self.check_resp_headers_in_get(resp) + self.check_resp_body(body, expected_inst_attrs) + # 4. Terminate a VNF instance terminate_req = paramgen.sample2_terminate() resp, body = self.terminate_vnf_instance(inst_id, terminate_req) self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) lcmocc_id = os.path.basename(resp.headers['Location']) self.wait_lcmocc_complete(lcmocc_id) @@ -143,5 +540,19 @@ class VnfLcmTest(base_v2.BaseSolV2Test): # update and terminate completion. time.sleep(10) + # check deletion of Heat-stack + stack_status, _ = self.heat_client.get_status(stack_name) + self.assertIsNone(stack_status) + + # 5. Delete a VNF instance resp, body = self.delete_vnf_instance(inst_id) self.assertEqual(204, resp.status_code) + self.check_resp_headers_in_delete(resp) + + # check deletion of VNF instance + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(404, resp.status_code) + + # check usageState of VNF Package + usage_state = self.get_vnf_package(self.vnf_pkg_2).get('usageState') + self.assertEqual('NOT_IN_USE', usage_state) diff --git a/tacker/tests/functional/sol_v2/utils.py b/tacker/tests/functional/sol_v2/utils.py index 14b048e56..32d1d5621 100644 --- a/tacker/tests/functional/sol_v2/utils.py +++ b/tacker/tests/functional/sol_v2/utils.py @@ -50,6 +50,18 @@ def make_zip(sample_dir, tmp_dir, vnfd_id, image_path=None): shutil.make_archive(zip_file_path, "zip", tmp_contents) +def create_network(network): + # assume OS_* environment variables are already set + subprocess.run( + ["openstack", "net", "create", network]) + + +def delete_network(network): + # assume OS_* environment variables are already set + subprocess.run( + ["openstack", "net", "delete", network]) + + def get_network_ids(networks): # assume OS_* environment variables are already set net_ids = {} @@ -61,6 +73,13 @@ def get_network_ids(networks): return net_ids +def create_subnet(subnet, network, sub_range, version): + # assume OS_* environment variables are already set + subprocess.run( + ["openstack", "subnet", "create", subnet, "--network", network, + "--subnet-range", sub_range, "--ip-version", version]) + + def get_subnet_ids(subnets): # assume OS_* environment variables are already set subnet_ids = {} @@ -70,3 +89,26 @@ def get_subnet_ids(subnets): capture_output=True, encoding='utf-8') subnet_ids[subnet] = json.loads(p.stdout)['id'] return subnet_ids + + +def create_port(port, network): + # assume OS_* environment variables are already set + subprocess.run( + ["openstack", "port", "create", port, "--network", network]) + + +def delete_port(port): + # assume OS_* environment variables are already set + subprocess.run( + ["openstack", "port", "delete", port]) + + +def get_port_ids(ports): + # assume OS_* environment variables are already set + port_ids = {} + for port in ports: + p = subprocess.run( + ["openstack", "port", "show", port, "-c", "id", "-f", "json"], + capture_output=True, encoding='utf-8') + port_ids[port] = json.loads(p.stdout)['id'] + return port_ids