Revise FT of V2-API

This patch revise the FT of V2-API.
The viewpoints of this revision are listed below:

* Add some LCM operations in FT(such as Subscriptions
  and VNFLCM operation occurrences)
* Revise VNF Package and request-body
* Strengthen the check points by assertion
* Fix the response-body related to the attribute that
  does not have the information to be returned

Change-Id: I681800f52b4e2eb29c320c007ed1cf2901b61267
This commit is contained in:
Hiroo Kitamura 2021-12-07 07:07:28 +00:00
parent c6b0ca5fe3
commit ff2e8da445
18 changed files with 1370 additions and 571 deletions

View File

@ -273,6 +273,12 @@
Multinodes job for SOL V2 devstack-based functional tests Multinodes job for SOL V2 devstack-based functional tests
host-vars: host-vars:
controller-tacker: 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 tox_envlist: dsvm-functional-sol-v2
- job: - job:

View File

@ -81,19 +81,24 @@ class VnfLcmControllerV2(sol_wsgi.SolAPIController):
vnfSoftwareVersion=pkg_info.vnfSoftwareVersion, vnfSoftwareVersion=pkg_info.vnfSoftwareVersion,
vnfdVersion=pkg_info.vnfdVersion, vnfdVersion=pkg_info.vnfdVersion,
instantiationState='NOT_INSTANTIATED', 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. # not set at the moment. will be set when instantiate.
# vimConnectionInfo # vimConnectionInfo
# instantiatedVnfInfo # 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) inst.create(context)
self.nfvo_client.send_inst_create_notification(context, inst, self.nfvo_client.send_inst_create_notification(context, inst,

View File

@ -17,6 +17,7 @@ import os
import shutil import shutil
import tempfile import tempfile
import time import time
import urllib
import yaml import yaml
from oslo_config import cfg from oslo_config import cfg
@ -38,7 +39,7 @@ VNF_PACKAGE_UPLOAD_TIMEOUT = 300
class BaseSolV2Test(base.BaseTestCase): class BaseSolV2Test(base.BaseTestCase):
"""Base test case class for SOL v2 functionl tests.""" """Base test case class for SOL v2 functional tests."""
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
@ -63,6 +64,10 @@ class BaseSolV2Test(base.BaseTestCase):
cls.tacker_client = http_client.HttpClient(auth) cls.tacker_client = http_client.HttpClient(auth)
cls.neutron_client = http_client.HttpClient(auth, cls.neutron_client = http_client.HttpClient(auth,
service_type='network') 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) cls.heat_client = heat_utils.HeatClient(vim_info)
@classmethod @classmethod
@ -138,6 +143,11 @@ class BaseSolV2Test(base.BaseTestCase):
cls.tacker_client.do_request(path, "DELETE") 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): def get_network_ids(self, networks):
path = "/v2.0/networks" path = "/v2.0/networks"
resp, body = self.neutron_client.do_request(path, "GET") resp, body = self.neutron_client.do_request(path, "GET")
@ -156,6 +166,100 @@ class BaseSolV2Test(base.BaseTestCase):
subnet_ids[subnet['name']] = subnet['id'] subnet_ids[subnet['name']] = subnet['id']
return subnet_ids 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): def create_vnf_instance(self, req_body):
path = "/vnflcm/v2/vnf_instances" path = "/vnflcm/v2/vnf_instances"
return self.tacker_client.do_request( return self.tacker_client.do_request(
@ -171,6 +275,13 @@ class BaseSolV2Test(base.BaseTestCase):
return self.tacker_client.do_request( return self.tacker_client.do_request(
path, "GET", version="2.0.0") 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): def instantiate_vnf_instance(self, inst_id, req_body):
path = "/vnflcm/v2/vnf_instances/{}/instantiate".format(inst_id) path = "/vnflcm/v2/vnf_instances/{}/instantiate".format(inst_id)
return self.tacker_client.do_request( return self.tacker_client.do_request(
@ -196,3 +307,92 @@ class BaseSolV2Test(base.BaseTestCase):
continue continue
else: # FAILED_TEMP or ROLLED_BACK else: # FAILED_TEMP or ROLLED_BACK
raise Exception("Operation failed. state: %s" % state) 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)

View File

@ -16,226 +16,451 @@
from oslo_utils import uuidutils 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): 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 { return {
"vnfdId": vnfd_id, "vnfdId": vnfd_id,
"vnfInstanceName": "sample1", "vnfInstanceName": "sample1",
"vnfInstanceDescription": "test sample1" "vnfInstanceDescription": "test sample1",
"metadata": {"dummy-key": "dummy-val"}
} }
def sample1_terminate(): 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 { return {
"terminationType": "FORCEFUL" "terminationType": "FORCEFUL"
} }
def sample1_instantiate(net_ids, subnet_ids, auth_url): def sample2_instantiate():
ext_vl_1 = { # Omit except for required attributes
"id": uuidutils.generate_uuid(), # NOTE: Only the following cardinality attributes are set.
"resourceId": net_ids['net0'], # - 1
"extCps": [ # - 1..N (1)
{
"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']}]}}]}
}
}
]
}
return { return {
"flavourId": "simple", "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"
}
} }

