Support fault notification for error handling

In the MgmtDriver of fault notification, the error handling operation
is supported.
This patch solves the problem of Tacker double-logging or canceling
alarms and missing alarms on the monitoring server during error
handling.

Implements: blueprint support-autoheal-queue
Change-Id: I658876b8ae8be9a0d7e8660c67e654e6ad7ef5e8
This commit is contained in:
YiFeng
2023-08-07 22:38:38 +09:00
committed by Yi Feng
parent 9e73ea9c84
commit f2d4ae6be5
15 changed files with 2605 additions and 60 deletions

View File

@@ -0,0 +1,9 @@
---
features:
- |
Support error handling for LCM using MgmtDriver in V2 API. We added the
``user_script_err_handling_data`` variable to hold the data that needs
to be preserved when the LCM fails.
At the same time, we updated the MgmtDriver of the FaultNotification
function to support error handling.

View File

@@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
import threading
from oslo_log import log as logging
@@ -99,13 +100,19 @@ class ConductorV2(object):
self.nfvo_client.send_lcmocc_notification(context, lcmocc,
inst, self.endpoint)
def _set_lcmocc_error(self, lcmocc, ex):
def _set_lcmocc_error(
self, lcmocc, ex, user_script_err_handling_data={}):
if isinstance(ex, sol_ex.SolException):
problem_details = ex.make_problem_details()
else:
# program bug. it occurs only under development.
problem_details = {'status': 500,
'detail': str(ex)}
if user_script_err_handling_data:
problem_details[
'userScriptErrHandlingData'] = user_script_err_handling_data
lcmocc.error = objects.ProblemDetails.from_dict(problem_details)
@log.log
@@ -123,6 +130,8 @@ class ConductorV2(object):
inst = inst_utils.get_inst(context, lcmocc.vnfInstanceId)
user_script_err_handling_data = {}
# NOTE: error cannot happen to here basically.
# if an error occurred lcmocc.operationState remains STARTING.
# see the log of the tacker-conductor to investigate the cause
@@ -177,7 +186,8 @@ class ConductorV2(object):
try:
self.vnflcm_driver.process(context, lcmocc, inst, grant_req,
grant, vnfd)
grant, vnfd,
user_script_err_handling_data)
lcmocc.operationState = fields.LcmOperationStateType.COMPLETED
# update inst and lcmocc at the same time
@@ -190,7 +200,7 @@ class ConductorV2(object):
except Exception as ex:
LOG.exception("PROCESSING %s failed", lcmocc.operation)
lcmocc.operationState = fields.LcmOperationStateType.FAILED_TEMP
self._set_lcmocc_error(lcmocc, ex)
self._set_lcmocc_error(lcmocc, ex, user_script_err_handling_data)
lcmocc.update(context)
# send notification COMPLETED or FAILED_TEMP
@@ -212,6 +222,13 @@ class ConductorV2(object):
inst = inst_utils.get_inst(context, lcmocc.vnfInstanceId)
if (lcmocc.obj_attr_is_set('error') and
lcmocc.error.obj_attr_is_set('userScriptErrHandlingData')):
user_script_err_handling_data = copy.deepcopy(
lcmocc.error.userScriptErrHandlingData)
else:
user_script_err_handling_data = {}
lcmocc.operationState = fields.LcmOperationStateType.PROCESSING
lcmocc.update(context)
# send notification PROCESSING
@@ -230,7 +247,8 @@ class ConductorV2(object):
self.vnflcm_driver.post_grant(context, lcmocc, inst, grant_req,
grant, vnfd)
self.vnflcm_driver.process(context, lcmocc, inst, grant_req,
grant, vnfd)
grant, vnfd,
user_script_err_handling_data)
lcmocc.operationState = fields.LcmOperationStateType.COMPLETED
lcmocc.error = None # clear error
@@ -245,7 +263,7 @@ class ConductorV2(object):
except Exception as ex:
LOG.exception("PROCESSING %s failed", lcmocc.operation)
lcmocc.operationState = fields.LcmOperationStateType.FAILED_TEMP
self._set_lcmocc_error(lcmocc, ex)
self._set_lcmocc_error(lcmocc, ex, user_script_err_handling_data)
lcmocc.update(context)
# grant_req and grant are already saved. they are not deleted
# while operationState is FAILED_TEMP.
@@ -269,6 +287,13 @@ class ConductorV2(object):
inst = inst_utils.get_inst(context, lcmocc.vnfInstanceId)
if (lcmocc.obj_attr_is_set('error') and
lcmocc.error.obj_attr_is_set('userScriptErrHandlingData')):
user_script_err_handling_data = copy.deepcopy(
lcmocc.error.userScriptErrHandlingData)
else:
user_script_err_handling_data = {}
lcmocc.operationState = fields.LcmOperationStateType.ROLLING_BACK
lcmocc.update(context)
# send notification ROLLING_BACK
@@ -283,9 +308,13 @@ class ConductorV2(object):
self.vnflcm_driver.post_grant(context, lcmocc, inst, grant_req,
grant, vnfd)
self.vnflcm_driver.rollback(context, lcmocc, inst, grant_req,
grant, vnfd)
grant, vnfd,
user_script_err_handling_data)
lcmocc.operationState = fields.LcmOperationStateType.ROLLED_BACK
if (lcmocc.obj_attr_is_set('error') and
lcmocc.error.obj_attr_is_set('userScriptErrHandlingData')):
del lcmocc.error.userScriptErrHandlingData
with context.session.begin(subtransactions=True):
lcmocc.update(context)
# NOTE: Basically inst is not changed. But there is a case
@@ -302,7 +331,7 @@ class ConductorV2(object):
except Exception as ex:
LOG.exception("ROLLING_BACK %s failed", lcmocc.operation)
lcmocc.operationState = fields.LcmOperationStateType.FAILED_TEMP
self._set_lcmocc_error(lcmocc, ex)
self._set_lcmocc_error(lcmocc, ex, user_script_err_handling_data)
lcmocc.update(context)
# grant_req and grant are already saved. they are not deleted
# while operationState is FAILED_TEMP.
@@ -325,13 +354,17 @@ class ConductorV2(object):
return
inst = inst_utils.get_inst(context, lcmocc.vnfInstanceId)
user_script_err_handling_data = {}
# send notification PROCESSING
self.nfvo_client.send_lcmocc_notification(context, lcmocc, inst,
self.endpoint)
try:
vnfd = self.nfvo_client.get_vnfd(context, inst.vnfdId)
self.vnflcm_driver.process(context, lcmocc, inst, None, None, vnfd)
self.vnflcm_driver.process(context, lcmocc, inst, None, None, vnfd,
user_script_err_handling_data)
lcmocc.operationState = fields.LcmOperationStateType.COMPLETED
# update inst and lcmocc at the same time
with context.session.begin(subtransactions=True):
@@ -340,7 +373,7 @@ class ConductorV2(object):
except Exception as ex:
LOG.exception("PROCESSING %s failed", lcmocc.operation)
lcmocc.operationState = fields.LcmOperationStateType.FAILED_TEMP
self._set_lcmocc_error(lcmocc, ex)
self._set_lcmocc_error(lcmocc, ex, user_script_err_handling_data)
lcmocc.update(context)
# send notification COMPLETED or FAILED_TEMP

View File

