support change_ext_conn v2 API

This patch implements change external VNF connectivity task
defined in ETSI NFV-SOL003 v3.3.1 5.4.11.

Functional tests will be provided with another patch.

Implements: blueprint support-nfv-solv3-change-external-connectivity
Change-Id: I3da41d31d79521907fc929497db327176d8ea8eb
This commit is contained in:
Itsuro Oda 2021-12-23 00:44:13 +00:00 committed by Ken Fujimoto
parent 5951218ee8
commit 83e4a8d526
19 changed files with 1763 additions and 150 deletions

View File

@ -0,0 +1,6 @@
---
features:
- |
Add the Version "2.0.0" of
Change external VNF connectivity API
based on ETSI NFV specifications.

View File

@ -111,6 +111,15 @@ rules = [
'path': VNF_INSTANCES_ID_PATH + '/scale'}
]
),
policy.DocumentedRuleDefault(
name=POLICY_NAME.format('change_ext_conn'),
check_str=RULE_ANY,
description="Change external vnf connectivity.",
operations=[
{'method': 'POST',
'path': VNF_INSTANCES_ID_PATH + '/change_ext_conn'}
]
),
# TODO(oda-g): add more lcm operations etc when implemented.

View File

@ -39,6 +39,7 @@ class VnflcmAPIRouterV2(sol_wsgi.SolAPIRouter):
("/vnf_instances/{id}/heal", {"POST": "heal"}),
("/vnf_instances/{id}/terminate", {"POST": "terminate"}),
("/vnf_instances/{id}/scale", {"POST": "scale"}),
("/vnf_instances/{id}/change_ext_conn", {"POST": "change_ext_conn"}),
("/api_versions", {"GET": "api_versions"}),
("/subscriptions", {"GET": "subscription_list",
"POST": "subscription_create"}),

View File