View File

@ -18,7 +18,9 @@ parameters:
type: string type: string
net5: net5:
type: string type: string
subnet: subnet1:
type: string
subnet2:
type: string type: string
resources: resources:
@ -27,7 +29,7 @@ resources:
properties: properties:
flavor: { get_param: flavor } flavor: { get_param: flavor }
name: VDU1 name: VDU1
block_device_mapping_v2: [{"volume_id": { get_resource: VirtualStorage }}] block_device_mapping_v2: [{"volume_id": { get_resource: VDU1-VirtualStorage }}]
networks: networks:
- port: - port:
get_resource: VDU1_CP1 get_resource: VDU1_CP1
@ -44,7 +46,7 @@ resources:
availability_zone: { get_param: zone } availability_zone: { get_param: zone }
VirtualStorage: VDU1-VirtualStorage:
type: OS::Cinder::Volume type: OS::Cinder::Volume
properties: properties:
image: { get_param: image } image: { get_param: image }
@ -61,6 +63,8 @@ resources:
type: OS::Neutron::Port type: OS::Neutron::Port
properties: properties:
network: { get_param: net1 } network: { get_param: net1 }
fixed_ips:
- subnet: { get_param: subnet1}
# extVL with numDynamicAddresses and subnet # extVL with numDynamicAddresses and subnet
VDU1_CP2: VDU1_CP2:
@ -68,7 +72,7 @@ resources:
properties: properties:
network: { get_param: net2 } network: { get_param: net2 }
fixed_ips: fixed_ips:
- subnet: { get_param: subnet} - subnet: { get_param: subnet2}
# delete the following line when extmanagedVLs' Ports are specified in instantiatevnfrequest # delete the following line when extmanagedVLs' Ports are specified in instantiatevnfrequest
VDU1_CP3: VDU1_CP3:

View File