@@ -94,7 +94,8 @@ class VnfLcmDriverV2(object):
method(context, lcmocc, inst, grant_req, grant, vnfd)
def _exec_mgmt_driver_script(self, operation, flavour_id, req, inst,
grant_req, grant, vnfd, new_vnfd=None):
grant_req, grant, vnfd,
user_script_err_handling_data, new_vnfd=None):
script = vnfd.get_interface_script(flavour_id, operation)
if script is None:
return
@@ -108,7 +109,8 @@ class VnfLcmDriverV2(object):
if grant_req is not None else None),
'grant_response': (grant.to_dict()
if grant is not None else None),
'tmp_csar_dir': tmp_csar_dir
'tmp_csar_dir': tmp_csar_dir,
'user_script_err_handling_data': user_script_err_handling_data
}
if new_vnfd:
new_csar_dir = new_vnfd.make_tmp_csar_dir()
@@ -122,21 +124,25 @@ class VnfLcmDriverV2(object):
vnfd.remove_tmp_csar_dir(tmp_csar_dir)
try:
output = pickle.loads(out.stdout)
if isinstance(output, dict):
if 'vnf_instance' in output:
_inst = objects.VnfInstanceV2.from_dict(
output['vnf_instance'])
inst.__dict__.update(_inst.__dict__)
if 'user_script_err_handling_data' in output:
user_script_err_handling_data.update(output[
'user_script_err_handling_data'])
except EOFError:
pass
except pickle.UnpicklingError:
pass
if out.returncode != 0:
LOG.debug("execute %s failed: %s", operation, out.stderr)
msg = "{} failed: {}".format(operation, out.stderr)
raise sol_ex.MgmtDriverExecutionFailed(sol_detail=msg)
else:
try:
output = pickle.loads(out.stdout)
if isinstance(output, dict) and 'vnf_instance' in output:
_inst = objects.VnfInstanceV2.from_dict(
output['vnf_instance'])
inst.__dict__.update(_inst.__dict__)
except EOFError:
pass
except pickle.UnpicklingError:
pass
LOG.debug("execute %s of %s success.", operation, script)
@@ -218,7 +224,8 @@ class VnfLcmDriverV2(object):
else:
return operation.lower()
def process(self, context, lcmocc, inst, grant_req, grant, vnfd):
def process(self, context, lcmocc, inst, grant_req, grant, vnfd,
user_script_err_handling_data):
# save inst to use updating lcmocc after process done
inst_saved = inst.obj_clone()
@@ -234,8 +241,9 @@ class VnfLcmDriverV2(object):
# NOTE: This is only the case that operation is MODIFY_INFO
# and vnf instance is never instantiated after creation.
flavour_id = None
self._exec_mgmt_driver_script(operation,
flavour_id, req, inst, grant_req, grant, vnfd)
self._exec_mgmt_driver_script(
operation, flavour_id, req, inst, grant_req, grant, vnfd,
user_script_err_handling_data)
# main process
method = getattr(self, "%s_%s" % (lcmocc.operation.lower(), 'process'))
@@ -249,21 +257,44 @@ class VnfLcmDriverV2(object):
context, req.vnfdId, all_contents=True)
operation = "{}_end".format(self._script_method_name(lcmocc.operation))
self._exec_mgmt_driver_script(
operation, flavour_id, req, inst,
grant_req, grant, vnfd, new_vnfd=new_vnfd)
operation, flavour_id, req, inst, grant_req, grant, vnfd,
user_script_err_handling_data, new_vnfd=new_vnfd)
self._make_inst_info_common(lcmocc, inst_saved, inst, vnfd)
lcmocc_utils.update_lcmocc(lcmocc, inst_saved, inst)
def rollback(self, context, lcmocc, inst, grant_req, grant, vnfd):
def rollback(self, context, lcmocc, inst, grant_req, grant, vnfd,
user_script_err_handling_data):
method = getattr(self,
"%s_%s" % (lcmocc.operation.lower(), 'rollback'),
None)
if method:
method(context, lcmocc, inst, grant_req, grant, vnfd)
else:
if not method:
raise sol_ex.RollbackNotSupported(op=lcmocc.operation)
# perform preamble LCM script
req = lcmocc.operationParams
operation_name = self._script_method_name(lcmocc.operation)
operation = f"{operation_name}_rollback_start"
if lcmocc.operation == v2fields.LcmOperationType.INSTANTIATE:
flavour_id = req.flavourId
elif inst.obj_attr_is_set('instantiatedVnfInfo'):
flavour_id = inst.instantiatedVnfInfo.flavourId
else:
# NOTE: This is only the case that operation is MODIFY_INFO
# and vnf instance is never instantiated after creation.
flavour_id = None
self._exec_mgmt_driver_script(
operation, flavour_id, req, inst, grant_req,
grant, vnfd, user_script_err_handling_data)
method(context, lcmocc, inst, grant_req, grant, vnfd)
# perform postamble LCM script
operation = f"{operation_name}_rollback_end"
self._exec_mgmt_driver_script(
operation, flavour_id, req, inst, grant_req,
grant, vnfd, user_script_err_handling_data)
def _get_link_ports(self, inst_req):
names = set()
if inst_req.obj_attr_is_set('extVirtualLinks'):

View File

@@ -20,12 +20,14 @@ import sys
class SampleScript(object):
def __init__(self, req, inst, grant_req, grant, csar_dir):
def __init__(self, req, inst, grant_req, grant, csar_dir,
user_script_err_handling_data):
self.req = req
self.inst = inst
self.grant_req = grant_req
self.grant = grant
self.csar_dir = csar_dir
self.user_script_err_handling_data = user_script_err_handling_data
def instantiate_start(self):
pass
@@ -49,13 +51,23 @@ def main():
grant_req = script_dict['grant_request']
grant = script_dict['grant_response']
csar_dir = script_dict['tmp_csar_dir']
user_script_err_handling_data = script_dict[
'user_script_err_handling_data']
script = SampleScript(req, inst, grant_req, grant, csar_dir)
script = SampleScript(req, inst, grant_req, grant, csar_dir,
user_script_err_handling_data)
try:
getattr(script, operation)()
except AttributeError:
output_dict = {}
sys.stdout.buffer.write(pickle.dumps(output_dict))
sys.stdout.flush()
raise Exception("{} is not included in the script.".format(operation))
output_dict = {}
sys.stdout.buffer.write(pickle.dumps(output_dict))
sys.stdout.flush()
if __name__ == "__main__":
try:

View File

