support retry operation task of v2 API
This patch implements retry operation task defined in ETSI NFV-SOL003 v3.3.1 5.4.14. Basically retry operation is not task API specific. Thus retry operation of a task API which is added in the future (ex. scale) will be supported naturally. Functional tests will be provided with another patch. Implements: blueprint support-nfv-solv3-error-handling Change-Id: I2717540f042882363cce3d1a0da22c71789a36cb
This commit is contained in:
parent
e501198b0f
commit
bf51c2bc80
5
releasenotes/notes/add-v2-retry-api-34667d944db1f54c.yaml
Executable file
5
releasenotes/notes/add-v2-retry-api-34667d944db1f54c.yaml
Executable file
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Add the Version "2.0.0" of Retry operation API
|
||||
based on ETSI NFV specifications.
|
@ -0,0 +1,78 @@
|
||||
# Copyright 2021 OpenStack Foundation
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
"""add grant and grant request
|
||||
|
||||
Revision ID: 3ff50553e9d3
|
||||
Revises: 70df18f71ba2
|
||||
Create Date: 2021-10-07 03:57:25.430532
|
||||
|
||||
"""
|
||||
|
||||
# flake8: noqa: E402
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '3ff50553e9d3'
|
||||
down_revision = '70df18f71ba2'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade(active_plugins=None, options=None):
|
||||
op.create_table('GrantV1',
|
||||
sa.Column('id', sa.String(length=255), nullable=False),
|
||||
sa.Column('vnfInstanceId', sa.String(length=255), nullable=False),
|
||||
sa.Column('vnfLcmOpOccId', sa.String(length=255), nullable=False),
|
||||
sa.Column('vimConnectionInfo', sa.JSON(), nullable=True),
|
||||
sa.Column('zones', sa.JSON(), nullable=True),
|
||||
sa.Column('zoneGroups', sa.JSON(), nullable=True),
|
||||
sa.Column('addResources', sa.JSON(), nullable=True),
|
||||
sa.Column('tempResources', sa.JSON(), nullable=True),
|
||||
sa.Column('removeResources', sa.JSON(), nullable=True),
|
||||
sa.Column('updateResources', sa.JSON(), nullable=True),
|
||||
sa.Column('vimAssets', sa.JSON(), nullable=True),
|
||||
sa.Column('extVirtualLinks', sa.JSON(), nullable=True),
|
||||
sa.Column('extManagedVirtualLinks', sa.JSON(), nullable=True),
|
||||
sa.Column('additionalParams', sa.JSON(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
mysql_engine='InnoDB'
|
||||
)
|
||||
|
||||
op.create_table('GrantRequestV1',
|
||||
sa.Column('vnfInstanceId', sa.String(length=255), nullable=False),
|
||||
sa.Column('vnfLcmOpOccId', sa.String(length=255), nullable=False),
|
||||
sa.Column('vnfdId', sa.String(length=255), nullable=False),
|
||||
sa.Column('dstVnfdId', sa.String(length=255), nullable=True),
|
||||
sa.Column('flavourId', sa.String(length=255), nullable=True),
|
||||
sa.Column('operation',
|
||||
sa.Enum('INSTANTIATE', 'SCALE', 'SCALE_TO_LEVEL',
|
||||
'CHANGE_FLAVOUR', 'TERMINATE', 'HEAL', 'OPERATE',
|
||||
'CHANGE_EXT_CONN', 'CREATE_SNAPSHOT',
|
||||
'REVERT_TO_SNAPSHOT', 'CHANGE_VNFPKG'),
|
||||
nullable=False),
|
||||
sa.Column('isAutomaticInvocation', sa.Boolean(), nullable=False),
|
||||
sa.Column('instantiationLevelId', sa.String(length=255),
|
||||
nullable=True),
|
||||
sa.Column('addResources', sa.JSON(), nullable=True),
|
||||
sa.Column('tempResources', sa.JSON(), nullable=True),
|
||||
sa.Column('removeResources', sa.JSON(), nullable=True),
|
||||
sa.Column('updateResources', sa.JSON(), nullable=True),
|
||||
sa.Column('placementConstraints', sa.JSON(), nullable=True),
|
||||
sa.Column('vimConstraints', sa.JSON(), nullable=True),
|
||||
sa.Column('additionalParams', sa.JSON(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('vnfLcmOpOccId'),
|
||||
mysql_engine='InnoDB'
|
||||
)
|
@ -1 +1 @@
|
||||
70df18f71ba2
|
||||
3ff50553e9d3
|
||||
|
@ -150,6 +150,15 @@ rules = [
|
||||
'path': VNF_LCM_OP_OCCS_ID_PATH}
|
||||
]
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name=POLICY_NAME.format('lcm_op_occ_retry'),
|
||||
check_str=RULE_ANY,
|
||||
description="Retry VnfLcmOpOcc.",
|
||||
operations=[
|
||||
{'method': 'POST',
|
||||
'path': VNF_LCM_OP_OCCS_ID_PATH + '/retry'}
|
||||
]
|
||||
),
|
||||
# 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
|
||||
|
@ -45,6 +45,7 @@ class VnflcmAPIRouterV2(sol_wsgi.SolAPIRouter):
|
||||
("/subscriptions/{id}", {"GET": "subscription_show",
|
||||
"DELETE": "subscription_delete"}),
|
||||
("/vnf_lcm_op_occs", {"GET": "lcm_op_occ_list"}),
|
||||
("/vnf_lcm_op_occs/{id}/retry", {"POST": "lcm_op_occ_retry"}),
|
||||
# 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
|
||||
|
@ -210,3 +210,12 @@ class ResponseTooBig(SolHttpError400):
|
||||
class LocalNfvoGrantFailed(SolHttpError403):
|
||||
title = 'Grant failed'
|
||||
# detail set in the code
|
||||
|
||||
|
||||
class LcmOpOccNotFailedTemp(SolHttpError409):
|
||||
message = _("LCM operation %(lcmocc_id)s not FAILED_TEMP.")
|
||||
|
||||
|
||||
class GrantRequestOrGrantNotFound(SolHttpError404):
|
||||
message = _("GrantRequest or Grant for LCM operation "
|
||||
"%(lcmocc_id)s not found.")
|
||||
|
@ -44,15 +44,21 @@ def lcmocc_href(lcmocc_id, endpoint):
|
||||
return "{}/v2/vnflcm/vnf_lcm_op_occs/{}".format(endpoint, lcmocc_id)
|
||||
|
||||
|
||||
def lcmocc_task_href(lcmocc_id, task, endpoint):
|
||||
return "{}/v2/vnflcm/vnf_lcm_op_occs/{}/{}".format(endpoint, lcmocc_id,
|
||||
task)
|
||||
|
||||
|
||||
def make_lcmocc_links(lcmocc, endpoint):
|
||||
links = objects.VnfLcmOpOccV2_Links()
|
||||
links.self = objects.Link(href=lcmocc_href(lcmocc.id, 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))
|
||||
# TODO(oda-g): add when implemented
|
||||
# links.grant
|
||||
# links.cancel
|
||||
# links.retry
|
||||
# links.rollback
|
||||
# links.fail
|
||||
# links.vnfSnapshot
|
||||
@ -179,3 +185,27 @@ def make_instantiate_lcmocc(lcmocc, inst):
|
||||
|
||||
def make_terminate_lcmocc(lcmocc, inst):
|
||||
_make_instantiate_lcmocc(lcmocc, inst, 'REMOVED')
|
||||
|
||||
|
||||
def get_grant_req_and_grant(context, lcmocc):
|
||||
grant_reqs = objects.GrantRequestV1.get_by_filter(context,
|
||||
vnfLcmOpOccId=lcmocc.id)
|
||||
grant = objects.GrantV1.get_by_id(context, lcmocc.grantId)
|
||||
if not grant_reqs or grant is None:
|
||||
raise sol_ex.GrantRequestOrGrantNotFound(lcmocc_id=lcmocc.id)
|
||||
|
||||
# len(grant_reqs) == 1 because vnfLcmOpOccId is primary key.
|
||||
return grant_reqs[0], grant
|
||||
|
||||
|
||||
def check_lcmocc_in_progress(context, inst_id):
|
||||
# if the controller or conductor executes an operation for the vnf
|
||||
# instance (i.e. operationState is ...ING), other operation for
|
||||
# the same vnf instance is exculed by the coordinator.
|
||||
# check here is existence of lcmocc for the vnf instance with
|
||||
# FAILED_TEMP operationState.
|
||||
lcmoccs = objects.VnfLcmOpOccV2.get_by_filter(
|
||||
context, vnfInstanceId=inst_id,
|
||||
operationState=fields.LcmOperationStateType.FAILED_TEMP)
|
||||
if lcmoccs:
|
||||
raise sol_ex.OtherOperationInProgress(inst_id=inst_id)
|
||||
|
@ -31,10 +31,16 @@ class VnfLcmRpcApiV2(object):
|
||||
fanout=False,
|
||||
version='1.0')
|
||||
|
||||
def start_lcm_op(self, context, lcmocc_id):
|
||||
def _cast_lcm_op(self, context, lcmocc_id, method):
|
||||
serializer = objects_base.TackerObjectSerializer()
|
||||
|
||||
client = rpc.get_client(self.target, version_cap=None,
|
||||
serializer=serializer)
|
||||
cctxt = client.prepare()
|
||||
cctxt.cast(context, 'start_lcm_op', lcmocc_id=lcmocc_id)
|
||||
cctxt.cast(context, method, lcmocc_id=lcmocc_id)
|
||||
|
||||
def start_lcm_op(self, context, lcmocc_id):
|
||||
self._cast_lcm_op(context, lcmocc_id, 'start_lcm_op')
|
||||
|
||||
def retry_lcm_op(self, context, lcmocc_id):
|
||||
self._cast_lcm_op(context, lcmocc_id, 'retry_lcm_op')
|
||||
|
@ -39,10 +39,6 @@ class ConductorV2(object):
|
||||
self.endpoint = CONF.v2_vnfm.endpoint
|
||||
self.nfvo_client = nfvo_client.NfvoClient()
|
||||
|
||||
def _get_lcm_op_method(self, op, postfix):
|
||||
method = getattr(self.vnflcm_driver, "%s_%s" % (op.lower(), postfix))
|
||||
return method
|
||||
|
||||
def _set_lcmocc_error(self, lcmocc, ex):
|
||||
if isinstance(ex, sol_ex.SolException):
|
||||
problem_details = ex.make_problem_details()
|
||||
@ -84,10 +80,13 @@ class ConductorV2(object):
|
||||
|
||||
# NOTE: perform grant exchange mainly but also perform
|
||||
# something to do at STATING phase ex. request check.
|
||||
grant_method = self._get_lcm_op_method(lcmocc.operation, 'grant')
|
||||
grant_req, grant = grant_method(context, lcmocc, inst, vnfd)
|
||||
grant_req, grant = self.vnflcm_driver.grant(context, lcmocc,
|
||||
inst, vnfd)
|
||||
self.vnflcm_driver.post_grant(context, lcmocc, inst, grant_req,
|
||||
grant, vnfd)
|
||||
|
||||
lcmocc.operationState = fields.LcmOperationStateType.PROCESSING
|
||||
lcmocc.grantId = grant.id
|
||||
lcmocc.update(context)
|
||||
except Exception as ex:
|
||||
LOG.exception("STARTING %s failed", lcmocc.operation)
|
||||
@ -103,17 +102,8 @@ class ConductorV2(object):
|
||||
return
|
||||
|
||||
try:
|
||||
# perform preamble LCM script
|
||||
start_method = self._get_lcm_op_method(lcmocc.operation, 'start')
|
||||
start_method(context, lcmocc, inst, grant_req, grant, vnfd)
|
||||
|
||||
process_method = self._get_lcm_op_method(lcmocc.operation,
|
||||
'process')
|
||||
process_method(context, lcmocc, inst, grant_req, grant, vnfd)
|
||||
|
||||
# perform postamble LCM script
|
||||
end_method = self._get_lcm_op_method(lcmocc.operation, 'end')
|
||||
end_method(context, lcmocc, inst, grant_req, grant, vnfd)
|
||||
self.vnflcm_driver.process(context, lcmocc, inst, grant_req,
|
||||
grant, vnfd)
|
||||
|
||||
lcmocc.operationState = fields.LcmOperationStateType.COMPLETED
|
||||
# update inst and lcmocc at the same time
|
||||
@ -124,7 +114,69 @@ class ConductorV2(object):
|
||||
LOG.exception("PROCESSING %s failed", lcmocc.operation)
|
||||
lcmocc.operationState = fields.LcmOperationStateType.FAILED_TEMP
|
||||
self._set_lcmocc_error(lcmocc, ex)
|
||||
lcmocc.update(context)
|
||||
with context.session.begin(subtransactions=True):
|
||||
# save grant_req and grant to be used when retry
|
||||
# NOTE: grant_req is saved because it is necessary to interpret
|
||||
# the contents of grant. Though grant can be gotten from NFVO,
|
||||
# it is saved here with grant_req so that it is not necessary
|
||||
# to communicate with NFVO when retry. They are saved temporary
|
||||
# and will be deleted when operationState becomes an end state
|
||||
# (COMPLETED/FAILED/ROLLED_BACK).
|
||||
grant_req.create(context)
|
||||
grant.create(context)
|
||||
lcmocc.update(context)
|
||||
|
||||
# send notification COMPLETED or FAILED_TEMP
|
||||
self.nfvo_client.send_lcmocc_notification(context, lcmocc, inst,
|
||||
self.endpoint)
|
||||
|
||||
@log.log
|
||||
def retry_lcm_op(self, context, lcmocc_id):
|
||||
lcmocc = lcmocc_utils.get_lcmocc(context, lcmocc_id)
|
||||
|
||||
self._retry_lcm_op(context, lcmocc)
|
||||
|
||||
@coordinate.lock_vnf_instance('{lcmocc.vnfInstanceId}', delay=True)
|
||||
def _retry_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.PROCESSING
|
||||
lcmocc.update(context)
|
||||
# send notification PROCESSING
|
||||
self.nfvo_client.send_lcmocc_notification(context, lcmocc, inst,
|
||||
self.endpoint)
|
||||
|
||||
try:
|
||||
vnfd = self.nfvo_client.get_vnfd(context, inst.vnfdId,
|
||||
all_contents=True)
|
||||
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.process(context, lcmocc, inst, grant_req,
|
||||
grant, vnfd)
|
||||
|
||||
lcmocc.operationState = fields.LcmOperationStateType.COMPLETED
|
||||
lcmocc.error = None # clear error
|
||||
# update inst and lcmocc at the same time
|
||||
with context.session.begin(subtransactions=True):
|
||||
inst.update(context)
|
||||
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("PROCESSING %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 COMPLETED or FAILED_TEMP
|
||||
self.nfvo_client.send_lcmocc_notification(context, lcmocc, inst,
|
||||
|
@ -28,6 +28,7 @@ from tacker.sol_refactored.common import vnf_instance_utils as inst_utils
|
||||
from tacker.sol_refactored.infra_drivers.openstack import openstack
|
||||
from tacker.sol_refactored.nfvo import nfvo_client
|
||||
from tacker.sol_refactored import objects
|
||||
from tacker.sol_refactored.objects.v2 import fields as v2fields
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -41,6 +42,68 @@ class VnfLcmDriverV2(object):
|
||||
self.endpoint = CONF.v2_vnfm.endpoint
|
||||
self.nfvo_client = nfvo_client.NfvoClient()
|
||||
|
||||
def grant(self, context, lcmocc, inst, vnfd):
|
||||
method = getattr(self, "%s_%s" % (lcmocc.operation.lower(), 'grant'))
|
||||
return method(context, lcmocc, inst, vnfd)
|
||||
|
||||
def post_grant(self, context, lcmocc, inst, grant_req, grant, vnfd):
|
||||
method = getattr(self,
|
||||
"%s_%s" % (lcmocc.operation.lower(), 'post_grant'),
|
||||
None)
|
||||
if method:
|
||||
method(context, lcmocc, inst, grant_req, grant, vnfd)
|
||||
|
||||
def _exec_mgmt_driver_script(self, operation, flavour_id, req, inst,
|
||||
grant_req, grant, vnfd):
|
||||
script = vnfd.get_interface_script(flavour_id, operation)
|
||||
if script is None:
|
||||
return
|
||||
|
||||
tmp_csar_dir = vnfd.make_tmp_csar_dir()
|
||||
script_dict = {
|
||||
'operation': operation,
|
||||
'request': req.to_dict(),
|
||||
'vnf_instance': inst.to_dict(),
|
||||
'grant_request': grant_req.to_dict(),
|
||||
'grant_response': grant.to_dict(),
|
||||
'tmp_csar_dir': tmp_csar_dir
|
||||
}
|
||||
# script is relative path to Definitions/xxx.yaml
|
||||
script_path = os.path.join(tmp_csar_dir, "Definitions", script)
|
||||
|
||||
out = subprocess.run(["python3", script_path],
|
||||
input=pickle.dumps(script_dict),
|
||||
capture_output=True)
|
||||
|
||||
vnfd.remove_tmp_csar_dir(tmp_csar_dir)
|
||||
|
||||
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)
|
||||
|
||||
LOG.debug("execute %s of %s success.", operation, script)
|
||||
|
||||
def process(self, context, lcmocc, inst, grant_req, grant, vnfd):
|
||||
# perform preamble LCM script
|
||||
req = lcmocc.operationParams
|
||||
operation = "%s_%s" % (lcmocc.operation.lower(), 'start')
|
||||
if lcmocc.operation == v2fields.LcmOperationType.INSTANTIATE:
|
||||
flavour_id = req.flavourId
|
||||
else:
|
||||
flavour_id = inst.instantiatedVnfInfo.flavourId
|
||||
self._exec_mgmt_driver_script(operation,
|
||||
flavour_id, req, inst, grant_req, grant, vnfd)
|
||||
|
||||
# main process
|
||||
method = getattr(self, "%s_%s" % (lcmocc.operation.lower(), 'process'))
|
||||
method(context, lcmocc, inst, grant_req, grant, vnfd)
|
||||
|
||||
# perform postamble LCM script
|
||||
operation = "%s_%s" % (lcmocc.operation.lower(), 'end')
|
||||
self._exec_mgmt_driver_script(operation,
|
||||
flavour_id, req, inst, grant_req, grant, vnfd)
|
||||
|
||||
def _get_link_ports(self, inst_req):
|
||||
names = []
|
||||
if inst_req.obj_attr_is_set('extVirtualLinks'):
|
||||
@ -167,19 +230,24 @@ class VnfLcmDriverV2(object):
|
||||
href=inst_utils.inst_href(inst.id, self.endpoint)))
|
||||
|
||||
# NOTE: if not granted, 403 error raised.
|
||||
grant_res = self.nfvo_client.grant(context, grant_req)
|
||||
grant = self.nfvo_client.grant(context, grant_req)
|
||||
|
||||
return grant_req, grant
|
||||
|
||||
def instantiate_post_grant(self, context, lcmocc, inst, grant_req,
|
||||
grant, vnfd):
|
||||
# set inst vimConnectionInfo
|
||||
req = lcmocc.operationParams
|
||||
vim_infos = {}
|
||||
if req.obj_attr_is_set('vimConnectionInfo'):
|
||||
vim_infos = req.vimConnectionInfo
|
||||
|
||||
if grant_res.obj_attr_is_set('vimConnectionInfo'):
|
||||
if grant.obj_attr_is_set('vimConnectionInfo'):
|
||||
# if NFVO returns vimConnectionInfo use it.
|
||||
# As the controller does for req.vimConnectionInfo, if accessInfo
|
||||
# or interfaceInfo is not specified, get them from VIM DB.
|
||||
# vimId must be in VIM DB.
|
||||
res_vim_infos = grant_res.vimConnectioninfo
|
||||
res_vim_infos = grant.vimConnectioninfo
|
||||
for key, res_vim_info in res_vim_infos.items():
|
||||
if not (res_vim_info.obj_attr_is_set('accessInfo') and
|
||||
res_vim_info.obj_attr_is_set('interfaceInfo')):
|
||||
@ -199,8 +267,6 @@ class VnfLcmDriverV2(object):
|
||||
|
||||
inst.vimConnectionInfo = vim_infos
|
||||
|
||||
return grant_req, grant_res
|
||||
|
||||
def instantiate_process(self, context, lcmocc, inst, grant_req,
|
||||
grant, vnfd):
|
||||
req = lcmocc.operationParams
|
||||
@ -215,49 +281,6 @@ class VnfLcmDriverV2(object):
|
||||
inst.instantiationState = 'INSTANTIATED'
|
||||
lcmocc_utils.make_instantiate_lcmocc(lcmocc, inst)
|
||||
|
||||
def _exec_mgmt_driver_script(self, operation, flavour_id, req, inst,
|
||||
grant_req, grant, vnfd):
|
||||
script = vnfd.get_interface_script(flavour_id, operation)
|
||||
if script is None:
|
||||
return
|
||||
|
||||
tmp_csar_dir = vnfd.make_tmp_csar_dir()
|
||||
script_dict = {
|
||||
'operation': operation,
|
||||
'request': req.to_dict(),
|
||||
'vnf_instance': inst.to_dict(),
|
||||
'grant_request': grant_req.to_dict(),
|
||||
'grant_response': grant.to_dict(),
|
||||
'tmp_csar_dir': tmp_csar_dir
|
||||
}
|
||||
# script is relative path to Definitions/xxx.yaml
|
||||
script_path = os.path.join(tmp_csar_dir, "Definitions", script)
|
||||
|
||||
out = subprocess.run(["python3", script_path],
|
||||
input=pickle.dumps(script_dict),
|
||||
capture_output=True)
|
||||
|
||||
vnfd.remove_tmp_csar_dir(tmp_csar_dir)
|
||||
|
||||
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)
|
||||
|
||||
LOG.debug("execute %s of %s success.", operation, script)
|
||||
|
||||
def instantiate_start(self, context, lcmocc, inst, grant_req,
|
||||
grant, vnfd):
|
||||
req = lcmocc.operationParams
|
||||
self._exec_mgmt_driver_script('instantiate_start',
|
||||
req.flavourId, req, inst, grant_req, grant, vnfd)
|
||||
|
||||
def instantiate_end(self, context, lcmocc, inst, grant_req,
|
||||
grant, vnfd):
|
||||
req = lcmocc.operationParams
|
||||
self._exec_mgmt_driver_script('instantiate_end',
|
||||
req.flavourId, req, inst, grant_req, grant, vnfd)
|
||||
|
||||
def terminate_grant(self, context, lcmocc, inst, vnfd):
|
||||
# grant exchange
|
||||
# NOTE: the api_version of NFVO supposes 1.4.0 at the moment.
|
||||
@ -375,17 +398,3 @@ class VnfLcmDriverV2(object):
|
||||
|
||||
# reset vimConnectionInfo
|
||||
inst.vimConnectionInfo = {}
|
||||
|
||||
def terminate_start(self, context, lcmocc, inst, grant_req,
|
||||
grant, vnfd):
|
||||
req = lcmocc.operationParams
|
||||
flavour_id = inst.instantiatedVnfInfo.flavourId
|
||||
self._exec_mgmt_driver_script('terminate_start',
|
||||
flavour_id, req, inst, grant_req, grant, vnfd)
|
||||
|
||||
def terminate_end(self, context, lcmocc, inst, grant_req,
|
||||
grant, vnfd):
|
||||
req = lcmocc.operationParams
|
||||
flavour_id = inst.instantiatedVnfInfo.flavourId
|
||||
self._exec_mgmt_driver_script('terminate_end',
|
||||
flavour_id, req, inst, grant_req, grant, vnfd)
|
||||
|
@ -129,11 +129,13 @@ class VnfLcmControllerV2(sol_wsgi.SolAPIController):
|
||||
@coordinate.lock_vnf_instance('{id}')
|
||||
def delete(self, request, id):
|
||||
context = request.context
|
||||
inst = inst_utils.get_inst(request.context, id)
|
||||
inst = inst_utils.get_inst(context, id)
|
||||
|
||||
if inst.instantiationState != 'NOT_INSTANTIATED':
|
||||
raise sol_ex.VnfInstanceIsInstantiated(inst_id=id)
|
||||
|
||||
lcmocc_utils.check_lcmocc_in_progress(context, id)
|
||||
|
||||
inst.delete(context)
|
||||
|
||||
# NOTE: inst record in DB deleted but inst object still
|
||||
@ -151,6 +153,8 @@ class VnfLcmControllerV2(sol_wsgi.SolAPIController):
|
||||
if inst.instantiationState != 'NOT_INSTANTIATED':
|
||||
raise sol_ex.VnfInstanceIsInstantiated(inst_id=id)
|
||||
|
||||
lcmocc_utils.check_lcmocc_in_progress(context, id)
|
||||
|
||||
now = datetime.utcnow()
|
||||
lcmocc = objects.VnfLcmOpOccV2(
|
||||
id=uuidutils.generate_uuid(),
|
||||
@ -192,6 +196,8 @@ class VnfLcmControllerV2(sol_wsgi.SolAPIController):
|
||||
if inst.instantiationState != 'INSTANTIATED':
|
||||
raise sol_ex.VnfInstanceIsNotInstantiated(inst_id=id)
|
||||
|
||||
lcmocc_utils.check_lcmocc_in_progress(context, id)
|
||||
|
||||
now = datetime.utcnow()
|
||||
lcmocc = objects.VnfLcmOpOccV2(
|
||||
id=uuidutils.generate_uuid(),
|
||||
@ -323,6 +329,21 @@ class VnfLcmControllerV2(sol_wsgi.SolAPIController):
|
||||
|
||||
return sol_wsgi.SolResponse(200, resp_body)
|
||||
|
||||
def lcm_op_occ_retry(self, request, id):
|
||||
context = request.context
|
||||
lcmocc = lcmocc_utils.get_lcmocc(context, id)
|
||||
|
||||
return self._lcm_op_occ_retry(context, lcmocc)
|
||||
|
||||
@coordinate.lock_vnf_instance('{lcmocc.vnfInstanceId}')
|
||||
def _lcm_op_occ_retry(self, context, lcmocc):
|
||||
if lcmocc.operationState != v2fields.LcmOperationStateType.FAILED_TEMP:
|
||||
raise sol_ex.LcmOpOccNotFailedTemp(lcmocc_id=lcmocc.id)
|
||||
|
||||
self.conductor_rpc.retry_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:
|
||||
|
@ -1,24 +0,0 @@
|
||||
# Copyright (C) 2021 Nippon Telegraph and Telephone Corporation
|
||||
# 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.
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_db.sqlalchemy import enginefacade
|
||||
|
||||
|
||||
context_manager = enginefacade.transaction_context()
|
||||
|
||||
|
||||
def configure():
|
||||
context_manager.configure(sqlite_fk=True, **cfg.CONF.database)
|
@ -98,3 +98,57 @@ class VnfLcmOpOccV2(model_base.BASE):
|
||||
changedExtConnectivity = sa.Column(sa.JSON(), nullable=True)
|
||||
modificationsTriggeredByVnfPkgChange = sa.Column(sa.JSON(), nullable=True)
|
||||
vnfSnapshotInfoId = sa.Column(sa.String(255), nullable=True)
|
||||
|
||||
|
||||
class GrantV1(model_base.BASE):
|
||||
"""Type: Grant
|
||||
|
||||
NFV-SOL 003
|
||||
- v3.3.1 9.5.2.3 (API version: 1.4.0)
|
||||
"""
|
||||
|
||||
__tablename__ = 'GrantV1'
|
||||
id = sa.Column(sa.String(255), nullable=False, primary_key=True)
|
||||
vnfInstanceId = sa.Column(sa.String(255), nullable=False)
|
||||
vnfLcmOpOccId = sa.Column(sa.String(255), nullable=False)
|
||||
vimConnectionInfo = sa.Column(sa.JSON(), nullable=True)
|
||||
zones = sa.Column(sa.JSON(), nullable=True)
|
||||
zoneGroups = sa.Column(sa.JSON(), nullable=True)
|
||||
addResources = sa.Column(sa.JSON(), nullable=True)
|
||||
tempResources = sa.Column(sa.JSON(), nullable=True)
|
||||
removeResources = sa.Column(sa.JSON(), nullable=True)
|
||||
updateResources = sa.Column(sa.JSON(), nullable=True)
|
||||
vimAssets = sa.Column(sa.JSON(), nullable=True)
|
||||
extVirtualLinks = sa.Column(sa.JSON(), nullable=True)
|
||||
extManagedVirtualLinks = sa.Column(sa.JSON(), nullable=True)
|
||||
additionalParams = sa.Column(sa.JSON(), nullable=True)
|
||||
|
||||
|
||||
class GrantRequestV1(model_base.BASE):
|
||||
"""Type: GrantRequest
|
||||
|
||||
NFV-SOL 003
|
||||
- v3.3.1 9.5.2.2 (API version: 1.4.0)
|
||||
"""
|
||||
|
||||
__tablename__ = 'GrantRequestV1'
|
||||
vnfInstanceId = sa.Column(sa.String(255), nullable=False)
|
||||
vnfLcmOpOccId = sa.Column(sa.String(255), nullable=False, primary_key=True)
|
||||
vnfdId = sa.Column(sa.String(255), nullable=False)
|
||||
dstVnfdId = sa.Column(sa.String(255), nullable=True)
|
||||
flavourId = sa.Column(sa.String(255), nullable=True)
|
||||
operation = sa.Column(sa.Enum(
|
||||
'INSTANTIATE', 'SCALE', 'SCALE_TO_LEVEL', 'CHANGE_FLAVOUR',
|
||||
'TERMINATE', 'HEAL', 'OPERATE', 'CHANGE_EXT_CONN',
|
||||
'CREATE_SNAPSHOT', 'REVERT_TO_SNAPSHOT', 'CHANGE_VNFPKG',
|
||||
create_constraint=True, validate_strings=True),
|
||||
nullable=False)
|
||||
isAutomaticInvocation = sa.Column(sa.Boolean, nullable=False)
|
||||
instantiationLevelId = sa.Column(sa.String(255), nullable=True)
|
||||
addResources = sa.Column(sa.JSON(), nullable=True)
|
||||
tempResources = sa.Column(sa.JSON(), nullable=True)
|
||||
removeResources = sa.Column(sa.JSON(), nullable=True)
|
||||
updateResources = sa.Column(sa.JSON(), nullable=True)
|
||||
placementConstraints = sa.Column(sa.JSON(), nullable=True)
|
||||
vimConstraints = sa.Column(sa.JSON(), nullable=True)
|
||||
additionalParams = sa.Column(sa.JSON(), nullable=True)
|
||||
|
@ -40,16 +40,30 @@ class HeatClient(object):
|
||||
self.client = http_client.HttpClient(auth,
|
||||
service_type='orchestration')
|
||||
|
||||
def create_stack(self, fields):
|
||||
def create_stack(self, fields, wait=True):
|
||||
path = "stacks"
|
||||
resp, body = self.client.do_request(path, "POST",
|
||||
expected_status=[201], body=fields)
|
||||
|
||||
def delete_stack(self, stack_name):
|
||||
if wait:
|
||||
self.wait_stack_create(fields["stack_name"])
|
||||
|
||||
def update_stack(self, stack_name, fields, wait=True):
|
||||
path = "stacks/{}".format(stack_name)
|
||||
resp, body = self.client.do_request(path, "PATCH",
|
||||
expected_status=[202], body=fields)
|
||||
|
||||
if wait:
|
||||
self.wait_stack_update(stack_name)
|
||||
|
||||
def delete_stack(self, stack_name, wait=True):
|
||||
path = "stacks/{}".format(stack_name)
|
||||
resp, body = self.client.do_request(path, "DELETE",
|
||||
expected_status=[204, 404])
|
||||
|
||||
if wait:
|
||||
self.wait_stack_delete(stack_name)
|
||||
|
||||
def get_status(self, stack_name):
|
||||
path = "stacks/{}".format(stack_name)
|
||||
resp, body = self.client.do_request(path, "GET",
|
||||
@ -100,6 +114,10 @@ class HeatClient(object):
|
||||
self._wait_completion(stack_name, "Stack create",
|
||||
"CREATE_COMPLETE", "CREATE_IN_PROGRESS", "CREATE_FAILED")
|
||||
|
||||
def wait_stack_update(self, stack_name):
|
||||
self._wait_completion(stack_name, "Stack update",
|
||||
"UPDATE_COMPLETE", "UPDATE_IN_PROGRESS", "UPDATE_FAILED")
|
||||
|
||||
def wait_stack_delete(self, stack_name):
|
||||
self._wait_completion(stack_name, "Stack delete",
|
||||
"DELETE_COMPLETE", "DELETE_IN_PROGRESS", "DELETE_FAILED",
|
||||
|
@ -46,14 +46,16 @@ class Openstack(object):
|
||||
|
||||
LOG.debug("stack fields: %s", fields)
|
||||
|
||||
# create stack
|
||||
stack_name = fields['stack_name']
|
||||
|
||||
# create or update stack
|
||||
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
|
||||
heat_client = heat_utils.HeatClient(vim_info)
|
||||
heat_client.create_stack(fields)
|
||||
|
||||
# wait stack created
|
||||
stack_name = fields['stack_name']
|
||||
heat_client.wait_stack_create(stack_name)
|
||||
status, _ = heat_client.get_status(stack_name)
|
||||
if status is None:
|
||||
heat_client.create_stack(fields)
|
||||
else:
|
||||
heat_client.update_stack(stack_name, fields)
|
||||
|
||||
# get stack resource
|
||||
heat_reses = heat_client.get_resources(stack_name)
|
||||
@ -532,4 +534,3 @@ class Openstack(object):
|
||||
heat_client = heat_utils.HeatClient(vim_info)
|
||||
stack_name = heat_utils.get_stack_name(inst)
|
||||
heat_client.delete_stack(stack_name)
|
||||
heat_client.wait_stack_delete(stack_name)
|
||||
|
@ -13,7 +13,6 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from tacker.sol_refactored.db import api as db_api
|
||||
from tacker.sol_refactored.objects.common import fields # noqa
|
||||
|
||||
# NOTE: You may scratch your head as you see code that imports
|
||||
@ -24,7 +23,7 @@ from tacker.sol_refactored.objects.common import fields # noqa
|
||||
# the object.
|
||||
|
||||
|
||||
def register_all(init_db=True):
|
||||
def register_all():
|
||||
# NOTE: You must make sure your object gets imported in this
|
||||
# function in order for it to be registered by services that may
|
||||
# need to receive it via RPC.
|
||||
@ -120,6 +119,3 @@ def register_all(init_db=True):
|
||||
__import__(objects_root + '.v2.vnf_snapshot')
|
||||
__import__(objects_root + '.v2.vnf_state_snapshot_info')
|
||||
__import__(objects_root + '.v2.vnf_virtual_link_resource_info')
|
||||
|
||||
if init_db:
|
||||
db_api.configure()
|
||||
|
@ -25,7 +25,7 @@ from oslo_utils import versionutils
|
||||
from oslo_versionedobjects import base as ovoo_base
|
||||
from oslo_versionedobjects import exception as ovoo_exc
|
||||
|
||||
from tacker.sol_refactored.db import api as db_api
|
||||
from tacker.db import api as db_api
|
||||
from tacker.sol_refactored.db.sqlalchemy import models
|
||||
from tacker.sol_refactored import objects
|
||||
from tacker.sol_refactored.objects import fields as obj_fields
|
||||
@ -221,6 +221,8 @@ class TackerObject(ovoo_base.VersionedObject):
|
||||
for name, field in self.fields.items():
|
||||
if not self.obj_attr_is_set(name):
|
||||
continue
|
||||
if getattr(self, name) is None:
|
||||
continue
|
||||
if isinstance(field, obj_fields.ObjectField):
|
||||
obj[name] = getattr(self, name).to_dict()
|
||||
elif isinstance(field, obj_fields.ListOfObjectsField):
|
||||
@ -378,6 +380,14 @@ class TackerPersistentObject(TackerObject):
|
||||
result = query.all()
|
||||
return [cls.from_db_obj(item) for item in result]
|
||||
|
||||
@classmethod
|
||||
@db_api.context_manager.reader
|
||||
def get_by_filter(cls, context, *args, **kwargs):
|
||||
model_cls = getattr(models, cls.__name__)
|
||||
query = context.session.query(model_cls).filter_by(**kwargs)
|
||||
result = query.all()
|
||||
return [cls.from_db_obj(item) for item in result]
|
||||
|
||||
@classmethod
|
||||
def from_db_obj(cls, db_obj):
|
||||
inst = cls()
|
||||
@ -417,6 +427,9 @@ class TackerPersistentObject(TackerObject):
|
||||
name_ = get_model_field(name)
|
||||
if not self.obj_attr_is_set(name):
|
||||
continue
|
||||
if getattr(self, name) is None:
|
||||
obj[name_] = None
|
||||
continue
|
||||
if isinstance(field, obj_fields.ObjectField):
|
||||
obj[name_] = getattr(self, name).to_json()
|
||||
elif isinstance(field, obj_fields.ListOfObjectsField):
|
||||
|
@ -21,7 +21,8 @@ from tacker.sol_refactored.objects.v1 import fields as v1fields
|
||||
# NFV-SOL 003
|
||||
# - v3.3.1 9.5.2.2 (API version: 1.4.0)
|
||||
@base.TackerObjectRegistry.register
|
||||
class GrantRequestV1(base.TackerObject, base.TackerObjectDictCompat):
|
||||
class GrantRequestV1(base.TackerPersistentObject,
|
||||
base.TackerObjectDictCompat):
|
||||
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
@ -89,6 +89,11 @@ class Client(object):
|
||||
path, "POST", body=req_body, version="2.0.0")
|
||||
self.print(resp, body)
|
||||
|
||||
def retry(self, id):
|
||||
path = self.path + '/' + id + '/retry'
|
||||
resp, body = self.client.do_request(path, "POST", version="2.0.0")
|
||||
self.print(resp, body)
|
||||
|
||||
|
||||
def usage():
|
||||
print("usage: cli resource action [arg...]")
|
||||
@ -105,67 +110,64 @@ def usage():
|
||||
print(" lcmocc list [body(path of content)]")
|
||||
print(" lcmocc show {id}")
|
||||
print(" lcmocc delete {id}")
|
||||
print(" lcmocc retry {id}")
|
||||
os._exit(1)
|
||||
|
||||
|
||||
def get_body(arg):
|
||||
with open(arg) as fp:
|
||||
return json.load(fp)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) < 3:
|
||||
usage()
|
||||
resource = sys.argv[1]
|
||||
action = sys.argv[2]
|
||||
if resource not in ["inst", "subsc", "lcmocc"]:
|
||||
usage()
|
||||
|
||||
if resource == "inst":
|
||||
if action not in ["create", "list", "show", "delete", "inst", "term"]:
|
||||
usage()
|
||||
if resource == "subsc":
|
||||
if action not in ["create", "list", "show", "delete"]:
|
||||
usage()
|
||||
if resource == "lcmocc":
|
||||
if action not in ["list", "show", "delete"]:
|
||||
usage()
|
||||
if action in ["create", "show", "delete"]:
|
||||
if len(sys.argv) != 4:
|
||||
usage()
|
||||
arg1 = sys.argv[3]
|
||||
elif action in ["inst", "term"]:
|
||||
if len(sys.argv) != 5:
|
||||
usage()
|
||||
arg1 = sys.argv[3]
|
||||
arg2 = sys.argv[4]
|
||||
else: # list
|
||||
arg1 = None
|
||||
if len(sys.argv) == 4:
|
||||
arg1 = sys.argv[3]
|
||||
elif len(sys.argv) != 3:
|
||||
usage()
|
||||
|
||||
if resource == "inst":
|
||||
client = Client("/vnflcm/v2/vnf_instances")
|
||||
elif resource == "subsc":
|
||||
if action not in ["create", "list", "show", "delete"]:
|
||||
usage()
|
||||
client = Client("/vnflcm/v2/subscriptions")
|
||||
elif resource == "lcmocc":
|
||||
if action not in ["list", "show", "delete", "retry"]:
|
||||
usage()
|
||||
client = Client("/vnflcm/v2/vnf_lcm_op_occs")
|
||||
else:
|
||||
usage()
|
||||
|
||||
if action == "create":
|
||||
with open(arg1) as fp:
|
||||
body = json.load(fp)
|
||||
client.create(body)
|
||||
if len(sys.argv) != 4:
|
||||
usage()
|
||||
client.create(get_body(sys.argv[3]))
|
||||
elif action == "list":
|
||||
body = None
|
||||
if arg1 is not None:
|
||||
with open(arg1) as fp:
|
||||
body = json.load(fp)
|
||||
if len(sys.argv) == 4:
|
||||
body = get_body(sys.argv[3])
|
||||
elif len(sys.argv) != 3:
|
||||
usage()
|
||||
client.list(body)
|
||||
elif action == "show":
|
||||
client.show(arg1)
|
||||
if len(sys.argv) != 4:
|
||||
usage()
|
||||
client.show(sys.argv[3])
|
||||
elif action == "delete":
|
||||
client.delete(arg1)
|
||||
if len(sys.argv) != 4:
|
||||
usage()
|
||||
client.delete(sys.argv[3])
|
||||
elif action == "inst":
|
||||
with open(arg2) as fp:
|
||||
body = json.load(fp)
|
||||
client.inst(arg1, body)
|
||||
if len(sys.argv) != 5:
|
||||
usage()
|
||||
client.inst(sys.argv[3], get_body(sys.argv[4]))
|
||||
elif action == "term":
|
||||
with open(arg2) as fp:
|
||||
body = json.load(fp)
|
||||
client.term(arg1, body)
|
||||
if len(sys.argv) != 5:
|
||||
usage()
|
||||
client.term(sys.argv[3], get_body(sys.argv[4]))
|
||||
elif action == "retry":
|
||||
if len(sys.argv) != 4:
|
||||
usage()
|
||||
client.retry(sys.argv[3])
|
||||
|
@ -47,7 +47,7 @@ class BaseSolV2Test(base.BaseTestCase):
|
||||
cfg.CONF(args=['--config-file', '/etc/tacker/tacker.conf'],
|
||||
project='tacker',
|
||||
version='%%prog %s' % version.version_info.release_string())
|
||||
objects.register_all(False)
|
||||
objects.register_all()
|
||||
|
||||
vim_info = cls.get_vim_info()
|
||||
cls.auth_url = vim_info.interfaceInfo['endpoint']
|
||||
|
266
tacker/tests/unit/sol_refactored/conductor/test_conductor_v2.py
Normal file
266
tacker/tests/unit/sol_refactored/conductor/test_conductor_v2.py
Normal file
@ -0,0 +1,266 @@
|
||||
# Copyright (C) 2021 Nippon Telegraph and Telephone Corporation
|
||||
# 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.
|
||||
|
||||
from datetime import datetime
|
||||
from unittest import mock
|
||||
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from tacker import context
|
||||
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.conductor import conductor_v2
|
||||
from tacker.sol_refactored.conductor import vnflcm_driver_v2
|
||||
from tacker.sol_refactored.nfvo import nfvo_client
|
||||
from tacker.sol_refactored import objects
|
||||
from tacker.sol_refactored.objects.v2 import fields
|
||||
from tacker.tests.unit.db import base as db_base
|
||||
|
||||
|
||||
class TestConductorV2(db_base.SqlTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestConductorV2, self).setUp()
|
||||
objects.register_all()
|
||||
self.conductor = conductor_v2.ConductorV2()
|
||||
self.context = context.get_admin_context()
|
||||
|
||||
def _create_inst_and_lcmocc(self,
|
||||
op_state=fields.LcmOperationStateType.STARTING):
|
||||
inst = objects.VnfInstanceV2(
|
||||
# required fields
|
||||
id=uuidutils.generate_uuid(),
|
||||
vnfdId=uuidutils.generate_uuid(),
|
||||
vnfProvider='provider',
|
||||
vnfProductName='product name',
|
||||
vnfSoftwareVersion='software version',
|
||||
vnfdVersion='vnfd version',
|
||||
instantiationState='NOT_INSTANTIATED'
|
||||
)
|
||||
|
||||
req = {"flavourId": "simple"} # instantiate request
|
||||
lcmocc = objects.VnfLcmOpOccV2(
|
||||
# required fields
|
||||
id=uuidutils.generate_uuid(),
|
||||
operationState=op_state,
|
||||
stateEnteredTime=datetime.utcnow(),
|
||||
startTime=datetime.utcnow(),
|
||||
vnfInstanceId=inst.id,
|
||||
operation=fields.LcmOperationType.INSTANTIATE,
|
||||
isAutomaticInvocation=False,
|
||||
isCancelPending=False,
|
||||
operationParams=req)
|
||||
|
||||
inst.create(self.context)
|
||||
lcmocc.create(self.context)
|
||||
|
||||
return lcmocc
|
||||
|
||||
def _make_grant_req_and_grant(self, lcmocc):
|
||||
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
|
||||
)
|
||||
|
||||
return grant_req, grant
|
||||
|
||||
def _create_grant_req_and_grant(self, lcmocc):
|
||||
grant_req, grant = self._make_grant_req_and_grant(lcmocc)
|
||||
grant_req.create(self.context)
|
||||
grant.create(self.context)
|
||||
lcmocc.grantId = grant.id
|
||||
lcmocc.update(self.context)
|
||||
|
||||
@mock.patch.object(nfvo_client.NfvoClient, 'send_lcmocc_notification')
|
||||
@mock.patch.object(nfvo_client.NfvoClient, 'get_vnfd')
|
||||
@mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'grant')
|
||||
@mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'post_grant')
|
||||
@mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'process')
|
||||
def test_start_lcm_op_completed(self, mocked_process, mocked_post_grant,
|
||||
mocked_grant, mocked_get_vnfd, mocked_send_lcmocc_notification):
|
||||
# prepare
|
||||
lcmocc = self._create_inst_and_lcmocc()
|
||||
mocked_get_vnfd.return_value = mock.Mock()
|
||||
mocked_grant.return_value = self._make_grant_req_and_grant(lcmocc)
|
||||
|
||||
op_state = []
|
||||
|
||||
def _store_state(context, lcmocc, inst, endpoint):
|
||||
op_state.append(lcmocc.operationState)
|
||||
|
||||
mocked_send_lcmocc_notification.side_effect = _store_state
|
||||
|
||||
# run start_lcm_op
|
||||
self.conductor.start_lcm_op(self.context, lcmocc.id)
|
||||
|
||||
# check operationState transition
|
||||
self.assertEqual(3, mocked_send_lcmocc_notification.call_count)
|
||||
self.assertEqual(fields.LcmOperationStateType.STARTING, op_state[0])
|
||||
self.assertEqual(fields.LcmOperationStateType.PROCESSING, op_state[1])
|
||||
self.assertEqual(fields.LcmOperationStateType.COMPLETED, op_state[2])
|
||||
|
||||
@mock.patch.object(nfvo_client.NfvoClient, 'send_lcmocc_notification')
|
||||
@mock.patch.object(nfvo_client.NfvoClient, 'get_vnfd')
|
||||
@mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'grant')
|
||||
def test_start_lcm_op_rolled_back(self,
|
||||
mocked_grant, mocked_get_vnfd, mocked_send_lcmocc_notification):
|
||||
# prepare
|
||||
lcmocc = self._create_inst_and_lcmocc()
|
||||
mocked_get_vnfd.return_value = mock.Mock()
|
||||
ex = sol_ex.LocalNfvoGrantFailed(sol_detail="unit test")
|
||||
mocked_grant.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 start_lcm_op
|
||||
self.conductor.start_lcm_op(self.context, lcmocc.id)
|
||||
|
||||
# check operationState transition
|
||||
self.assertEqual(2, mocked_send_lcmocc_notification.call_count)
|
||||
self.assertEqual(fields.LcmOperationStateType.STARTING, op_state[0])
|
||||
self.assertEqual(fields.LcmOperationStateType.ROLLED_BACK, 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())
|
||||
|
||||
@mock.patch.object(nfvo_client.NfvoClient, 'send_lcmocc_notification')
|
||||
@mock.patch.object(nfvo_client.NfvoClient, 'get_vnfd')
|
||||
@mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'grant')
|
||||
@mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'post_grant')
|
||||
@mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'process')
|
||||
def test_start_lcm_op_failed_temp(self, mocked_process, mocked_post_grant,
|
||||
mocked_grant, mocked_get_vnfd, mocked_send_lcmocc_notification):
|
||||
# prepare
|
||||
lcmocc = self._create_inst_and_lcmocc()
|
||||
mocked_get_vnfd.return_value = mock.Mock()
|
||||
mocked_grant.return_value = self._make_grant_req_and_grant(lcmocc)
|
||||
ex = sol_ex.StackOperationFailed(sol_detail="unit test",
|
||||
sol_title="stack failed")
|
||||
mocked_process.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 start_lcm_op
|
||||
self.conductor.start_lcm_op(self.context, lcmocc.id)
|
||||
|
||||
# check operationState transition
|
||||
self.assertEqual(3, mocked_send_lcmocc_notification.call_count)
|
||||
self.assertEqual(fields.LcmOperationStateType.STARTING, op_state[0])
|
||||
self.assertEqual(fields.LcmOperationStateType.PROCESSING, op_state[1])
|
||||
self.assertEqual(fields.LcmOperationStateType.FAILED_TEMP, op_state[2])
|
||||
|
||||
# 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 are saved to DB
|
||||
# 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, 'process')
|
||||
def test_retry_lcm_op_completed(self, mocked_process, 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 retry_lcm_op
|
||||
self.conductor.retry_lcm_op(self.context, lcmocc.id)
|
||||
|
||||
# check operationState transition
|
||||
self.assertEqual(2, mocked_send_lcmocc_notification.call_count)
|
||||
self.assertEqual(fields.LcmOperationStateType.PROCESSING, op_state[0])
|
||||
self.assertEqual(fields.LcmOperationStateType.COMPLETED, 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, 'process')
|
||||
def test_retry_lcm_op_failed_temp(self, mocked_process, 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_process.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 retry_lcm_op
|
||||
self.conductor.retry_lcm_op(self.context, lcmocc.id)
|
||||
|
||||
# check operationState transition
|
||||
self.assertEqual(2, mocked_send_lcmocc_notification.call_count)
|
||||
self.assertEqual(fields.LcmOperationStateType.PROCESSING, 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)
|
@ -13,6 +13,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from datetime import datetime
|
||||
from unittest import mock
|
||||
|
||||
from oslo_utils import uuidutils
|
||||
@ -23,6 +24,7 @@ from tacker.sol_refactored.common import exceptions as sol_ex
|
||||
from tacker.sol_refactored.controller import vnflcm_v2
|
||||
from tacker.sol_refactored.nfvo import nfvo_client
|
||||
from tacker.sol_refactored import objects
|
||||
from tacker.sol_refactored.objects.v2 import fields
|
||||
from tacker.tests.unit.db import base as db_base
|
||||
|
||||
|
||||
@ -30,11 +32,42 @@ class TestVnflcmV2(db_base.SqlTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestVnflcmV2, self).setUp()
|
||||
objects.register_all(False)
|
||||
objects.register_all()
|
||||
self.controller = vnflcm_v2.VnfLcmControllerV2()
|
||||
self.context = context.get_admin_context()
|
||||
self.context.api_version = api_version.APIVersion("2.0.0")
|
||||
self.request = mock.Mock()
|
||||
self.request.context = context.get_admin_context()
|
||||
self.request.context.api_version = api_version.APIVersion("2.0.0")
|
||||
self.request.context = self.context
|
||||
|
||||
def _create_inst_and_lcmocc(self, inst_state, op_state):
|
||||
inst = objects.VnfInstanceV2(
|
||||
# required fields
|
||||
id=uuidutils.generate_uuid(),
|
||||
vnfdId=uuidutils.generate_uuid(),
|
||||
vnfProvider='provider',
|
||||
vnfProductName='product name',
|
||||
vnfSoftwareVersion='software version',
|
||||
vnfdVersion='vnfd version',
|
||||
instantiationState=inst_state
|
||||
)
|
||||
|
||||
req = {"flavourId": "simple"} # instantate request
|
||||
lcmocc = objects.VnfLcmOpOccV2(
|
||||
# required fields
|
||||
id=uuidutils.generate_uuid(),
|
||||
operationState=op_state,
|
||||
stateEnteredTime=datetime.utcnow(),
|
||||
startTime=datetime.utcnow(),
|
||||
vnfInstanceId=inst.id,
|
||||
operation=fields.LcmOperationType.INSTANTIATE,
|
||||
isAutomaticInvocation=False,
|
||||
isCancelPending=False,
|
||||
operationParams=req)
|
||||
|
||||
inst.create(self.context)
|
||||
lcmocc.create(self.context)
|
||||
|
||||
return inst.id, lcmocc.id
|
||||
|
||||
@mock.patch.object(nfvo_client.NfvoClient, 'get_vnf_package_info_vnfd')
|
||||
def test_create_pkg_disabled(self, mocked_get_vnf_package_info_vnfd):
|
||||
@ -57,6 +90,56 @@ class TestVnflcmV2(db_base.SqlTestCase):
|
||||
self.assertRaises(sol_ex.VnfdIdNotEnabled,
|
||||
self.controller.create, request=self.request, body=body)
|
||||
|
||||
def test_delete_instantiated(self):
|
||||
inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED',
|
||||
fields.LcmOperationStateType.COMPLETED)
|
||||
|
||||
self.assertRaises(sol_ex.VnfInstanceIsInstantiated,
|
||||
self.controller.delete, request=self.request, id=inst_id)
|
||||
|
||||
def test_delete_lcmocc_in_progress(self):
|
||||
inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED',
|
||||
fields.LcmOperationStateType.FAILED_TEMP)
|
||||
|
||||
self.assertRaises(sol_ex.OtherOperationInProgress,
|
||||
self.controller.delete, request=self.request, id=inst_id)
|
||||
|
||||
def test_instantiate_instantiated(self):
|
||||
inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED',
|
||||
fields.LcmOperationStateType.COMPLETED)
|
||||
body = {"flavourId": "small"}
|
||||
|
||||
self.assertRaises(sol_ex.VnfInstanceIsInstantiated,
|
||||
self.controller.instantiate, request=self.request, id=inst_id,
|
||||
body=body)
|
||||
|
||||
def test_instantiate_lcmocc_in_progress(self):
|
||||
inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED',
|
||||
fields.LcmOperationStateType.FAILED_TEMP)
|
||||
body = {"flavourId": "small"}
|
||||
|
||||
self.assertRaises(sol_ex.OtherOperationInProgress,
|
||||
self.controller.instantiate, request=self.request, id=inst_id,
|
||||
body=body)
|
||||
|
||||
def test_terminate_not_instantiated(self):
|
||||
inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED',
|
||||
fields.LcmOperationStateType.COMPLETED)
|
||||
body = {"terminationType": "FORCEFUL"}
|
||||
|
||||
self.assertRaises(sol_ex.VnfInstanceIsNotInstantiated,
|
||||
self.controller.terminate, request=self.request, id=inst_id,
|
||||
body=body)
|
||||
|
||||
def test_terminate_lcmocc_in_progress(self):
|
||||
inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED',
|
||||
fields.LcmOperationStateType.FAILED_TEMP)
|
||||
body = {"terminationType": "FORCEFUL"}
|
||||
|
||||
self.assertRaises(sol_ex.OtherOperationInProgress,
|
||||
self.controller.terminate, request=self.request, id=inst_id,
|
||||
body=body)
|
||||
|
||||
def test_invalid_subscripion(self):
|
||||
body = {
|
||||
"callbackUri": "http://127.0.0.1:6789/notification",
|
||||
@ -92,3 +175,11 @@ class TestVnflcmV2(db_base.SqlTestCase):
|
||||
body=body)
|
||||
self.assertEqual("'TLS_CERT' is not supported at the moment.",
|
||||
ex.detail)
|
||||
|
||||
def test_retry_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_retry, request=self.request,
|
||||
id=lcmocc_id)
|
||||
|
Loading…
Reference in New Issue
Block a user