@ -16,11 +16,12 @@ resources:
type: VDU1.yaml type: VDU1.yaml
properties: properties:
flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] } 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] } zone: { get_param: [ nfv, VDU, VDU1, locationConstraints] }
net1: { get_param: [ nfv, CP, VDU1_CP1, network] } net1: { get_param: [ nfv, CP, VDU1_CP1, network] }
net2: { get_param: [ nfv, CP, VDU1_CP2, 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 } net3: { get_resource: internalVL1 }
net4: { get_resource: internalVL2 } net4: { get_resource: internalVL2 }
net5: { get_resource: internalVL3 } net5: { get_resource: internalVL3 }
@ -44,11 +45,12 @@ resources:
type: OS::Nova::Server type: OS::Nova::Server
properties: properties:
flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] } flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] }
image: { get_param: [ nfv, VDU, VDU2, vcImageId] } name: VDU2
availability_zone: { get_param: [ nfv, VDU, VDU2, locationConstraints ] } availability_zone: { get_param: [ nfv, VDU, VDU2, locationConstraints ] }
block_device_mapping_v2: [{"volume_id": { get_resource: VDU2-VirtualStorage }}]
networks: networks:
- port: - port: { get_param: [ nfv, CP, VDU2_CP1-1, port ] }
get_resource: VDU2_CP1 - port: { get_param: [ nfv, CP, VDU2_CP1-2, port ] }
- port: - port:
get_resource: VDU2_CP2 get_resource: VDU2_CP2
- port: - port:
@ -58,13 +60,17 @@ resources:
- port: - port:
get_resource: VDU2_CP5 get_resource: VDU2_CP5
# extVL with FixedIP VDU2-VirtualStorage:
VDU2_CP1: type: OS::Cinder::Volume
type: OS::Neutron::Port
properties: properties:
network: { get_param: [ nfv, CP, VDU2_CP1, network ] } image: { get_param: [ nfv, VDU, VDU2-VirtualStorage, vcImageId] }
fixed_ips: size: 1
- ip_address: { get_param: [nfv, CP, VDU2_CP1, fixed_ips, 0, ip_address]} volume_type: { get_resource: multi }
multi:
type: OS::Cinder::VolumeType
properties:
name: VDU2-multi
metadata: { multiattach: "<is> True" }
# extVL with FixedIP and Subnet # extVL with FixedIP and Subnet
VDU2_CP2: VDU2_CP2:
@ -74,6 +80,7 @@ resources:
fixed_ips: fixed_ips:
- ip_address: { get_param: [nfv, CP, VDU2_CP2, fixed_ips, 0, ip_address]} - 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, 0, subnet]}
- subnet: { get_param: [nfv, CP, VDU2_CP2, fixed_ips, 1, subnet]}
VDU2_CP3: VDU2_CP3:
type: OS::Neutron::Port type: OS::Neutron::Port

View File

