support rollback operation task of v2 API
This patch implements rollback operation task defined in ETSI NFV-SOL003 v3.3.1 5.4.15. This patch supports rollback operation of existing task API, i.e instantiate. Note that rollback operation of terminate is not supported. Rollback operation of a task API which is added in the future (ex. scale) will be implemented in the same patch of implementing the API. Functional tests will be provided with another patch. Implements: blueprint support-nfv-solv3-error-handling Change-Id: I3b2924287f7b30a85c250602a3f5ecbffd449a5e
This commit is contained in:
parent
bf51c2bc80
commit
4b5204b213
5
releasenotes/notes/add-v2-rollback-api-1b53e7b9c89d5281.yaml
Executable file
5
releasenotes/notes/add-v2-rollback-api-1b53e7b9c89d5281.yaml
Executable file
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Add the Version "2.0.0" of Rollback operation API
|
||||
based on ETSI NFV specifications.
|
@ -159,6 +159,15 @@ rules = [
|
||||
'path': VNF_LCM_OP_OCCS_ID_PATH + '/retry'}
|
||||
]
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name=POLICY_NAME.format('lcm_op_occ_rollback'),
|
||||
check_str=RULE_ANY,
|
||||
description="Rollback VnfLcmOpOcc.",
|
||||
operations=[
|
||||
{'method': 'POST',
|
||||
'path': VNF_LCM_OP_OCCS_ID_PATH + '/rollback'}
|
||||
]
|
||||
),
|
||||
# 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
|
||||
|
@ -46,6 +46,7 @@ class VnflcmAPIRouterV2(sol_wsgi.SolAPIRouter):
|
||||
"DELETE": "subscription_delete"}),
|
||||
("/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"}),
|
||||
# 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
|
||||
|
@ -219,3 +219,7 @@ class LcmOpOccNotFailedTemp(SolHttpError409):
|
||||
class GrantRequestOrGrantNotFound(SolHttpError404):
|
||||
message = _("GrantRequest or Grant for LCM operation "
|
||||
"%(lcmocc_id)s not found.")
|
||||
|
||||
|
||||
class RollbackNotSupported(SolHttpError422):
|
||||
message = _("Rollback of %(op)s is not supported.")
|
||||
|
@ -56,10 +56,11 @@ def make_lcmocc_links(lcmocc, endpoint):
|
||||
href=inst_utils.inst_href(lcmocc.vnfInstanceId, endpoint))
|
||||
links.retry = objects.Link(
|
||||
href=lcmocc_task_href(lcmocc.vnfInstanceId, 'retry', endpoint))
|
||||
links.rollback = objects.Link(
|
||||
href=lcmocc_task_href(lcmocc.vnfInstanceId, 'rollback', endpoint))
|
||||
# TODO(oda-g): add when implemented
|
||||
# links.grant
|
||||
# links.cancel
|
||||
# links.rollback
|
||||
# links.fail
|
||||
# links.vnfSnapshot
|
||||
|
||||
|
@ -44,3 +44,6 @@ class VnfLcmRpcApiV2(object):
|
||||
|
||||
def retry_lcm_op(self, context, lcmocc_id):
|
||||
self._cast_lcm_op(context, lcmocc_id, 'retry_lcm_op')
|
||||
|
||||
def rollback_lcm_op(self, context, lcmocc_id):
|
||||
self._cast_lcm_op(context, lcmocc_id, 'rollback_lcm_op')
|
||||
|
@ -181,3 +181,55 @@ class ConductorV2(object):
|
||||
# send notification COMPLETED or FAILED_TEMP
|
||||
self.nfvo_client.send_lcmocc_notification(context, lcmocc, inst,
|
||||
self.endpoint)
|
||||
|
||||
@log.log
|
||||
def rollback_lcm_op(self, context, lcmocc_id):
|
||||
lcmocc = lcmocc_utils.get_lcmocc(context, lcmocc_id)
|
||||
|
||||
self._rollback_lcm_op(context, lcmocc)
|
||||
|
||||
@coordinate.lock_vnf_instance('{lcmocc.vnfInstanceId}', delay=True)
|
||||
def _rollback_lcm_op(self, context, lcmocc):
|
||||
# just consistency check
|
||||
if lcmocc.operationState != fields.LcmOperationStateType.FAILED_TEMP:
|
||||
LOG.error("VnfLcmOpOcc unexpected operationState.")
|
||||
return
|
||||
|
||||
inst = inst_utils.get_inst(context, lcmocc.vnfInstanceId)
|
||||
|
||||
lcmocc.operationState = fields.LcmOperationStateType.ROLLING_BACK
|
||||
lcmocc.update(context)
|
||||
# send notification ROLLING_BACK
|
||||
self.nfvo_client.send_lcmocc_notification(context, lcmocc, inst,
|
||||
self.endpoint)
|
||||
|
||||
try:
|
||||
vnfd = self.nfvo_client.get_vnfd(context, inst.vnfdId)
|
||||
grant_req, grant = lcmocc_utils.get_grant_req_and_grant(context,
|
||||
lcmocc)
|
||||
self.vnflcm_driver.post_grant(context, lcmocc, inst, grant_req,
|
||||
grant, vnfd)
|
||||
self.vnflcm_driver.rollback(context, lcmocc, inst, grant_req,
|
||||
grant, vnfd)
|
||||
|
||||
lcmocc.operationState = fields.LcmOperationStateType.ROLLED_BACK
|
||||
with context.session.begin(subtransactions=True):
|
||||
# it is not necessary to update inst DB because it was not
|
||||
# changed when the operationState became FAILED_TEMP.
|
||||
# NOTE: inst object may be changed in driver's rollback
|
||||
# method temporary but must not save it.
|
||||
lcmocc.update(context)
|
||||
# grant_req and grant are not necessary any more.
|
||||
grant_req.delete(context)
|
||||
grant.delete(context)
|
||||
except Exception as ex:
|
||||
LOG.exception("ROLLING_BACK %s failed", lcmocc.operation)
|
||||
lcmocc.operationState = fields.LcmOperationStateType.FAILED_TEMP
|
||||
self._set_lcmocc_error(lcmocc, ex)
|
||||
lcmocc.update(context)
|
||||
# grant_req and grant are already saved. they are not deleted
|
||||
# while oprationState is FAILED_TEMP.
|
||||
|
||||
# send notification ROLLED_BACK or FAILED_TEMP
|
||||
self.nfvo_client.send_lcmocc_notification(context, lcmocc, inst,
|
||||
self.endpoint)
|
||||
|
@ -104,6 +104,15 @@ class VnfLcmDriverV2(object):
|
||||
self._exec_mgmt_driver_script(operation,
|
||||
flavour_id, req, inst, grant_req, grant, vnfd)
|
||||
|
||||
def rollback(self, context, lcmocc, inst, grant_req, grant, vnfd):
|
||||
method = getattr(self,
|
||||
"%s_%s" % (lcmocc.operation.lower(), 'rollback'),
|
||||
None)
|
||||
if method:
|
||||
method(context, lcmocc, inst, grant_req, grant, vnfd)
|
||||
else:
|
||||
raise sol_ex.RollbackNotSupported(op=lcmocc.operation)
|
||||
|
||||
def _get_link_ports(self, inst_req):
|
||||
names = []
|
||||
if inst_req.obj_attr_is_set('extVirtualLinks'):
|
||||
@ -281,6 +290,17 @@ class VnfLcmDriverV2(object):
|
||||
inst.instantiationState = 'INSTANTIATED'
|
||||
lcmocc_utils.make_instantiate_lcmocc(lcmocc, inst)
|
||||
|
||||
def instantiate_rollback(self, context, lcmocc, inst, grant_req,
|
||||
grant, vnfd):
|
||||
req = lcmocc.operationParams
|
||||
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
|
||||
if vim_info.vimType == 'ETSINFV.OPENSTACK_KEYSTONE.V_3':
|
||||
driver = openstack.Openstack()
|
||||
driver.instantiate_rollback(req, inst, grant_req, grant, vnfd)
|
||||
else:
|
||||
# only support openstack at the moment
|
||||
raise sol_ex.SolException(sol_detail='not support vim type')
|
||||
|
||||
def terminate_grant(self, context, lcmocc, inst, vnfd):
|
||||
# grant exchange
|
||||
# NOTE: the api_version of NFVO supposes 1.4.0 at the moment.
|
||||
|
@ -344,6 +344,21 @@ class VnfLcmControllerV2(sol_wsgi.SolAPIController):
|
||||
|
||||
return sol_wsgi.SolResponse(202, None)
|
||||
|
||||
def lcm_op_occ_rollback(self, request, id):
|
||||
context = request.context
|
||||
lcmocc = lcmocc_utils.get_lcmocc(context, id)
|
||||
|
||||
return self._lcm_op_occ_rollback(context, lcmocc)
|
||||
|
||||
@coordinate.lock_vnf_instance('{lcmocc.vnfInstanceId}')
|
||||
def _lcm_op_occ_rollback(self, context, lcmocc):
|
||||
if lcmocc.operationState != v2fields.LcmOperationStateType.FAILED_TEMP:
|
||||
raise sol_ex.LcmOpOccNotFailedTemp(lcmocc_id=lcmocc.id)
|
||||
|
||||
self.conductor_rpc.rollback_lcm_op(context, lcmocc.id)
|
||||
|
||||
return sol_wsgi.SolResponse(202, None)
|
||||
|
||||
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:
|
||||
|
@ -522,6 +522,14 @@ class Openstack(object):
|
||||
|
||||
inst.instantiatedVnfInfo = inst_vnf_info
|
||||
|
||||
def instantiate_rollback(self, req, inst, grant_req, grant, vnfd):
|
||||
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
|
||||
heat_client = heat_utils.HeatClient(vim_info)
|
||||
stack_name = heat_utils.get_stack_name(inst)
|
||||
status, _ = heat_client.get_status(stack_name)
|
||||
if status is not None:
|
||||
heat_client.delete_stack(stack_name)
|
||||
|
||||
def terminate(self, req, inst, grant_req, grant, vnfd):
|
||||
if req.terminationType == 'GRACEFUL':
|
||||
timeout = CONF.v2_vnfm.default_graceful_termination_timeout
|
||||
|
@ -94,6 +94,10 @@ class Client(object):
|
||||
resp, body = self.client.do_request(path, "POST", version="2.0.0")
|
||||
self.print(resp, body)
|
||||
|
||||
def rollback(self, id):
|
||||
path = self.path + '/' + id + '/rollback'
|
||||
resp, body = self.client.do_request(path, "POST", version="2.0.0")
|
||||
|
||||
|
||||
def usage():
|
||||
print("usage: cli resource action [arg...]")
|
||||
@ -111,6 +115,7 @@ def usage():
|
||||
print(" lcmocc show {id}")
|
||||
print(" lcmocc delete {id}")
|
||||
print(" lcmocc retry {id}")
|
||||
print(" lcmocc rollback {id}")
|
||||
os._exit(1)
|
||||
|
||||
|
||||
@ -134,7 +139,7 @@ if __name__ == '__main__':
|
||||
usage()
|
||||
client = Client("/vnflcm/v2/subscriptions")
|
||||
elif resource == "lcmocc":
|
||||
if action not in ["list", "show", "delete", "retry"]:
|
||||
if action not in ["list", "show", "delete", "retry", "rollback"]:
|
||||
usage()
|
||||
client = Client("/vnflcm/v2/vnf_lcm_op_occs")
|
||||
else:
|
||||
@ -171,3 +176,7 @@ if __name__ == '__main__':
|
||||
if len(sys.argv) != 4:
|
||||
usage()
|
||||
client.retry(sys.argv[3])
|
||||
elif action == "rollback":
|
||||
if len(sys.argv) != 4:
|
||||
usage()
|
||||
client.rollback(sys.argv[3])
|
||||
|
@ -264,3 +264,78 @@ class TestConductorV2(db_base.SqlTestCase):
|
||||
# check grant_req and grant remain
|
||||
# it's OK if no exception raised
|
||||
lcmocc_utils.get_grant_req_and_grant(self.context, lcmocc)
|
||||
|
||||
@mock.patch.object(nfvo_client.NfvoClient, 'send_lcmocc_notification')
|
||||
@mock.patch.object(nfvo_client.NfvoClient, 'get_vnfd')
|
||||
@mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'post_grant')
|
||||
@mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'rollback')
|
||||
def test_rollback_lcm_op_rolled_back(self, mocked_rollback,
|
||||
mocked_post_grant, mocked_get_vnfd,
|
||||
mocked_send_lcmocc_notification):
|
||||
# prepare
|
||||
lcmocc = self._create_inst_and_lcmocc(
|
||||
op_state=fields.LcmOperationStateType.FAILED_TEMP)
|
||||
self._create_grant_req_and_grant(lcmocc)
|
||||
mocked_get_vnfd.return_value = mock.Mock()
|
||||
|
||||
op_state = []
|
||||
|
||||
def _store_state(context, lcmocc, inst, endpoint):
|
||||
op_state.append(lcmocc.operationState)
|
||||
|
||||
mocked_send_lcmocc_notification.side_effect = _store_state
|
||||
|
||||
# run rollback_lcm_op
|
||||
self.conductor.rollback_lcm_op(self.context, lcmocc.id)
|
||||
|
||||
# check operationState transition
|
||||
self.assertEqual(2, mocked_send_lcmocc_notification.call_count)
|
||||
self.assertEqual(fields.LcmOperationStateType.ROLLING_BACK,
|
||||
op_state[0])
|
||||
self.assertEqual(fields.LcmOperationStateType.ROLLED_BACK, op_state[1])
|
||||
|
||||
# check grant_req and grant are deleted
|
||||
self.assertRaises(sol_ex.GrantRequestOrGrantNotFound,
|
||||
lcmocc_utils.get_grant_req_and_grant, self.context, lcmocc)
|
||||
|
||||
@mock.patch.object(nfvo_client.NfvoClient, 'send_lcmocc_notification')
|
||||
@mock.patch.object(nfvo_client.NfvoClient, 'get_vnfd')
|
||||
@mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'post_grant')
|
||||
@mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'rollback')
|
||||
def test_rollback_lcm_op_failed_temp(self, mocked_rollback,
|
||||
mocked_post_grant, mocked_get_vnfd,
|
||||
mocked_send_lcmocc_notification):
|
||||
# prepare
|
||||
lcmocc = self._create_inst_and_lcmocc(
|
||||
op_state=fields.LcmOperationStateType.FAILED_TEMP)
|
||||
self._create_grant_req_and_grant(lcmocc)
|
||||
mocked_get_vnfd.return_value = mock.Mock()
|
||||
ex = sol_ex.StackOperationFailed(sol_detail="unit test",
|
||||
sol_title="stack failed")
|
||||
mocked_rollback.side_effect = ex
|
||||
|
||||
op_state = []
|
||||
|
||||
def _store_state(context, lcmocc, inst, endpoint):
|
||||
op_state.append(lcmocc.operationState)
|
||||
|
||||
mocked_send_lcmocc_notification.side_effect = _store_state
|
||||
|
||||
# run rollback_lcm_op
|
||||
self.conductor.rollback_lcm_op(self.context, lcmocc.id)
|
||||
|
||||
# check operationState transition
|
||||
self.assertEqual(2, mocked_send_lcmocc_notification.call_count)
|
||||
self.assertEqual(fields.LcmOperationStateType.ROLLING_BACK,
|
||||
op_state[0])
|
||||
self.assertEqual(fields.LcmOperationStateType.FAILED_TEMP, op_state[1])
|
||||
|
||||
# check lcmocc.error
|
||||
# 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()
|
||||
self.assertEqual(expected, lcmocc.error.to_dict())
|
||||
|
||||
# check grant_req and grant remain
|
||||
# it's OK if no exception raised
|
||||
lcmocc_utils.get_grant_req_and_grant(self.context, lcmocc)
|
||||
|
@ -183,3 +183,11 @@ class TestVnflcmV2(db_base.SqlTestCase):
|
||||
self.assertRaises(sol_ex.LcmOpOccNotFailedTemp,
|
||||
self.controller.lcm_op_occ_retry, request=self.request,
|
||||
id=lcmocc_id)
|
||||
|
||||
def test_rollback_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_rollback, request=self.request,
|
||||
id=lcmocc_id)
|
||||
|
Loading…
Reference in New Issue
Block a user