Support for marking VNF Lifecycle as Failed
This feature will enable the client to use an API to mark a VNF Lifecycle Management Operation occurrence as "finally failed". It is based on the ETSI NFV specification where "FAILED" is "finally failed". Once the operation is marked as "finally failed",it can no longer be retried or rolled back anymore. - As defined in SOL003, operation status can change from FAILED_TEMP to FAILED. - When VNFM receive this API , VNFM send Notification(POST) to requested node. - Regarding support of Fail Response parameter, will provide with separated patch. Implements: blueprint support-error-handling Spec: https://specs.openstack.org/openstack/tacker-specs/specs/wallaby/support-error-handling-based-on-ETSI-NFV.html Change-Id: Ieb221c6e2dd4ed6c873123325efb95f09af4a135
This commit is contained in:
parent
7e8a7351fa
commit
c4db781ebc
|
@ -273,6 +273,21 @@ cause:
|
|||
in: body
|
||||
required: false
|
||||
type: string
|
||||
changed_ext_connectivity:
|
||||
description: |
|
||||
Information about changed external connectivity,
|
||||
if applicable
|
||||
in: body
|
||||
required: false
|
||||
type: object
|
||||
changed_ext_connectivity_id:
|
||||
description: |
|
||||
Identifier of the external VL and the related external VL
|
||||
information instance. The identifier is assigned by the
|
||||
NFV-MANO entity that manages this VL instance.
|
||||
in: body
|
||||
required: false
|
||||
type: string
|
||||
changed_info:
|
||||
description: |
|
||||
Information about the changed VNF instance information,
|
||||
|
@ -662,6 +677,15 @@ graceful_termination_timeout:
|
|||
in: body
|
||||
required: false
|
||||
type: string
|
||||
grant_id:
|
||||
description: |
|
||||
Identifier of the grant related to this VNF LCM operation
|
||||
occurrence. Shall be set to the value of the "id" attribute in
|
||||
the "Grant" representing the associated "Individual Grant",
|
||||
if such grant exists.
|
||||
in: body
|
||||
required: false
|
||||
type: string
|
||||
instantiated_vnf_info:
|
||||
description: |
|
||||
Information specific to an instantiated VNF instance. This attribute shall
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"operation":"INSTANTIATE",
|
||||
"stateEnteredTime":"2021-01-22 13:41:30.869833+00:00",
|
||||
"id":"22962087-0494-484f-85e9-ef855e702633",
|
||||
"isCancelPending":false,
|
||||
"vnfInstanceId":"a9d0fe86-a3d0-4cbf-a117-578ff182fd7e",
|
||||
"startTime":"2021-01-22 13:41:03+00:00",
|
||||
"isAutomaticInvocation":false,
|
||||
"operationState":"FAILED",
|
||||
"operationParams":"{\"flavourId\": \"default\", \"instantiationLevelId\": \"n-msc-min\", \"additionalParams\": {\"lcm-operation-user-data\": \"./UserData/lcm_user_data.py\", \"lcm-operation-user-data-class\": \"SampleUserData\", \"sbc_name\": \"sbc\", \"slb_name\": \"slb\", \"image\": \"cirros-0.4.0-x86_64-disk\"}}",
|
||||
"error":{
|
||||
"status":500,
|
||||
"detail":"MANUAL ERROR"
|
||||
},
|
||||
"_links":{
|
||||
"self":{
|
||||
"href":"https://sample1.com/vnflcm/v1/vnf_lcm_op_occs/22962087-0494-484f-85e9-ef855e702633"
|
||||
},
|
||||
"vnfInstance":{
|
||||
"href":"https://sample1.com/vnflcm/v1/vnf_instances/a9d0fe86-a3d0-4cbf-a117-578ff182fd7e"
|
||||
},
|
||||
"retry":{
|
||||
"href":"https://sample1.com/vnflcm/v1/vnf_lcm_op_occs/22962087-0494-484f-85e9-ef855e702633/retry"
|
||||
},
|
||||
"rollback":{
|
||||
"href":"https://sample1.com/vnflcm/v1/vnf_lcm_op_occs/22962087-0494-484f-85e9-ef855e702633/rollback"
|
||||
},
|
||||
"fail":{
|
||||
"href":"https://sample1.com/vnflcm/v1/vnf_lcm_op_occs/22962087-0494-484f-85e9-ef855e702633/fail"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -810,6 +810,127 @@ Request Parameters
|
|||
|
||||
- vnfLcmOpOccId: vnf_lcm_op_occ_id
|
||||
|
||||
Fail a VNF lifecycle operation
|
||||
===================================
|
||||
|
||||
.. rest_method:: POST /vnflcm/v1/vnf_lcm_op_occs/{vnfLcmOpOccId}/fail
|
||||
|
||||
The POST method marks a VNF lifecycle management operation occurrence as "finally failed" if that operation
|
||||
occurrence is in "FAILED_TEMP" state
|
||||
|
||||
In case of success, the "operationState" attribute in the representation of the parent resource shall be changed to
|
||||
"FAILED" and the applicable "result" notification shall be emitted to indicate that the execution of the underlying
|
||||
VNF LCM operation occurrence has finally and unrecoverably failed.
|
||||
|
||||
Response Codes
|
||||
--------------
|
||||
|
||||
.. rest_status_code:: success status.yaml
|
||||
|
||||
- 200
|
||||
|
||||
.. rest_status_code:: error status.yaml
|
||||
|
||||
- 401
|
||||
- 403
|
||||
- 404
|
||||
- 409
|
||||
|
||||
Request Parameters
|
||||
------------------
|
||||
|
||||
.. rest_parameters:: parameters_vnflcm.yaml
|
||||
|
||||
- vnfLcmOpOccId: vnf_lcm_op_occ_id
|
||||
|
||||
Response Parameters
|
||||
-------------------
|
||||
|
||||
.. rest_parameters:: parameters_vnflcm.yaml
|
||||
|
||||
- id: vnf_lcm_op_occ_id_response
|
||||
- operationState: operation_state
|
||||
- stateEnteredTime: state_entered_time
|
||||
- startTime: start_time
|
||||
- vnfInstanceId: vnf_lcm_vnf_instance_id
|
||||
- grantId: grant_id
|
||||
- operation: operation
|
||||
- isAutomaticInvocation: is_automatic_invocation
|
||||
- operationParams: operation_params
|
||||
- error: error
|
||||
- title: error_title
|
||||
- status: error_status
|
||||
- detail: error_detail
|
||||
- isCancelPending: is_cancel_pending
|
||||
- _links: vnf_instance_links
|
||||
- resourceChanges: resource_changes
|
||||
- affectedVnfcs: affected_vnfcs
|
||||
- id: affected_vnfcs_id
|
||||
- vduId: affected_vnfcs_vdu_id
|
||||
- changeType: affected_vnfcs_change_type
|
||||
- computeResource: vnfc_resource_info_compute_resource
|
||||
- vimConnectionId: vim_connection_id
|
||||
- resourceId: resource_handle_resource_id
|
||||
- vimLevelResourceType: resource_handle_vim_level_resource_type
|
||||
- affectedVnfcCpIds: affected_vnfc_cp_ids
|
||||
- addedStorageResourceIds: added_storage_resource_ids
|
||||
- removedStorageResourceIds: removed_storage_resource_ids
|
||||
- affectedVirtualLinks: affected_virtual_links
|
||||
- id: affected_virtual_links_id
|
||||
- vnfVirtualLinkDescId: vnf_virtual_link_resource_info_vnf_virtual_link_desc_id
|
||||
- changeType: affected_virtual_links_change_type
|
||||
- networkResource: vnf_virtual_link_resource_info_network_resource
|
||||
- vimConnectionId: vim_connection_id
|
||||
- resourceId: resource_handle_resource_id
|
||||
- vimLevelResourceType: resource_handle_vim_level_resource_type
|
||||
- affectedVirtualStorages: affected_virtual_storages
|
||||
- id: affected_virtual_storages_id
|
||||
- virtualStorageDescId: affected_virtual_storages_virtual_storage_desc_id
|
||||
- changeType: affected_virtual_storages_change_type
|
||||
- storageResource: virtual_storage_resource_info_storage_resource
|
||||
- vimConnectionId: vim_connection_id
|
||||
- resourceId: resource_handle_resource_id
|
||||
- vimLevelResourceType: resource_handle_vim_level_resource_type
|
||||
- changedInfo: changed_info
|
||||
- vnfInstanceName: changed_info_vnf_instance_name
|
||||
- vnfInstanceDescription: changed_info_vnf_instance_description
|
||||
- metadata: changed_info_metadata
|
||||
- vimConnectionInfo: changed_info_vim_connection_info
|
||||
- id: vim_connection_info_id
|
||||
- vimId: vim_connection_info_vim_id
|
||||
- vimType: vim_connection_info_vim_type
|
||||
- interfaceInfo: vim_connection_info_interface_info
|
||||
- endpoint: vim_connection_info_interface_info_endpoint
|
||||
- accessInfo: vim_connection_info_access_info
|
||||
- username: vim_connection_info_access_info_username
|
||||
- region: vim_connection_info_access_info_region
|
||||
- password: vim_connection_info_access_info_password
|
||||
- tenant: vim_connection_info_access_info_tenant
|
||||
- vnfPkgId: changed_info_vnf_pkg_id
|
||||
- vnfdId: changed_info_vnfd_id
|
||||
- vnfProvider: changed_info_vnf_provider
|
||||
- vnfProductName: changed_info_vnf_product_name
|
||||
- vnfSotwareVersion: changed_info_vnf_sotware_version
|
||||
- vnfdVersion: changed_info_vnfd_version
|
||||
- changedExtConnectivity: changed_ext_connectivity
|
||||
- id: changed_ext_connectivity_id
|
||||
- resourceHandle: resource_handle
|
||||
- vimConnectionId: vim_connection_id
|
||||
- resourceId: resource_handle_resource_id
|
||||
- vimLevelResourceType: resource_handle_vim_level_resource_type
|
||||
- extLinkPorts: ext_link_ports
|
||||
- id: ext_link_port_id
|
||||
- resourceHandle: resource_handle
|
||||
- vimConnectionId: vim_connection_id
|
||||
- vimLevelResourceType: resource_handle_vim_level_resource_type
|
||||
- cpInstanceId: cp_instance_id
|
||||
|
||||
Response Example
|
||||
----------------
|
||||
|
||||
.. literalinclude:: samples/vnflcm/fail-vnf-instance-response.json
|
||||
:language: javascript
|
||||
|
||||
Create a new subscription
|
||||
=========================
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
import requests
|
||||
import tacker.conf
|
||||
|
@ -258,47 +259,66 @@ class VnfLcmController(wsgi.Controller):
|
|||
% vim_id
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
def _notification_process(self, context, vnf_instance,
|
||||
lcm_operation, request, body, is_auto=False):
|
||||
vnf_lcm_op_occs_id = uuidutils.generate_uuid()
|
||||
error_point = 0
|
||||
operation_params = jsonutils.dumps(body)
|
||||
try:
|
||||
# call create lcm op occs here
|
||||
LOG.debug('Create LCM OP OCCS')
|
||||
vnf_lcm_op_occs = objects.VnfLcmOpOcc(
|
||||
context=context,
|
||||
id=vnf_lcm_op_occs_id,
|
||||
operation_state=fields.LcmOccsOperationState.STARTING,
|
||||
start_time=timeutils.utcnow(),
|
||||
state_entered_time=timeutils.utcnow(),
|
||||
vnf_instance_id=vnf_instance.id,
|
||||
is_cancel_pending=is_auto,
|
||||
operation=lcm_operation,
|
||||
is_automatic_invocation=is_auto,
|
||||
operation_params=operation_params,
|
||||
error_point=error_point)
|
||||
vnf_lcm_op_occs.create()
|
||||
except Exception:
|
||||
msg = _("Failed to create LCM occurrence")
|
||||
raise webob.exc.HTTPInternalServerError(explanation=msg)
|
||||
def _notification_process(
|
||||
self, context, vnf_instance, lcm_operation, request, body,
|
||||
vnf_lcm_op_occs=None,
|
||||
operation_state=fields.LcmOccsOperationState.STARTING,
|
||||
notification_status=fields.LcmOccsNotificationStatus.START,
|
||||
affected_resources=None, is_auto=False):
|
||||
LOG.debug('START NOTIFICATION PROCESS')
|
||||
vnf_url = self._get_vnf_instance_href(vnf_instance)
|
||||
|
||||
vnf_lcm_url = self._get_vnf_lcm_op_occs_href(vnf_lcm_op_occs_id)
|
||||
notification = {
|
||||
'notificationType':
|
||||
fields.LcmOccsNotificationType.VNF_OP_OCC_NOTIFICATION,
|
||||
'notificationStatus': fields.LcmOccsNotificationStatus.START,
|
||||
'operationState': fields.LcmOccsOperationState.STARTING,
|
||||
'notificationStatus': notification_status,
|
||||
'operationState': operation_state,
|
||||
'vnfInstanceId': vnf_instance.id,
|
||||
'operation': lcm_operation,
|
||||
'isAutomaticInvocation': is_auto,
|
||||
'vnfLcmOpOccId': vnf_lcm_op_occs_id,
|
||||
'_links': {
|
||||
'vnfInstance': {
|
||||
'href': self._get_vnf_instance_href(vnf_instance)},
|
||||
'vnfLcmOpOcc': {
|
||||
'href': vnf_lcm_url}}}
|
||||
'href': vnf_url},
|
||||
'vnfLcmOpOcc': {}}}
|
||||
|
||||
if operation_state is fields.LcmOccsOperationState.FAILED:
|
||||
vnf_lcm_op_occs_id = vnf_lcm_op_occs.id
|
||||
|
||||
notification['affectedVnfcs'] = affected_resources.get(
|
||||
'affectedVnfcs', [])
|
||||
notification['affectedVirtualLinks'] = affected_resources.get(
|
||||
'affectedVirtualLinks', [])
|
||||
notification['affectedVirtualStorages'] = affected_resources.get(
|
||||
'affectedVirtualStorages', [])
|
||||
notification['error'] = str(vnf_lcm_op_occs.error)
|
||||
|
||||
else:
|
||||
vnf_lcm_op_occs_id = uuidutils.generate_uuid()
|
||||
error_point = 0
|
||||
operation_params = jsonutils.dumps(body)
|
||||
try:
|
||||
# call create lcm op occs here
|
||||
LOG.debug('Create LCM OP OCCS')
|
||||
vnf_lcm_op_occs = objects.VnfLcmOpOcc(
|
||||
context=context,
|
||||
id=vnf_lcm_op_occs_id,
|
||||
operation_state=operation_state,
|
||||
start_time=timeutils.utcnow(),
|
||||
state_entered_time=timeutils.utcnow(),
|
||||
vnf_instance_id=vnf_instance.id,
|
||||
is_cancel_pending=is_auto,
|
||||
operation=lcm_operation,
|
||||
is_automatic_invocation=is_auto,
|
||||
operation_params=operation_params,
|
||||
error_point=error_point)
|
||||
vnf_lcm_op_occs.create()
|
||||
except Exception:
|
||||
msg = _("Failed to create LCM occurrence")
|
||||
raise webob.exc.HTTPInternalServerError(explanation=msg)
|
||||
|
||||
vnf_lcm_url = self._get_vnf_lcm_op_occs_href(vnf_lcm_op_occs_id)
|
||||
notification['vnfLcmOpOccId'] = vnf_lcm_op_occs_id
|
||||
notification['_links']['vnfLcmOpOcc']['href'] = vnf_lcm_url
|
||||
# call send notification
|
||||
try:
|
||||
self.rpc_api.send_notification(context, notification)
|
||||
|
@ -1235,6 +1255,101 @@ class VnfLcmController(wsgi.Controller):
|
|||
return self._make_problem_detail(
|
||||
str(e), 500, title='Internal Server Error')
|
||||
|
||||
# TODO(esto-aln): For adding it to make it consistent
|
||||
# We change vnf status here. In near future, we plan to
|
||||
# delete this method.
|
||||
def _update_vnf_fail_status(self, context, vnf_instance_id,
|
||||
new_status):
|
||||
self._vnfm_plugin.update_vnf_fail_status(
|
||||
context, vnf_instance_id, new_status)
|
||||
|
||||
@wsgi.response(http_client.OK)
|
||||
@wsgi.expected_errors((http_client.BAD_REQUEST, http_client.FORBIDDEN,
|
||||
http_client.NOT_FOUND))
|
||||
def fail(self, request, id):
|
||||
context = request.environ['tacker.context']
|
||||
context.can(vnf_lcm_policies.VNFLCM % 'fail')
|
||||
|
||||
try:
|
||||
vnf_lcm_op_occs = objects.VnfLcmOpOcc.get_by_id(context, id)
|
||||
operation = vnf_lcm_op_occs.operation
|
||||
|
||||
if vnf_lcm_op_occs.operation_state != 'FAILED_TEMP':
|
||||
return self._make_problem_detail(
|
||||
'State is not FAILED_TEMP', 409, title='Conflict')
|
||||
|
||||
vnf_instance_id = vnf_lcm_op_occs.vnf_instance_id
|
||||
vnf_instance = self._get_vnf_instance(context, vnf_instance_id)
|
||||
except webob.exc.HTTPNotFound as e:
|
||||
return self._make_problem_detail(
|
||||
str(e), 404, title='VNF NOT FOUND')
|
||||
except exceptions.NotFound as e:
|
||||
return self._make_problem_detail(
|
||||
str(e), 404, title='VNF LCM NOT FOUND')
|
||||
except Exception as e:
|
||||
LOG.error(traceback.format_exc())
|
||||
return self._make_problem_detail(
|
||||
str(e), 500, title='Internal Server Error')
|
||||
|
||||
try:
|
||||
old_vnf_instance = copy.deepcopy(vnf_instance)
|
||||
|
||||
vnf_lcm_op_occs.operation_state = "FAILED"
|
||||
vnf_lcm_op_occs.state_entered_time = \
|
||||
datetime.datetime.utcnow().isoformat()
|
||||
vnf_lcm_op_occs.updated_at = vnf_lcm_op_occs.state_entered_time
|
||||
|
||||
error_details = objects.ProblemDetails(
|
||||
context=context,
|
||||
status=500,
|
||||
detail=str(vnf_lcm_op_occs.error)
|
||||
)
|
||||
vnf_lcm_op_occs.error = error_details
|
||||
|
||||
# TODO(esto-aln): For adding it to make it consistent
|
||||
# We change vnf status here. In near future, we plan to
|
||||
# delete this branch.
|
||||
if vnf_instance.instantiation_state == \
|
||||
fields.VnfInstanceState.INSTANTIATED:
|
||||
new_status = constants.ACTIVE
|
||||
else:
|
||||
new_status = constants.INACTIVE
|
||||
|
||||
self._update_vnf_fail_status(context, vnf_instance.id,
|
||||
new_status)
|
||||
vnf_instance.task_state = None
|
||||
vnf_instance.save()
|
||||
|
||||
affected_resources = vnflcm_utils._get_affected_resources(
|
||||
old_vnf_instance=old_vnf_instance,
|
||||
new_vnf_instance=vnf_instance)
|
||||
resource_change_obj = jsonutils.dumps(
|
||||
utils.convert_camelcase_to_snakecase(affected_resources))
|
||||
changed_resource = objects.ResourceChanges.obj_from_primitive(
|
||||
resource_change_obj, context)
|
||||
vnf_lcm_op_occs.resource_changes = changed_resource
|
||||
vnf_lcm_op_occs.save()
|
||||
except Exception as ex:
|
||||
error_msg = "Error in VNF Fail for vnf {} because {}".format(
|
||||
vnf_instance.id, encodeutils.exception_to_unicode(ex))
|
||||
LOG.error(error_msg)
|
||||
raise exceptions.TackerException(message=error_msg)
|
||||
|
||||
return self._fail(context, vnf_instance, vnf_lcm_op_occs,
|
||||
operation, affected_resources)
|
||||
|
||||
def _fail(self, context, vnf_instance, vnf_lcm_op_occs,
|
||||
operation, affected_resources):
|
||||
|
||||
self._notification_process(
|
||||
context, vnf_instance, operation, {}, {},
|
||||
vnf_lcm_op_occs=vnf_lcm_op_occs,
|
||||
operation_state=fields.LcmOccsOperationState.FAILED,
|
||||
notification_status=fields.LcmOccsNotificationStatus.RESULT,
|
||||
affected_resources=affected_resources)
|
||||
|
||||
return self._view_builder.show_lcm_op_occs(vnf_lcm_op_occs)
|
||||
|
||||
def _make_problem_detail(
|
||||
self,
|
||||
detail,
|
||||
|
|
|
@ -119,6 +119,12 @@ class VnflcmAPIRouter(wsgi.Router):
|
|||
"/vnf_lcm_op_occs/{id}/rollback",
|
||||
methods, controller, default_resource)
|
||||
|
||||
# {apiRoot}/vnflcm/v1/vnf_lcm_op_occs/{vnfLcmOpOccId}/fail resource
|
||||
methods = {"POST": "fail"}
|
||||
self._setup_route(mapper,
|
||||
"/vnf_lcm_op_occs/{id}/fail",
|
||||
methods, controller, default_resource)
|
||||
|
||||
methods = {"GET": "subscription_list", "POST": "register_subscription"}
|
||||
self._setup_route(mapper, "/subscriptions",
|
||||
methods, controller, default_resource)
|
||||
|
|
|
@ -1388,7 +1388,8 @@ class Conductor(manager.Manager):
|
|||
operation_state)
|
||||
vnf_notif = self._get_vnf_notify(context, vnf_lcm_op_occs_id)
|
||||
vnf_notif.operation_state = operation_state
|
||||
if operation_state == fields.LcmOccsOperationState.FAILED_TEMP:
|
||||
if operation_state == fields.LcmOccsOperationState.FAILED_TEMP or \
|
||||
operation_state == fields.LcmOccsOperationState.FAILED:
|
||||
vnf_notif.error_point = error_point
|
||||
error_details = objects.ProblemDetails(
|
||||
context=context,
|
||||
|
@ -1426,7 +1427,8 @@ class Conductor(manager.Manager):
|
|||
% vnf_lcm_op_occs_id}}}
|
||||
|
||||
if(operation_state == fields.LcmOccsOperationState.COMPLETED or
|
||||
operation_state == fields.LcmOccsOperationState.FAILED_TEMP):
|
||||
operation_state == fields.LcmOccsOperationState.FAILED_TEMP or
|
||||
operation_state == fields.LcmOccsOperationState.FAILED):
|
||||
affected_resources = vnflcm_utils._get_affected_resources(
|
||||
old_vnf_instance=old_vnf_instance,
|
||||
new_vnf_instance=vnf_instance)
|
||||
|
@ -1447,7 +1449,10 @@ class Conductor(manager.Manager):
|
|||
notification_data['notificationStatus'] = \
|
||||
fields.LcmOccsNotificationStatus.RESULT
|
||||
|
||||
if operation_state == fields.LcmOccsOperationState.FAILED_TEMP:
|
||||
if operation_state == \
|
||||
fields.LcmOccsOperationState.FAILED_TEMP \
|
||||
or operation_state == \
|
||||
fields.LcmOccsOperationState.FAILED:
|
||||
notification_data['error'] = error
|
||||
|
||||
# send notification
|
||||
|
|
|
@ -522,6 +522,14 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
|
|||
raise vnfm.VNFInUse(vnf_id=vnf_id)
|
||||
return True
|
||||
|
||||
def update_vnf_fail_status(self,
|
||||
context,
|
||||
vnf_id,
|
||||
status):
|
||||
with context.session.begin(subtransactions=True):
|
||||
self._update_vnf_status_db(
|
||||
context, vnf_id, ['ERROR'], status)
|
||||
|
||||
def _update_vnf_scaling_status(self,
|
||||
context,
|
||||
policy,
|
||||
|
|
|
@ -223,8 +223,10 @@ class LcmOccsOperationState(BaseTackerEnum):
|
|||
PROCESSING = 'PROCESSING'
|
||||
COMPLETED = 'COMPLETED'
|
||||
FAILED_TEMP = 'FAILED_TEMP'
|
||||
FAILED = 'FAILED'
|
||||
|
||||
ALL = (STARTING, PROCESSING, COMPLETED, FAILED_TEMP)
|
||||
ALL = (STARTING, PROCESSING, COMPLETED,
|
||||
FAILED_TEMP, FAILED)
|
||||
|
||||
|
||||
class LcmOccsOperationType(BaseTackerEnum):
|
||||
|
|
|
@ -143,6 +143,17 @@ rules = [
|
|||
}
|
||||
]
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name=VNFLCM % 'fail',
|
||||
check_str=base.RULE_ADMIN_OR_OWNER,
|
||||
description="Fail a VNF instance.",
|
||||
operations=[
|
||||
{
|
||||
'method': 'POST',
|
||||
'path': '/vnflcm/v1/vnf_lcm_op_occs/{vnfLcmOpOccId}/fail'
|
||||
}
|
||||
]
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -460,6 +460,15 @@ class BaseVnfLcmTest(base.BaseTackerTest):
|
|||
|
||||
return resp, response_body
|
||||
|
||||
def _fail_op_occs(self, vnf_lcm_op_occs_id):
|
||||
fail_url = os.path.join(
|
||||
self.base_vnf_lcm_op_occs_url,
|
||||
vnf_lcm_op_occs_id, 'fail')
|
||||
resp, response_body = self.http_client.do_request(
|
||||
fail_url, "POST")
|
||||
|
||||
return resp, response_body
|
||||
|
||||
def _show_op_occs(self, vnf_lcm_op_occs_id):
|
||||
show_url = os.path.join(
|
||||
self.base_vnf_lcm_op_occs_url,
|
||||
|
@ -505,6 +514,44 @@ class BaseVnfLcmTest(base.BaseTackerTest):
|
|||
|
||||
return target_stakcs[0]
|
||||
|
||||
def _delete_heat_stack(self, stack_id):
|
||||
self.h_client.stacks.delete(stack_id)
|
||||
|
||||
def _wait_until_stack_ready(self, stack_id, expected_status):
|
||||
start_time = time.time()
|
||||
callback_url = os.path.join(
|
||||
MOCK_NOTIFY_CALLBACK_URL,
|
||||
self._testMethodName)
|
||||
|
||||
while True:
|
||||
stack = self.h_client.stacks.get(stack_id)
|
||||
actual_status = stack.stack_status
|
||||
print(
|
||||
("Wait:callback_url=<%s>, " +
|
||||
"wait_status=<%s> ") %
|
||||
(callback_url, actual_status),
|
||||
flush=True)
|
||||
|
||||
if actual_status == expected_status:
|
||||
return None
|
||||
|
||||
if time.time() - start_time > VNF_LCM_DONE_TIMEOUT:
|
||||
if actual_status:
|
||||
error = (
|
||||
"LCM incomplete timeout, " +
|
||||
" stack %(stack_id)s" +
|
||||
" is %(actual)s," +
|
||||
"expected status should be %(expected)s")
|
||||
self.fail(
|
||||
error % {
|
||||
"stack_id": stack_id,
|
||||
"expected": expected_status,
|
||||
"actual": actual_status})
|
||||
else:
|
||||
self.fail("LCM incomplete timeout")
|
||||
|
||||
time.sleep(RETRY_WAIT_TIME)
|
||||
|
||||
def _get_heat_resource_list(self, stack_id, nested_depth=0):
|
||||
try:
|
||||
resources = self.h_client.resources.list(
|
||||
|
@ -960,6 +1007,22 @@ class BaseVnfLcmTest(base.BaseTackerTest):
|
|||
'VnfLcmOperationOccurrenceNotification',
|
||||
'ROLLED_BACK')
|
||||
|
||||
def assert_fail_vnf(self, resp, vnf_instance_id):
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
# FT-checkpoint: Notification
|
||||
callback_url = os.path.join(
|
||||
MOCK_NOTIFY_CALLBACK_URL,
|
||||
self._testMethodName)
|
||||
notify_mock_responses = self._filter_notify_history(callback_url,
|
||||
vnf_instance_id)
|
||||
|
||||
self.assertEqual(1, len(notify_mock_responses))
|
||||
self.assert_notification_mock_response(
|
||||
notify_mock_responses[0],
|
||||
'VnfLcmOperationOccurrenceNotification',
|
||||
'FAILED')
|
||||
|
||||
def assert_update_vnf(
|
||||
self,
|
||||
resp,
|
||||
|
|
|
@ -16,6 +16,7 @@ from oslo_utils import uuidutils
|
|||
from tacker.objects import fields
|
||||
from tacker.tests.functional.sol.vnflcm import base as vnflcm_base
|
||||
from tacker.tests.functional.sol.vnflcm import fake_vnflcm
|
||||
from tacker.vnfm.infra_drivers.openstack import constants as infra_cnst
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
|
@ -977,6 +978,220 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest):
|
|||
resp, response_body = self._delete_subscription(subscription_id)
|
||||
self.assertEqual(204, resp.status_code)
|
||||
|
||||
def test_fail_instantiate(self):
|
||||
"""Test fail operation for instantiation.
|
||||
|
||||
In this test case, we do following steps.
|
||||
- Create subscription.
|
||||
- Create VNF package.
|
||||
- Upload VNF package.
|
||||
- Create VNF instance.
|
||||
- Instantiate VNF(Will fail).
|
||||
- Get vnflcmOpOccId to fail.
|
||||
- Fail instantiation operation.
|
||||
- Get opOccs information.
|
||||
- Delete subscription.
|
||||
"""
|
||||
# Create subscription and register it.
|
||||
request_body = fake_vnflcm.Subscription.make_create_request_body(
|
||||
'http://localhost:{}{}'.format(
|
||||
vnflcm_base.FAKE_SERVER_MANAGER.SERVER_PORT,
|
||||
os.path.join(vnflcm_base.MOCK_NOTIFY_CALLBACK_URL,
|
||||
self._testMethodName)))
|
||||
resp, response_body = self._register_subscription(request_body)
|
||||
self.assertEqual(201, resp.status_code)
|
||||
self.assert_http_header_location_for_subscription(resp.headers)
|
||||
subscription_id = response_body.get('id')
|
||||
self.addCleanup(self._delete_subscription, subscription_id)
|
||||
|
||||
# Pre Setting: Create vnf package.
|
||||
sample_name = 'functional3'
|
||||
csar_package_path = os.path.abspath(
|
||||
os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"../../../etc/samples/etsi/nfv",
|
||||
sample_name))
|
||||
tempname, _ = vnflcm_base._create_csar_with_unique_vnfd_id(
|
||||
csar_package_path)
|
||||
# upload vnf package
|
||||
vnf_package_id, vnfd_id = vnflcm_base._create_and_upload_vnf_package(
|
||||
self.tacker_client, user_defined_data={
|
||||
"key": sample_name}, temp_csar_path=tempname)
|
||||
|
||||
# Post Setting: Reserve deleting vnf package.
|
||||
self.addCleanup(
|
||||
vnflcm_base._delete_vnf_package,
|
||||
self.tacker_client,
|
||||
vnf_package_id)
|
||||
|
||||
# Create vnf instance
|
||||
resp, vnf_instance = self._create_vnf_instance_from_body(
|
||||
fake_vnflcm.VnfInstances.make_create_request_body(vnfd_id))
|
||||
vnf_instance_id = vnf_instance['id']
|
||||
self._wait_lcm_done(vnf_instance_id=vnf_instance_id)
|
||||
self.assert_create_vnf(resp, vnf_instance, vnf_package_id)
|
||||
self.addCleanup(self._delete_vnf_instance, vnf_instance_id)
|
||||
|
||||
# Failed instantiate VNF
|
||||
request_body = fake_vnflcm.VnfInstances.make_inst_request_body(
|
||||
self.vim['tenant_id'], self.ext_networks, self.ext_mngd_networks,
|
||||
self.ext_link_ports, self.ext_subnets)
|
||||
resp, _ = self._instantiate_vnf_instance(vnf_instance_id, request_body)
|
||||
self._wait_lcm_done('FAILED_TEMP', vnf_instance_id=vnf_instance_id)
|
||||
|
||||
callback_url = os.path.join(
|
||||
vnflcm_base.MOCK_NOTIFY_CALLBACK_URL,
|
||||
self._testMethodName)
|
||||
notify_mock_responses = vnflcm_base.FAKE_SERVER_MANAGER.get_history(
|
||||
callback_url)
|
||||
vnflcm_base.FAKE_SERVER_MANAGER.clear_history(
|
||||
callback_url)
|
||||
|
||||
# get vnflcm_op_occ_id
|
||||
vnflcm_op_occ_id = notify_mock_responses[0].request_body.get(
|
||||
'vnfLcmOpOccId')
|
||||
self.assertIsNotNone(vnflcm_op_occ_id)
|
||||
|
||||
# fail
|
||||
resp, _ = self._fail_op_occs(vnflcm_op_occ_id)
|
||||
self._wait_lcm_done('FAILED', vnf_instance_id=vnf_instance_id)
|
||||
self.assert_fail_vnf(resp, vnf_instance_id)
|
||||
|
||||
# occ-show
|
||||
resp, op_occs_info = self._show_op_occs(vnflcm_op_occ_id)
|
||||
self._assert_occ_show(resp, op_occs_info)
|
||||
|
||||
# Delete Stack
|
||||
stack = self._get_heat_stack(vnf_instance_id)
|
||||
self._delete_heat_stack(stack.id)
|
||||
self._wait_until_stack_ready(
|
||||
stack.id, infra_cnst.STACK_DELETE_COMPLETE)
|
||||
|
||||
# Delete VNF
|
||||
resp, _ = self._delete_vnf_instance(vnf_instance_id)
|
||||
self._wait_lcm_done(vnf_instance_id=vnf_instance_id)
|
||||
self.assert_delete_vnf(resp, vnf_instance_id, vnf_package_id)
|
||||
|
||||
# Subscription delete
|
||||
resp, response_body = self._delete_subscription(subscription_id)
|
||||
self.assertEqual(204, resp.status_code)
|
||||
|
||||
def test_fail_scale_out(self):
|
||||
"""Test fail operation for Scale-Out operation.
|
||||
|
||||
In this test case, we do following steps.
|
||||
- Create subscription.
|
||||
- Create VNF package.
|
||||
- Upload VNF package.
|
||||
- Create VNF instance.
|
||||
- Instantiate VNF.
|
||||
- Scale-Out(Will fail).
|
||||
- Get vnfcmOpOccId to fail.
|
||||
- Fail Scale-Out operation.
|
||||
- Get opOccs information.
|
||||
- Terminate VNF.
|
||||
- Delete subscription.
|
||||
"""
|
||||
# Create subscription and register it.
|
||||
request_body = fake_vnflcm.Subscription.make_create_request_body(
|
||||
'http://localhost:{}{}'.format(
|
||||
vnflcm_base.FAKE_SERVER_MANAGER.SERVER_PORT,
|
||||
os.path.join(vnflcm_base.MOCK_NOTIFY_CALLBACK_URL,
|
||||
self._testMethodName)))
|
||||
resp, response_body = self._register_subscription(request_body)
|
||||
self.assertEqual(201, resp.status_code)
|
||||
self.assert_http_header_location_for_subscription(resp.headers)
|
||||
subscription_id = response_body.get('id')
|
||||
self.addCleanup(self._delete_subscription, subscription_id)
|
||||
|
||||
# Pre Setting: Create vnf package.
|
||||
sample_name = 'functional4'
|
||||
csar_package_path = os.path.abspath(
|
||||
os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"../../../etc/samples/etsi/nfv",
|
||||
sample_name))
|
||||
tempname, _ = vnflcm_base._create_csar_with_unique_vnfd_id(
|
||||
csar_package_path)
|
||||
# upload vnf package
|
||||
vnf_package_id, vnfd_id = vnflcm_base._create_and_upload_vnf_package(
|
||||
self.tacker_client, user_defined_data={
|
||||
"key": sample_name}, temp_csar_path=tempname)
|
||||
|
||||
# Post Setting: Reserve deleting vnf package.
|
||||
self.addCleanup(
|
||||
vnflcm_base._delete_vnf_package,
|
||||
self.tacker_client,
|
||||
vnf_package_id)
|
||||
|
||||
# Create vnf instance
|
||||
resp, vnf_instance = self._create_vnf_instance_from_body(
|
||||
fake_vnflcm.VnfInstances.make_create_request_body(vnfd_id))
|
||||
vnf_instance_id = vnf_instance['id']
|
||||
self._wait_lcm_done(vnf_instance_id=vnf_instance_id)
|
||||
self.assert_create_vnf(resp, vnf_instance, vnf_package_id)
|
||||
self.addCleanup(self._delete_vnf_instance, vnf_instance_id)
|
||||
|
||||
# instantiate VNF
|
||||
request_body = fake_vnflcm.VnfInstances.make_inst_request_body(
|
||||
self.vim['tenant_id'], self.ext_networks, self.ext_mngd_networks,
|
||||
self.ext_link_ports, self.ext_subnets)
|
||||
self._instantiate_vnf_instance(vnf_instance_id, request_body)
|
||||
self._wait_lcm_done('COMPLETED', vnf_instance_id=vnf_instance_id)
|
||||
vnflcm_base.FAKE_SERVER_MANAGER.clear_history(
|
||||
os.path.join(vnflcm_base.MOCK_NOTIFY_CALLBACK_URL,
|
||||
self._testMethodName))
|
||||
|
||||
# Fail Scale-out vnf instance
|
||||
request_body = fake_vnflcm.VnfInstances.make_scale_request_body(
|
||||
'SCALE_OUT')
|
||||
resp, _ = self._scale_vnf_instance(vnf_instance_id, request_body)
|
||||
self._wait_lcm_done('FAILED_TEMP', vnf_instance_id=vnf_instance_id)
|
||||
|
||||
callback_url = os.path.join(
|
||||
vnflcm_base.MOCK_NOTIFY_CALLBACK_URL, self._testMethodName)
|
||||
notify_mock_responses = vnflcm_base.FAKE_SERVER_MANAGER.get_history(
|
||||
callback_url)
|
||||
vnflcm_base.FAKE_SERVER_MANAGER.clear_history(
|
||||
callback_url)
|
||||
|
||||
# get vnflcm_op_occ_id
|
||||
vnflcm_op_occ_id = notify_mock_responses[0].request_body.get(
|
||||
'vnfLcmOpOccId')
|
||||
self.assertIsNotNone(vnflcm_op_occ_id)
|
||||
|
||||
# fail
|
||||
resp, _ = self._fail_op_occs(vnflcm_op_occ_id)
|
||||
self._wait_lcm_done('FAILED', vnf_instance_id=vnf_instance_id)
|
||||
self.assert_fail_vnf(resp, vnf_instance_id)
|
||||
|
||||
# occ-show
|
||||
resp, op_occs_info = self._show_op_occs(vnflcm_op_occ_id)
|
||||
self._assert_occ_show(resp, op_occs_info)
|
||||
|
||||
# Terminate VNF
|
||||
stack = self._get_heat_stack(vnf_instance_id)
|
||||
resources_list = self._get_heat_resource_list(stack.id)
|
||||
resource_name_list = [r.resource_name for r in resources_list]
|
||||
glance_image_id_list = self._get_glance_image_list_from_stack_resource(
|
||||
stack.id, resource_name_list)
|
||||
|
||||
terminate_req_body = fake_vnflcm.VnfInstances.make_term_request_body()
|
||||
resp, _ = self._terminate_vnf_instance(vnf_instance_id,
|
||||
terminate_req_body)
|
||||
self._wait_lcm_done('COMPLETED', vnf_instance_id=vnf_instance_id)
|
||||
self.assert_terminate_vnf(resp, vnf_instance_id, stack.id,
|
||||
resource_name_list, glance_image_id_list, vnf_package_id)
|
||||
|
||||
# Delete VNF
|
||||
resp, _ = self._delete_vnf_instance(vnf_instance_id)
|
||||
self._wait_lcm_done(vnf_instance_id=vnf_instance_id)
|
||||
self.assert_delete_vnf(resp, vnf_instance_id, vnf_package_id)
|
||||
|
||||
# Subscription delete
|
||||
resp, response_body = self._delete_subscription(subscription_id)
|
||||
self.assertEqual(204, resp.status_code)
|
||||
|
||||
def assert_create_vnf(self, resp, vnf_instance, vnf_pkg_id):
|
||||
super().assert_create_vnf(resp, vnf_instance)
|
||||
|
||||
|
|
|
@ -905,6 +905,22 @@ def vnflcm_rollback_insta(error_point=7):
|
|||
created_at=dt)
|
||||
|
||||
|
||||
def vnflcm_fail_insta(error_point=7):
|
||||
default_datetime = datetime.datetime(
|
||||
2000, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC)
|
||||
return objects.VnfLcmOpOcc(
|
||||
state_entered_time=default_datetime,
|
||||
start_time=default_datetime,
|
||||
vnf_instance_id=uuidsentinel.vnf_instance_id,
|
||||
operation='INSTANTIATE',
|
||||
operation_state='FAILED_TEMP',
|
||||
is_automatic_invocation=False,
|
||||
operation_params='{}',
|
||||
error_point=error_point,
|
||||
id=constants.UUID,
|
||||
created_at=default_datetime)
|
||||
|
||||
|
||||
def vnflcm_rollback_active():
|
||||
dt = datetime.datetime(2000, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC)
|
||||
return objects.VnfLcmOpOcc(
|
||||
|
|
|
@ -3067,3 +3067,66 @@ class TestController(base.TestCase):
|
|||
|
||||
resp = req.get_response(self.app)
|
||||
self.assertEqual(http_client.NOT_FOUND, resp.status_code)
|
||||
|
||||
@mock.patch.object(controller.VnfLcmController,
|
||||
"_update_vnf_fail_status")
|
||||
@mock.patch('tacker.api.views.vnf_lcm.ViewBuilder'
|
||||
'._get_vnf_lcm_op_occs')
|
||||
@mock.patch.object(objects.VnfInstance, "save")
|
||||
@mock.patch.object(objects.VnfInstance, "get_by_id")
|
||||
@mock.patch.object(objects.VnfLcmOpOcc, "save")
|
||||
@mock.patch.object(objects.VnfLcmOpOcc, "get_by_id")
|
||||
def test_fail_lcm_op_occs(self, mock_lcm_get_by_id,
|
||||
mock_lcm_save, mock_vnf_get_by_id,
|
||||
mock_vnf_save, mock_view,
|
||||
mock_update):
|
||||
req = fake_request.HTTPRequest.blank(
|
||||
'/vnf_lcm_op_occs/%s/fail' % constants.UUID)
|
||||
mock_lcm_get_by_id.return_value = fakes.vnflcm_fail_insta()
|
||||
mock_view.return_value = fakes.VNFLCMOPOCC_RESPONSE
|
||||
res_dict = self.controller.fail(req, constants.UUID)
|
||||
self.assertEqual(fakes.VNFLCMOPOCC_RESPONSE, res_dict)
|
||||
|
||||
@mock.patch.object(objects.VnfInstance, "get_by_id")
|
||||
@mock.patch.object(objects.VnfLcmOpOcc, "get_by_id")
|
||||
def test_fail_lcm_op_occs_not_found(self, mock_lcm_get_by_id,
|
||||
mock_vnf_get_by_id):
|
||||
req = fake_request.HTTPRequest.blank(
|
||||
'/vnf_lcm_op_occs/%s/fail' % constants.UUID)
|
||||
mock_lcm_get_by_id.side_effect = exceptions.NotFound()
|
||||
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
req.method = 'POST'
|
||||
res_dict = self.controller.fail(req, constants.UUID)
|
||||
|
||||
self.assertEqual(http_client.NOT_FOUND, res_dict.status_code)
|
||||
|
||||
@mock.patch.object(objects.VnfInstance, "get_by_id")
|
||||
@mock.patch.object(objects.VnfLcmOpOcc, "get_by_id")
|
||||
def test_fail_lcm_op_occs_vnf_not_found(self, mock_lcm_get_by_id,
|
||||
mock_vnf_get_by_id):
|
||||
req = fake_request.HTTPRequest.blank(
|
||||
'/vnf_lcm_op_occs/%s/fail' % constants.UUID)
|
||||
mock_lcm_get_by_id.return_value = fakes.vnflcm_fail_insta()
|
||||
mock_vnf_get_by_id.side_effect = exceptions.VnfInstanceNotFound()
|
||||
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
req.method = 'POST'
|
||||
res_dict = self.controller.fail(req, constants.UUID)
|
||||
|
||||
self.assertEqual('VNF NOT FOUND', res_dict.json['title'])
|
||||
self.assertEqual(http_client.NOT_FOUND, res_dict.status_code)
|
||||
|
||||
@mock.patch.object(objects.VnfLcmOpOcc, "get_by_id")
|
||||
def test_fail_lcm_op_occs_vnf_not_failed_temp(self, mock_lcm_get_by_id):
|
||||
req = fake_request.HTTPRequest.blank(
|
||||
'/vnf_lcm_op_occs/%s/fail' % constants.UUID)
|
||||
vnflcm_fail_object = fakes.vnflcm_fail_insta()
|
||||
vnflcm_fail_object.operation_state = 'STARTED'
|
||||
mock_lcm_get_by_id.return_value = vnflcm_fail_object
|
||||
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
req.method = 'POST'
|
||||
res_dict = self.controller.fail(req, constants.UUID)
|
||||
|
||||
self.assertEqual(http_client.CONFLICT, res_dict.status_code)
|
||||
|
|
Loading…
Reference in New Issue