@ -34,7 +34,8 @@ topology_template:
flavour_id: simple flavour_id: simple
requirements: requirements:
virtual_link_external1_1: [ VDU1_CP1, virtual_link ] 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_1: [ VDU1_CP2, virtual_link ]
virtual_link_external2_2: [ VDU2_CP2, virtual_link ] virtual_link_external2_2: [ VDU2_CP2, virtual_link ]
@ -79,7 +80,7 @@ topology_template:
virtual_local_storage: virtual_local_storage:
- size_of_storage: 3 GB - size_of_storage: 3 GB
requirements: requirements:
- virtual_storage: VirtualStorage - virtual_storage: VDU1-VirtualStorage
VDU2: VDU2:
type: tosca.nodes.nfv.Vdu.Compute type: tosca.nodes.nfv.Vdu.Compute
@ -89,17 +90,6 @@ topology_template:
vdu_profile: vdu_profile:
min_number_of_instances: 1 min_number_of_instances: 1
max_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: capabilities:
virtual_compute: virtual_compute:
properties: properties:
@ -115,19 +105,17 @@ topology_template:
num_virtual_cpu: 1 num_virtual_cpu: 1
virtual_local_storage: virtual_local_storage:
- size_of_storage: 3 GB - size_of_storage: 3 GB
artifacts: requirements:
sw_image: - virtual_storage: VDU2-VirtualStorage
type: tosca.artifacts.nfv.SwImage
file: ../Files/images/cirros-0.5.2-x86_64-disk.img
VirtualStorage: VDU1-VirtualStorage:
type: tosca.nodes.nfv.Vdu.VirtualBlockStorage type: tosca.nodes.nfv.Vdu.VirtualBlockStorage
properties: properties:
virtual_block_storage_data: virtual_block_storage_data:
size_of_storage: 1 GB size_of_storage: 1 GB
rdma_enabled: true rdma_enabled: true
sw_image_data: sw_image_data:
name: cirros-0.5.2-x86_64-disk name: VDU1-VirtualStorage-image
version: '0.5.2' version: '0.5.2'
checksum: checksum:
algorithm: sha-256 algorithm: sha-256
@ -137,6 +125,32 @@ topology_template:
min_disk: 0 GB min_disk: 0 GB
min_ram: 256 MB min_ram: 256 MB
size: 12 GB 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: VDU1_CP1:
type: tosca.nodes.nfv.VduCp type: tosca.nodes.nfv.VduCp
@ -181,7 +195,7 @@ topology_template:
- virtual_binding: VDU1 - virtual_binding: VDU1
- virtual_link: internalVL3 - virtual_link: internalVL3
VDU2_CP1: VDU2_CP1-1:
type: tosca.nodes.nfv.VduCp type: tosca.nodes.nfv.VduCp
properties: properties:
layer_protocols: [ ipv4 ] layer_protocols: [ ipv4 ]
@ -189,7 +203,7 @@ topology_template:
requirements: requirements:
- virtual_binding: VDU2 - virtual_binding: VDU2
VDU2_CP2: VDU2_CP1-2:
type: tosca.nodes.nfv.VduCp type: tosca.nodes.nfv.VduCp
properties: properties:
layer_protocols: [ ipv4 ] layer_protocols: [ ipv4 ]
@ -197,20 +211,28 @@ topology_template:
requirements: requirements:
- virtual_binding: VDU2 - virtual_binding: VDU2
VDU2_CP3: VDU2_CP2:
type: tosca.nodes.nfv.VduCp type: tosca.nodes.nfv.VduCp
properties: properties:
layer_protocols: [ ipv4 ] layer_protocols: [ ipv4 ]
order: 2 order: 2
requirements: requirements:
- virtual_binding: VDU2 - virtual_binding: VDU2
VDU2_CP3:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 3
requirements:
- virtual_binding: VDU2
- virtual_link: internalVL1 - virtual_link: internalVL1
VDU2_CP4: VDU2_CP4:
type: tosca.nodes.nfv.VduCp type: tosca.nodes.nfv.VduCp
properties: properties:
layer_protocols: [ ipv4 ] layer_protocols: [ ipv4 ]
order: 3 order: 4
requirements: requirements:
- virtual_binding: VDU2 - virtual_binding: VDU2
- virtual_link: internalVL2 - virtual_link: internalVL2
@ -219,7 +241,7 @@ topology_template:
type: tosca.nodes.nfv.VduCp type: tosca.nodes.nfv.VduCp
properties: properties:
layer_protocols: [ ipv4 ] layer_protocols: [ ipv4 ]
order: 4 order: 5
requirements: requirements:
- virtual_binding: VDU2 - virtual_binding: VDU2
- virtual_link: internalVL3 - virtual_link: internalVL3

View File

@ -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)

View File

@ -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 from tacker.sol_refactored.infra_drivers.openstack import userdata_utils
class DefaultUserData(userdata_utils.AbstractUserData): class UserData(userdata_utils.AbstractUserData):
@staticmethod @staticmethod
def instantiate(req, inst, grant_req, grant, tmp_csar_dir): 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) vnfd = userdata_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
flavour_id = req['flavourId'] flavour_id = req['flavourId']
@ -65,6 +89,16 @@ class DefaultUserData(userdata_utils.AbstractUserData):
'ip_address') 'ip_address')
fixed_ips.append(ips_i) fixed_ips.append(ips_i)
cp_value['fixed_ips'] = fixed_ips 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) userdata_utils.apply_ext_managed_vls(top_hot, req, grant)

View File