@@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
import os
import pickle
import sys
@@ -32,20 +33,81 @@ CONF = cfg.CONF
class ServerNotificationMgmtDriver(object):
def __init__(self, req, inst, grant_req, grant, csar_dir):
def __init__(self, req, inst, grant_req, grant, csar_dir,
user_script_err_handling_data):
self.req = req
self.inst = inst
self.grant_req = grant_req
self.grant = grant
self.csar_dir = csar_dir
self.user_script_err_handling_data = user_script_err_handling_data
auth_handle = http_client.NoAuthHandle()
self.client = http_client.HttpClient(auth_handle)
CONF(project='tacker')
rpc.init(CONF)
self.rpc = conductor_rpc_v2.VnfLcmRpcApiV2()
def _merge_recent_register(self):
"""Merge alarms information
This method will compare the information in vnf_instance
and user_script_err_handling_data, merging the state of
user_script_err_handling_data with the state of vnf_instance
to make them consistent.
"""
if self.user_script_err_handling_data.get('serverNotification'):
tmp_data = copy.deepcopy(self.user_script_err_handling_data)
rm_server_id = []
for server_notify in tmp_data.get('serverNotification'):
server_id = server_notify.get('serverId')
res = [res for res in self.inst['instantiatedVnfInfo'][
'vnfcResourceInfo'] if res['computeResource'][
'resourceId'] == server_id]
if res:
alarm_id = server_notify.get('alarmId')
if alarm_id:
# Since there is registered alarm on the
# monitoring server side, add it to vnfcResourceInfo.
if 'metadata' not in res[0]:
res[0]['metadata'] = {}
res[0]['metadata']['server_notification'] = {
"alarmId": alarm_id}
else:
# Since there is no registered alarm on the
# monitoring server side, delete it from
# vnfcResourceInfo.
if res[0]['metadata'].get('server_notification'):
del res[0]['metadata']['server_notification']
else:
# Since the alarm registration remains only on the
# monitoring server side, unregister it.
server_notifier_uri, _, tenant = self.get_params()
if server_notify.get('alarmId'):
self._request_unregister(
server_notifier_uri, tenant, server_id,
server_notify.get('alarmId'))
rm_server_id.append(server_id)
self.user_script_err_handling_data['serverNotification'] = [
data for data in self.user_script_err_handling_data[
'serverNotification']
if data['serverId'] not in rm_server_id
]
def _instantiate_scale_rollback(self):
if self.user_script_err_handling_data.get('serverNotification'):
tmp_data = copy.deepcopy(self.user_script_err_handling_data)
server_notifier_uri, _, tenant = self.get_params()
for server_notify in tmp_data.get('serverNotification'):
server_id = server_notify.get('serverId')
alarm_id = server_notify.get('alarmId')
if server_id and alarm_id:
self._request_unregister(
server_notifier_uri, tenant, server_id, alarm_id)
def make_output_dict(self):
return {'vnf_instance': self.inst}
return {'vnf_instance': self.inst, 'user_script_err_handling_data':
self.user_script_err_handling_data}
def request_remove_timer(self, vnf_instance_id):
self.rpc.server_notification_remove_timer(
@@ -66,6 +128,22 @@ class ServerNotificationMgmtDriver(object):
LOG.debug(
"server_notification unregistration is processed: %d.",
resp.status_code)
if ('serverNotification' not in
self.user_script_err_handling_data):
self.user_script_err_handling_data[
'serverNotification'] = []
rm_target = [data for data in
self.user_script_err_handling_data[
'serverNotification']
if data.get('serverId')
and data['serverId'] == server_id]
if rm_target and rm_target[0].get('alarmId'):
del rm_target[0]['alarmId']
else:
self.user_script_err_handling_data[
'serverNotification'].append({"serverId": server_id})
except sol_ex.SolException as e:
# Even if unregistration is failed for a single alarm_id,
# Unregistration should be done for remaining alarm_ids.
@@ -125,6 +203,20 @@ class ServerNotificationMgmtDriver(object):
LOG.debug(
"server_notification registration is processed: %d. "
"alarm_id: %s", resp.status_code, res_body['alarm_id'])
if 'serverNotification' not in self.user_script_err_handling_data:
self.user_script_err_handling_data['serverNotification'] = []
add_target = [
data for data in self.user_script_err_handling_data[
'serverNotification']
if data.get('serverId') and data['serverId'] == server_id
]
if add_target:
add_target[0]['alarmId'] = res_body['alarm_id']
else:
self.user_script_err_handling_data['serverNotification'].append(
{'serverId': server_id, 'alarmId': res_body['alarm_id']})
return {'alarmId': res_body['alarm_id'], 'serverId': server_id}
def request_register(self):
@@ -147,9 +239,7 @@ class ServerNotificationMgmtDriver(object):
fault_id = None
tenant = None
additional_params = self.req.get('additionalParams', None)
if 'instantiatedVnfInfo' not in self.inst:
return (None, None, None)
vnf_info = self.inst['instantiatedVnfInfo']
vnf_info = self.inst.get('instantiatedVnfInfo')
if (additional_params and
'ServerNotifierUri' in additional_params and
@@ -172,6 +262,7 @@ class ServerNotificationMgmtDriver(object):
return (None, None, None)
def terminate_start(self):
self._merge_recent_register()
at_least_one_id_unregistered = self.request_unregister()
if at_least_one_id_unregistered:
self.request_remove_timer(self.inst['id'])
@@ -184,6 +275,7 @@ class ServerNotificationMgmtDriver(object):
def scale_start(self):
if self.req['type'] != 'SCALE_IN':
return
self._merge_recent_register()
vnfc_res_ids = [res_def['resource']['resourceId']
for res_def in self.grant_req['removeResources']
if res_def.get('type', None) == 'COMPUTE']
@@ -192,6 +284,7 @@ class ServerNotificationMgmtDriver(object):
return self.make_output_dict()
def heal_start(self):
self._merge_recent_register()
isall = ('additionalParams' in self.req and
self.req['additionalParams'].get('all', False) and
'vnfcInstanceId' not in self.req)
@@ -203,6 +296,7 @@ class ServerNotificationMgmtDriver(object):
return self.make_output_dict()
def instantiate_end(self):
self._merge_recent_register()
self.request_register()
server_notifier_uri, fault_id, _ = self.get_params()
vnf_info = self.inst['instantiatedVnfInfo']
@@ -215,6 +309,7 @@ class ServerNotificationMgmtDriver(object):
def scale_end(self):
if self.req['type'] != 'SCALE_OUT':
return
self._merge_recent_register()
self.request_register()
return self.make_output_dict()
@@ -228,6 +323,14 @@ class ServerNotificationMgmtDriver(object):
def terminate_end(self):
pass
def instantiate_rollback_start(self):
self._instantiate_scale_rollback()
return self.make_output_dict()
def scale_rollback_start(self):
self._instantiate_scale_rollback()
return self.make_output_dict()
def main():
script_dict = pickle.load(sys.stdin.buffer)
@@ -238,12 +341,23 @@ def main():
grant_req = script_dict['grant_request']
grant = script_dict['grant_response']
csar_dir = script_dict['tmp_csar_dir']
user_script_err_handling_data = script_dict[
'user_script_err_handling_data']
script = ServerNotificationMgmtDriver(
req, inst, grant_req, grant, csar_dir)
output_dict = getattr(script, operation)()
sys.stdout.buffer.write(pickle.dumps(output_dict))
sys.stdout.flush()
try:
script = ServerNotificationMgmtDriver(
req, inst, grant_req, grant, csar_dir,
user_script_err_handling_data)
output_dict = getattr(script, operation)()
sys.stdout.buffer.write(pickle.dumps(output_dict))
sys.stdout.flush()
except Exception as mgmt_ex:
output_dict = {
"user_script_err_handling_data": user_script_err_handling_data
}
sys.stdout.buffer.write(pickle.dumps(output_dict))
sys.stdout.flush()
raise common_ex.MgmtDriverOtherError(error_message=str(mgmt_ex))
if __name__ == "__main__":

View File

@@ -31,4 +31,7 @@ class ProblemDetails(base.TackerObject, base.TackerObjectDictCompat):
'status': fields.IntegerField(nullable=False),
'detail': fields.StringField(nullable=False),
'instance': fields.UriField(nullable=True),
# NOTE: userScriptErrHandlingData is not defined in SOL003.
# It is original definition of Tacker.
'userScriptErrHandlingData': fields.KeyValuePairsField(nullable=True),
}

File diff suppressed because it is too large Load Diff

View File

@@ -161,6 +161,8 @@ class BaseSolV2Test(base_v2.BaseTackerTestV2):
def get_server_details(self, server_name):
path = "/servers/detail"
resp, resp_body = self.nova_client.do_request(path, "GET")
if server_name is None:
return resp_body.get('servers')
server_details = None
for server in resp_body.get('servers'):

View File

@@ -60,6 +60,10 @@ topology_template:
implementation: sample-script
modify_information_start:
implementation: sample-script
instantiate_rollback_start:
implementation: sample-script
scale_rollback_start:
implementation: sample-script
artifacts:
sample-script:
description: Sample script

View File

@@ -6,6 +6,34 @@ imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
interface_types:
sample.test.Vnflcm:
derived_from: tosca.interfaces.nfv.Vnflcm
instantiate_start:
description: Invoked before instantiate
instantiate_end:
description: Invoked after instantiate
heal_start:
description: Invoked before heal
heal_end:
description: Invoked after heal
scale_start:
description: Invoked before scale
scale_end:
description: Invoked after scale
terminate_start:
description: Invoked before terminate
terminate_end:
description: Invoked after terminate
change_external_connectivity_start:
description: Invoked before change_external_connectivity
modify_information_start:
description: Invoked before modify_information
instantiate_rollback_start:
description: Invoked before instantiate_rollback
scale_rollback_start:
description: Invoked before scale_rollback
node_types:
company.provider.VNF:
derived_from: tosca.nodes.nfv.VNF
@@ -52,4 +80,4 @@ node_types:
capability: tosca.capabilities.nfv.VirtualLinkable
interfaces:
Vnflcm:
type: tosca.interfaces.nfv.Vnflcm
type: sample.test.Vnflcm

View File

@@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
import functools
import os
import pickle
import sys
@@ -32,20 +34,88 @@ CONF = cfg.CONF
class ServerNotificationMgmtDriver(object):
def __init__(self, req, inst, grant_req, grant, csar_dir):
def __init__(self, req, inst, grant_req, grant, csar_dir,
user_script_err_handling_data):
self.req = req
self.inst = inst
self.grant_req = grant_req
self.grant = grant
self.csar_dir = csar_dir
self.user_script_err_handling_data = user_script_err_handling_data
auth_handle = http_client.NoAuthHandle()
self.client = http_client.HttpClient(auth_handle)
CONF(project='tacker')
rpc.init(CONF)
self.rpc = conductor_rpc_v2.VnfLcmRpcApiV2()
def _fail(self, method):
if os.path.exists(f'/tmp/{method}'):
raise Exception(f'test {method} error')
def __getattr__(self, name):
return functools.partial(self._fail, name)
def _merge_recent_register(self):
"""Merge alarms information
This method will compare the information in vnf_instance
and user_script_err_handling_data, merging the state of
user_script_err_handling_data with the state of vnf_instance
to make them consistent.
"""
if self.user_script_err_handling_data.get('serverNotification'):
tmp_data = copy.deepcopy(self.user_script_err_handling_data)
rm_server_id = []
for server_notify in tmp_data.get('serverNotification'):
server_id = server_notify.get('serverId')
res = [res for res in self.inst['instantiatedVnfInfo'][
'vnfcResourceInfo'] if res['computeResource'][
'resourceId'] == server_id]
if res:
alarm_id = server_notify.get('alarmId')
if alarm_id:
# Since there is registered alarm on the
# monitoring server side, add it to vnfcResourceInfo.
if 'metadata' not in res[0]:
res[0]['metadata'] = {}
res[0]['metadata']['server_notification'] = {
"alarmId": alarm_id}
else:
# Since there is no registered alarm on the
# monitoring server side, delete it from
# vnfcResourceInfo.
if res[0]['metadata'].get('server_notification'):
del res[0]['metadata']['server_notification']
else:
# Since the alarm registration remains only on the
# monitoring server side, unregister it.
server_notifier_uri, _, tenant = self.get_params()
if server_notify.get('alarmId'):
self._request_unregister(
server_notifier_uri, tenant, server_id,
server_notify.get('alarmId'))
rm_server_id.append(server_id)
self.user_script_err_handling_data['serverNotification'] = [
data for data in self.user_script_err_handling_data[
'serverNotification']
if data['serverId'] not in rm_server_id
]
def _instantiate_scale_rollback(self):
if self.user_script_err_handling_data.get('serverNotification'):
tmp_data = copy.deepcopy(self.user_script_err_handling_data)
server_notifier_uri, _, tenant = self.get_params()
for server_notify in tmp_data.get('serverNotification'):
server_id = server_notify.get('serverId')
alarm_id = server_notify.get('alarmId')
if server_id and alarm_id:
self._request_unregister(
server_notifier_uri, tenant, server_id, alarm_id)
def make_output_dict(self):
return {'vnf_instance': self.inst}
return {'vnf_instance': self.inst, 'user_script_err_handling_data':
self.user_script_err_handling_data}
def request_remove_timer(self, vnf_instance_id):
self.rpc.server_notification_remove_timer(
@@ -66,6 +136,22 @@ class ServerNotificationMgmtDriver(object):
LOG.debug(
"server_notification unregistration is processed: %d.",
resp.status_code)
if ('serverNotification' not in
self.user_script_err_handling_data):
self.user_script_err_handling_data[
'serverNotification'] = []
rm_target = [data for data in
self.user_script_err_handling_data[
'serverNotification']
if data.get('serverId')
and data['serverId'] == server_id]
if rm_target and rm_target[0].get('alarmId'):
del rm_target[0]['alarmId']
else:
self.user_script_err_handling_data[
'serverNotification'].append({"serverId": server_id})
except sol_ex.SolException as e:
# Even if unregistration is failed for a single alarm_id,
# Unregistration should be done for remaining alarm_ids.
@@ -125,6 +211,20 @@ class ServerNotificationMgmtDriver(object):
LOG.debug(
"server_notification registration is processed: %d. "
"alarm_id: %s", resp.status_code, res_body['alarm_id'])
if 'serverNotification' not in self.user_script_err_handling_data:
self.user_script_err_handling_data['serverNotification'] = []
add_target = [
data for data in self.user_script_err_handling_data[
'serverNotification']
if data.get('serverId') and data['serverId'] == server_id
]
if add_target:
add_target[0]['alarmId'] = res_body['alarm_id']
else:
self.user_script_err_handling_data['serverNotification'].append(
{'serverId': server_id, 'alarmId': res_body['alarm_id']})
return {'alarmId': res_body['alarm_id'], 'serverId': server_id}
def request_register(self):
@@ -147,9 +247,7 @@ class ServerNotificationMgmtDriver(object):
fault_id = None
tenant = None
additional_params = self.req.get('additionalParams', None)
if 'instantiatedVnfInfo' not in self.inst:
return (None, None, None)
vnf_info = self.inst['instantiatedVnfInfo']
vnf_info = self.inst.get('instantiatedVnfInfo')
if (additional_params and
'ServerNotifierUri' in additional_params and
@@ -172,6 +270,7 @@ class ServerNotificationMgmtDriver(object):
return (None, None, None)
def terminate_start(self):
self._merge_recent_register()
at_least_one_id_unregistered = self.request_unregister()
if at_least_one_id_unregistered:
self.request_remove_timer(self.inst['id'])
@@ -179,19 +278,25 @@ class ServerNotificationMgmtDriver(object):
if ('metadata' in self.inst['instantiatedVnfInfo'] and
key in self.inst['instantiatedVnfInfo']['metadata']):
del self.inst['instantiatedVnfInfo']['metadata'][key]
if os.path.exists('/tmp/terminate_start'):
raise Exception('test terminate_start error')
return self.make_output_dict()
def scale_start(self):
if self.req['type'] != 'SCALE_IN':
return
self._merge_recent_register()
vnfc_res_ids = [res_def['resource']['resourceId']
for res_def in self.grant_req['removeResources']
if res_def.get('type', None) == 'COMPUTE']
self.request_unregister(
isall=False, vnfc_res_ids=vnfc_res_ids)
if os.path.exists('/tmp/scale_start'):
raise Exception('test scale_start error')
return self.make_output_dict()
def heal_start(self):
self._merge_recent_register()
isall = ('additionalParams' in self.req and
self.req['additionalParams'].get('all', False) and
'vnfcInstanceId' not in self.req)
@@ -200,9 +305,12 @@ class ServerNotificationMgmtDriver(object):
if res_def.get('type', None) == 'COMPUTE']
self.request_unregister(
isall=isall, vnfc_res_ids=vnfc_res_ids)
if os.path.exists('/tmp/heal_start'):
raise Exception('test heal_start error')
return self.make_output_dict()
def instantiate_end(self):
self._merge_recent_register()
self.request_register()
server_notifier_uri, fault_id, _ = self.get_params()
vnf_info = self.inst['instantiatedVnfInfo']
@@ -210,16 +318,23 @@ class ServerNotificationMgmtDriver(object):
vnf_info['metadata'] = {}
vnf_info['metadata']['ServerNotifierUri'] = server_notifier_uri
vnf_info['metadata']['ServerNotifierFaultID'] = fault_id
if os.path.exists('/tmp/instantiate_end'):
raise Exception('test instantiate_end error')
return self.make_output_dict()
def scale_end(self):
if self.req['type'] != 'SCALE_OUT':
return
self._merge_recent_register()
self.request_register()
if os.path.exists('/tmp/scale_end'):
raise Exception('test scale_end error')
return self.make_output_dict()
def heal_end(self):
self.request_register()
if os.path.exists('/tmp/heal_end'):
raise Exception('test heal_end error')
return self.make_output_dict()
def instantiate_start(self):
@@ -228,6 +343,18 @@ class ServerNotificationMgmtDriver(object):
def terminate_end(self):
pass
def instantiate_rollback_start(self):
self._instantiate_scale_rollback()
if os.path.exists('/tmp/instantiate_rollback_start'):
raise Exception('test instantiate_rollback_start error')
return self.make_output_dict()
def scale_rollback_start(self):
self._instantiate_scale_rollback()
if os.path.exists('/tmp/scale_rollback_start'):
raise Exception('test scale_rollback_start error')
return self.make_output_dict()
def main():
script_dict = pickle.load(sys.stdin.buffer)
@@ -238,12 +365,23 @@ def main():
grant_req = script_dict['grant_request']
grant = script_dict['grant_response']
csar_dir = script_dict['tmp_csar_dir']
user_script_err_handling_data = script_dict[
'user_script_err_handling_data']
script = ServerNotificationMgmtDriver(
req, inst, grant_req, grant, csar_dir)
output_dict = getattr(script, operation)()
sys.stdout.buffer.write(pickle.dumps(output_dict))
sys.stdout.flush()
try:
script = ServerNotificationMgmtDriver(
req, inst, grant_req, grant, csar_dir,
user_script_err_handling_data)
output_dict = getattr(script, operation)()
sys.stdout.buffer.write(pickle.dumps(output_dict))
sys.stdout.flush()
except Exception as mgmt_ex:
output_dict = {
"user_script_err_handling_data": user_script_err_handling_data
}
sys.stdout.buffer.write(pickle.dumps(output_dict))
sys.stdout.flush()
raise common_ex.MgmtDriverOtherError(error_message=str(mgmt_ex))
if __name__ == "__main__":

View File

@@ -312,6 +312,8 @@ class TestConductorV2(db_base.SqlTestCase):
# prepare
lcmocc = self._create_inst_and_lcmocc(
op_state=fields.LcmOperationStateType.FAILED_TEMP)
problem_details = {'userScriptErrHandlingData': {"key": "value"}}
lcmocc.error = objects.ProblemDetails.from_dict(problem_details)
self._create_grant_req_and_grant(lcmocc)
mocked_get_vnfd.return_value = mock.Mock()
ex = sol_ex.StackOperationFailed(sol_detail="unit test",
@@ -337,6 +339,7 @@ class TestConductorV2(db_base.SqlTestCase):
# get lcmocc from DB to be sure lcmocc saved to DB
lcmocc = lcmocc_utils.get_lcmocc(self.context, lcmocc.id)
expected = ex.make_problem_details()
expected['userScriptErrHandlingData'] = {"key": "value"}
self.assertEqual(expected, lcmocc.error.to_dict())
# check grant_req and grant remain
@@ -353,6 +356,8 @@ class TestConductorV2(db_base.SqlTestCase):
# prepare
lcmocc = self._create_inst_and_lcmocc(
op_state=fields.LcmOperationStateType.FAILED_TEMP)
problem_details = {'userScriptErrHandlingData': {"key": "value"}}
lcmocc.error = objects.ProblemDetails.from_dict(problem_details)
self._create_grant_req_and_grant(lcmocc)
mocked_get_vnfd.return_value = mock.Mock()
@@ -386,6 +391,8 @@ class TestConductorV2(db_base.SqlTestCase):
# prepare
lcmocc = self._create_inst_and_lcmocc(
op_state=fields.LcmOperationStateType.FAILED_TEMP)
problem_details = {'userScriptErrHandlingData': {"key": "value"}}
lcmocc.error = objects.ProblemDetails.from_dict(problem_details)
self._create_grant_req_and_grant(lcmocc)
mocked_get_vnfd.return_value = mock.Mock()
ex = sol_ex.StackOperationFailed(sol_detail="unit test",
@@ -412,6 +419,7 @@ class TestConductorV2(db_base.SqlTestCase):
# get lcmocc from DB to be sure lcmocc saved to DB
lcmocc = lcmocc_utils.get_lcmocc(self.context, lcmocc.id)
expected = ex.make_problem_details()
expected['userScriptErrHandlingData'] = {"key": "value"}
self.assertEqual(expected, lcmocc.error.to_dict())
# check grant_req and grant remain

View File

@@ -15,6 +15,8 @@
import copy
from datetime import datetime
import os
import pickle
import subprocess
from unittest import mock
from kubernetes import client
@@ -2264,7 +2266,7 @@ class TestVnfLcmDriverV2(base.BaseTestCase):
isCancelPending=False,
operationParams=req_inst)
self.driver.process(
self.context, lcmocc, inst, grant_req, grant, self.vnfd_1)
self.context, lcmocc, inst, grant_req, grant, self.vnfd_1, {})
# other operation
inst = objects.VnfInstanceV2(
@@ -2293,7 +2295,7 @@ class TestVnfLcmDriverV2(base.BaseTestCase):
operationParams=objects.TerminateVnfRequest(
terminationType='FORCEFUL'))
self.driver.process(
self.context, lcmocc, inst, grant_req, grant, self.vnfd_1)
self.context, lcmocc, inst, grant_req, grant, self.vnfd_1, {})
# no flavour_id
inst = objects.VnfInstanceV2(
@@ -2319,10 +2321,11 @@ class TestVnfLcmDriverV2(base.BaseTestCase):
operationParams=objects.VnfInfoModificationRequest()
)
self.driver.process(
self.context, lcmocc, inst, grant_req, grant, self.vnfd_1)
self.context, lcmocc, inst, grant_req, grant, self.vnfd_1, {})
@mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'scale_rollback')
@mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'instantiate_rollback')
def test_rollback(self, mock_inst_rollback):
def test_rollback(self, mock_inst_rollback, mock_scale_rollback):
# instantiate
req_inst = objects.InstantiateVnfRequest.from_dict(
_inst_req_example)
@@ -2353,7 +2356,7 @@ class TestVnfLcmDriverV2(base.BaseTestCase):
isCancelPending=False,
operationParams=req_inst)
self.driver.rollback(
self.context, lcmocc, inst, grant_req, grant, self.vnfd_1)
self.context, lcmocc, inst, grant_req, grant, self.vnfd_1, {})
# no rollback
lcmocc = objects.VnfLcmOpOccV2(
@@ -2370,7 +2373,74 @@ class TestVnfLcmDriverV2(base.BaseTestCase):
terminationType='FORCEFUL'))
self.assertRaises(
sol_ex.RollbackNotSupported, self.driver.rollback,
self.context, lcmocc, inst, grant_req, grant, self.vnfd_1)
self.context, lcmocc, inst, grant_req, grant, self.vnfd_1, {})
# mgmt rollback
req_scale = objects.ScaleVnfRequest(
type='SCALE_OUT', aspectId='aspect_id', numberOfSteps=1)
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.SCALE,
isAutomaticInvocation=False,
isCancelPending=False,
operationParams=req_scale)
self.driver.rollback(
self.context, lcmocc, inst, grant_req, grant, self.vnfd_1, {})
inst.instantiatedVnfInfo = objects.VnfInstanceV2_InstantiatedVnfInfo(
flavourId='flavour_id')
self.driver.rollback(
self.context, lcmocc, inst, grant_req, grant, self.vnfd_1, {})
@mock.patch.object(pickle, 'loads')
@mock.patch.object(vnfd_utils.Vnfd, 'remove_tmp_csar_dir')
@mock.patch.object(subprocess, 'run')
@mock.patch.object(os.path, 'join')
@mock.patch.object(vnfd_utils.Vnfd, 'make_tmp_csar_dir')
@mock.patch.object(vnfd_utils.Vnfd, 'get_interface_script')
def test_exec_mgmt_driver_script_eof_error(
self, mock_script, mock_dir, mock_script_path, mock_out,
mock_remove, mock_output):
# instantiate
req_inst = objects.InstantiateVnfRequest.from_dict(
_inst_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='NOT_INSTANTIATED',
vimConnectionInfo=req_inst.vimConnectionInfo
)
grant_req = objects.GrantRequestV1(
operation=fields.LcmOperationType.INSTANTIATE
)
grant = objects.GrantV1()
mock_script.return_value = 'sample_script.py'
mock_dir.return_value = 'path'
mock_script_path.return_value = '../mgmt_drivers/sample_script.py'
mock_output.return_value = {
'vnf_instance': {
'id': 'id', 'vnfdId': 'vnfdId', 'vnfProvider': 'vnfProvider',
'vnfProductName': 'vnfProductName',
'vnfSoftwareVersion': 'vnfSoftwareVersion',
'vnfdVersion': 'vnfdVersion',
'instantiationState': 'INSTANTIATED'},
'user_script_err_handling_data': {'key': 'value'}}
user_script_err_handling_data = {}
self.assertRaises(
sol_ex.MgmtDriverExecutionFailed,
self.driver._exec_mgmt_driver_script, 'INSTANTIATE', 'flavour_id',
req_inst, inst, grant_req, grant, self.vnfd_1,
user_script_err_handling_data)
def test_instantiate_grant_error(self):
# instantiate