@ -93,6 +93,26 @@ ScaleVnfRequest_V200 = {
'additionalProperties': True,
}
# SOL003 5.5.2.11
ChangeExtVnfConnectivityRequest_V200 = {
'type': 'object',
'properties': {
'extVirtualLinks': {
'type': 'array',
'items': common_types.ExtVirtualLinkData
},
'vimConnectionInfo': {
'type': 'object',
'patternProperties': {
'^.*$': common_types.VimConnectionInfo
},
},
'additionalParams': parameter_types.keyvalue_pairs,
},
'required': ['extVirtualLinks'],
'additionalProperties': True,
}
# SOL013 8.3.4
_SubscriptionAuthentication = {
'type': 'object',

View File

@ -329,7 +329,8 @@ def _change_vnf_info(lcmocc, inst_saved, inst):
def update_lcmocc(lcmocc, inst_saved, inst):
# if operation is MODIFY_INFO, make changedInfo of lcmocc.
# for other operations, make ResourceChanges of lcmocc
# from instantiatedVnfInfo.
# from instantiatedVnfInfo. In addition if operation is
# CHANGE_EXT_CONN, make changedExtConnectivity of lcmocc.
# NOTE: grant related info such as resourceDefinitionId, zoneId
# and so on are not included in lcmocc since such info are not
# included in instantiatedVnfInfo.
@ -431,6 +432,53 @@ def update_lcmocc(lcmocc, inst_saved, inst):
change_info.affectedExtLinkPorts = affected_ext_link_ports
lcmocc.resourceChanges = change_info
if lcmocc.operation == fields.LcmOperationType.CHANGE_EXT_CONN:
_, added_ext_vls, common_ext_vls = _calc_diff('extVirtualLinkInfo')
def _get_ext_vl(vl_id, vl_array):
for vl in vl_array:
if vl.id == vl_id:
return vl
chg_ext_conn = [_get_ext_vl(ext_vl_id, inst_info.extVirtualLinkInfo)
for ext_vl_id in added_ext_vls]
for ext_vl_id in common_ext_vls:
ext_vl = _get_ext_vl(ext_vl_id, inst_info.extVirtualLinkInfo)
ext_vl_saved = _get_ext_vl(ext_vl_id,
inst_info_saved.extVirtualLinkInfo)
cp_data = []
if ext_vl.obj_attr_is_set('currentVnfExtCpData'):
cp_data = sorted(ext_vl.to_dict()['currentVnfExtCpData'],
key=lambda x: x['cpdId'])
cp_data_saved = []
if ext_vl_saved.obj_attr_is_set('currentVnfExtCpData'):
cp_data_saved = sorted(
ext_vl_saved.to_dict()['currentVnfExtCpData'],
key=lambda x: x['cpdId'])
if cp_data != cp_data_saved:
chg_ext_conn.append(ext_vl)
continue
# NOTE: For ports created by the heat, the id is not changed if
# its resourceId is not changed. But for ports given outside the
# heat, the resourceId may be changed without changing the id,
# so it is a policy to set changedExtConnectivity when there is
# a change in "id" or "resourceHandle.resourceId".
port_ids = set()
if ext_vl.obj_attr_is_set('extLinkPorts'):
port_ids = {(port.id, port.resourceHandle.resourceId)
for port in ext_vl.extLinkPorts}
port_ids_saved = set()
if ext_vl_saved.obj_attr_is_set('extLinkPorts'):
port_ids_saved = {(port.id, port.resourceHandle.resourceId)
for port in ext_vl_saved.extLinkPorts}
if port_ids != port_ids_saved:
chg_ext_conn.append(ext_vl)
if chg_ext_conn:
lcmocc.changedExtConnectivity = chg_ext_conn
def get_grant_req_and_grant(context, lcmocc):
if lcmocc.operation == fields.LcmOperationType.MODIFY_INFO:

View File

@ -47,10 +47,9 @@ def make_inst_links(inst, endpoint):
else: # 'INSTANTIATED'
links.terminate = objects.Link(href=self_href + "/terminate")
links.scale = objects.Link(href=self_href + "/scale")
links.changeExtConn = objects.Link(href=self_href + "/change_ext_conn")
# TODO(oda-g): add when the operation supported
# links.heal = objects.Link(href=self_href + "/heal")
# links.changeExtConn = objects.Link(
# href=self_href + "/change_ext_conn")
# etc.
return links

View File

@ -215,11 +215,12 @@ class ConductorV2(object):
lcmocc.operationState = fields.LcmOperationStateType.ROLLED_BACK
with context.session.begin(subtransactions=True):
# it is not necessary to update inst DB because it was not
# changed when the operationState became FAILED_TEMP.
# NOTE: inst object may be changed in driver's rollback
# method temporary but must not save it.
lcmocc.update(context)
# NOTE: Basically inst is not changed. But there is a case
# that VIM resources may be changed while rollback. Only
# change_ext_conn_rollback at the moment.
if lcmocc.operation == fields.LcmOperationType.CHANGE_EXT_CONN:
inst.update(context)
# grant_req and grant are not necessary any more.
if grant_req is not None:
grant_req.delete(context)

View File

@ -36,6 +36,11 @@ LOG = logging.getLogger(__name__)
CONF = config.CONF
# sub method for making id
def _make_combination_id(a, b):
return '{}-{}'.format(a, b)
class VnfLcmDriverV2(object):
def __init__(self):
@ -256,7 +261,7 @@ class VnfLcmDriverV2(object):
for cp_name in cp_names:
add_reses.append(
objects.ResourceDefinitionV1(
id="{}-{}".format(cp_name, vdu_res_id),
id=_make_combination_id(cp_name, vdu_res_id),
type='LINKPORT',
resourceTemplateId=cp_name
)
@ -264,7 +269,7 @@ class VnfLcmDriverV2(object):
for storage_name in storage_names:
add_reses.append(
objects.ResourceDefinitionV1(
id="{}-{}".format(storage_name, vdu_res_id),
id=_make_combination_id(storage_name, vdu_res_id),
type='STORAGE',
resourceTemplateId=storage_name
)
@ -427,7 +432,7 @@ class VnfLcmDriverV2(object):
# deleted.
continue
res_def = objects.ResourceDefinitionV1(
id="{}-{}".format(cp_info.cpdId, vdu_res_id),
id=_make_combination_id(cp_info.cpdId, vdu_res_id),
resourceTemplateId=cp_info.cpdId,
type='LINKPORT')
rm_reses.append(res_def)
@ -443,7 +448,8 @@ class VnfLcmDriverV2(object):
str_name = inst_str.virtualStorageDescId
rm_reses.append(
objects.ResourceDefinitionV1(
id="{}-{}".format(str_name, vdu_res_id),
id=_make_combination_id(str_name,
vdu_res_id),
type='STORAGE',
resourceTemplateId=str_name,
resource=inst_str.storageResource
@ -650,6 +656,17 @@ class VnfLcmDriverV2(object):
setattr(inst, attr, inst_utils.json_merge_patch(
base, getattr(req, attr)))
def _merge_vim_connection_info(self, inst, req):
# used by MODIFY_INFO and CHANGE_EXT_CONN
# inst.vimConnectionInfo exists since req.vimConnectionInfo
# can be set only if vnf instance is INSTANTIATED.
inst_viminfo = inst.to_dict()['vimConnectionInfo']
req_viminfo = req.to_dict()['vimConnectionInfo']
merge = inst_utils.json_merge_patch(inst_viminfo, req_viminfo)
inst.vimConnectionInfo = {
key: objects.VimConnectionInfo.from_dict(value)
for key, value in merge.items()}
def modify_info_process(self, context, lcmocc, inst, grant_req,
grant, vnfd):
req = lcmocc.operationParams
@ -685,14 +702,7 @@ class VnfLcmDriverV2(object):
self._modify_from_req(inst, req, attr)
if req.obj_attr_is_set('vimConnectionInfo'):
# inst.vimConnectionInfo exists since req.vimConnectionInfo
# can be set only if vnf instance is INSTANTIATED.
inst_viminfo = inst.to_dict()['vimConnectionInfo']
req_viminfo = req.to_dict()['vimConnectionInfo']
merge = inst_utils.json_merge_patch(inst_viminfo, req_viminfo)
inst.vimConnectionInfo = {
key: objects.VimConnectionInfo.from_dict(value)
for key, value in merge.items()}
self._merge_vim_connection_info(inst, req)
if req.obj_attr_is_set('vnfcInfoModifications'):
for vnfc_mod in req.vnfcInfoModifications:
@ -712,3 +722,106 @@ class VnfLcmDriverV2(object):
grant, vnfd):
# DB is not updated, rollback does nothing and makes it successful.
pass
def change_ext_conn_grant(self, grant_req, req, inst, vnfd):
inst_info = inst.instantiatedVnfInfo
cp_names = set()
link_ports = set()
for ext_vl in req.extVirtualLinks:
for ext_cp in ext_vl.extCps:
cp_names.add(ext_cp.cpdId)
for cp_config in ext_cp.cpConfig.values():
if cp_config.obj_attr_is_set('linkPortId'):
link_ports.add(ext_cp.cpdId)
add_reses = []
rm_reses = []
update_reses = []
rm_vnfc_cps = {}
for inst_vnfc in inst_info.vnfcResourceInfo:
if not inst_vnfc.obj_attr_is_set('vnfcCpInfo'):
continue
vnfc_cps = {cp_info.cpdId for cp_info in inst_vnfc.vnfcCpInfo}
if not vnfc_cps & cp_names:
continue
old_vdu_res_id = uuidutils.generate_uuid()
new_vdu_res_id = uuidutils.generate_uuid()
update_reses.append(
objects.ResourceDefinitionV1(
id=new_vdu_res_id,
type='COMPUTE',
resourceTemplateId=inst_vnfc.vduId,
resource=inst_vnfc.computeResource
)
)
for cp_info in inst_vnfc.vnfcCpInfo:
if cp_info.cpdId not in cp_names:
continue
if cp_info.obj_attr_is_set('vnfExtCpId'):
# if there is not vnfExtCpId, it means extLinkPorts of
# extVirtualLinks was specified.
res_def = objects.ResourceDefinitionV1(
id=_make_combination_id(cp_info.cpdId, old_vdu_res_id),
resourceTemplateId=cp_info.cpdId,
type='LINKPORT')
rm_reses.append(res_def)
rm_vnfc_cps[cp_info.vnfExtCpId] = res_def
if cp_info.cpdId not in link_ports:
add_reses.append(
objects.ResourceDefinitionV1(
id=_make_combination_id(cp_info.cpdId,
new_vdu_res_id),
resourceTemplateId=cp_info.cpdId,
type='LINKPORT'
)
)
# fill resourceHandle of rm_reses
if inst_info.obj_attr_is_set('extVirtualLinkInfo'):
for ext_vl in inst_info.extVirtualLinkInfo:
if ext_vl.obj_attr_is_set('extLinkPorts'):
for port in ext_vl.extLinkPorts:
if (port.obj_attr_is_set('cpInstanceId') and
port.cpInstanceId in rm_vnfc_cps):
res_def = rm_vnfc_cps[port.cpInstanceId]
res_def.resource = port.resourceHandle
if add_reses:
grant_req.addResources = add_reses
if rm_reses:
grant_req.removeResources = rm_reses
if update_reses:
grant_req.updateResources = update_reses
if req.obj_attr_is_set('additionalParams'):
grant_req.additionalParams = req.additionalParams
def change_ext_conn_process(self, context, lcmocc, inst, grant_req,
grant, vnfd):
req = lcmocc.operationParams
if req.obj_attr_is_set('vimConnectionInfo'):
self._merge_vim_connection_info(inst, req)
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
if vim_info.vimType == 'ETSINFV.OPENSTACK_KEYSTONE.V_3':
driver = openstack.Openstack()
driver.change_ext_conn(req, inst, grant_req, grant, vnfd)
else:
# only support openstack at the moment
raise sol_ex.SolException(sol_detail='not support vim type')
def change_ext_conn_rollback(self, context, lcmocc, inst, grant_req,
grant, vnfd):
req = lcmocc.operationParams
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
if vim_info.vimType == 'ETSINFV.OPENSTACK_KEYSTONE.V_3':
driver = openstack.Openstack()
driver.change_ext_conn_rollback(req, inst, grant_req, grant, vnfd)
else:
# only support openstack at the moment
raise sol_ex.SolException(sol_detail='not support vim type')

View File

@ -321,6 +321,27 @@ class VnfLcmControllerV2(sol_wsgi.SolAPIController):
return sol_wsgi.SolResponse(202, None, location=location)
@validator.schema(schema.ChangeExtVnfConnectivityRequest_V200, '2.0.0')
@coordinate.lock_vnf_instance('{id}')
def change_ext_conn(self, request, id, body):
context = request.context
inst = inst_utils.get_inst(context, id)
if inst.instantiationState != 'INSTANTIATED':
raise sol_ex.VnfInstanceIsNotInstantiated(inst_id=id)
lcmocc_utils.check_lcmocc_in_progress(context, id)
lcmocc = self._new_lcmocc(
id, v2fields.LcmOperationType.CHANGE_EXT_CONN, body)
lcmocc.create(context)
self.conductor_rpc.start_lcm_op(context, lcmocc.id)
location = lcmocc_utils.lcmocc_href(lcmocc.id, self.endpoint)
return sol_wsgi.SolResponse(202, None, location=location)
@validator.schema(schema.LccnSubscriptionRequest_V200, '2.0.0')
def subscription_create(self, request, body):
context = request.context

View File

@ -73,16 +73,15 @@ class Openstack(object):
def instantiate(self, req, inst, grant_req, grant, vnfd):
# make HOT
fields = self._make_hot(req, inst, grant_req, grant, vnfd)
LOG.debug("stack fields: %s", fields)
stack_name = fields['stack_name']
# create or update stack
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
heat_client = heat_utils.HeatClient(vim_info)
stack_name = heat_utils.get_stack_name(inst)
status, _ = heat_client.get_status(stack_name)
if status is None:
fields['stack_name'] = stack_name
heat_client.create_stack(fields)
else:
heat_client.update_stack(stack_name, fields)
@ -127,15 +126,12 @@ class Openstack(object):
return fields
def scale(self, req, inst, grant_req, grant, vnfd):
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
heat_client = heat_utils.HeatClient(vim_info)
# make HOT
fields = self._make_hot(req, inst, grant_req, grant, vnfd)
LOG.debug("stack fields: %s", fields)
stack_name = fields.pop('stack_name')
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
heat_client = heat_utils.HeatClient(vim_info)
# mark unhealthy to servers to be removed if scale in
if req.type == 'SCALE_IN':
@ -153,6 +149,7 @@ class Openstack(object):
vnfc.metadata['parent_resource_name'])
# update stack
stack_name = heat_utils.get_stack_name(inst)
fields = self._update_nfv_dict(heat_client, stack_name, fields)
heat_client.update_stack(stack_name, fields)
@ -194,6 +191,40 @@ class Openstack(object):
# NOTE: instantiatedVnfInfo is not necessary to update since it
# should be same as before scale API started.
def change_ext_conn(self, req, inst, grant_req, grant, vnfd):
# make HOT
fields = self._make_hot(req, inst, grant_req, grant, vnfd)
LOG.debug("stack fields: %s", fields)
# update stack
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
heat_client = heat_utils.HeatClient(vim_info)
stack_name = heat_utils.get_stack_name(inst)
fields = self._update_nfv_dict(heat_client, stack_name, fields)
heat_client.update_stack(stack_name, fields)
# get stack resource
heat_reses = heat_client.get_resources(stack_name)
# make instantiated_vnf_info
self._make_instantiated_vnf_info(req, inst, grant_req, grant, vnfd,
heat_reses)
def change_ext_conn_rollback(self, req, inst, grant_req, grant, vnfd):
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
heat_client = heat_utils.HeatClient(vim_info)
stack_name = heat_utils.get_stack_name(inst)
fields = self._update_nfv_dict(heat_client, stack_name,
userdata_default.DefaultUserData.change_ext_conn_rollback(
req, inst, grant_req, grant, vnfd.csar_dir))
heat_client.update_stack(stack_name, fields)
# NOTE: it is necessary to re-create instantiatedVnfInfo because
# ports may be changed.
heat_reses = heat_client.get_resources(stack_name)
self._make_instantiated_vnf_info(req, inst, grant_req, grant, vnfd,
heat_reses)
def _make_hot(self, req, inst, grant_req, grant, vnfd):
if grant_req.operation == v2fields.LcmOperationType.INSTANTIATE:
flavour_id = req.flavourId
@ -248,8 +279,6 @@ class Openstack(object):
fields = pickle.loads(out.stdout)
stack_name = heat_utils.get_stack_name(inst)
fields['stack_name'] = stack_name
fields['timeout_mins'] = (
CONF.v2_vnfm.openstack_vim_stack_create_timeout)
@ -308,66 +337,80 @@ class Openstack(object):
return proto_info
def _make_link_ports(self, req_ext_vl, ext_cp_infos):
link_ports = []
if not req_ext_vl.obj_attr_is_set('extLinkPorts'):
return link_ports
for req_link_port in req_ext_vl.extLinkPorts:
link_port = objects.ExtLinkPortInfoV2(
id=_make_link_port_id(req_link_port.id),
resourceHandle=req_link_port.resourceHandle,
)
ext_cp_info = objects.VnfExtCpInfoV2(
id=_make_cp_info_id(link_port.id),
extLinkPortId=link_port.id
# associatedVnfcCpId may set later
)
link_port.cpInstanceId = ext_cp_info.id
for ext_cp in req_ext_vl.extCps:
found = False
for key, cp_conf in ext_cp.cpConfig.items():
if (cp_conf.obj_attr_is_set('linkPortId') and
cp_conf.linkPortId == req_link_port.id):
ext_cp_info.cpdId = ext_cp.cpdId
ext_cp_info.cpConfigId = key
# NOTE: cpProtocolInfo can't be filled
found = True
break
if found:
break
link_ports.append(link_port)
ext_cp_infos.append(ext_cp_info)
return link_ports
def _make_ext_vl_from_req(self, req_ext_vl, ext_cp_infos):
ext_vl = objects.ExtVirtualLinkInfoV2(
id=req_ext_vl.id,
resourceHandle=objects.ResourceHandle(
resourceId=req_ext_vl.resourceId
),
currentVnfExtCpData=req_ext_vl.extCps
)
if req_ext_vl.obj_attr_is_set('vimConnectionId'):
ext_vl.resourceHandle.vimConnectionId = (
req_ext_vl.vimConnectionId)
if req_ext_vl.obj_attr_is_set('resourceProviderId'):
ext_vl.resourceHandle.resourceProviderId = (
req_ext_vl.resourceProviderId)
link_ports = self._make_link_ports(req_ext_vl, ext_cp_infos)
if link_ports:
ext_vl.extLinkPorts = link_ports
ext_vl.extLinkPorts = link_ports
return ext_vl
def _make_ext_vl_info_from_req(self, req, grant, ext_cp_infos):
# make extVirtualLinkInfo
ext_vls = []
req_ext_vls = []
if grant.obj_attr_is_set('extVirtualLinks'):
req_ext_vls = grant.extVirtualLinks
elif req.obj_attr_is_set('extVirtualLinks'):
req_ext_vls = req.extVirtualLinks
for req_ext_vl in req_ext_vls:
ext_vl = objects.ExtVirtualLinkInfoV2(
id=req_ext_vl.id,
resourceHandle=objects.ResourceHandle(
resourceId=req_ext_vl.resourceId
),
currentVnfExtCpData=req_ext_vl.extCps
)
if req_ext_vl.obj_attr_is_set('vimConnectionId'):
ext_vl.resourceHandle.vimConnectionId = (
req_ext_vl.vimConnectionId)
if req_ext_vl.obj_attr_is_set('resourceProviderId'):
ext_vl.resourceHandle.resourceProviderId = (
req_ext_vl.resourceProviderId)
return [self._make_ext_vl_from_req(req_ext_vl, ext_cp_infos)
for req_ext_vl in req_ext_vls]
ext_vls.append(ext_vl)
if not req_ext_vl.obj_attr_is_set('extLinkPorts'):
continue
link_ports = []
for req_link_port in req_ext_vl.extLinkPorts:
link_port = objects.ExtLinkPortInfoV2(
id=_make_link_port_id(req_link_port.id),
resourceHandle=req_link_port.resourceHandle,
)
ext_cp_info = objects.VnfExtCpInfoV2(
id=_make_cp_info_id(link_port.id),
extLinkPortId=link_port.id
# associatedVnfcCpId may set later
)
link_port.cpInstanceId = ext_cp_info.id
for ext_cp in req_ext_vl.extCps:
found = False
for key, cp_conf in ext_cp.cpConfig.items():
if (cp_conf.obj_attr_is_set('linkPortId') and
cp_conf.linkPortId == req_link_port.id):
ext_cp_info.cpdId = ext_cp.cpdId
ext_cp_info.cpConfigId = key
# NOTE: cpProtocolInfo can't be filled
found = True
break
if found:
break
link_ports.append(link_port)
ext_cp_infos.append(ext_cp_info)
ext_vl.extLinkPorts = link_ports
return ext_vls
def _find_ext_cp_info(self, link_port, ext_cp_infos):
for ext_cp in ext_cp_infos:
if ext_cp.id == link_port.cpInstanceId:
return ext_cp
# never reach here
def _make_ext_vl_info_from_inst(self, old_inst_vnf_info, ext_cp_infos):
# make extVirtualLinkInfo from old inst.extVirtualLinkInfo
@ -384,20 +427,83 @@ class Openstack(object):
continue
new_link_ports = []
for link_port in ext_vl.extLinkPorts:
if not _is_link_port(link_port.id):
# port created by heat. re-create later
continue
new_link_ports.append(link_port)
for ext_cp in old_cp_infos:
if ext_cp.id == link_port.cpInstanceId:
ext_cp_infos.append(ext_cp)
break
if _is_link_port(link_port.id):
new_link_ports.append(link_port)
ext_cp_infos.append(self._find_ext_cp_info(link_port,
old_cp_infos))
ext_vl.extLinkPorts = new_link_ports
return ext_vls
def _make_ext_vl_info_from_req_and_inst(self, req, grant, old_inst_info,
ext_cp_infos):
req_ext_vls = []
if grant.obj_attr_is_set('extVirtualLinks'):
req_ext_vls = grant.extVirtualLinks
elif req.obj_attr_is_set('extVirtualLinks'):
req_ext_vls = req.extVirtualLinks
req_ext_vl_ids = {ext_vl.id for ext_vl in req_ext_vls}
inst_ext_vl_ids = set()
if old_inst_info.obj_attr_is_set('extVirtualLinkInfo'):
inst_ext_vl_ids = {ext_vl.id
for ext_vl in old_inst_info.extVirtualLinkInfo}
added_ext_vl_ids = req_ext_vl_ids - inst_ext_vl_ids
req_all_cp_names = {ext_cp.cpdId
for req_ext_vl in req_ext_vls
for ext_cp in req_ext_vl.extCps}
ext_vls = [self._make_ext_vl_from_req(req_ext_vl, ext_cp_infos)
for req_ext_vl in req_ext_vls
# added ext_vls
if req_ext_vl.id in added_ext_vl_ids]
old_ext_vls = []
old_cp_infos = []
if old_inst_info.obj_attr_is_set('extVirtualLinkInfo'):
old_ext_vls = old_inst_info.extVirtualLinkInfo
if old_inst_info.obj_attr_is_set('extCpInfo'):
old_cp_infos = old_inst_info.extCpInfo
for ext_vl in old_ext_vls:
old_ext_cp_data = ext_vl.currentVnfExtCpData
old_link_ports = (ext_vl.extLinkPorts
if ext_vl.obj_attr_is_set('extLinkPorts') else [])
new_ext_cp_data = []
new_link_ports = []
for ext_cp_data in old_ext_cp_data:
if ext_cp_data.cpdId not in req_all_cp_names:
new_ext_cp_data.append(ext_cp_data)
for link_port in old_link_ports:
ext_cp = self._find_ext_cp_info(link_port, old_cp_infos)
if (ext_cp.cpdId not in req_all_cp_names and
_is_link_port(link_port.id)):
new_link_ports.append(link_port)
ext_cp_infos.append(ext_cp)
def _find_req_ext_vl(ext_vl):
for req_ext_vl in req_ext_vls:
if req_ext_vl.id == ext_vl.id:
return req_ext_vl
req_ext_vl = _find_req_ext_vl(ext_vl)
if req_ext_vl is not None:
new_ext_cp_data += req_ext_vl.extCps
new_link_ports += self._make_link_ports(req_ext_vl,
ext_cp_infos)
if new_ext_cp_data:
# if it is empty, it means all cps of this ext_vl are replaced
# by another ext_vl.
ext_vl.currentVnfExtCpData = new_ext_cp_data
ext_vl.extLinkPorts = new_link_ports
ext_vls.append(ext_vl)
return ext_vls
def _make_ext_mgd_vl_info_from_req(self, vnfd, flavour_id, req, grant):
# make extManagedVirtualLinkInfo
ext_mgd_vls = []
@ -557,9 +663,8 @@ class Openstack(object):
def _make_instantiated_vnf_info(self, req, inst, grant_req, grant, vnfd,
heat_reses):
init = False
if grant_req.operation == v2fields.LcmOperationType.INSTANTIATE:
init = True
op = grant_req.operation
if op == v2fields.LcmOperationType.INSTANTIATE:
flavour_id = req.flavourId
else:
flavour_id = inst.instantiatedVnfInfo.flavourId
@ -636,15 +741,19 @@ class Openstack(object):
]
ext_cp_infos = []
if init:
if op == v2fields.LcmOperationType.INSTANTIATE:
ext_vl_infos = self._make_ext_vl_info_from_req(
req, grant, ext_cp_infos)
ext_mgd_vl_infos = self._make_ext_mgd_vl_info_from_req(vnfd,
flavour_id, req, grant)
else:
old_inst_vnf_info = inst.instantiatedVnfInfo
ext_vl_infos = self._make_ext_vl_info_from_inst(
old_inst_vnf_info, ext_cp_infos)
if op == v2fields.LcmOperationType.CHANGE_EXT_CONN:
ext_vl_infos = self._make_ext_vl_info_from_req_and_inst(
req, grant, old_inst_vnf_info, ext_cp_infos)
else:
ext_vl_infos = self._make_ext_vl_info_from_inst(
old_inst_vnf_info, ext_cp_infos)
ext_mgd_vl_infos = self._make_ext_mgd_vl_info_from_inst(
old_inst_vnf_info)
@ -701,7 +810,7 @@ class Openstack(object):
# because the association of compute resource and port resource
# is not identified.
# make new instatiatedVnfInfo and replace
# make new instantiatedVnfInfo and replace
inst_vnf_info = objects.VnfInstanceV2_InstantiatedVnfInfo(
flavourId=flavour_id,
vnfState='STARTED',

View File

@ -144,3 +144,97 @@ class DefaultUserData(userdata_utils.AbstractUserData):
fields = {'parameters': {'nfv': {'VDU': new_vdus}}}
return fields
@staticmethod
def change_ext_conn(req, inst, grant_req, grant, tmp_csar_dir):
# change_ext_conn is interested in 'CP' only.
# This method returns only 'CP' part in the 'nfv' dict from
# ChangeExtVnfConnectivityRequest.
# It is applied to json merge patch against the existing 'nfv'
# dict by the caller.
# NOTE: complete 'nfv' dict can not be made at the moment
# since InstantiateVnfRequest is necessary to make it.
vnfd = userdata_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
flavour_id = inst['instantiatedVnfInfo']['flavourId']
hot_dict = vnfd.get_base_hot(flavour_id)
top_hot = hot_dict['template']
nfv_dict = userdata_utils.init_nfv_dict(top_hot)
cps = nfv_dict.get('CP', {})
new_cps = {}
for cp_name, cp_value in cps.items():
if 'network' in cp_value:
network = userdata_utils.get_param_network(cp_name, grant, req)
if network is None:
continue
new_cps.setdefault(cp_name, {})
new_cps[cp_name]['network'] = network
if 'fixed_ips' in cp_value:
ext_fixed_ips = userdata_utils.get_param_fixed_ips(
cp_name, grant, req)
fixed_ips = []
for i in range(len(ext_fixed_ips)):
if i not in cp_value['fixed_ips']:
break
ips_i = cp_value['fixed_ips'][i]
if 'subnet' in ips_i:
ips_i['subnet'] = ext_fixed_ips[i].get('subnet')
if 'ip_address' in ips_i:
ips_i['ip_address'] = ext_fixed_ips[i].get(
'ip_address')
fixed_ips.append(ips_i)
new_cps.setdefault(cp_name, {})
new_cps[cp_name]['fixed_ips'] = fixed_ips
fields = {'parameters': {'nfv': {'CP': new_cps}}}
return fields
@staticmethod
def change_ext_conn_rollback(req, inst, grant_req, grant, tmp_csar_dir):
# NOTE: This method is not called by a userdata script but
# is called by the openstack infra_driver directly now.
# It is thought that it is suitable that this method defines
# here since it is very likely to scale method above.
vnfd = userdata_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
flavour_id = inst['instantiatedVnfInfo']['flavourId']
hot_dict = vnfd.get_base_hot(flavour_id)
top_hot = hot_dict['template']
nfv_dict = userdata_utils.init_nfv_dict(top_hot)
cps = nfv_dict.get('CP', {})
new_cps = {}
for cp_name, cp_value in cps.items():
if 'network' in cp_value:
network = userdata_utils.get_param_network_from_inst(
cp_name, inst)
if network is None:
continue
new_cps.setdefault(cp_name, {})
new_cps[cp_name]['network'] = network
if 'fixed_ips' in cp_value:
ext_fixed_ips = userdata_utils.get_param_fixed_ips_from_inst(
cp_name, inst)
fixed_ips = []
for i in range(len(ext_fixed_ips)):
if i not in cp_value['fixed_ips']:
break
ips_i = cp_value['fixed_ips'][i]
if 'subnet' in ips_i:
ips_i['subnet'] = ext_fixed_ips[i].get('subnet')
if 'ip_address' in ips_i:
ips_i['ip_address'] = ext_fixed_ips[i].get(
'ip_address')
fixed_ips.append(ips_i)
new_cps.setdefault(cp_name, {})
new_cps[cp_name]['fixed_ips'] = fixed_ips
fields = {'parameters': {'nfv': {'CP': new_cps}}}
return fields

View File

@ -45,6 +45,11 @@ class AbstractUserData(metaclass=abc.ABCMeta):
def scale(req, inst, grant_req, grant, tmp_csar_dir):
raise sol_ex.UserDataClassNotImplemented()
@staticmethod
@abc.abstractmethod
def change_ext_conn(req, inst, grant_req, grant, tmp_csar_dir):
raise sol_ex.UserDataClassNotImplemented()
def get_vnfd(vnfd_id, csar_dir):
vnfd = vnfd_utils.Vnfd(vnfd_id)
@ -210,6 +215,20 @@ def get_param_fixed_ips(cp_name, grant, req):
return _get_fixed_ips_from_extcp(extcp)
def get_param_network_from_inst(cp_name, inst):
for vl in inst['instantiatedVnfInfo'].get('extVirtualLinkInfo', []):
for extcp in vl.get('currentVnfExtCpData', []):
if extcp['cpdId'] == cp_name:
return vl['resourceHandle']['resourceId']
def get_param_fixed_ips_from_inst(cp_name, inst):
for vl in inst['instantiatedVnfInfo'].get('extVirtualLinkInfo', []):
for extcp in vl.get('currentVnfExtCpData', []):
if extcp['cpdId'] == cp_name:
return _get_fixed_ips_from_extcp(extcp)
def apply_ext_managed_vls(hot_dict, req, grant):
# see grant first then instantiateVnfRequest
mgd_vls = (grant.get('extManagedVirtualLinks', []) +

View File

@ -20,8 +20,8 @@ from tacker.sol_refactored.objects import fields
# NFV-SOL 003
# - v3.3.1 5.5.2.11 (API version: 2.0.0)
@base.TackerObjectRegistry.register
class ChangeExtVnfConnectivityRequestV2(base.TackerObject,
base.TackerObjectDictCompat):
class ChangeExtVnfConnectivityRequest(base.TackerObject,
base.TackerObjectDictCompat):
# Version 1.0: Initial version
VERSION = '1.0'

View File

@ -100,6 +100,12 @@ class Client(object):
path, "POST", body=req_body, version="2.0.0")
self.print(resp, body)
def chg_ext_conn(self, id, req_body):
path = self.path + '/' + id + '/change_ext_conn'
resp, body = self.client.do_request(
path, "POST", body=req_body, version="2.0.0")
self.print(resp, body)
def retry(self, id):
path = self.path + '/' + id + '/retry'
resp, body = self.client.do_request(path, "POST", version="2.0.0")
@ -126,6 +132,7 @@ def usage():
print(" inst inst {id} body(path of content)")
print(" inst term {id} body(path of content)")
print(" inst scale {id} body(path of content)")
print(" inst chg_ext_conn {id} body(path of content)")
print(" subsc create body(path of content)")
print(" subsc list [body(path of content)]")
print(" subsc show {id}")
@ -152,7 +159,7 @@ if __name__ == '__main__':
if resource == "inst":
if action not in ["create", "list", "show", "delete", "update",
"inst", "term", "scale"]:
"inst", "term", "scale", "chg_ext_conn"]:
usage()
client = Client("/vnflcm/v2/vnf_instances")
elif resource == "subsc":
@ -202,6 +209,10 @@ if __name__ == '__main__':
if len(sys.argv) != 5:
usage()
client.scale(sys.argv[3], get_body(sys.argv[4]))
elif action == "chg_ext_conn":
if len(sys.argv) != 5:
usage()
client.chg_ext_conn(sys.argv[3], get_body(sys.argv[4]))
elif action == "retry":
if len(sys.argv) != 4:
usage()

View File

@ -81,6 +81,109 @@ _inst_info_example_1 = {
}
],
# "currentVnfExtCpData": omitted
},
# following two data are for test_update_lcmocc_change_ext_conn
{
"id": "cba2e572-2cc5-4d11-af60-728615883206",
"resourceHandle": {
"resourceId": "4529d333-dbcc-4d93-9b64-210647712569"
},
"extLinkPorts": [],
"currentVnfExtCpData": [
{
"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"
]
}
]
}
}
]
}
}
}
]
},
{
"id": "fc131806-8a4f-43a3-82c0-a38e87fe87bd",
"resourceHandle": {
"resourceId": "5529d333-dbcc-4d93-9b64-210647712569"
},
"extLinkPorts": [],
"currentVnfExtCpData": [
{
"cpdId": "VDU1_CP2",
"cpConfig": {
"VDU1_CP2_1": {
"cpProtocolData": [
{
"layerProtocol": "IP_OVER_ETHERNET",
"ipOverEthernet": {
"ipAddresses": [
{
"type": "IPV4",
"numDynamicAddresses": 1,
}
]
}
}
]
}
}
},
{
"cpdId": "VDU2_CP2",
"cpConfig": {
"VDU2_CP2_1": {
"cpProtocolData": [
{
"layerProtocol": "IP_OVER_ETHERNET",
"ipOverEthernet": {
"ipAddresses": [
{
"type": "IPV4",
"fixedAddresses": [
"10.10.1.102"
],
}
]
}
}
]
}
}
}
]
}
],
"extManagedVirtualLinkInfo": [
@ -616,6 +719,190 @@ _inst_info_example_2 = {
# "vnfcInfo": omitted
}
# example_3 is changed external virtual link ports from example_1.
_inst_info_example_3 = {
# "flavourId", "vnfState", "scaleStatus", "maxScaleLevels" are omitted
# "extCpInfo": omitted
"extVirtualLinkInfo": [
{
"id": "bbf0932a-6142-4ea8-93cd-8059dba594a1",
"resourceHandle": {
"resourceId": "3529d333-dbcc-4d93-9b64-210647712569"
},
"extLinkPorts": [
{
"id": "res_id_VDU1_CP1_1",
"resourceHandle": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU1_CP1_1",
"vimLevelResourceType": "OS::Neutron::Port"
},
"cpInstanceId": "cp-res_id_VDU1_CP1_1"
}
],
# "currentVnfExtCpData": omitted
},
{
"id": "2ff742fc-da6d-423a-8fba-6aa6af8da6f2",
"resourceHandle": {
"resourceId": "9113aff7-9ba2-43f4-8b1e-fff80ae001c5"
},
"extLinkPorts": [
{
"id": "res_id_VDU2_CP1_modified",
"resourceHandle": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU2_CP1_modified",
"vimLevelResourceType": "OS::Neutron::Port"
},
"cpInstanceId": "cp-res_id_VDU2_CP1"
},
],
# "currentVnfExtCpData": omitted
},
{
"id": "790949df-c7b3-4926-a559-3895412f1dfe",
"resourceHandle": {
"resourceId": "367e5b3b-34dc-47f2-85b8-c39e3272893a"
},
"extLinkPorts": [
{
"id": "res_id_VDU2_CP2",
"resourceHandle": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU2_CP2",
"vimLevelResourceType": "OS::Neutron::Port"
},
"cpInstanceId": "cp-res_id_VDU2_CP2"
},
{
"id": "res_id_VDU1_CP2_1",
"resourceHandle": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU1_CP2_1",
"vimLevelResourceType": "OS::Neutron::Port"
},
"cpInstanceId": "cp-res_id_VDU1_CP2_1"
}
],
# "currentVnfExtCpData": omitted
},
{ # same as _inst_info_example_1
"id": "cba2e572-2cc5-4d11-af60-728615883206",
"resourceHandle": {
"resourceId": "4529d333-dbcc-4d93-9b64-210647712569"
},
"extLinkPorts": [],
"currentVnfExtCpData": [
{
"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"
]
}
]
}
}
]
}
}
}
]
},
{ # ip address of VDU2_CP2 is changed
"id": "fc131806-8a4f-43a3-82c0-a38e87fe87bd",
"resourceHandle": {
"resourceId": "5529d333-dbcc-4d93-9b64-210647712569"
},
"extLinkPorts": [],
"currentVnfExtCpData": [
{
"cpdId": "VDU1_CP2",
"cpConfig": {
"VDU1_CP2_1": {
"cpProtocolData": [
{
"layerProtocol": "IP_OVER_ETHERNET",
"ipOverEthernet": {
"ipAddresses": [
{
"type": "IPV4",
"numDynamicAddresses": 1,
}
]
}
}
]
}
}
},
{
"cpdId": "VDU2_CP2",
"cpConfig": {
"VDU2_CP2_1": {
"cpProtocolData": [
{
"layerProtocol": "IP_OVER_ETHERNET",
"ipOverEthernet": {
"ipAddresses": [
{
"type": "IPV4",
"fixedAddresses": [
"10.10.1.103"
],
}
]
}
}
]
}
}
}
]
}
],
# NOTE: vnfcCpInfo of VnfcResourceInfo is changed exactly, but it is
# not looked at lcmocc_update.
"vnfcResourceInfo": _inst_info_example_1["vnfcResourceInfo"],
# other members are same as example_1
"extManagedVirtualLinkInfo":
_inst_info_example_1["extManagedVirtualLinkInfo"],
"vnfVirtualLinkResourceInfo":
_inst_info_example_1["vnfVirtualLinkResourceInfo"],
"virtualStorageResourceInfo":
_inst_info_example_1["virtualStorageResourceInfo"],
# "vnfcInfo": omitted
}
# expected results
_expected_resource_changes_instantiate = {
"affectedVnfcs": [
@ -1233,6 +1520,38 @@ _expected_changedInfo = {
]
}
_expected_resource_changes_change_ext_conn = {
"affectedExtLinkPorts": [
{
"id": "res_id_VDU2_CP1",
"changeType": "REMOVED",
"extCpInstanceId": "cp-res_id_VDU2_CP1",
"resourceHandle": {
"resourceId": "res_id_VDU2_CP1",
"vimConnectionId": "vim_connection_id",
"vimLevelResourceType": "OS::Neutron::Port"
}
},
{
"id": "res_id_VDU2_CP1_modified",
"changeType": "ADDED",
"extCpInstanceId": "cp-res_id_VDU2_CP1",
"resourceHandle": {
"resourceId": "res_id_VDU2_CP1_modified",
"vimConnectionId": "vim_connection_id",
"vimLevelResourceType": "OS::Neutron::Port"
}
}
]
}
_expected_changed_ext_connectivity = [
# sort by id
_inst_info_example_3['extVirtualLinkInfo'][1],
_inst_info_example_3['extVirtualLinkInfo'][0],
_inst_info_example_3['extVirtualLinkInfo'][4]
]
class TestLcmOpOccUtils(base.BaseTestCase):
@ -1366,3 +1685,28 @@ class TestLcmOpOccUtils(base.BaseTestCase):
# check changedInfo
lcmocc = lcmocc.to_dict()
self.assertEqual(_expected_changedInfo, lcmocc['changedInfo'])
def test_update_lcmocc_change_ext_conn(self):
# prepare
inst_saved = objects.VnfInstanceV2()
inst_saved.instantiatedVnfInfo = (
objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict(
_inst_info_example_1))
inst = objects.VnfInstanceV2()
inst.instantiatedVnfInfo = (
objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict(
_inst_info_example_3))
lcmocc = objects.VnfLcmOpOccV2(
operation=fields.LcmOperationType.CHANGE_EXT_CONN)
# execute update_lcmocc
lcmocc_utils.update_lcmocc(lcmocc, inst_saved, inst)
# check resourceChanges
lcmocc = lcmocc.to_dict()
self.assertEqual(
_expected_resource_changes_change_ext_conn,
self._sort_resource_changes(lcmocc['resourceChanges']))
self.assertEqual(
_expected_changed_ext_connectivity,
sorted(lcmocc['changedExtConnectivity'], key=lambda x: x['id']))

View File

@ -126,6 +126,46 @@ _inst_req_example = {
}
}
# ChangeExtVnfConnectivityRequest example for change_ext_conn grant test
_ext_vl_3 = {
"id": uuidutils.generate_uuid(),
"resourceId": 'net2_id',
"extCps": [
{
"cpdId": "VDU1_CP2",
"cpConfig": {
"VDU1_CP2_1": {
"cpProtocolData": [{
"layerProtocol": "IP_OVER_ETHERNET",
"ipOverEthernet": {
"ipAddresses": [{
"type": "IPV4",
"numDynamicAddresses": 1,
"subnetId": 'subnet2_id'}]}}]}
}
},
{
"cpdId": "VDU2_CP1",
"cpConfig": {
"VDU2_CP1_1": {
"linkPortId": "link_port_id"
}
}
}
],
"extLinkPorts": [
{
"id": "link_port_id",
"resourceHandle": {
"resourceId": "res_id_VDU2_CP1"
}
}
]
}
_change_ext_conn_req_example = {
"extVirtualLinks": [_ext_vl_3]
}
# instantiatedVnfInfo example for terminate/scale grant test
# NOTE:
# - some identifiers are modified to make check easy.
@ -1227,3 +1267,107 @@ class TestVnfLcmDriverV2(base.BaseTestCase):
}
self.assertEqual(expected_modify_result, inst)
@mock.patch.object(nfvo_client.NfvoClient, 'grant')
def test_change_ext_conn_grant(self, mocked_grant):
# prepare
req = objects.ChangeExtVnfConnectivityRequest.from_dict(
_change_ext_conn_req_example)
inst = objects.VnfInstanceV2(
# required fields
id=uuidutils.generate_uuid(),
vnfdId=SAMPLE_VNFD_ID,
vnfProvider='provider',
vnfProductName='product name',
vnfSoftwareVersion='software version',
vnfdVersion='vnfd version',
instantiationState='INSTANTIATED'
)
inst_info = objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict(
_inst_info_example)
inst.instantiatedVnfInfo = inst_info
lcmocc = objects.VnfLcmOpOccV2(
# required fields
id=uuidutils.generate_uuid(),
operationState=fields.LcmOperationStateType.STARTING,
stateEnteredTime=datetime.utcnow(),
startTime=datetime.utcnow(),
vnfInstanceId=inst.id,
operation=fields.LcmOperationType.CHANGE_EXT_CONN,
isAutomaticInvocation=False,
isCancelPending=False,
operationParams=req
)
mocked_grant.return_value = objects.GrantV1()
# run change_ext_conn_grant
grant_req, _ = self.driver.grant(
self.context, lcmocc, inst, self.vnfd_1)
# check grant_req is constructed according to intention
grant_req = grant_req.to_dict()
expected_fixed_items = {
'vnfInstanceId': inst.id,
'vnfLcmOpOccId': lcmocc.id,
'vnfdId': SAMPLE_VNFD_ID,
'operation': 'CHANGE_EXT_CONN',
'isAutomaticInvocation': False,
'_links': self._grant_req_links(lcmocc.id, inst.id)
}
for key, value in expected_fixed_items.items():
self.assertEqual(value, grant_req[key])
# check updateResources
update_reses = grant_req['updateResources']
check_reses = {
'COMPUTE': {'VDU1': [], 'VDU2': []}
}
expected_res_ids = {
'COMPUTE': {
'VDU1': ['res_id_VDU1_1', 'res_id_VDU1_2'],
'VDU2': ['res_id_VDU2']
}
}
for res in update_reses:
check_reses[res['type']][res['resourceTemplateId']].append(
res['resource']['resourceId'])
for key, value in check_reses.items():
for name, ids in value.items():
self.assertEqual(expected_res_ids[key][name], ids)
# check removeResources
rm_reses = grant_req['removeResources']
check_reses = {
'LINKPORT': {'VDU1_CP2': [], 'VDU2_CP1': []}
}
expected_res_ids = {
'LINKPORT': {
'VDU1_CP2': ['res_id_VDU1_1_CP2', 'res_id_VDU1_2_CP2'],
'VDU2_CP1': ['res_id_VDU2_CP1']
}
}
for res in rm_reses:
check_reses[res['type']][res['resourceTemplateId']].append(
res['resource']['resourceId'])
for key, value in check_reses.items():
for name, ids in value.items():
self.assertEqual(expected_res_ids[key][name], ids)
# check addResources
add_reses = grant_req['addResources']
check_reses = {
'LINKPORT': {'VDU1_CP2': []}
}
expected_num = {
'LINKPORT': {'VDU1_CP2': 2}
}
for res in add_reses:
check_reses[res['type']][res['resourceTemplateId']].append(
res['id'])
for key, value in check_reses.items():
for name, ids in value.items():
self.assertEqual(expected_num[key][name], len(ids))