@ -42,11 +42,22 @@ shutil.rmtree(tmp_dir)
create_req = paramgen.sample1_create(vnfd_id) create_req = paramgen.sample1_create(vnfd_id)
terminate_req = paramgen.sample1_terminate() terminate_req = paramgen.sample1_terminate()
net_ids = utils.get_network_ids(['net0', 'net1', 'net_mgmt']) print('#####################################################################\n'
subnet_ids = utils.get_subnet_ids(['subnet0', 'subnet1']) '# 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( 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: with open("create_req", "w") as f:
f.write(json.dumps(create_req, indent=2)) f.write(json.dumps(create_req, indent=2))

View File

@ -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')

View File

@ -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')

View File

@ -6,84 +6,25 @@ parameters:
type: string type: string
image: image:
type: string type: string
net1:
type: string
net2:
type: string
net3:
type: string
net4:
type: string
net5: net5:
type: string type: string
subnet: affinity:
type: string type: string
# uncomment when BUG "https://storyboard.openstack.org/#!/story/2009164" fixed
# affinity:
# type: string
resources: resources:
VDU1: VDU1:
type: OS::Nova::Server type: OS::Nova::Server
properties: properties:
flavor: { get_param: flavor } flavor: { get_param: flavor }
image: { get_param: image }
name: VDU1 name: VDU1
block_device_mapping_v2: [{"volume_id": { get_resource: VirtualStorage }}]
networks: networks:
- port: - port:
get_resource: VDU1_CP1 get_resource: VDU1_CP1
- port: scheduler_hints:
get_resource: VDU1_CP2 group: {get_param: affinity }
# 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
# 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: "<is> True" }
# extVL without FixedIP or with numDynamicAddresses
VDU1_CP1: 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 type: OS::Neutron::Port
properties: properties:
network: { get_param: net5 } network: { get_param: net5 }

View File

@ -16,15 +16,9 @@ resources:
type: VDU1.yaml type: VDU1.yaml
properties: properties:
flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] } flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] }
image: { get_param: [ nfv, VDU, VirtualStorage, vcImageId ] } image: { get_param: [ nfv, VDU, VDU1, 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 }
net5: { get_resource: internalVL3 } 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: VDU1_scale_out:
type: OS::Heat::ScalingPolicy type: OS::Heat::ScalingPolicy
properties: properties:
@ -44,80 +38,23 @@ resources:
type: OS::Nova::Server type: OS::Nova::Server
properties: properties:
flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] } flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] }
name: VDU2
image: { get_param: [ nfv, VDU, VDU2, vcImageId] } image: { get_param: [ nfv, VDU, VDU2, vcImageId] }
networks: networks:
- port: - port:
get_resource: VDU2_CP1 get_resource: VDU2_CP1
- port: scheduler_hints:
get_resource: VDU2_CP2 group: {get_resource: nfvi_node_affinity }
- 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 }
# extVL with FixedIP
VDU2_CP1: VDU2_CP1:
type: OS::Neutron::Port type: OS::Neutron::Port
properties: 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 } 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: internalVL3:
type: OS::Neutron::Net 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: internalVL3_subnet:
type: OS::Neutron::Subnet type: OS::Neutron::Subnet
properties: properties:
@ -126,11 +63,10 @@ resources:
get_resource: internalVL3 get_resource: internalVL3
cidr: 192.168.5.0/24 cidr: 192.168.5.0/24
# uncomment when BUG "https://storyboard.openstack.org/#!/story/2009164" fixed nfvi_node_affinity:
# nfvi_node_affinity: type: OS::Nova::ServerGroup
# type: OS::Nova::ServerGroup properties:
# properties: name: nfvi_node_affinity
# name: nfvi_node_affinity policies: [ 'affinity' ]
# policies: [ 'affinity' ]
outputs: {} outputs: {}

View File