View File

@@ -216,3 +216,103 @@ def fake_replica_set(
name, image, conf_key, conf_name, sec_key, sec_name)
)
)
fault_notif_inst_example = {
"id": "c80f7afa-65f3-4be6-94ae-fdf438ac2d61",
"vnfInstanceName": "FaultNotification",
"vnfdId": "16ca1a07-2453-47f1-9f00-7ca2dce0a5ea",
"vnfProvider": "Company",
"vnfProductName": "Sample VNF",
"vnfSoftwareVersion": "1.0",
"vnfdVersion": "1.0",
"vimConnectionInfo": {
"vim1": {
"vimType": "ETSINFV.OPENSTACK_KEYSTONE.V_3",
"vimId": 'vim id',
"interfaceInfo": {"endpoint": "http://localhost/identity/v3"},
"accessInfo": {
"username": "nfv_user",
"region": "RegionOne",
"password": "devstack",
"project": "nfv",
"projectDomain": "Default",
"userDomain": "Default"
}
}
},
"instantiationState": "INSTANTIATED",
"instantiatedVnfInfo": {
"flavourId": "simple",
"vnfState": "STARTED",
"vnfcResourceInfo": [{
"id": "daemonset-vdu5-wh824",
"vduId": "VDU5",
"computeResource": {
"resourceId": "s0",
"vimLevelResourceType": "DaemonSet"
},
"metadata": {
"server_notification": {
"alarmId": "xxx"
}
}
}, {
"id": "deployment2-vdu6-6f8c5c5ddb-9ptn9",
"vduId": "VDU6",
"computeResource": {
"resourceId": "s1",
"vimLevelResourceType": "Deployment"
}
}, {
"id": "env-test",
"vduId": "VDU3",
"computeResource": {
"resourceId": "s2",
"vimLevelResourceType": "Pod"
},
"metadata": {
"server_notification": {
"alarmId": "a2"
}
}
}, {
"id": "env-test2",
"vduId": "VDU7",
"computeResource": {
"resourceId": "env-test2",
"vimLevelResourceType": "Pod"
},
"metadata": {}
}],
"metadata": {
"ServerNotifierUri": "http://192.168.10.147:55555",
"ServerNotifierFaultID": ["1234"]
}
}
}
fault_notif_inst_req_example = {
"flavourId": "simple",
"instantiationLevelId": "instantiation_level_2",
"additionalParams": {
"ServerNotifierUri": "http://server_notifier:1234",
"ServerNotifierFaultID": ["1234"]
},
"vimConnectionInfo": {
"vim1": {
"vimType": "ETSINFV.OPENSTACK_KEYSTONE.V_3",
"vimId": 'vim id',
"interfaceInfo": {"endpoint": "http://localhost/identity/v3"},
"accessInfo": {
"username": "nfv_user",
"region": "RegionOne",
"password": "devstack",
"project": "nfv",
"projectDomain": "Default",
"userDomain": "Default"
}
}
}
}