View File

@ -29,6 +29,34 @@ from tacker.sol_refactored.objects.v2 import fields
from tacker.tests.unit.db import base as db_base
_change_ext_conn_req_example = {
"extVirtualLinks": [
{
"id": "id_ext_vl_1",
"resourceId": "res_id_ext_vl_1",
"extCps": [
{
"cpdId": "VDU2_CP2",
"cpConfig": {
"VDU2_CP2_1": {
"linkPortId": "link_port_id_VDU2_CP2"
}
}
}
],
"extLinkPorts": [
{
"id": "link_port_id_VDU2_CP2",
"resourceHandle": {
"resourceId": "res_id_VDU2_CP2"
}
}
]
}
]
}
class TestVnflcmV2(db_base.SqlTestCase):
def setUp(self):
@ -459,3 +487,19 @@ class TestVnflcmV2(db_base.SqlTestCase):
self.assertRaises(sol_ex.SolValidationError,
self.controller.update, request=self.request, id=inst.id,
body=body)
def test_change_ext_conn_not_instantiated(self):
inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED',
fields.LcmOperationStateType.COMPLETED)
self.assertRaises(sol_ex.VnfInstanceIsNotInstantiated,
self.controller.change_ext_conn, request=self.request, id=inst_id,
body=_change_ext_conn_req_example)
def test_change_ext_conn_lcmocc_in_progress(self):
inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED',
fields.LcmOperationStateType.FAILED_TEMP)
self.assertRaises(sol_ex.OtherOperationInProgress,
self.controller.change_ext_conn, request=self.request, id=inst_id,
body=_change_ext_conn_req_example)