@ -32,11 +32,6 @@ topology_template:
node_type: company.provider.VNF node_type: company.provider.VNF
properties: properties:
flavour_id: simple 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: node_templates:
VNF: VNF:
@ -67,6 +62,17 @@ topology_template:
vdu_profile: vdu_profile:
min_number_of_instances: 1 min_number_of_instances: 1
max_number_of_instances: 3 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: capabilities:
virtual_compute: virtual_compute:
properties: properties:
@ -82,8 +88,6 @@ topology_template:
num_virtual_cpu: 1 num_virtual_cpu: 1
virtual_local_storage: virtual_local_storage:
- size_of_storage: 3 GB - size_of_storage: 3 GB
requirements:
- virtual_storage: VirtualStorage
VDU2: VDU2:
type: tosca.nodes.nfv.Vdu.Compute type: tosca.nodes.nfv.Vdu.Compute
@ -120,59 +124,7 @@ topology_template:
virtual_local_storage: virtual_local_storage:
- size_of_storage: 3 GB - 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: 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 type: tosca.nodes.nfv.VduCp
properties: properties:
layer_protocols: [ ipv4 ] layer_protocols: [ ipv4 ]
@ -182,40 +134,6 @@ topology_template:
- virtual_link: internalVL3 - virtual_link: internalVL3
VDU2_CP1: 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 type: tosca.nodes.nfv.VduCp
properties: properties:
layer_protocols: [ ipv4 ] layer_protocols: [ ipv4 ]
@ -224,44 +142,6 @@ topology_template:
- virtual_binding: VDU2 - virtual_binding: VDU2
- virtual_link: internalVL3 - 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: internalVL3:
type: tosca.nodes.nfv.VnfVirtualLink type: tosca.nodes.nfv.VnfVirtualLink
properties: properties:
@ -357,34 +237,6 @@ topology_template:
number_of_instances: 1 number_of_instances: 1
targets: [ VDU2 ] 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: - internalVL3_instantiation_levels:
type: tosca.policies.nfv.VirtualLinkInstantiationLevels type: tosca.policies.nfv.VirtualLinkInstantiationLevels
properties: properties:

View File

