support heal VNF task of v2 API

This patch implements heal VNF task defined in ETSI NFV-SOL003
v3.3.1 5.4.9. This patch supports heal defined in ETSI NFV-SOL002
v3.3.1 5.4.9 too.

This is the last patch which adds v2 API in the Yoga release.
Some nit refactors overall v2 APIs are included in this patch.

Functional tests will be provided with another patch.

Implements: blueprint support-nfv-solv3-heal-vnf
Change-Id: Iaa8b575f9738513cd67717f8e169dd0c0f1c92b4
This commit is contained in:
Itsuro Oda 2022-01-27 01:21:33 +00:00
parent 1864ecf52a
commit 08c234bd64
20 changed files with 1178 additions and 95 deletions

View File

@ -0,0 +1,5 @@
---
features:
- |
Add the Version "2.0.0" of Heal VNF API
based on ETSI NFV specifications.

View File

@ -122,7 +122,8 @@ keyvalue_pairs = {
{'type': 'array'},
{'type': 'string', 'maxLength': 255},
{'type': 'object'},
{'type': 'null'}
{'type': 'null'},
{'type': 'boolean'}
]
}
},

View File

@ -111,6 +111,15 @@ rules = [
'path': VNF_INSTANCES_ID_PATH + '/scale'}
]
),
policy.DocumentedRuleDefault(
name=POLICY_NAME.format('heal'),
check_str=RULE_ANY,
description="Heal vnf instance.",
operations=[
{'method': 'POST',
'path': VNF_INSTANCES_ID_PATH + '/heal'}
]
),
policy.DocumentedRuleDefault(
name=POLICY_NAME.format('change_ext_conn'),
check_str=RULE_ANY,
@ -120,9 +129,7 @@ rules = [
'path': VNF_INSTANCES_ID_PATH + '/change_ext_conn'}
]
),
# TODO(oda-g): add more lcm operations etc when implemented.
# NOTE: add when the operation supported
policy.DocumentedRuleDefault(
name=POLICY_NAME.format('subscription_create'),
check_str=RULE_ANY,

View File

@ -93,6 +93,23 @@ ScaleVnfRequest_V200 = {
'additionalProperties': True,
}
# SOL002 5.5.2.9
# SOL003 5.5.2.9
HealVnfRequest_V200 = {
'type': 'object',
'properties': {
'cause': {'type': 'string', 'maxLength': 255},
'additionalParams': parameter_types.keyvalue_pairs,
# NOTE: following fields support only NFV-SOL 002
'vnfcInstanceId': {
'type': 'array',
'items': common_types.Identifier
},
'healScript': {'type': 'string', 'maxLength': 255},
},
'additionalProperties': True,
}
# SOL003 5.5.2.11
ChangeExtVnfConnectivityRequest_V200 = {
'type': 'object',

View File

@ -119,7 +119,7 @@ def make_lcmocc_notif_data(subsc, lcmocc, endpoint):
return notif_data
def _make_affected_vnfc(vnfc, change_type):
def _make_affected_vnfc(vnfc, change_type, strgs):
affected_vnfc = objects.AffectedVnfcV2(
id=vnfc.id,
vduId=vnfc.vduId,
@ -130,11 +130,17 @@ def _make_affected_vnfc(vnfc, change_type):
cp_ids = [cp.id for cp in vnfc.vnfcCpInfo]
affected_vnfc.affectedVnfcCpIds = cp_ids
if vnfc.obj_attr_is_set('storageResourceIds'):
str_ids = vnfc.storageResourceIds
# NOTE: in case of heal, volume may not be deleted/re-created.
if change_type == 'ADDED':
affected_vnfc.addedStorageResourceIds = str_ids
str_ids = [str_id for str_id in vnfc.storageResourceIds
if str_id in strgs]
if str_ids:
affected_vnfc.addedStorageResourceIds = str_ids
else: # 'REMOVED'
affected_vnfc.removedStorageResourceIds = str_ids
str_ids = [str_id for str_id in vnfc.storageResourceIds
if str_id in strgs]
if str_ids:
affected_vnfc.removedStorageResourceIds = str_ids
return affected_vnfc
@ -170,7 +176,8 @@ def _make_affected_vls_link_port_change(vls_saved, vls, common_vls):
new_ports = {port.id for port in vl.vnfLinkPorts}
add_ports = new_ports - old_ports
rm_ports = old_ports - new_ports
# assume there are not add_ports and rm_ports at the same time.
# NOTE: Only for extManagedVirtualLink in case of heal
# (SOL003 all=true) there may be both of add_ports and rm_ports.
if add_ports:
affected_vl = objects.AffectedVirtualLinkV2(
id=new_vl.id,
@ -180,7 +187,7 @@ def _make_affected_vls_link_port_change(vls_saved, vls, common_vls):
vnfLinkPortIds=list(add_ports)
)
affected_vls.append(affected_vl)
elif rm_ports:
if rm_ports:
affected_vl = objects.AffectedVirtualLinkV2(
id=old_vl.id,
vnfVirtualLinkDescId=old_vl.vnfVirtualLinkDescId,
@ -361,16 +368,33 @@ def update_lcmocc(lcmocc, inst_saved, inst):
# return removed_objs, added_objs, common_objs
return objs_saved - objs, objs - objs_saved, objs_saved & objs
removed_strgs, added_strgs, _ = _calc_diff('virtualStorageResourceInfo')
affected_strgs = []
if removed_strgs:
affected_strgs += [
_make_affected_strg(strg, 'REMOVED')
for strg in inst_info_saved.virtualStorageResourceInfo
if strg.id in removed_strgs
]
if added_strgs:
affected_strgs += [_make_affected_strg(strg, 'ADDED')
for strg in inst_info.virtualStorageResourceInfo
if strg.id in added_strgs]
removed_vnfcs, added_vnfcs, _ = _calc_diff('vnfcResourceInfo')
affected_vnfcs = []
if removed_vnfcs:
affected_vnfcs += [_make_affected_vnfc(vnfc, 'REMOVED')
for vnfc in inst_info_saved.vnfcResourceInfo
if vnfc.id in removed_vnfcs]
affected_vnfcs += [
_make_affected_vnfc(vnfc, 'REMOVED', removed_strgs)
for vnfc in inst_info_saved.vnfcResourceInfo
if vnfc.id in removed_vnfcs
]
if added_vnfcs:
affected_vnfcs += [_make_affected_vnfc(vnfc, 'ADDED')
for vnfc in inst_info.vnfcResourceInfo
if vnfc.id in added_vnfcs]
affected_vnfcs += [
_make_affected_vnfc(vnfc, 'ADDED', added_strgs)
for vnfc in inst_info.vnfcResourceInfo
if vnfc.id in added_vnfcs
]
removed_vls, added_vls, common_vls = _calc_diff(
'vnfVirtualLinkResourceInfo')
@ -403,19 +427,6 @@ def update_lcmocc(lcmocc, inst_saved, inst):
inst_info_saved.extManagedVirtualLinkInfo,
inst_info.extManagedVirtualLinkInfo, common_mgd_vls)
removed_strgs, added_strgs, _ = _calc_diff('virtualStorageResourceInfo')
affected_strgs = []
if removed_strgs:
affected_strgs += [
_make_affected_strg(strg, 'REMOVED')
for strg in inst_info_saved.virtualStorageResourceInfo
if strg.id in removed_strgs
]
if added_strgs:
affected_strgs += [_make_affected_strg(strg, 'ADDED')
for strg in inst_info.virtualStorageResourceInfo
if strg.id in added_strgs]
affected_ext_link_ports = _make_affected_ext_link_ports(
inst_info_saved, inst_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.heal = objects.Link(href=self_href + "/heal")
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")
# etc.
# NOTE: add when the operation supported
return links

View File

@ -36,6 +36,15 @@ LOG = logging.getLogger(__name__)
CONF = config.CONF
# common sub method
def _get_obj_by_id(obj_id, obj_array):
# This method assumes that an object that id is obj_id exists.
for obj in obj_array:
if obj.id == obj_id:
return obj
# not reach here
# sub method for making id
def _make_combination_id(a, b):
return '{}-{}'.format(a, b)
@ -408,20 +417,32 @@ class VnfLcmDriverV2(object):
# only support openstack at the moment
raise sol_ex.SolException(sol_detail='not support vim type')
def _make_res_def_for_remove_vnfcs(self, inst_info, inst_vnfcs):
# common part of terminate and scale in
def _make_res_def_for_remove_vnfcs(self, inst_info, inst_vnfcs,
re_create=False):
# common part of terminate, scale-in and heal.
# only heal calls with re_create=True.
rm_reses = []
add_reses = []
vnfc_cps = {}
for inst_vnfc in inst_vnfcs:
vdu_res_id = uuidutils.generate_uuid()
rm_vdu_res_id = uuidutils.generate_uuid()
rm_reses.append(
objects.ResourceDefinitionV1(
id=vdu_res_id,
id=rm_vdu_res_id,
type='COMPUTE',
resourceTemplateId=inst_vnfc.vduId,
resource=inst_vnfc.computeResource
)
)
if re_create:
add_vdu_res_id = uuidutils.generate_uuid()
add_reses.append(
objects.ResourceDefinitionV1(
id=add_vdu_res_id,
type='COMPUTE',
resourceTemplateId=inst_vnfc.vduId,
)
)
if inst_vnfc.obj_attr_is_set('vnfcCpInfo'):
for cp_info in inst_vnfc.vnfcCpInfo:
@ -432,7 +453,7 @@ class VnfLcmDriverV2(object):
# deleted.
continue
res_def = objects.ResourceDefinitionV1(
id=_make_combination_id(cp_info.cpdId, vdu_res_id),
id=_make_combination_id(cp_info.cpdId, rm_vdu_res_id),
resourceTemplateId=cp_info.cpdId,
type='LINKPORT')
rm_reses.append(res_def)
@ -441,21 +462,38 @@ class VnfLcmDriverV2(object):
else: # vnfLinkPortId
vnfc_cps[cp_info.vnfLinkPortId] = res_def
if re_create:
add_reses.append(
objects.ResourceDefinitionV1(
id=_make_combination_id(cp_info.cpdId,
add_vdu_res_id),
resourceTemplateId=cp_info.cpdId,
type='LINKPORT'
)
)
if inst_vnfc.obj_attr_is_set('storageResourceIds'):
for storage_id in inst_vnfc.storageResourceIds:
for inst_str in inst_info.virtualStorageResourceInfo:
if inst_str.id == storage_id:
str_name = inst_str.virtualStorageDescId
rm_reses.append(
objects.ResourceDefinitionV1(
id=_make_combination_id(str_name,
vdu_res_id),
type='STORAGE',
resourceTemplateId=str_name,
resource=inst_str.storageResource
)
inst_str = _get_obj_by_id(storage_id,
inst_info.virtualStorageResourceInfo)
str_name = inst_str.virtualStorageDescId
rm_reses.append(
objects.ResourceDefinitionV1(
id=_make_combination_id(str_name, rm_vdu_res_id),
type='STORAGE',
resourceTemplateId=str_name,
resource=inst_str.storageResource
)
)
if re_create:
add_reses.append(
objects.ResourceDefinitionV1(
id=_make_combination_id(str_name,
add_vdu_res_id),
type='STORAGE',
resourceTemplateId=str_name
)
break
)
# fill resourceHandle of ports
if inst_info.obj_attr_is_set('vnfVirtualLinkResourceInfo'):
@ -484,25 +522,25 @@ class VnfLcmDriverV2(object):
res_def = vnfc_cps[port.id]
res_def.resource = port.resourceHandle
return rm_reses
return rm_reses, add_reses
def terminate_grant(self, grant_req, req, inst, vnfd):
inst_info = inst.instantiatedVnfInfo
rm_reses = []
if inst_info.obj_attr_is_set('vnfcResourceInfo'):
rm_reses += self._make_res_def_for_remove_vnfcs(
rm_reses, _ = self._make_res_def_for_remove_vnfcs(
inst_info, inst_info.vnfcResourceInfo)
if inst_info.obj_attr_is_set('vnfVirtualLinkResourceInfo'):
for inst_vl in inst_info.vnfVirtualLinkResourceInfo:
rm_reses.append(
objects.ResourceDefinitionV1(
id=uuidutils.generate_uuid(),
type='VL',
resourceTemplateId=inst_vl.vnfVirtualLinkDescId,
resource=inst_vl.networkResource
)
rm_reses += [
objects.ResourceDefinitionV1(
id=uuidutils.generate_uuid(),
type='VL',
resourceTemplateId=inst_vl.vnfVirtualLinkDescId,
resource=inst_vl.networkResource
)
for inst_vl in inst_info.vnfVirtualLinkResourceInfo
]
if rm_reses:
grant_req.removeResources = rm_reses
@ -580,11 +618,10 @@ class VnfLcmDriverV2(object):
vdu_storage_names = []
if inst_vnfc.obj_attr_is_set('storageResourceIds'):
for storage_id in inst_vnfc.storageResourceIds:
for storage_res in inst_info.virtualStorageResourceInfo:
if storage_res.id == storage_id:
vdu_storage_names.append(
storage_res.virtualStorageDescId)
break
storage_res = _get_obj_by_id(storage_id,
inst_info.virtualStorageResourceInfo)
vdu_storage_names.append(
storage_res.virtualStorageDescId)
add_reses += self._make_res_def_for_new_vdu(vdu_name,
num_inst, vdu_cp_names, vdu_storage_names)
@ -613,7 +650,7 @@ class VnfLcmDriverV2(object):
if count == num_inst:
break
rm_reses = self._make_res_def_for_remove_vnfcs(inst_info, rm_vnfcs)
rm_reses, _ = self._make_res_def_for_remove_vnfcs(inst_info, rm_vnfcs)
if rm_reses:
grant_req.removeResources = rm_reses
@ -723,6 +760,105 @@ class VnfLcmDriverV2(object):
# DB is not updated, rollback does nothing and makes it successful.
pass
def _set_rm_add_reses(self, rm_reses, add_reses, res_type, res_name,
res_obj, rm_id, add_id):
rm_reses.append(
objects.ResourceDefinitionV1(
id=rm_id,
type=res_type,
resourceTemplateId=res_name,
resource=res_obj
)
)
add_reses.append(
objects.ResourceDefinitionV1(
id=add_id,
type=res_type,
resourceTemplateId=res_name
)
)
def _make_SOL002_heal_grant_request(self, grant_req, req, inst_info,
is_all):
rm_reses = []
add_reses = []
for vnfc in inst_info.vnfcInfo:
if vnfc.id not in req.vnfcInstanceId:
continue
inst_vnfc = _get_obj_by_id(vnfc.vnfcResourceInfoId,
inst_info.vnfcResourceInfo)
rm_vdu_res_id = uuidutils.generate_uuid()
add_vdu_res_id = uuidutils.generate_uuid()
self._set_rm_add_reses(rm_reses, add_reses, 'COMPUTE',
inst_vnfc.vduId, inst_vnfc.computeResource,
rm_vdu_res_id, add_vdu_res_id)
if is_all and inst_vnfc.obj_attr_is_set('storageResourceIds'):
for storage_id in inst_vnfc.storageResourceIds:
inst_str = _get_obj_by_id(storage_id,
inst_info.virtualStorageResourceInfo)
str_name = inst_str.virtualStorageDescId
self._set_rm_add_reses(rm_reses, add_reses, 'STORAGE',
str_name, inst_str.storageResource,
_make_combination_id(str_name, rm_vdu_res_id),
_make_combination_id(str_name, add_vdu_res_id))
if rm_reses:
grant_req.removeResources = rm_reses
grant_req.addResources = add_reses
def _make_SOL003_heal_grant_request(self, grant_req, req, inst_info,
is_all):
rm_reses = []
add_reses = []
if is_all:
if inst_info.obj_attr_is_set('vnfcResourceInfo'):
rm_reses, add_reses = self._make_res_def_for_remove_vnfcs(
inst_info, inst_info.vnfcResourceInfo, re_create=True)
if inst_info.obj_attr_is_set('vnfVirtualLinkResourceInfo'):
for inst_vl in inst_info.vnfVirtualLinkResourceInfo:
self._set_rm_add_reses(rm_reses, add_reses, 'VL',
inst_vl.vnfVirtualLinkDescId, inst_vl.networkResource,
uuidutils.generate_uuid(), uuidutils.generate_uuid())
else:
if inst_info.obj_attr_is_set('vnfcResourceInfo'):
for inst_vnfc in inst_info.vnfcResourceInfo:
self._set_rm_add_reses(rm_reses, add_reses, 'COMPUTE',
inst_vnfc.vduId, inst_vnfc.computeResource,
uuidutils.generate_uuid(), uuidutils.generate_uuid())
if rm_reses:
grant_req.removeResources = rm_reses
grant_req.addResources = add_reses
def heal_grant(self, grant_req, req, inst, vnfd):
inst_info = inst.instantiatedVnfInfo
is_all = False # default is False
if req.obj_attr_is_set('additionalParams'):
is_all = req.additionalParams.get('all', False)
grant_req.additionalParams = req.additionalParams
if req.obj_attr_is_set('vnfcInstanceId'):
self._make_SOL002_heal_grant_request(grant_req, req, inst_info,
is_all)
else:
self._make_SOL003_heal_grant_request(grant_req, req, inst_info,
is_all)
def heal_process(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.heal(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_grant(self, grant_req, req, inst, vnfd):
inst_info = inst.instantiatedVnfInfo

View File

@ -321,6 +321,43 @@ class VnfLcmControllerV2(sol_wsgi.SolAPIController):
return sol_wsgi.SolResponse(202, None, location=location)
@validator.schema(schema.HealVnfRequest_V200, '2.0.0')
@coordinate.lock_vnf_instance('{id}')
def heal(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)
# check parameter for later use
is_all = body.get('additionalParams', {}).get('all', False)
if not isinstance(is_all, bool):
raise sol_ex.SolValidationError(
detail="additionalParams['all'] must be bool.")
if 'vnfcInstanceId' in body:
inst_info = inst.instantiatedVnfInfo
vnfc_id = []
if inst_info.obj_attr_is_set('vnfcInfo'):
vnfc_id = [vnfc.id for vnfc in inst_info.vnfcInfo]
for req_vnfc_id in body['vnfcInstanceId']:
if req_vnfc_id not in vnfc_id:
raise sol_ex.SolValidationError(
detail="vnfcInstanceId(%s) does not exist."
% req_vnfc_id)
lcmocc = self._new_lcmocc(id, v2fields.LcmOperationType.HEAL, 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.ChangeExtVnfConnectivityRequest_V200, '2.0.0')
@coordinate.lock_vnf_instance('{id}')
def change_ext_conn(self, request, id, body):

View File

@ -85,21 +85,20 @@ class HeatClient(object):
return body['resources']
def _wait_completion(self, stack_name, operation, complete_status,
progress_status, failed_status, none_is_done=False):
progress_status, failed_status):
# NOTE: timeout is specified for each stack operation. so it is
# not forever loop.
def _check_status():
status, status_reason = self.get_status(stack_name)
if (status == complete_status or
(status is None and none_is_done)):
if status in complete_status:
LOG.info("%s %s done.", operation, stack_name)
raise loopingcall.LoopingCallDone()
elif status == failed_status:
elif status in failed_status:
LOG.error("% %s failed.", operation, stack_name)
sol_title = "%s failed" % operation
raise sol_ex.StackOperationFailed(sol_title=sol_title,
sol_detail=status_reason)
elif status != progress_status:
elif status not in progress_status:
LOG.error("%s %s failed. status: %s", operation,
stack_name, status)
sol_title = "%s failed" % operation
@ -112,16 +111,20 @@ class HeatClient(object):
def wait_stack_create(self, stack_name):
self._wait_completion(stack_name, "Stack create",
"CREATE_COMPLETE", "CREATE_IN_PROGRESS", "CREATE_FAILED")
["CREATE_COMPLETE"], ["CREATE_IN_PROGRESS"], ["CREATE_FAILED"])
def wait_stack_update(self, stack_name):
self._wait_completion(stack_name, "Stack update",
"UPDATE_COMPLETE", "UPDATE_IN_PROGRESS", "UPDATE_FAILED")
["UPDATE_COMPLETE"], ["UPDATE_IN_PROGRESS"], ["UPDATE_FAILED"])
def wait_stack_delete(self, stack_name):
# NOTE: wait until stack is deleted in the DB since it is necessary
# for some operations (ex. heal-all).
# It is expected that it takes short time after "DELETE_COMPLETE".
# So timeout after "DELETE_COMPLETE" is not specified.
self._wait_completion(stack_name, "Stack delete",
"DELETE_COMPLETE", "DELETE_IN_PROGRESS", "DELETE_FAILED",
none_is_done=True)
[None], ["DELETE_IN_PROGRESS", "DELETE_COMPLETE"],
["DELETE_FAILED"])
def get_parameters(self, stack_name):
path = "stacks/{}".format(stack_name)
@ -139,6 +142,20 @@ class HeatClient(object):
resp, body = self.client.do_request(path, "PATCH",
expected_status=[200], body=fields)
def get_template(self, stack_name):
path = "stacks/{}/template".format(stack_name)
resp, body = self.client.do_request(path, "GET",
expected_status=[200])
return body
def get_files(self, stack_name):
path = "stacks/{}/files".format(stack_name)
resp, body = self.client.do_request(path, "GET",
expected_status=[200])
return body
def get_reses_by_types(heat_reses, types):
return [res for res in heat_reses if res['resource_type'] in types]

View File

@ -225,6 +225,73 @@ class Openstack(object):
self._make_instantiated_vnf_info(req, inst, grant_req, grant, vnfd,
heat_reses)
def heal(self, req, inst, grant_req, grant, vnfd):
# make HOT
# NOTE: _make_hot() is called as other operations, but it returns
# empty 'nfv' dict by default. Therefore _update_nfv_dict() returns
# current heat parameters as is.
fields = self._make_hot(req, inst, grant_req, grant, vnfd)
LOG.debug("stack fields: %s", fields)
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)
# "re_create" is set to True only when SOL003 heal(without
# vnfcInstanceId) and "all=True" in additionalParams.
re_create = False
if (req.obj_attr_is_set('additionalParams') and
req.additionalParams.get('all', False) and
not req.obj_attr_is_set('vnfcInstanceId')):
re_create = True
if re_create:
# NOTE: DefaultUserData::heal() don't care about "template" and
# "files". Get and set current "template" and "files".
if "template" not in fields:
fields["template"] = heat_client.get_template(stack_name)
if "files" not in fields:
fields["files"] = heat_client.get_files(stack_name)
fields["stack_name"] = stack_name
# stack delete and create
heat_client.delete_stack(stack_name)
heat_client.create_stack(fields)
else:
# mark unhealthy to target resources.
# As the target resources has been already selected in
# constructing grant_req, use it here.
inst_info = inst.instantiatedVnfInfo
vnfc_res_ids = [res_def.resource.resourceId
for res_def in grant_req.removeResources
if res_def.type == 'COMPUTE']
for vnfc in inst_info.vnfcResourceInfo:
if vnfc.computeResource.resourceId in vnfc_res_ids:
heat_client.mark_unhealthy(
vnfc.metadata['stack_id'], vnfc.vduId)
storage_ids = [res_def.resource.resourceId
for res_def in grant_req.removeResources
if res_def.type == 'STORAGE']
if storage_ids:
for storage_info in inst_info.virtualStorageResourceInfo:
if storage_info.storageResource.resourceId in storage_ids:
heat_client.mark_unhealthy(
storage_info.metadata['stack_id'],
storage_info.virtualStorageDescId)
# update stack
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 _make_hot(self, req, inst, grant_req, grant, vnfd):
if grant_req.operation == v2fields.LcmOperationType.INSTANTIATE:
flavour_id = req.flavourId
@ -652,6 +719,7 @@ class Openstack(object):
def _make_vnfc_metadata(self, server_res, heat_reses):
metadata = {
'creation_time': server_res['creation_time'],
'stack_id': heat_utils.get_resource_stack_id(server_res)
}
parent_res = heat_utils.get_parent_resource(server_res, heat_reses)
if parent_res:
@ -692,7 +760,10 @@ class Openstack(object):
objects.VirtualStorageResourceInfoV2(
id=res_id,
virtualStorageDescId=res['resource_name'],
storageResource=_res_to_handle(res)
storageResource=_res_to_handle(res),
metadata={
'stack_id': heat_utils.get_resource_stack_id(res)
}
)
for res_id, res in storage_reses.items()
]

View File

@ -238,3 +238,11 @@ class DefaultUserData(userdata_utils.AbstractUserData):
fields = {'parameters': {'nfv': {'CP': new_cps}}}
return fields
@staticmethod
def heal(req, inst, grant_req, grant, tmp_csar_dir):
# It is not necessary to change parameters at heal basically.
fields = {'parameters': {'nfv': {}}}
return fields

View File

@ -50,6 +50,11 @@ class AbstractUserData(metaclass=abc.ABCMeta):
def change_ext_conn(req, inst, grant_req, grant, tmp_csar_dir):
raise sol_ex.UserDataClassNotImplemented()
@staticmethod
@abc.abstractmethod
def heal(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)

View File

@ -20,7 +20,7 @@ from tacker.sol_refactored.objects import fields
# NFV-SOL 003
# - v3.3.1 5.5.2.9 (API version: 2.0.0)
@base.TackerObjectRegistry.register
class HealVnfRequestV2(base.TackerObject, base.TackerObjectDictCompat):
class HealVnfRequest(base.TackerObject, base.TackerObjectDictCompat):
# Version 1.0: Initial version
VERSION = '1.0'
@ -30,5 +30,7 @@ class HealVnfRequestV2(base.TackerObject, base.TackerObjectDictCompat):
'additionalParams': fields.KeyValuePairsField(nullable=True),
# NOTE: following fields are defined in NFV-SOL 002 v3.3.1 5.5.2.9
'vnfcInstanceId': fields.ListOfStringsField(nullable=True),
# NOTE: 'healScript' is not supported. It can be specified but
# make no effect at all.
'healScript': fields.StringField(nullable=True),
}

View File

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

View File

@ -27,7 +27,7 @@ class OperationParam(fields.FieldType):
raise ValueError(_("'operation' must have been coerced "
"before 'operationParams'"))
if obj.operation == v2fields.LcmOperationType.INSTANTIATE:
cls = objects.InstantiateVnfRequestV2
cls = objects.InstantiateVnfRequest
elif obj.operation == v2fields.LcmOperationType.SCALE:
cls = objects.ScaleVnfRequest
elif obj.operation == v2fields.LcmOperationType.SCALE_TO_LEVEL:

View File

@ -100,6 +100,12 @@ class Client(object):
path, "POST", body=req_body, version="2.0.0")
self.print(resp, body)
def heal(self, id, req_body):
path = self.path + '/' + id + '/heal'
resp, body = self.client.do_request(
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(
@ -132,6 +138,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 heal {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)]")
@ -159,7 +166,7 @@ if __name__ == '__main__':
if resource == "inst":
if action not in ["create", "list", "show", "delete", "update",
"inst", "term", "scale", "chg_ext_conn"]:
"inst", "term", "scale", "heal", "chg_ext_conn"]:
usage()
client = Client("/vnflcm/v2/vnf_instances")
elif resource == "subsc":
@ -209,6 +216,10 @@ if __name__ == '__main__':
if len(sys.argv) != 5:
usage()
client.scale(sys.argv[3], get_body(sys.argv[4]))
elif action == "heal":
if len(sys.argv) != 5:
usage()
client.heal(sys.argv[3], get_body(sys.argv[4]))
elif action == "chg_ext_conn":
if len(sys.argv) != 5:
usage()

View File

@ -891,7 +891,7 @@ _inst_info_example_3 = {
}
],
# NOTE: vnfcCpInfo of VnfcResourceInfo is changed exactly, but it is
# not looked at lcmocc_update.
# not looked at update_lcmocc.
"vnfcResourceInfo": _inst_info_example_1["vnfcResourceInfo"],
# other members are same as example_1
"extManagedVirtualLinkInfo":
@ -903,6 +903,202 @@ _inst_info_example_3 = {
# "vnfcInfo": omitted
}
# example_4 is for update_lcmocc test in case of heal. based on example_2.
# * VDU1_1: update server only. VDU1_2: update both server and volume.
# * ports of extMangagedVirtualLink are re-created.
# NOTE: combination of the above sentences can not be happened really.
# it is the data for unit test.
_inst_info_example_4 = {
# "flavourId", "vnfState", "scaleStatus", "maxScaleLevels" are omitted
# "extCpInfo": omitted
"extVirtualLinkInfo": _inst_info_example_2["extVirtualLinkInfo"],
# network resource is not changed but ports are re-receated.
# this is for check of SOL003 all=True case.
"extManagedVirtualLinkInfo": [
{
"id": "res_id_internalVL1",
"vnfVirtualLinkDescId": "internalVL1",
"networkResource": {
"resourceId": "res_id_internalVL1"
},
"vnfLinkPorts": [
{
"id": "res_id_VDU2_CP3_changed",
"resourceHandle": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU2_CP3_changed",
"vimLevelResourceType": "OS::Neutron::Port"
},
"cpInstanceId": "VDU2_CP3-res_id_VDU2",
"cpInstanceType": "VNFC_CP"
},
{
"id": "res_id_VDU1_CP3_1_changed",
"resourceHandle": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU1_CP3_1_changed",
"vimLevelResourceType": "OS::Neutron::Port"
},
"cpInstanceId": "VDU1_CP3-res_id_VDU1_1",
"cpInstanceType": "VNFC_CP"
},
{
"id": "res_id_VDU1_CP3_2_changed",
"resourceHandle": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU1_CP3_2_changed",
"vimLevelResourceType": "OS::Neutron::Port"
},
"cpInstanceId": "VDU1_CP3-res_id_VDU1_2",
"cpInstanceType": "VNFC_CP"
}
]
}
],
"vnfcResourceInfo": [
{
"id": "res_id_VDU1_2_new",
"vduId": "VDU1",
"computeResource": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU1_2_new",
"vimLevelResourceType": "OS::Nova::Server"
},
"storageResourceIds": [
"res_id_VirtualStorage_2_new"
],
"vnfcCpInfo": [
{
"id": "VDU1_CP1-res_id_VDU1_2_new",
"cpdId": "VDU1_CP1",
"vnfExtCpId": "cp-res_id_VDU1_CP1_2_new"
},
{
"id": "VDU1_CP2-res_id_VDU1_2_new",
"cpdId": "VDU1_CP2",
"vnfExtCpId": "cp-res_id_VDU1_CP2_2_new"
},
{
"id": "VDU1_CP3-res_id_VDU1_2_new",
"cpdId": "VDU1_CP3",
"vnfLinkPortId": "res_id_VDU1_CP3_2_new"
},
{
"id": "VDU1_CP4-res_id_VDU1_2_new",
"cpdId": "VDU1_CP4",
"vnfLinkPortId": "res_id_VDU1_CP4_2_new"
},
{
"id": "VDU1_CP5-res_id_VDU1_2_new",
"cpdId": "VDU1_CP5",
"vnfLinkPortId": "res_id_VDU1_CP5_2_new"
}
],
# "metadata": omitted
},
{
"id": "res_id_VDU1_1_new",
"vduId": "VDU1",
"computeResource": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU1_1_new",
"vimLevelResourceType": "OS::Nova::Server"
},
"storageResourceIds": [
"res_id_VirtualStorage_1"
],
"vnfcCpInfo": [
{
"id": "VDU1_CP1-res_id_VDU1_1_new",
"cpdId": "VDU1_CP1",
"vnfExtCpId": "cp-res_id_VDU1_CP1_1_new"
},
{
"id": "VDU1_CP2-res_id_VDU1_1_new",
"cpdId": "VDU1_CP2",
"vnfExtCpId": "cp-res_id_VDU1_CP2_1_new"
},
{
"id": "VDU1_CP3-res_id_VDU1_1_new",
"cpdId": "VDU1_CP3",
"vnfLinkPortId": "res_id_VDU1_CP3_1_new"
},
{
"id": "VDU1_CP4-res_id_VDU1_1_new",
"cpdId": "VDU1_CP4",
"vnfLinkPortId": "res_id_VDU1_CP4_1_new"
},
{
"id": "VDU1_CP5-res_id_VDU1_1_new",
"cpdId": "VDU1_CP5",
"vnfLinkPortId": "res_id_VDU1_CP5_1_new"
}
],
# "metadata": omitted
},
{
"id": "res_id_VDU2",
"vduId": "VDU2",
"computeResource": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU2",
"vimLevelResourceType": "OS::Nova::Server"
},
"vnfcCpInfo": [
{
"id": "VDU2_CP1-res_id_VDU2",
"cpdId": "VDU2_CP1",
"vnfExtCpId": "cp-res_id_VDU2_CP1"
},
{
"id": "VDU2_CP2-res_id_VDU2",
"cpdId": "VDU2_CP2",
"vnfExtCpId": "cp-res_id_VDU2_CP2"
},
{
"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": omitted
}
],
"vnfVirtualLinkResourceInfo":
_inst_info_example_2["vnfVirtualLinkResourceInfo"],
"virtualStorageResourceInfo": [
{
"id": "res_id_VirtualStorage_1",
"virtualStorageDescId": "VirtualStorage",
"storageResource": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VirtualStorage_1",
"vimLevelResourceType": "OS::Cinder::Volume"
}
},
{
"id": "res_id_VirtualStorage_2_new",
"virtualStorageDescId": "VirtualStorage",
"storageResource": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VirtualStorage_2_new",
"vimLevelResourceType": "OS::Cinder::Volume"
}
}
],
# "vnfcInfo": omitted
}
# expected results
_expected_resource_changes_instantiate = {
"affectedVnfcs": [
@ -1552,6 +1748,137 @@ _expected_changed_ext_connectivity = [
_inst_info_example_3['extVirtualLinkInfo'][4]
]
_expected_resource_changes_heal = {
"affectedVnfcs": [
{
"id": "res_id_VDU1_1",
"vduId": "VDU1",
"changeType": "REMOVED",
"computeResource": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU1_1",
"vimLevelResourceType": "OS::Nova::Server"
},
"affectedVnfcCpIds": [
"VDU1_CP1-res_id_VDU1_1",
"VDU1_CP2-res_id_VDU1_1",
"VDU1_CP3-res_id_VDU1_1",
"VDU1_CP4-res_id_VDU1_1",
"VDU1_CP5-res_id_VDU1_1"
]
},
{
"id": "res_id_VDU1_1_new",
"vduId": "VDU1",
"changeType": "ADDED",
"computeResource": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU1_1_new",
"vimLevelResourceType": "OS::Nova::Server"
},
"affectedVnfcCpIds": [
"VDU1_CP1-res_id_VDU1_1_new",
"VDU1_CP2-res_id_VDU1_1_new",
"VDU1_CP3-res_id_VDU1_1_new",
"VDU1_CP4-res_id_VDU1_1_new",
"VDU1_CP5-res_id_VDU1_1_new"
]
},
{
"id": "res_id_VDU1_2",
"vduId": "VDU1",
"changeType": "REMOVED",
"computeResource": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU1_2",
"vimLevelResourceType": "OS::Nova::Server"
},
"affectedVnfcCpIds": [
"VDU1_CP1-res_id_VDU1_2",
"VDU1_CP2-res_id_VDU1_2",
"VDU1_CP3-res_id_VDU1_2",
"VDU1_CP4-res_id_VDU1_2",
"VDU1_CP5-res_id_VDU1_2"
],
"removedStorageResourceIds": [
"res_id_VirtualStorage_2"
]
},
{
"id": "res_id_VDU1_2_new",
"vduId": "VDU1",
"changeType": "ADDED",
"computeResource": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VDU1_2_new",
"vimLevelResourceType": "OS::Nova::Server"
},
"affectedVnfcCpIds": [
"VDU1_CP1-res_id_VDU1_2_new",
"VDU1_CP2-res_id_VDU1_2_new",
"VDU1_CP3-res_id_VDU1_2_new",
"VDU1_CP4-res_id_VDU1_2_new",
"VDU1_CP5-res_id_VDU1_2_new"
],
"addedStorageResourceIds": [
"res_id_VirtualStorage_2_new"
]
},
],
"affectedVirtualLinks": [
# NOTE: id is same but expected LINK_PORT_ADDED is first based on the
# code.
{
"id": "res_id_internalVL1",
"vnfVirtualLinkDescId": "internalVL1",
"changeType": "LINK_PORT_ADDED",
"networkResource": {
"resourceId": "res_id_internalVL1"
},
"vnfLinkPortIds": [
"res_id_VDU1_CP3_1_changed",
"res_id_VDU1_CP3_2_changed",
"res_id_VDU2_CP3_changed"
]
},
{
"id": "res_id_internalVL1",
"vnfVirtualLinkDescId": "internalVL1",
"changeType": "LINK_PORT_REMOVED",
"networkResource": {
"resourceId": "res_id_internalVL1"
},
"vnfLinkPortIds": [
"res_id_VDU1_CP3_1",
"res_id_VDU1_CP3_2",
"res_id_VDU2_CP3"
]
}
],
"affectedVirtualStorages": [
{
"id": "res_id_VirtualStorage_2",
"virtualStorageDescId": "VirtualStorage",
"changeType": "REMOVED",
"storageResource": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VirtualStorage_2",
"vimLevelResourceType": "OS::Cinder::Volume"
}
},
{
"id": "res_id_VirtualStorage_2_new",
"virtualStorageDescId": "VirtualStorage",
"changeType": "ADDED",
"storageResource": {
"vimConnectionId": "vim_connection_id",
"resourceId": "res_id_VirtualStorage_2_new",
"vimLevelResourceType": "OS::Cinder::Volume"
}
}
]
}
class TestLcmOpOccUtils(base.BaseTestCase):
@ -1710,3 +2037,27 @@ class TestLcmOpOccUtils(base.BaseTestCase):
self.assertEqual(
_expected_changed_ext_connectivity,
sorted(lcmocc['changedExtConnectivity'], key=lambda x: x['id']))
def test_update_lcmocc_heal(self):
# NOTE: from the view point of update_lcmocc unit test coverage,
# it is enough to check server and/or volume update.
# prepare
inst_saved = objects.VnfInstanceV2()
inst_saved.instantiatedVnfInfo = (
objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict(
_inst_info_example_2))
inst = objects.VnfInstanceV2()
inst.instantiatedVnfInfo = (
objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict(
_inst_info_example_4))
lcmocc = objects.VnfLcmOpOccV2(
operation=fields.LcmOperationType.HEAL)
# execute update_lcmocc
lcmocc_utils.update_lcmocc(lcmocc, inst_saved, inst)
# check resourceChanges
lcmocc = lcmocc.to_dict()
self.assertEqual(
_expected_resource_changes_heal,
self._sort_resource_changes(lcmocc['resourceChanges']))

