diff --git a/releasenotes/notes/add-v2-fail-api-b35b605f262210b2.yaml b/releasenotes/notes/add-v2-fail-api-b35b605f262210b2.yaml new file mode 100755 index 000000000..52a35de48 --- /dev/null +++ b/releasenotes/notes/add-v2-fail-api-b35b605f262210b2.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add the Version "2.0.0" of Fail operation API + based on ETSI NFV specifications. diff --git a/tacker/sol_refactored/api/policies/vnflcm_v2.py b/tacker/sol_refactored/api/policies/vnflcm_v2.py index 4f1274d66..51c01251f 100644 --- a/tacker/sol_refactored/api/policies/vnflcm_v2.py +++ b/tacker/sol_refactored/api/policies/vnflcm_v2.py @@ -168,6 +168,15 @@ rules = [ 'path': VNF_LCM_OP_OCCS_ID_PATH + '/rollback'} ] ), + policy.DocumentedRuleDefault( + name=POLICY_NAME.format('lcm_op_occ_fail'), + check_str=RULE_ANY, + description="Fail VnfLcmOpOcc.", + operations=[ + {'method': 'POST', + 'path': VNF_LCM_OP_OCCS_ID_PATH + '/fail'} + ] + ), # NOTE: 'DELETE' is not defined in the specification. It is for test # use since it is convenient to be able to delete under development. # It is available when config parameter diff --git a/tacker/sol_refactored/api/router.py b/tacker/sol_refactored/api/router.py index bced19106..005d84d05 100644 --- a/tacker/sol_refactored/api/router.py +++ b/tacker/sol_refactored/api/router.py @@ -47,6 +47,7 @@ class VnflcmAPIRouterV2(sol_wsgi.SolAPIRouter): ("/vnf_lcm_op_occs", {"GET": "lcm_op_occ_list"}), ("/vnf_lcm_op_occs/{id}/retry", {"POST": "lcm_op_occ_retry"}), ("/vnf_lcm_op_occs/{id}/rollback", {"POST": "lcm_op_occ_rollback"}), + ("/vnf_lcm_op_occs/{id}/fail", {"POST": "lcm_op_occ_fail"}), # NOTE: 'DELETE' is not defined in the specification. It is for test # use since it is convenient to be able to delete under development. # It is available when config parameter diff --git a/tacker/sol_refactored/common/lcm_op_occ_utils.py b/tacker/sol_refactored/common/lcm_op_occ_utils.py index 830ba78c4..686936fea 100644 --- a/tacker/sol_refactored/common/lcm_op_occ_utils.py +++ b/tacker/sol_refactored/common/lcm_op_occ_utils.py @@ -55,13 +55,14 @@ def make_lcmocc_links(lcmocc, endpoint): links.vnfInstance = objects.Link( href=inst_utils.inst_href(lcmocc.vnfInstanceId, endpoint)) links.retry = objects.Link( - href=lcmocc_task_href(lcmocc.vnfInstanceId, 'retry', endpoint)) + href=lcmocc_task_href(lcmocc.id, 'retry', endpoint)) links.rollback = objects.Link( - href=lcmocc_task_href(lcmocc.vnfInstanceId, 'rollback', endpoint)) + href=lcmocc_task_href(lcmocc.id, 'rollback', endpoint)) + links.fail = objects.Link( + href=lcmocc_task_href(lcmocc.id, 'fail', endpoint)) # TODO(oda-g): add when implemented # links.grant # links.cancel - # links.fail # links.vnfSnapshot return links diff --git a/tacker/sol_refactored/controller/vnflcm_v2.py b/tacker/sol_refactored/controller/vnflcm_v2.py index 6dfbb4b21..f46d83e28 100644 --- a/tacker/sol_refactored/controller/vnflcm_v2.py +++ b/tacker/sol_refactored/controller/vnflcm_v2.py @@ -359,6 +359,35 @@ class VnfLcmControllerV2(sol_wsgi.SolAPIController): return sol_wsgi.SolResponse(202, None) + def lcm_op_occ_fail(self, request, id): + context = request.context + lcmocc = lcmocc_utils.get_lcmocc(context, id) + + return self._lcm_op_occ_fail(context, lcmocc) + + @coordinate.lock_vnf_instance('{lcmocc.vnfInstanceId}') + def _lcm_op_occ_fail(self, context, lcmocc): + if lcmocc.operationState != v2fields.LcmOperationStateType.FAILED_TEMP: + raise sol_ex.LcmOpOccNotFailedTemp(lcmocc_id=lcmocc.id) + + inst = inst_utils.get_inst(context, lcmocc.vnfInstanceId) + grant_req, grant = lcmocc_utils.get_grant_req_and_grant(context, + lcmocc) + + lcmocc.operationState = v2fields.LcmOperationStateType.FAILED + with context.session.begin(subtransactions=True): + lcmocc.update(context) + grant_req.delete(context) + grant.delete(context) + + # send notification FAILED + self.nfvo_client.send_lcmocc_notification(context, lcmocc, inst, + self.endpoint) + + resp_body = self._lcmocc_view.detail(lcmocc) + + return sol_wsgi.SolResponse(200, resp_body) + def lcm_op_occ_delete(self, request, id): # not allowed to delete on the specification if not CONF.v2_vnfm.test_enable_lcm_op_occ_delete: diff --git a/tacker/sol_refactored/test-tools/cli.py b/tacker/sol_refactored/test-tools/cli.py index a25007b98..c6ed952f3 100644 --- a/tacker/sol_refactored/test-tools/cli.py +++ b/tacker/sol_refactored/test-tools/cli.py @@ -98,6 +98,11 @@ class Client(object): path = self.path + '/' + id + '/rollback' resp, body = self.client.do_request(path, "POST", version="2.0.0") + def fail(self, id): + path = self.path + '/' + id + '/fail' + resp, body = self.client.do_request(path, "POST", version="2.0.0") + self.print(resp, body) + def usage(): print("usage: cli resource action [arg...]") @@ -116,6 +121,7 @@ def usage(): print(" lcmocc delete {id}") print(" lcmocc retry {id}") print(" lcmocc rollback {id}") + print(" lcmocc fail {id}") os._exit(1) @@ -139,7 +145,8 @@ if __name__ == '__main__': usage() client = Client("/vnflcm/v2/subscriptions") elif resource == "lcmocc": - if action not in ["list", "show", "delete", "retry", "rollback"]: + if action not in ["list", "show", "delete", "retry", "rollback", + "fail"]: usage() client = Client("/vnflcm/v2/vnf_lcm_op_occs") else: @@ -180,3 +187,7 @@ if __name__ == '__main__': if len(sys.argv) != 4: usage() client.rollback(sys.argv[3]) + elif action == "fail": + if len(sys.argv) != 4: + usage() + client.fail(sys.argv[3]) diff --git a/tacker/tests/unit/sol_refactored/controller/test_vnflcm_v2.py b/tacker/tests/unit/sol_refactored/controller/test_vnflcm_v2.py index d554b8ef9..f85983909 100644 --- a/tacker/tests/unit/sol_refactored/controller/test_vnflcm_v2.py +++ b/tacker/tests/unit/sol_refactored/controller/test_vnflcm_v2.py @@ -21,6 +21,7 @@ from oslo_utils import uuidutils from tacker import context from tacker.sol_refactored.api import api_version from tacker.sol_refactored.common import exceptions as sol_ex +from tacker.sol_refactored.common import lcm_op_occ_utils as lcmocc_utils from tacker.sol_refactored.controller import vnflcm_v2 from tacker.sol_refactored.nfvo import nfvo_client from tacker.sol_refactored import objects @@ -39,7 +40,7 @@ class TestVnflcmV2(db_base.SqlTestCase): self.request = mock.Mock() self.request.context = self.context - def _create_inst_and_lcmocc(self, inst_state, op_state): + def _set_inst_and_lcmocc(self, inst_state, op_state): inst = objects.VnfInstanceV2( # required fields id=uuidutils.generate_uuid(), @@ -62,7 +63,13 @@ class TestVnflcmV2(db_base.SqlTestCase): operation=fields.LcmOperationType.INSTANTIATE, isAutomaticInvocation=False, isCancelPending=False, - operationParams=req) + operationParams=req + ) + + return inst, lcmocc + + def _create_inst_and_lcmocc(self, inst_state, op_state): + inst, lcmocc = self._set_inst_and_lcmocc(inst_state, op_state) inst.create(self.context) lcmocc.create(self.context) @@ -191,3 +198,64 @@ class TestVnflcmV2(db_base.SqlTestCase): self.assertRaises(sol_ex.LcmOpOccNotFailedTemp, self.controller.lcm_op_occ_rollback, request=self.request, id=lcmocc_id) + + def test_fail_not_failed_temp(self): + _, lcmocc_id = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + + self.assertRaises(sol_ex.LcmOpOccNotFailedTemp, + self.controller.lcm_op_occ_fail, request=self.request, + id=lcmocc_id) + + def _prepare_db_for_fail(self): + inst, lcmocc = self._set_inst_and_lcmocc('NOT_INSTANTIATED', + fields.LcmOperationStateType.FAILED_TEMP) + + inst.create(self.context) + lcmocc.create(self.context) + + grant_req = objects.GrantRequestV1( + # required fields + vnfInstanceId=lcmocc.vnfInstanceId, + vnfLcmOpOccId=lcmocc.id, + vnfdId=uuidutils.generate_uuid(), + operation=lcmocc.operation, + isAutomaticInvocation=lcmocc.isAutomaticInvocation + ) + + grant = objects.GrantV1( + # required fields + id=uuidutils.generate_uuid(), + vnfInstanceId=lcmocc.vnfInstanceId, + vnfLcmOpOccId=lcmocc.id + ) + + grant_req.create(self.context) + grant.create(self.context) + lcmocc.grantId = grant.id + lcmocc.update(self.context) + + return lcmocc + + @mock.patch.object(nfvo_client.NfvoClient, 'send_lcmocc_notification') + def test_lcm_op_occ_fail(self, mocked_send_lcmocc_notification): + # prepare + lcmocc = self._prepare_db_for_fail() + + op_state = [] + + def _store_state(context, lcmocc, inst, endpoint): + op_state.append(lcmocc.operationState) + + mocked_send_lcmocc_notification.side_effect = _store_state + + # run lcm_op_occ_fail + self.controller.lcm_op_occ_fail(self.request, lcmocc.id) + + # check operationstate + self.assertEqual(1, mocked_send_lcmocc_notification.call_count) + self.assertEqual(fields.LcmOperationStateType.FAILED, op_state[0]) + + # check grant_req and grant are deleted + self.assertRaises(sol_ex.GrantRequestOrGrantNotFound, + lcmocc_utils.get_grant_req_and_grant, self.context, lcmocc)