@ -34,12 +34,7 @@ shutil.rmtree(tmp_dir)
create_req = paramgen.sample2_create(vnfd_id) create_req = paramgen.sample2_create(vnfd_id)
terminate_req = paramgen.sample2_terminate() terminate_req = paramgen.sample2_terminate()
instantiate_req = paramgen.sample2_instantiate()
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")
with open("create_req", "w") as f: with open("create_req", "w") as f:
f.write(json.dumps(create_req, indent=2)) f.write(json.dumps(create_req, indent=2))

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import ddt
import os import os
import time import time
@ -20,6 +21,7 @@ from tacker.tests.functional.sol_v2 import base_v2
from tacker.tests.functional.sol_v2 import paramgen from tacker.tests.functional.sol_v2 import paramgen
@ddt.ddt
class VnfLcmTest(base_v2.BaseSolV2Test): class VnfLcmTest(base_v2.BaseSolV2Test):
@classmethod @classmethod
@ -53,10 +55,18 @@ class VnfLcmTest(base_v2.BaseSolV2Test):
super(VnfLcmTest, self).setUp() super(VnfLcmTest, self).setUp()
def test_api_versions(self): 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" path = "/vnflcm/api_versions"
resp, body = self.tacker_client.do_request( resp, body = self.tacker_client.do_request(
path, "GET", version="2.0.0") path, "GET", version="2.0.0")
self.assertEqual(200, resp.status_code) self.assertEqual(200, resp.status_code)
self.check_resp_headers_in_get(resp)
expected_body = { expected_body = {
"uriPrefix": "/vnflcm", "uriPrefix": "/vnflcm",
"apiVersions": [ "apiVersions": [
@ -70,6 +80,7 @@ class VnfLcmTest(base_v2.BaseSolV2Test):
resp, body = self.tacker_client.do_request( resp, body = self.tacker_client.do_request(
path, "GET", version="2.0.0") path, "GET", version="2.0.0")
self.assertEqual(200, resp.status_code) self.assertEqual(200, resp.status_code)
self.check_resp_headers_in_get(resp)
expected_body = { expected_body = {
"uriPrefix": "/vnflcm/v2", "uriPrefix": "/vnflcm/v2",
"apiVersions": [ "apiVersions": [
@ -78,29 +89,334 @@ class VnfLcmTest(base_v2.BaseSolV2Test):
} }
self.assertEqual(body, expected_body) 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): 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) create_req = paramgen.sample1_create(self.vnfd_id_1)
resp, body = self.create_vnf_instance(create_req) resp, body = self.create_vnf_instance(create_req)
self.assertEqual(201, resp.status_code) 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'] inst_id = body['id']
net_ids = self.get_network_ids(['net0', 'net1', 'net_mgmt']) # check usageState of VNF Package
subnet_ids = self.get_subnet_ids(['subnet0', 'subnet1']) 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( 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) resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req)
self.assertEqual(202, resp.status_code) self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location']) lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id) 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) resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code) 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=<list>
# -> 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=<list>
# -> 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=<list>
# -> 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=<list>
# -> 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() terminate_req = paramgen.sample1_terminate()
resp, body = self.terminate_vnf_instance(inst_id, terminate_req) resp, body = self.terminate_vnf_instance(inst_id, terminate_req)
self.assertEqual(202, resp.status_code) self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location']) lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id) self.wait_lcmocc_complete(lcmocc_id)
@ -109,32 +425,113 @@ class VnfLcmTest(base_v2.BaseSolV2Test):
# update and terminate completion. # update and terminate completion.
time.sleep(10) 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) resp, body = self.delete_vnf_instance(inst_id)
self.assertEqual(204, resp.status_code) 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): 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) create_req = paramgen.sample2_create(self.vnfd_id_2)
resp, body = self.create_vnf_instance(create_req) resp, body = self.create_vnf_instance(create_req)
self.assertEqual(201, resp.status_code) 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'] inst_id = body['id']
net_ids = self.get_network_ids(['net0', 'net1', 'net_mgmt']) # check usageState of VNF Package
subnet_ids = self.get_subnet_ids(['subnet0', 'subnet1']) usage_state = self.get_vnf_package(self.vnf_pkg_2).get('usageState')
instantiate_req = paramgen.sample2_instantiate( self.assertEqual('IN_USE', usage_state)
net_ids, subnet_ids, self.auth_url)
# 2. Instantiate a VNF instance
instantiate_req = paramgen.sample2_instantiate()
resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req) resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req)
self.assertEqual(202, resp.status_code) self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location']) lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id) 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) resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code) 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() terminate_req = paramgen.sample2_terminate()
resp, body = self.terminate_vnf_instance(inst_id, terminate_req) resp, body = self.terminate_vnf_instance(inst_id, terminate_req)
self.assertEqual(202, resp.status_code) self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location']) lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id) self.wait_lcmocc_complete(lcmocc_id)
@ -143,5 +540,19 @@ class VnfLcmTest(base_v2.BaseSolV2Test):
# update and terminate completion. # update and terminate completion.
time.sleep(10) 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) resp, body = self.delete_vnf_instance(inst_id)
self.assertEqual(204, resp.status_code) 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)

View File

@ -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) 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): def get_network_ids(networks):
# assume OS_* environment variables are already set # assume OS_* environment variables are already set
net_ids = {} net_ids = {}
@ -61,6 +73,13 @@ def get_network_ids(networks):
return net_ids 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): def get_subnet_ids(subnets):
# assume OS_* environment variables are already set # assume OS_* environment variables are already set
subnet_ids = {} subnet_ids = {}
@ -70,3 +89,26 @@ def get_subnet_ids(subnets):
capture_output=True, encoding='utf-8') capture_output=True, encoding='utf-8')
subnet_ids[subnet] = json.loads(p.stdout)['id'] subnet_ids[subnet] = json.loads(p.stdout)['id']
return subnet_ids 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