View File

@ -341,7 +341,7 @@ _inst_info_example = {
],
"vnfcResourceInfo": [
{
"id": "cdf36e11-f6ca-4c80-aaf1-0d2e764a2f3a",
"id": "vnfc_res_info_id_VDU2",
"vduId": "VDU2",
"computeResource": {
# "vimConnectionId": omitted
@ -377,7 +377,7 @@ _inst_info_example = {
]
},
{
"id": "c8cb522d-ddf8-4136-9c85-92bab8f2993d",
"id": "vnfc_res_info_id_VDU1_1",
"vduId": "VDU1",
"computeResource": {
# "vimConnectionId": omitted
@ -414,7 +414,7 @@ _inst_info_example = {
]
},
{
"id": "9f6537ca-9fe3-4fa3-8e57-87930f90a1c6",
"id": "vnfc_res_info_id_VDU1_2",
"vduId": "VDU1",
"computeResource": {
# "vimConnectionId": omitted
@ -556,7 +556,26 @@ _inst_info_example = {
}
}
],
# "vnfcInfo": omitted
"vnfcInfo": [
{
"id": "VDU2-vnfc_res_info_id_VDU2",
"vduId": "VDU2",
"vnfcResourceInfoId": "vnfc_res_info_id_VDU2",
"vnfcState": "STARTED"
},
{
"id": "VDU1-vnfc_res_info_id_VDU1_1",
"vduId": "VDU1",
"vnfcResourceInfoId": "vnfc_res_info_id_VDU1_1",
"vnfcState": "STARTED"
},
{
"id": "VDU1-vnfc_res_info_id_VDU1_2",
"vduId": "VDU1",
"vnfcResourceInfoId": "vnfc_res_info_id_VDU1_2",
"vnfcState": "STARTED"
}
]
}
# modify_info_process example
@ -638,7 +657,7 @@ class TestVnfLcmDriverV2(base.BaseTestCase):
@mock.patch.object(nfvo_client.NfvoClient, 'grant')
def test_instantiate_grant(self, mocked_grant):
# prepare
req = objects.InstantiateVnfRequestV2.from_dict(_inst_req_example)
req = objects.InstantiateVnfRequest.from_dict(_inst_req_example)
inst = objects.VnfInstanceV2(
# required fields
id=uuidutils.generate_uuid(),
@ -951,7 +970,7 @@ class TestVnfLcmDriverV2(base.BaseTestCase):
instantiatedVnfInfo=objects.VnfInstanceV2_InstantiatedVnfInfo()
)
inst = inst_saved.obj_clone()
req = objects.InstantiateVnfRequestV2.from_dict(_inst_req_example)
req = objects.InstantiateVnfRequest.from_dict(_inst_req_example)
lcmocc = objects.VnfLcmOpOccV2(
# only set used members in the method
operation=fields.LcmOperationType.INSTANTIATE,
@ -1371,3 +1390,301 @@ class TestVnfLcmDriverV2(base.BaseTestCase):
for key, value in check_reses.items():
for name, ids in value.items():
self.assertEqual(expected_num[key][name], len(ids))
def _heal_grant_prepare(self, req):
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.HEAL,
isAutomaticInvocation=False,
isCancelPending=False,
operationParams=req)
return inst, lcmocc
@mock.patch.object(nfvo_client.NfvoClient, 'grant')
def test_heal_grant_SOL002(self, mocked_grant):
# prepare
req = objects.HealVnfRequest(
vnfcInstanceId=["VDU1-vnfc_res_info_id_VDU1_2",
"VDU2-vnfc_res_info_id_VDU2"]
)
inst, lcmocc = self._heal_grant_prepare(req)
mocked_grant.return_value = objects.GrantV1()
# run heal_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': 'HEAL',
'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 removeResources
rm_reses = grant_req['removeResources']
check_reses = {
'COMPUTE': {'VDU1': [], 'VDU2': []}
}
expected_res_ids = {
'COMPUTE': {
'VDU1': ['res_id_VDU1_2'],
'VDU2': ['res_id_VDU2']
}
}
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 = {
'COMPUTE': {'VDU1': [], 'VDU2': []}
}
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(len(expected_res_ids[key][name]), len(ids))
@mock.patch.object(nfvo_client.NfvoClient, 'grant')
def test_heal_grant_SOL002_all(self, mocked_grant):
# prepare
req = objects.HealVnfRequest(
vnfcInstanceId=["VDU1-vnfc_res_info_id_VDU1_2",
"VDU2-vnfc_res_info_id_VDU2"],
additionalParams={'all': True}
)
inst, lcmocc = self._heal_grant_prepare(req)
mocked_grant.return_value = objects.GrantV1()
# run heal_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': 'HEAL',
'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 removeResources
rm_reses = grant_req['removeResources']
check_reses = {
'COMPUTE': {'VDU1': [], 'VDU2': []},
'STORAGE': {'VirtualStorage': []}
}
expected_res_ids = {
'COMPUTE': {
'VDU1': ['res_id_VDU1_2'],
'VDU2': ['res_id_VDU2']
},
'STORAGE': {
'VirtualStorage': ['res_id_VirtualStorage_2']
}
}
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 = {
'COMPUTE': {'VDU1': [], 'VDU2': []},
'STORAGE': {'VirtualStorage': []}
}
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(len(expected_res_ids[key][name]), len(ids))
@mock.patch.object(nfvo_client.NfvoClient, 'grant')
def test_heal_grant_SOL003(self, mocked_grant):
# prepare
req = objects.HealVnfRequest()
inst, lcmocc = self._heal_grant_prepare(req)
mocked_grant.return_value = objects.GrantV1()
# run heal_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': 'HEAL',
'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 removeResources
rm_reses = grant_req['removeResources']
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 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 = {
'COMPUTE': {'VDU1': [], 'VDU2': []}
}
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(len(expected_res_ids[key][name]), len(ids))
@mock.patch.object(nfvo_client.NfvoClient, 'grant')
def test_heal_grant_SOL003_all(self, mocked_grant):
# prepare
req = objects.HealVnfRequest(
additionalParams={'all': True}
)
inst, lcmocc = self._heal_grant_prepare(req)
mocked_grant.return_value = objects.GrantV1()
# run heal_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': 'HEAL',
'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 removeResources
rm_reses = grant_req['removeResources']
check_reses = {
'COMPUTE': {'VDU1': [], 'VDU2': []},
'STORAGE': {'VirtualStorage': []},
'LINKPORT': {'VDU1_CP1': [], 'VDU1_CP2': [], 'VDU1_CP3': [],
'VDU1_CP4': [], 'VDU1_CP5': [],
'VDU2_CP1': [], 'VDU2_CP2': [], 'VDU2_CP3': [],
'VDU2_CP4': [], 'VDU2_CP5': []},
'VL': {'internalVL2': [], 'internalVL3': []}
}
expected_res_ids = {
'COMPUTE': {
'VDU1': ['res_id_VDU1_1', 'res_id_VDU1_2'],
'VDU2': ['res_id_VDU2']
},
'STORAGE': {
'VirtualStorage': ['res_id_VirtualStorage_1',
'res_id_VirtualStorage_2']
},
'LINKPORT': {
'VDU1_CP1': ['res_id_VDU1_1_CP1'],
'VDU1_CP2': ['res_id_VDU1_1_CP2', 'res_id_VDU1_2_CP2'],
'VDU1_CP3': ['res_id_VDU1_1_CP3', 'res_id_VDU1_2_CP3'],
'VDU1_CP4': ['res_id_VDU1_1_CP4', 'res_id_VDU1_2_CP4'],
'VDU1_CP5': ['res_id_VDU1_1_CP5', 'res_id_VDU1_2_CP5'],
'VDU2_CP1': ['res_id_VDU2_CP1'],
'VDU2_CP2': ['res_id_VDU2_CP2'],
'VDU2_CP3': ['res_id_VDU2_CP3'],
'VDU2_CP4': ['res_id_VDU2_CP4'],
'VDU2_CP5': ['res_id_VDU2_CP5']
},
'VL': {
'internalVL2': ['res_id_internalVL2'],
'internalVL3': ['res_id_internalVL3']
}
}
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 = {
'COMPUTE': {'VDU1': [], 'VDU2': []},
'STORAGE': {'VirtualStorage': []},
'LINKPORT': {'VDU1_CP1': [], 'VDU1_CP2': [], 'VDU1_CP3': [],
'VDU1_CP4': [], 'VDU1_CP5': [],
'VDU2_CP1': [], 'VDU2_CP2': [], 'VDU2_CP3': [],
'VDU2_CP4': [], 'VDU2_CP5': []},
'VL': {'internalVL2': [], 'internalVL3': []}
}
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(len(expected_res_ids[key][name]), len(ids))

View File

@ -503,3 +503,83 @@ class TestVnflcmV2(db_base.SqlTestCase):
self.assertRaises(sol_ex.OtherOperationInProgress,
self.controller.change_ext_conn, request=self.request, id=inst_id,
body=_change_ext_conn_req_example)
def test_heal_not_instantiated(self):
inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED',
fields.LcmOperationStateType.COMPLETED)
body = {"cause": "Healing VNF instance"}
self.assertRaises(sol_ex.VnfInstanceIsNotInstantiated,
self.controller.heal, request=self.request, id=inst_id,
body=body)
def test_heal_lcmocc_in_progress(self):
inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED',
fields.LcmOperationStateType.FAILED_TEMP)
body = {"cause": "Healing VNF instance"}
self.assertRaises(sol_ex.OtherOperationInProgress,
self.controller.heal, request=self.request, id=inst_id,
body=body)
def _prepare_db_for_heal_param_check(self):
inst = objects.VnfInstanceV2(
# required fields
id=uuidutils.generate_uuid(),
vnfdId=uuidutils.generate_uuid(),
vnfProvider='provider',
vnfProductName='product name',
vnfSoftwareVersion='software version',
vnfdVersion='vnfd version',
instantiationState='INSTANTIATED'
)
inst.instantiatedVnfInfo = objects.VnfInstanceV2_InstantiatedVnfInfo(
flavourId='small',
vnfState='STARTED',
vnfcInfo=[
objects.VnfcInfoV2(
id="VDU2-vnfc_res_info_id_VDU2",
vduId="VDU2",
vnfcResourceInfoId="vnfc_res_info_id_VDU2",
vnfcState="STARTED"
),
objects.VnfcInfoV2(
id="VDU1-vnfc_res_info_id_VDU1",
vduId="VDU1",
vnfcResourceInfoId="vnfc_res_info_id_VDU1",
vnfcState="STARTED"
),
objects.VnfcInfoV2(
id="VDU1-vnfc_res_info_id_VDU2",
vduId="VDU1",
vnfcResourceInfoId="vnfc_res_info_id_VDU2",
vnfcState="STARTED"
),
]
)
inst.create(self.context)
return inst.id
def test_heal_invalid_additional_params(self):
inst_id = self._prepare_db_for_heal_param_check()
body = {
"additionalParams": {"all": "string"}
}
self.assertRaises(sol_ex.SolValidationError,
self.controller.heal, request=self.request, id=inst_id,
body=body)
def test_heal_invalid_vnfcinstance_id(self):
inst_id = self._prepare_db_for_heal_param_check()
body = {
"vnfcInstanceId": [
"VDU2-vnfc_res_info_id_VDU2",
"VDU2-vnfc_res_info_id_VDU1"
]
}
self.assertRaises(sol_ex.SolValidationError,
self.controller.heal, request=self.request, id=inst_id,
body=body)