View File

@ -142,7 +142,84 @@ _instantiate_req_example = {
}
}
# heat resources example
# ChangeExtVnfConnectivityRequest example
_change_ext_conn_req_example = {
"extVirtualLinks": [
{
"id": "id_ext_vl_3",
"resourceId": "res_id_ext_vl_3",
"extCps": [
{
"cpdId": "VDU2_CP1",
"cpConfig": {
"VDU2_CP1_1": {
"cpProtocolData": [
{
"layerProtocol": "IP_OVER_ETHERNET",
"ipOverEthernet": {
"ipAddresses": [
{
"type": "IPV4",
"fixedAddresses": [
"20.10.0.102"
]
}
]
}
}
]
}
}
}
]
},
{
"id": "id_ext_vl_4",
"resourceId": "res_id_id_ext_vl_4",
"extCps": [
{
"cpdId": "VDU1_CP2",
"cpConfig": {
"VDU1_CP2_1": {
"cpProtocolData": [
{
"layerProtocol": "IP_OVER_ETHERNET",
"ipOverEthernet": {
"ipAddresses": [
{
"type": "IPV4",
"numDynamicAddresses": 1,
"subnetId": "res_id_subnet_4"
}
]
}
}
]
}
}
},
{
"cpdId": "VDU2_CP2",
"cpConfig": {
"VDU2_CP2_1": {
"linkPortId": "link_port_id_VDU2_CP2_modified"
}
}
}
],
"extLinkPorts": [
{
"id": "link_port_id_VDU2_CP2_modified",
"resourceHandle": {
"resourceId": "res_id_VDU2_CP2_modified"
}
}
]
}
]
}
# heat resources examples
# NOTE:
# - following attributes which are not related to tests are omitted.
# updated_time, logical_resource_id, resource_status, resource_status_reason
@ -169,7 +246,7 @@ _stack_id_VDU1_2 = (
"myet4efobvvp-aptv6apap2h5/dd94d2ae-a02b-4fab-a492-514c422299ec")
_href_VDU1_2 = "".join((_url, _stack_id_VDU1_2))
_heat_reses_example = [
_heat_reses_example_base = [
{
"creation_time": "2021-12-10T00:40:46Z",
"resource_name": "VDU2",
@ -183,21 +260,6 @@ _heat_reses_example = [
],
"required_by": []
},
{
"creation_time": "2021-12-10T00:40:46Z",
"resource_name": "VDU2_CP1",
"physical_resource_id": "res_id_VDU2_CP1",
"resource_type": "OS::Neutron::Port",
"links": [
{
"href": _href,
"rel": "stack"
}
],
"required_by": [
"VDU2"
]
},
{
"creation_time": "2021-12-10T00:40:47Z",
"resource_name": "VDU2_CP5",
@ -437,22 +499,6 @@ _heat_reses_example = [
],
"parent_resource": "bemybz4ugeso"
},
{
"creation_time": "2021-12-10T00:41:45Z",
"resource_name": "VDU1_CP2",
"physical_resource_id": "res_id_VDU1_CP2_1",
"resource_type": "OS::Neutron::Port",
"links": [
{
"href": _href_VDU1_1,
"rel": "stack"
}
],
"required_by": [
"VDU1"
],
"parent_resource": "bemybz4ugeso"
},
{
"creation_time": "2021-12-10T00:41:45Z",
"resource_name": "VDU1_CP1",
@ -564,22 +610,6 @@ _heat_reses_example = [
],
"parent_resource": "myet4efobvvp"
},
{
"creation_time": "2021-12-10T01:03:53Z",
"resource_name": "VDU1_CP2",
"physical_resource_id": "res_id_VDU1_CP2_2",
"resource_type": "OS::Neutron::Port",
"links": [
{
"href": _href_VDU1_2,
"rel": "stack"
}
],
"required_by": [
"VDU1"
],
"parent_resource": "myet4efobvvp"
},
{
"creation_time": "2021-12-10T01:03:53Z",
"resource_name": "VDU1_CP1",
@ -647,7 +677,115 @@ _heat_reses_example = [
}
]
# expected results
_heat_reses_example_cps_before = [
{
"creation_time": "2021-12-10T00:40:46Z",
"resource_name": "VDU2_CP1",
"physical_resource_id": "res_id_VDU2_CP1",
"resource_type": "OS::Neutron::Port",
"links": [
{
"href": _href,
"rel": "stack"
}
],
"required_by": [
"VDU2"
]
},
{
"creation_time": "2021-12-10T00:41:45Z",
"resource_name": "VDU1_CP2",
"physical_resource_id": "res_id_VDU1_CP2_1",
"resource_type": "OS::Neutron::Port",
"links": [
{
"href": _href_VDU1_1,
"rel": "stack"
}
],
"required_by": [
"VDU1"
],
"parent_resource": "bemybz4ugeso"
},
{
"creation_time": "2021-12-10T01:03:53Z",
"resource_name": "VDU1_CP2",
"physical_resource_id": "res_id_VDU1_CP2_2",
"resource_type": "OS::Neutron::Port",
"links": [
{
"href": _href_VDU1_2,
"rel": "stack"
}
],
"required_by": [
"VDU1"
],
"parent_resource": "myet4efobvvp"
}
]
_heat_reses_example_cps_after = [
{
"creation_time": "2021-12-10T00:40:46Z",
"resource_name": "VDU2_CP1",
"physical_resource_id": "res_id_VDU2_CP1_modified",
"resource_type": "OS::Neutron::Port",
"links": [
{
"href": _href,
"rel": "stack"
}
],
"required_by": [
"VDU2"
]
},
{
"creation_time": "2021-12-10T00:41:45Z",
"resource_name": "VDU1_CP2",
"physical_resource_id": "res_id_VDU1_CP2_1_modified",
"resource_type": "OS::Neutron::Port",
"links": [
{
"href": _href_VDU1_1,
"rel": "stack"
}
],
"required_by": [
"VDU1"
],
"parent_resource": "bemybz4ugeso"
},
{
"creation_time": "2021-12-10T01:03:53Z",
"resource_name": "VDU1_CP2",
"physical_resource_id": "res_id_VDU1_CP2_2_modified",
"resource_type": "OS::Neutron::Port",
"links": [
{
"href": _href_VDU1_2,
"rel": "stack"
}
],
"required_by": [
"VDU1"
],
"parent_resource": "myet4efobvvp"
}
]
# heat resources example for other than change_ext_conn
_heat_reses_example = (
_heat_reses_example_base + _heat_reses_example_cps_before)
# heat resources example after executing change_ext_conn
_heat_reses_example_change_ext_conn = (
_heat_reses_example_base + _heat_reses_example_cps_after)
# expected results (other than change_ext_conn)
_expected_inst_info = {
"flavourId": "simple",
"vnfState": "STARTED",
@ -1228,6 +1366,417 @@ _expected_inst_info_vnfc_updated["vnfcInfo"] = [
}
]
# expected results for change_ext_conn
_expected_inst_info_change_ext_conn = {
"flavourId": "simple",
"vnfState": "STARTED",
"extCpInfo": [
{
'id': 'cp-req-link_port_id_VDU2_CP2_modified',
'cpdId': 'VDU2_CP2',
'cpConfigId': 'VDU2_CP2_1',
'extLinkPortId': 'req-link_port_id_VDU2_CP2_modified',
},
{
"id": "cp-res_id_VDU1_CP1_1",
"cpdId": "VDU1_CP1",
"cpConfigId": "VDU1_CP1_1",
"cpProtocolInfo": [
{
"layerProtocol": "IP_OVER_ETHERNET",
"ipOverEthernet": {
"ipAddresses": [
{
"type": "IPV4",
"isDynamic": True
}
]
}
}
],
"extLinkPortId": "res_id_VDU1_CP1_1",
"associatedVnfcCpId": "VDU1_CP1-res_id_VDU1_1"
},
{
"id": "cp-res_id_VDU1_CP1_2",
"cpdId": "VDU1_CP1",
"cpConfigId": "VDU1_CP1_1",
"cpProtocolInfo": [
{
"layerProtocol": "IP_OVER_ETHERNET",
"ipOverEthernet": {
"ipAddresses": [
{
"type": "IPV4",
"isDynamic": True
}
]
}
}
],
"extLinkPortId": "res_id_VDU1_CP1_2",
"associatedVnfcCpId": "VDU1_CP1-res_id_VDU1_2"
},
{
"id": "cp-res_id_VDU1_CP2_1_modified",
"cpdId": "VDU1_CP2",
"cpConfigId": "VDU1_CP2_1",
"cpProtocolInfo": [
{
"layerProtocol": "IP_OVER_ETHERNET",
"ipOverEthernet": {
"ipAddresses": [
{
"type": "IPV4",
"isDynamic": True,
"subnetId": "res_id_subnet_4"
}
]
}
}
],
"extLinkPortId": "res_id_VDU1_CP2_1_modified",
"associatedVnfcCpId": "VDU1_CP2-res_id_VDU1_1"
},
{
"id": "cp-res_id_VDU1_CP2_2_modified",
"cpdId": "VDU1_CP2",
"cpConfigId": "VDU1_CP2_1",
"cpProtocolInfo": [
{
"layerProtocol": "IP_OVER_ETHERNET",
"ipOverEthernet": {
"ipAddresses": [
{
"type": "IPV4",
"isDynamic": True,
"subnetId": "res_id_subnet_4"
}
]
}
}
],
"extLinkPortId": "res_id_VDU1_CP2_2_modified",
"associatedVnfcCpId": "VDU1_CP2-res_id_VDU1_2"
},
{
"id": "cp-res_id_VDU2_CP1_modified",
"cpdId": "VDU2_CP1",
"cpConfigId": "VDU2_CP1_1",
"cpProtocolInfo": [
{
"layerProtocol": "IP_OVER_ETHERNET",
"ipOverEthernet": {
"ipAddresses": [
{
"type": "IPV4",
"addresses": [
"20.10.0.102"
]
}
]
}
}
],
"extLinkPortId": "res_id_VDU2_CP1_modified",
"associatedVnfcCpId": "VDU2_CP1-res_id_VDU2"
}
],
"extVirtualLinkInfo": [
{
"id": "id_ext_vl_1",
"resourceHandle": {
"resourceId": "res_id_ext_vl_1"
},
"extLinkPorts": [
{
"id": "res_id_VDU1_CP1_1",
"resourceHandle": {
"vimConnectionId": "vim_id_1",
"resourceId": "res_id_VDU1_CP1_1",
"vimLevelResourceType": "OS::Neutron::Port"
},
"cpInstanceId": "cp-res_id_VDU1_CP1_1"
},
{
"id": "res_id_VDU1_CP1_2",
"resourceHandle": {
"vimConnectionId": "vim_id_1",
"resourceId": "res_id_VDU1_CP1_2",
"vimLevelResourceType": "OS::Neutron::Port"
},
"cpInstanceId": "cp-res_id_VDU1_CP1_2"
}
],
"currentVnfExtCpData": [
{
"cpdId": "VDU1_CP1",
"cpConfig": {
"VDU1_CP1_1": {
"cpProtocolData": [
{
"layerProtocol": "IP_OVER_ETHERNET",
"ipOverEthernet": {
"ipAddresses": [
{
"type": "IPV4",
"numDynamicAddresses": 1
}
]
}
}
]
}
}
}
]
},
{
"id": "id_ext_vl_3",
"resourceHandle": {
"resourceId": "res_id_ext_vl_3"
},
"extLinkPorts": [
{
"id": "res_id_VDU2_CP1_modified",
"resourceHandle": {
"vimConnectionId": "vim_id_1",
"resourceId": "res_id_VDU2_CP1_modified",
"vimLevelResourceType": "OS::Neutron::Port"
},
"cpInstanceId": "cp-res_id_VDU2_CP1_modified"
}
],
"currentVnfExtCpData": [
{
"cpdId": "VDU2_CP1",
"cpConfig": {
"VDU2_CP1_1": {
"cpProtocolData": [
{
"layerProtocol": "IP_OVER_ETHERNET",
"ipOverEthernet": {
"ipAddresses": [
{
"type": "IPV4",
"fixedAddresses": [
"20.10.0.102"
]
}
]
}
}
]
}
}
}
]
},
{
"id": "id_ext_vl_4",
"resourceHandle": {
"resourceId": "res_id_id_ext_vl_4"
},
"extLinkPorts": [
{
"id": "req-link_port_id_VDU2_CP2_modified",
"resourceHandle": {
"resourceId": "res_id_VDU2_CP2_modified",
},
"cpInstanceId": "cp-req-link_port_id_VDU2_CP2_modified"
},
{
"id": "res_id_VDU1_CP2_1_modified",
"resourceHandle": {
"vimConnectionId": "vim_id_1",
"resourceId": "res_id_VDU1_CP2_1_modified",
"vimLevelResourceType": "OS::Neutron::Port"
},
"cpInstanceId": "cp-res_id_VDU1_CP2_1_modified"
},
{
"id": "res_id_VDU1_CP2_2_modified",
"resourceHandle": {
"vimConnectionId": "vim_id_1",
"resourceId": "res_id_VDU1_CP2_2_modified",
"vimLevelResourceType": "OS::Neutron::Port"
},
"cpInstanceId": "cp-res_id_VDU1_CP2_2_modified"
}
],
"currentVnfExtCpData": [
{
"cpdId": "VDU1_CP2",
"cpConfig": {
"VDU1_CP2_1": {
"cpProtocolData": [
{
"layerProtocol": "IP_OVER_ETHERNET",
"ipOverEthernet": {
"ipAddresses": [
{
"type": "IPV4",
"numDynamicAddresses": 1,
"subnetId": "res_id_subnet_4"
}
]
}
}
]
}
}
},
{
"cpdId": "VDU2_CP2",
"cpConfig": {
"VDU2_CP2_1": {
"linkPortId": "link_port_id_VDU2_CP2_modified"
}
}
}
]
}
],
"vnfcResourceInfo": [
{
"id": "res_id_VDU1_2",
"vduId": "VDU1",
"computeResource": {
"vimConnectionId": "vim_id_1",
"resourceId": "res_id_VDU1_2",
"vimLevelResourceType": "OS::Nova::Server"
},
"storageResourceIds": [
"res_id_VirtualStorage_2"
],
"vnfcCpInfo": [
{
"id": "VDU1_CP1-res_id_VDU1_2",
"cpdId": "VDU1_CP1",
"vnfExtCpId": "cp-res_id_VDU1_CP1_2"
},
{
"id": "VDU1_CP2-res_id_VDU1_2",
"cpdId": "VDU1_CP2",
"vnfExtCpId": "cp-res_id_VDU1_CP2_2_modified"
},
{
"id": "VDU1_CP3-res_id_VDU1_2",
"cpdId": "VDU1_CP3",
"vnfLinkPortId": "res_id_VDU1_CP3_2"
},
{
"id": "VDU1_CP4-res_id_VDU1_2",
"cpdId": "VDU1_CP4",
"vnfLinkPortId": "res_id_VDU1_CP4_2"
},
{
"id": "VDU1_CP5-res_id_VDU1_2",
"cpdId": "VDU1_CP5",
"vnfLinkPortId": "res_id_VDU1_CP5_2"
}
],
"metadata": {
"creation_time": "2021-12-10T01:03:49Z",
"parent_stack_id": _stack_id_VDU1_scale,
"parent_resource_name": "myet4efobvvp"
}
},
{
"id": "res_id_VDU1_1",
"vduId": "VDU1",
"computeResource": {
"vimConnectionId": "vim_id_1",
"resourceId": "res_id_VDU1_1",
"vimLevelResourceType": "OS::Nova::Server"
},
"storageResourceIds": [
"res_id_VirtualStorage_1"
],
"vnfcCpInfo": [
{
"id": "VDU1_CP1-res_id_VDU1_1",
"cpdId": "VDU1_CP1",
"vnfExtCpId": "cp-res_id_VDU1_CP1_1"
},
{
"id": "VDU1_CP2-res_id_VDU1_1",
"cpdId": "VDU1_CP2",
"vnfExtCpId": "cp-res_id_VDU1_CP2_1_modified"
},
{
"id": "VDU1_CP3-res_id_VDU1_1",
"cpdId": "VDU1_CP3",
"vnfLinkPortId": "res_id_VDU1_CP3_1"
},
{
"id": "VDU1_CP4-res_id_VDU1_1",
"cpdId": "VDU1_CP4",
"vnfLinkPortId": "res_id_VDU1_CP4_1"
},
{
"id": "VDU1_CP5-res_id_VDU1_1",
"cpdId": "VDU1_CP5",
"vnfLinkPortId": "res_id_VDU1_CP5_1"
}
],
"metadata": {
"creation_time": "2021-12-10T00:41:43Z",
"parent_stack_id": _stack_id_VDU1_scale,
"parent_resource_name": "bemybz4ugeso"
}
},
{
"id": "res_id_VDU2",
"vduId": "VDU2",
"computeResource": {
"vimConnectionId": "vim_id_1",
"resourceId": "res_id_VDU2",
"vimLevelResourceType": "OS::Nova::Server"
},
"vnfcCpInfo": [
{
"id": "VDU2_CP1-res_id_VDU2",
"cpdId": "VDU2_CP1",
"vnfExtCpId": "cp-res_id_VDU2_CP1_modified"
},
{
"id": "VDU2_CP2-res_id_VDU2",
"cpdId": "VDU2_CP2",
# "vnfExtCpId" does not exist since it is specified by
# linkPortIds.
},
{
"id": "VDU2_CP3-res_id_VDU2",
"cpdId": "VDU2_CP3",
"vnfLinkPortId": "res_id_VDU2_CP3"
},
{
"id": "VDU2_CP4-res_id_VDU2",
"cpdId": "VDU2_CP4",
"vnfLinkPortId": "res_id_VDU2_CP4"
},
{
"id": "VDU2_CP5-res_id_VDU2",
"cpdId": "VDU2_CP5",
"vnfLinkPortId": "res_id_VDU2_CP5"
}
],
"metadata": {
"creation_time": "2021-12-10T00:40:46Z"
}
}
],
# other members are not changed from _expected_inst_info
"extManagedVirtualLinkInfo":
_expected_inst_info["extManagedVirtualLinkInfo"],
"vnfVirtualLinkResourceInfo":
_expected_inst_info["vnfVirtualLinkResourceInfo"],
"virtualStorageResourceInfo":
_expected_inst_info["virtualStorageResourceInfo"],
"vnfcInfo": _expected_inst_info["vnfcInfo"]
}
class TestOpenstack(base.BaseTestCase):
@ -1258,6 +1807,7 @@ class TestOpenstack(base.BaseTestCase):
if "extVirtualLinkInfo" in expected:
self.assertIn("extVirtualLinkInfo", result)
result["extVirtualLinkInfo"].sort(key=_get_key)
for ext_vl in result["extVirtualLinkInfo"]:
if "extLinkPorts" in ext_vl:
ext_vl["extLinkPorts"].sort(key=_get_key)
@ -1350,3 +1900,30 @@ class TestOpenstack(base.BaseTestCase):
# check
result = inst.to_dict()["instantiatedVnfInfo"]
self._check_inst_info(_expected_inst_info_vnfc_updated, result)
def test_make_instantiated_vnf_info_change_ext_conn(self):
# prepare
req = objects.ChangeExtVnfConnectivityRequest.from_dict(
_change_ext_conn_req_example)
inst_info = objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict(
_expected_inst_info)
vim_info = {
"vim1": objects.VimConnectionInfo.from_dict(
_vim_connection_info_example)
}
inst = objects.VnfInstanceV2(
instantiatedVnfInfo=inst_info,
vimConnectionInfo=vim_info
)
grant_req = objects.GrantRequestV1(
operation=fields.LcmOperationType.CHANGE_EXT_CONN
)
grant = objects.GrantV1()
# execute make_instantiated_vnf_info
self.driver._make_instantiated_vnf_info(req, inst, grant_req, grant,
self.vnfd_1, _heat_reses_example_change_ext_conn)
# check
result = inst.to_dict()["instantiatedVnfInfo"]
self._check_inst_info(_expected_inst_info_change_ext_conn, result)

View File

@ -210,6 +210,59 @@ class TestUserDataUtils(base.BaseTestCase):
result = userdata_utils.get_param_fixed_ips('VDU2_CP2', {}, req)
self.assertEqual(expected_result, result)
def _inst_example_get_network_fixed_ips_from_inst(self):
ext_cp = {
"cpdId": "VDU2_CP2",
"cpConfig": {
"VDU2_CP2_1": {
"cpProtocolData": [
{
"layerProtocol": "IP_OVER_ETHERNET",
"ipOverEthernet": {
"ipAddresses": [
{
"type": "IPV4",
"fixedAddresses": [
"ip_address"
],
"subnetId": "subnet_id"
}
]
}
}
]
}
}
}
inst = {
"instantiatedVnfInfo": {
"extVirtualLinkInfo": [
{
"id": "8b49f4b6-1ff9-4a03-99cf-ff445b788436",
"resourceHandle": {
"resourceId": "ext_vl_res_id"
},
"currentVnfExtCpData": [ext_cp]
}
]
}
}
return inst
def test_get_parama_network_from_inst(self):
inst = self._inst_example_get_network_fixed_ips_from_inst()
result = userdata_utils.get_param_network_from_inst('VDU2_CP2', inst)
self.assertEqual("ext_vl_res_id", result)
def test_get_param_fixed_ips_from_inst(self):
inst = self._inst_example_get_network_fixed_ips_from_inst()
expected_result = [{'ip_address': 'ip_address', 'subnet': 'subnet_id'}]
result = userdata_utils.get_param_fixed_ips_from_inst('VDU2_CP2', inst)
self.assertEqual(expected_result, result)
def test_apply_ext_managed_vls(self):
hot_dict = self.vnfd_1.get_base_hot(SAMPLE_FLAVOUR_ID)
top_hot = hot_dict['template']