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:
Aldinson Esto 2021-01-23 04:29:21 +09:00
parent 7e8a7351fa
commit c4db781ebc
13 changed files with 716 additions and 35 deletions

View File

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

View File

@ -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"
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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'
}
]
),
]

View File

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

View File

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

View File

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

View File

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