View File

@ -1129,7 +1129,8 @@ _expected_inst_info = {
"metadata": {
"creation_time": "2021-12-10T01:03:49Z",
"parent_stack_id": _stack_id_VDU1_scale,
"parent_resource_name": "myet4efobvvp"
"parent_resource_name": "myet4efobvvp",
"stack_id": _stack_id_VDU1_2
}
},
{
@ -1173,7 +1174,8 @@ _expected_inst_info = {
"metadata": {
"creation_time": "2021-12-10T00:41:43Z",
"parent_stack_id": _stack_id_VDU1_scale,
"parent_resource_name": "bemybz4ugeso"
"parent_resource_name": "bemybz4ugeso",
"stack_id": _stack_id_VDU1_1
}
},
{
@ -1213,7 +1215,8 @@ _expected_inst_info = {
}
],
"metadata": {
"creation_time": "2021-12-10T00:40:46Z"
"creation_time": "2021-12-10T00:40:46Z",
"stack_id": _stack_id
}
}
],
@ -1309,7 +1312,8 @@ _expected_inst_info = {
"vimConnectionId": "vim_id_1",
"resourceId": "res_id_VirtualStorage_1",
"vimLevelResourceType": "OS::Cinder::Volume"
}
},
"metadata": {"stack_id": _stack_id_VDU1_1}
},
{
"id": "res_id_VirtualStorage_2",
@ -1318,7 +1322,8 @@ _expected_inst_info = {
"vimConnectionId": "vim_id_1",
"resourceId": "res_id_VirtualStorage_2",
"vimLevelResourceType": "OS::Cinder::Volume"
}
},
"metadata": {"stack_id": _stack_id_VDU1_2}
}
],
"vnfcInfo": [
@ -1679,7 +1684,8 @@ _expected_inst_info_change_ext_conn = {
"metadata": {
"creation_time": "2021-12-10T01:03:49Z",
"parent_stack_id": _stack_id_VDU1_scale,
"parent_resource_name": "myet4efobvvp"
"parent_resource_name": "myet4efobvvp",
"stack_id": _stack_id_VDU1_2
}
},
{
@ -1723,7 +1729,8 @@ _expected_inst_info_change_ext_conn = {
"metadata": {
"creation_time": "2021-12-10T00:41:43Z",
"parent_stack_id": _stack_id_VDU1_scale,
"parent_resource_name": "bemybz4ugeso"
"parent_resource_name": "bemybz4ugeso",
"stack_id": _stack_id_VDU1_1
}
},
{
@ -1763,7 +1770,8 @@ _expected_inst_info_change_ext_conn = {
}
],
"metadata": {
"creation_time": "2021-12-10T00:40:46Z"
"creation_time": "2021-12-10T00:40:46Z",
"stack_id": _stack_id
}
}
],
@ -1857,7 +1865,7 @@ class TestOpenstack(base.BaseTestCase):
def test_make_instantiated_vnf_info_new(self):
# prepare
req = objects.InstantiateVnfRequestV2.from_dict(
req = objects.InstantiateVnfRequest.from_dict(
_instantiate_req_example)
inst = objects.VnfInstanceV2(
vimConnectionInfo=req.vimConnectionInfo