View File

@@ -0,0 +1,596 @@
# Copyright (C) 2023 Fujitsu
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
import pickle
import sys
import webob
from tacker.common import exceptions
from tacker.common import rpc
from tacker.sol_refactored.common import http_client
from tacker.sol_refactored.common import vnfd_utils
from tacker.sol_refactored.conductor import conductor_rpc_v2
from tacker.sol_refactored.mgmt_drivers import (
server_notification as mgmt_driver)
from tacker.sol_refactored import objects
from tacker.tests.unit import base
from tacker.tests.unit.sol_refactored.mgmt_drivers import fakes
from unittest import mock
SAMPLE_VNFD_ID = "16ca1a07-2453-47f1-9f00-7ca2dce0a5ea"
class TestServerNotification(base.TestCase):
def setUp(self):
super(TestServerNotification, self).setUp()
objects.register_all()
self.vnfd = vnfd_utils.Vnfd(SAMPLE_VNFD_ID)
self.inst = copy.deepcopy(fakes.fault_notif_inst_example)
self.csar_dir = None
@mock.patch.object(http_client.HttpClient, 'do_request')
@mock.patch.object(rpc, 'init')
@mock.patch.object(mgmt_driver, 'CONF')
@mock.patch.object(sys.stdout.buffer, 'write')
@mock.patch.object(pickle, 'load')
def test_instantiate_rollback_main(
self, mock_script, mock_write, mock_conf, mock_init, mock_resp):
user_script_err_handling_data = {
'serverNotification': [
{'serverId': 's0', 'alarmId': 'a0'}]}
req = objects.InstantiateVnfRequest.from_dict(
fakes.fault_notif_inst_req_example)
mock_script.return_value = {
"operation": "instantiate_rollback_start",
"request": req,
"vnf_instance": self.inst,
"grant_request": None,
"grant_response": None,
"tmp_csar_dir": self.csar_dir,
"user_script_err_handling_data": user_script_err_handling_data
}
resp = webob.Response()
resp.status_code = 202
mock_resp.return_value = resp, {}
mgmt_driver.main()
err_handling_data_expected = {
'serverNotification': [{'serverId': 's0'}]}
self.assertEqual(
err_handling_data_expected, user_script_err_handling_data)
@mock.patch.object(http_client.HttpClient, 'do_request')
@mock.patch.object(rpc, 'init')
@mock.patch.object(mgmt_driver, 'CONF')
def test_instantiate_end(
self, mock_conf, mock_init, mock_resp):
user_script_err_handling_data = {
'serverNotification': [
{'serverId': 's0', 'alarmId': 'a0'}]}
resp = webob.Response()
resp.status_code = 202
mock_resp.return_value = resp, {'alarm_id': 'alarm_id'}
del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][2]
del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][1]
req = objects.InstantiateVnfRequest.from_dict(
fakes.fault_notif_inst_req_example)
server_notification = mgmt_driver.ServerNotificationMgmtDriver(
req, self.inst, None, None, self.csar_dir,
user_script_err_handling_data)
server_notification.instantiate_end()
err_handling_data_expected = {
'serverNotification': [
{'serverId': 's0', 'alarmId': 'a0'},
{'serverId': 'env-test2', 'alarmId': 'alarm_id'}]}
self.assertEqual(
err_handling_data_expected, user_script_err_handling_data)
self.assertEqual(
{'alarmId': 'a0'}, self.inst['instantiatedVnfInfo'][
'vnfcResourceInfo'][0]['metadata']['server_notification'])
self.assertEqual(
{'alarmId': 'alarm_id'},
self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][1][
'metadata']['server_notification'])
@mock.patch.object(http_client.HttpClient, 'do_request')
@mock.patch.object(rpc, 'init')
@mock.patch.object(mgmt_driver, 'CONF')
def test_merge_recent_register(
self, mock_conf, mock_init, mock_resp):
user_script_err_handling_data = {
'serverNotification': [
{'serverId': 's0', 'alarmId': 'a0'},
{'serverId': 's1', 'alarmId': 'a1'},
{'serverId': 's2'}, {'serverId': 's3', 'alarmId': 'a3'}]}
resp = webob.Response()
resp.status_code = 202
mock_resp.return_value = resp, {'alarm_id': 'alarm_id'}
req = objects.InstantiateVnfRequest.from_dict(
fakes.fault_notif_inst_req_example)
server_notification = mgmt_driver.ServerNotificationMgmtDriver(
req, self.inst, None, None, self.csar_dir,
user_script_err_handling_data)
server_notification._merge_recent_register()
err_handling_data_expected = {
'serverNotification': [
{'serverId': 's0', 'alarmId': 'a0'},
{'serverId': 's1', 'alarmId': 'a1'},
{'serverId': 's2'}]}
self.assertEqual(
err_handling_data_expected, user_script_err_handling_data)
self.assertEqual(
{'alarmId': 'a0'}, self.inst['instantiatedVnfInfo'][
'vnfcResourceInfo'][0]['metadata']['server_notification'])
self.assertEqual(
{'alarmId': 'a1'}, self.inst['instantiatedVnfInfo'][
'vnfcResourceInfo'][1]['metadata']['server_notification'])
self.assertEqual(
None, self.inst['instantiatedVnfInfo'][
'vnfcResourceInfo'][2]['metadata'].get('server_notification'))
@mock.patch.object(http_client.HttpClient, 'do_request')
@mock.patch.object(rpc, 'init')
@mock.patch.object(mgmt_driver, 'CONF')
def test_heal_start(
self, mock_conf, mock_init, mock_resp):
user_script_err_handling_data = {
'serverNotification': [
{'serverId': 's0', 'alarmId': 'a0'}]}
resp = webob.Response()
resp.status_code = 202
mock_resp.return_value = resp, {'alarm_id': 'alarm_id'}
del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][3]
del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][2]
del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][1]
req = objects.HealVnfRequest(additionalParams={"all": True})
grant_req = objects.GrantRequestV1(
removeResources=[objects.ResourceDefinitionV1(
type='COMPUTE', resourceTemplateId='VDU1',
resource=objects.ResourceHandle(
resourceId="s0"))])
grant = objects.GrantV1()
server_notification = mgmt_driver.ServerNotificationMgmtDriver(
req, self.inst, grant_req, grant, self.csar_dir,
user_script_err_handling_data)
server_notification.heal_start()
err_handling_data_expected = {
'serverNotification': [
{'serverId': 's0'}]}
self.assertEqual(
err_handling_data_expected, user_script_err_handling_data)
self.assertEqual(
None, self.inst['instantiatedVnfInfo'][
'vnfcResourceInfo'][0]['metadata'].get('server_notification'))
@mock.patch.object(http_client.HttpClient, 'do_request')
@mock.patch.object(rpc, 'init')
@mock.patch.object(mgmt_driver, 'CONF')
def test_heal_end(
self, mock_conf, mock_init, mock_resp):
user_script_err_handling_data = {
'serverNotification': [
{'serverId': 's1', 'alarmId': 'a1'}]}
resp = webob.Response()
resp.status_code = 202
mock_resp.return_value = resp, {'alarm_id': 'alarm_id'}
del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][3]
del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][2]
del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][0]
req = objects.HealVnfRequest(additionalParams={"all": True})
grant_req = objects.GrantRequestV1(
removeResources=[objects.ResourceDefinitionV1(
type='COMPUTE', resourceTemplateId='VDU1',
resource=objects.ResourceHandle(
resourceId="s1"))])
grant = objects.GrantV1()
server_notification = mgmt_driver.ServerNotificationMgmtDriver(
req, self.inst, grant_req, grant, self.csar_dir,
user_script_err_handling_data)
server_notification.heal_end()
err_handling_data_expected = {
'serverNotification': [
{'serverId': 's1', 'alarmId': 'alarm_id'}]}
self.assertEqual(
err_handling_data_expected, user_script_err_handling_data)
self.assertEqual(
{'alarmId': 'alarm_id'}, self.inst['instantiatedVnfInfo'][
'vnfcResourceInfo'][0]['metadata'].get('server_notification'))
@mock.patch.object(http_client.HttpClient, 'do_request')
@mock.patch.object(rpc, 'init')
@mock.patch.object(mgmt_driver, 'CONF')
def test_scale_start(
self, mock_conf, mock_init, mock_resp):
user_script_err_handling_data = {
'serverNotification': [
{'serverId': 's0', 'alarmId': 'a0'}]}
resp = webob.Response()
resp.status_code = 202
mock_resp.return_value = resp, {'alarm_id': 'alarm_id'}
del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][3]
del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][2]
del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][1]
req = objects.ScaleVnfRequest(
type='SCALE_IN', aspectId='aspect_id', numberOfSteps=1)
grant_req = objects.GrantRequestV1(
removeResources=[objects.ResourceDefinitionV1(
type='COMPUTE', resourceTemplateId='VDU1',
resource=objects.ResourceHandle(
resourceId="s0"))])
grant = objects.GrantV1()
server_notification = mgmt_driver.ServerNotificationMgmtDriver(
req, self.inst, grant_req, grant, self.csar_dir,
user_script_err_handling_data)
server_notification.scale_start()
err_handling_data_expected = {
'serverNotification': [
{'serverId': 's0'}]}
self.assertEqual(
err_handling_data_expected, user_script_err_handling_data)
self.assertEqual(
None, self.inst['instantiatedVnfInfo'][
'vnfcResourceInfo'][0]['metadata'].get('server_notification'))
@mock.patch.object(http_client.HttpClient, 'do_request')
@mock.patch.object(rpc, 'init')
@mock.patch.object(mgmt_driver, 'CONF')
def test_scale_end(
self, mock_conf, mock_init, mock_resp):
user_script_err_handling_data = {
'serverNotification': [
{'serverId': 's0', 'alarmId': 'a0'}]}
resp = webob.Response()
resp.status_code = 202
mock_resp.return_value = resp, {'alarm_id': 'alarm_id'}
del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][3]
del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][2]
del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][1]
req = objects.ScaleVnfRequest(
type='SCALE_OUT', aspectId='aspect_id', numberOfSteps=1)
grant_req = objects.GrantRequestV1(
removeResources=[objects.ResourceDefinitionV1(
type='COMPUTE', resourceTemplateId='VDU1',
resource=objects.ResourceHandle(
resourceId="s0"))])
grant = objects.GrantV1()
server_notification = mgmt_driver.ServerNotificationMgmtDriver(
req, self.inst, grant_req, grant, self.csar_dir,
user_script_err_handling_data)
server_notification.scale_end()
err_handling_data_expected = {
'serverNotification': [
{'serverId': 's0', 'alarmId': 'a0'}]}
self.assertEqual(
err_handling_data_expected, user_script_err_handling_data)
self.assertEqual(
{'alarmId': 'a0'}, self.inst['instantiatedVnfInfo'][
'vnfcResourceInfo'][0]['metadata'].get('server_notification'))
@mock.patch.object(conductor_rpc_v2.VnfLcmRpcApiV2,
'server_notification_remove_timer')
@mock.patch.object(http_client.HttpClient, 'do_request')
@mock.patch.object(rpc, 'init')
@mock.patch.object(mgmt_driver, 'CONF')
def test_terminate_start(
self, mock_conf, mock_init, mock_resp, mock_timer):
user_script_err_handling_data = {
'serverNotification': [
{'serverId': 's0', 'alarmId': 'a0'}]}
resp = webob.Response()
resp.status_code = 202
mock_resp.return_value = resp, {'alarm_id': 'alarm_id'}
del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][3]
del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][2]
del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][1]
req = objects.ScaleVnfRequest(
type='SCALE_OUT', aspectId='aspect_id', numberOfSteps=1)
grant_req = objects.GrantRequestV1(
removeResources=[objects.ResourceDefinitionV1(
type='COMPUTE', resourceTemplateId='VDU1',
resource=objects.ResourceHandle(
resourceId="s0"))])
grant = objects.GrantV1()
server_notification = mgmt_driver.ServerNotificationMgmtDriver(
req, self.inst, grant_req, grant, self.csar_dir,
user_script_err_handling_data)
server_notification.terminate_start()
err_handling_data_expected = {
'serverNotification': [
{'serverId': 's0'}]}
self.assertEqual(
err_handling_data_expected, user_script_err_handling_data)
self.assertEqual(
None, self.inst['instantiatedVnfInfo'][
'vnfcResourceInfo'][0]['metadata'].get('server_notification'))
@mock.patch.object(http_client.HttpClient, 'do_request')
@mock.patch.object(rpc, 'init')
@mock.patch.object(mgmt_driver, 'CONF')
def test_scale_rollback_start(
self, mock_conf, mock_init, mock_resp):
user_script_err_handling_data = {
'serverNotification': [
{'serverId': 's0', 'alarmId': 'a0'}]}
resp = webob.Response()
resp.status_code = 202
mock_resp.return_value = resp, {'alarm_id': 'alarm_id'}
del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][3]
del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][2]
del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][1]
req = objects.ScaleVnfRequest(
type='SCALE_OUT', aspectId='aspect_id', numberOfSteps=1)
grant_req = objects.GrantRequestV1(
removeResources=[objects.ResourceDefinitionV1(
type='COMPUTE', resourceTemplateId='VDU1',
resource=objects.ResourceHandle(
resourceId="s0"))])
grant = objects.GrantV1()
server_notification = mgmt_driver.ServerNotificationMgmtDriver(
req, self.inst, grant_req, grant, self.csar_dir,
user_script_err_handling_data)
server_notification.scale_rollback_start()
err_handling_data_expected = {
'serverNotification': [
{'serverId': 's0'}]}
self.assertEqual(
err_handling_data_expected, user_script_err_handling_data)
self.assertEqual(
{'alarmId': 'xxx'}, self.inst['instantiatedVnfInfo'][
'vnfcResourceInfo'][0]['metadata'].get('server_notification'))
@mock.patch.object(sys.stdout.buffer, 'write')
@mock.patch.object(http_client.HttpClient, 'do_request')
@mock.patch.object(rpc, 'init')
@mock.patch.object(mgmt_driver, 'CONF')
@mock.patch.object(pickle, 'load')
def test_heal_end_main_error(
self, mock_script, mock_conf, mock_init, mock_resp, mock_write):
user_script_err_handling_data = None
resp = webob.Response()
resp.status_code = 400
mock_resp.return_value = resp, None
req = objects.HealVnfRequest(additionalParams={"all": True})
grant_req = objects.GrantRequestV1(
removeResources=[objects.ResourceDefinitionV1(
type='COMPUTE', resourceTemplateId='VDU1',
resource=objects.ResourceHandle(
resourceId="s1"))])
grant = objects.GrantV1()
mock_script.return_value = {
"operation": "heal_end",
"request": req,
"vnf_instance": self.inst,
"grant_request": grant_req,
"grant_response": grant,
"tmp_csar_dir": self.csar_dir,
"user_script_err_handling_data": user_script_err_handling_data
}
self.assertRaises(
exceptions.MgmtDriverOtherError, mgmt_driver.main)
@mock.patch.object(sys.stdout.buffer, 'write')
@mock.patch.object(rpc, 'init')
@mock.patch.object(mgmt_driver, 'CONF')
@mock.patch.object(pickle, 'load')
def test_instantiate_start(
self, mock_script, mock_conf, mock_init, mock_write):
user_script_err_handling_data = None
mock_script.return_value = {
"operation": "instantiate_start",
"request": None,
"vnf_instance": self.inst,
"grant_request": None,
"grant_response": None,
"tmp_csar_dir": self.csar_dir,
"user_script_err_handling_data": user_script_err_handling_data
}
mgmt_driver.main()
self.assertEqual(None, user_script_err_handling_data)
@mock.patch.object(sys.stdout.buffer, 'write')
@mock.patch.object(rpc, 'init')
@mock.patch.object(mgmt_driver, 'CONF')
@mock.patch.object(pickle, 'load')
def test_terminate_end(
self, mock_script, mock_conf, mock_init, mock_write):
user_script_err_handling_data = None
mock_script.return_value = {
"operation": "terminate_end",
"request": None,
"vnf_instance": self.inst,
"grant_request": None,
"grant_response": None,
"tmp_csar_dir": self.csar_dir,
"user_script_err_handling_data": user_script_err_handling_data
}
mgmt_driver.main()
self.assertEqual(None, user_script_err_handling_data)
@mock.patch.object(sys.stdout.buffer, 'write')
@mock.patch.object(rpc, 'init')
@mock.patch.object(mgmt_driver, 'CONF')
@mock.patch.object(pickle, 'load')
def test_scale_end_type(
self, mock_script, mock_conf, mock_init, mock_write):
user_script_err_handling_data = None
req = objects.ScaleVnfRequest(
type='SCALE_IN', aspectId='aspect_id', numberOfSteps=1)
mock_script.return_value = {
"operation": "scale_end",
"request": req,
"vnf_instance": self.inst,
"grant_request": None,
"grant_response": None,
"tmp_csar_dir": self.csar_dir,
"user_script_err_handling_data": user_script_err_handling_data
}
mgmt_driver.main()
self.assertEqual(None, user_script_err_handling_data)
@mock.patch.object(sys.stdout.buffer, 'write')
@mock.patch.object(rpc, 'init')
@mock.patch.object(mgmt_driver, 'CONF')
@mock.patch.object(pickle, 'load')
def test_scale_start_type(
self, mock_script, mock_conf, mock_init, mock_write):
user_script_err_handling_data = None
req = objects.ScaleVnfRequest(
type='SCALE_OUT', aspectId='aspect_id', numberOfSteps=1)
mock_script.return_value = {
"operation": "scale_start",
"request": req,
"vnf_instance": self.inst,
"grant_request": None,
"grant_response": None,
"tmp_csar_dir": self.csar_dir,
"user_script_err_handling_data": user_script_err_handling_data
}
mgmt_driver.main()
self.assertEqual(None, user_script_err_handling_data)
@mock.patch.object(http_client.HttpClient, 'do_request')
@mock.patch.object(rpc, 'init')
@mock.patch.object(mgmt_driver, 'CONF')
def test_instantiate_end_no_metadata(
self, mock_conf, mock_init, mock_resp):
user_script_err_handling_data = {
'serverNotification': [
{'serverId': 's0', 'alarmId': 'a0'}]}
resp = webob.Response()
resp.status_code = 202
mock_resp.return_value = resp, {'alarm_id': 'alarm_id'}
del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][2]
del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][1]
del self.inst['instantiatedVnfInfo']['metadata']
req = objects.InstantiateVnfRequest.from_dict(
fakes.fault_notif_inst_req_example)
server_notification = mgmt_driver.ServerNotificationMgmtDriver(
req, self.inst, None, None, self.csar_dir,
user_script_err_handling_data)
server_notification.instantiate_end()
err_handling_data_expected = {
'serverNotification': [
{'serverId': 's0', 'alarmId': 'a0'},
{'serverId': 'env-test2', 'alarmId': 'alarm_id'}]}
self.assertEqual(
err_handling_data_expected, user_script_err_handling_data)
self.assertEqual(
{'alarmId': 'a0'}, self.inst['instantiatedVnfInfo'][
'vnfcResourceInfo'][0]['metadata']['server_notification'])
self.assertEqual(
{'alarmId': 'alarm_id'},
self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][1][
'metadata']['server_notification'])
@mock.patch.object(rpc, 'init')
@mock.patch.object(mgmt_driver, 'CONF')
def test_get_params_none(self, mock_conf, mock_init):
user_script_err_handling_data = {
'serverNotification': [
{'serverId': 's0', 'alarmId': 'a0'},
{'serverId': 's1', 'alarmId': 'a1'},
{'serverId': 's2'}, {'serverId': 's3', 'alarmId': 'a3'}]}
req = objects.InstantiateVnfRequest.from_dict(
fakes.fault_notif_inst_req_example)
server_notification = mgmt_driver.ServerNotificationMgmtDriver(
req, self.inst, None, None, self.csar_dir,
user_script_err_handling_data)
del self.inst['vimConnectionInfo']
result1 = server_notification.get_params()
self.assertEqual((None, None, None), result1)
del self.inst['instantiatedVnfInfo']
result2 = server_notification.get_params()
self.assertEqual((None, None, None), result2)
@mock.patch.object(http_client.HttpClient, 'do_request')
@mock.patch.object(rpc, 'init')
@mock.patch.object(mgmt_driver, 'CONF')
def test_request_register_no_server_notification(
self, mock_conf, mock_init, mock_resp):
vnfc_resource = {'computeResource': {'resourceId': 's0'}}
user_script_err_handling_data = {}
req = objects.InstantiateVnfRequest.from_dict(
fakes.fault_notif_inst_req_example)
resp = webob.Response()
resp.status_code = 202
mock_resp.return_value = resp, {'alarm_id': 'alarm_id'}
server_notification = mgmt_driver.ServerNotificationMgmtDriver(
req, self.inst, None, None, self.csar_dir,
user_script_err_handling_data)
server_notification._request_register(vnfc_resource)
err_handling_data_expected = {
'serverNotification': [
{'serverId': 's0', 'alarmId': 'alarm_id'}]}
self.assertEqual(
err_handling_data_expected, user_script_err_handling_data)
@mock.patch.object(mgmt_driver.ServerNotificationMgmtDriver,
'_request_unregister')
@mock.patch.object(mgmt_driver.ServerNotificationMgmtDriver, 'get_params')
@mock.patch.object(rpc, 'init')
@mock.patch.object(mgmt_driver, 'CONF')
def test_request_register_cancel(
self, mock_conf, mock_init, mock_params, mock_unregister):
rsc_list = [{
'computeResource': {'resourceId': 's0'},
'metadata': {'server_notification': {'alarmId': 'a0'}}}]
mock_params.return_value = (None, None, None)
user_script_err_handling_data = {}
req = objects.InstantiateVnfRequest.from_dict(
fakes.fault_notif_inst_req_example)
server_notification = mgmt_driver.ServerNotificationMgmtDriver(
req, self.inst, None, None, self.csar_dir,
user_script_err_handling_data)
server_notification.request_register_cancel(rsc_list)
self.assertEqual(1, mock_unregister.call_count)
@mock.patch.object(http_client.HttpClient, 'do_request')
@mock.patch.object(rpc, 'init')
@mock.patch.object(mgmt_driver, 'CONF')
def test_request_unregister_400(
self, mock_conf, mock_init, mock_resp):
user_script_err_handling_data = {}
req = objects.InstantiateVnfRequest.from_dict(
fakes.fault_notif_inst_req_example)
resp = webob.Response()
resp.status_code = 400
mock_resp.return_value = resp, {'alarm_id': 'alarm_id'}
server_notification = mgmt_driver.ServerNotificationMgmtDriver(
req, self.inst, None, None, self.csar_dir,
user_script_err_handling_data)
server_notification._request_unregister('', '', 's0', '')
self.assertEqual(1, mock_resp.call_count)
@mock.patch.object(http_client.HttpClient, 'do_request')
@mock.patch.object(rpc, 'init')
@mock.patch.object(mgmt_driver, 'CONF')
def test_request_unregister_no_server_notification(
self, mock_conf, mock_init, mock_resp):
user_script_err_handling_data = {}
req = objects.InstantiateVnfRequest.from_dict(
fakes.fault_notif_inst_req_example)
resp = webob.Response()
resp.status_code = 200
mock_resp.return_value = resp, {'alarm_id': 'alarm_id'}
server_notification = mgmt_driver.ServerNotificationMgmtDriver(
req, self.inst, None, None, self.csar_dir,
user_script_err_handling_data)
server_notification._request_unregister('', '', 's0', '')
self.assertEqual(
{'serverNotification': [{'serverId': 's0'}]},
user_script_err_handling_data)