Merge "support retry operation task of v2 API"
This commit is contained in:
commit
4f2104a35b
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}
|
'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
|
# NOTE: 'DELETE' is not defined in the specification. It is for test
|
||||||
# use since it is convenient to be able to delete under development.
|
# use since it is convenient to be able to delete under development.
|
||||||
# It is available when config parameter
|
# It is available when config parameter
|
||||||
|
@ -45,6 +45,7 @@ class VnflcmAPIRouterV2(sol_wsgi.SolAPIRouter):
|
|||||||
("/subscriptions/{id}", {"GET": "subscription_show",
|
("/subscriptions/{id}", {"GET": "subscription_show",
|
||||||
"DELETE": "subscription_delete"}),
|
"DELETE": "subscription_delete"}),
|
||||||
("/vnf_lcm_op_occs", {"GET": "lcm_op_occ_list"}),
|
("/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
|
# NOTE: 'DELETE' is not defined in the specification. It is for test
|
||||||
# use since it is convenient to be able to delete under development.
|
# use since it is convenient to be able to delete under development.
|
||||||
# It is available when config parameter
|
# It is available when config parameter
|
||||||
|
@ -210,3 +210,12 @@ class ResponseTooBig(SolHttpError400):
|
|||||||
class LocalNfvoGrantFailed(SolHttpError403):
|
class LocalNfvoGrantFailed(SolHttpError403):
|
||||||
title = 'Grant failed'
|
title = 'Grant failed'
|
||||||
# detail set in the code
|
# 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)
|
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):
|
def make_lcmocc_links(lcmocc, endpoint):
|
||||||
links = objects.VnfLcmOpOccV2_Links()
|
links = objects.VnfLcmOpOccV2_Links()
|
||||||
links.self = objects.Link(href=lcmocc_href(lcmocc.id, endpoint))
|
links.self = objects.Link(href=lcmocc_href(lcmocc.id, endpoint))
|
||||||
links.vnfInstance = objects.Link(
|
links.vnfInstance = objects.Link(
|
||||||
href=inst_utils.inst_href(lcmocc.vnfInstanceId, endpoint))
|
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
|
# TODO(oda-g): add when implemented
|
||||||
# links.grant
|
# links.grant
|
||||||
# links.cancel
|
# links.cancel
|
||||||
# links.retry
|
|
||||||
# links.rollback
|
# links.rollback
|
||||||
# links.fail
|
# links.fail
|
||||||
# links.vnfSnapshot
|
# links.vnfSnapshot
|
||||||
@ -179,3 +185,27 @@ def make_instantiate_lcmocc(lcmocc, inst):
|
|||||||
|
|
||||||
def make_terminate_lcmocc(lcmocc, inst):
|
def make_terminate_lcmocc(lcmocc, inst):
|
||||||
_make_instantiate_lcmocc(lcmocc, inst, 'REMOVED')
|
_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,
|
fanout=False,
|
||||||
version='1.0')
|
version='1.0')
|
||||||
|
|
||||||
def start_lcm_op(self, context, lcmocc_id):
|
def _cast_lcm_op(self, context, lcmocc_id, method):
|
||||||
serializer = objects_base.TackerObjectSerializer()
|
serializer = objects_base.TackerObjectSerializer()
|
||||||
|
|
||||||
client = rpc.get_client(self.target, version_cap=None,
|
client = rpc.get_client(self.target, version_cap=None,
|
||||||
serializer=serializer)
|
serializer=serializer)
|
||||||
cctxt = client.prepare()
|
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.endpoint = CONF.v2_vnfm.endpoint
|
||||||
self.nfvo_client = nfvo_client.NfvoClient()
|
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):
|
def _set_lcmocc_error(self, lcmocc, ex):
|
||||||
if isinstance(ex, sol_ex.SolException):
|
if isinstance(ex, sol_ex.SolException):
|
||||||
problem_details = ex.make_problem_details()
|
problem_details = ex.make_problem_details()
|
||||||
@ -84,10 +80,13 @@ class ConductorV2(object):
|
|||||||
|
|
||||||
# NOTE: perform grant exchange mainly but also perform
|
# NOTE: perform grant exchange mainly but also perform
|
||||||
# something to do at STATING phase ex. request check.
|
# something to do at STATING phase ex. request check.
|
||||||
grant_method = self._get_lcm_op_method(lcmocc.operation, 'grant')
|
grant_req, grant = self.vnflcm_driver.grant(context, lcmocc,
|
||||||
grant_req, grant = grant_method(context, lcmocc, inst, vnfd)
|
inst, vnfd)
|
||||||
|
self.vnflcm_driver.post_grant(context, lcmocc, inst, grant_req,
|
||||||
|
grant, vnfd)
|
||||||
|
|
||||||
lcmocc.operationState = fields.LcmOperationStateType.PROCESSING
|
lcmocc.operationState = fields.LcmOperationStateType.PROCESSING
|
||||||
|
lcmocc.grantId = grant.id
|
||||||
lcmocc.update(context)
|
lcmocc.update(context)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.exception("STARTING %s failed", lcmocc.operation)
|
LOG.exception("STARTING %s failed", lcmocc.operation)
|
||||||
@ -103,17 +102,8 @@ class ConductorV2(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# perform preamble LCM script
|
self.vnflcm_driver.process(context, lcmocc, inst, grant_req,
|
||||||
start_method = self._get_lcm_op_method(lcmocc.operation, 'start')
|
grant, vnfd)
|
||||||
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)
|
|
||||||
|
|
||||||
lcmocc.operationState = fields.LcmOperationStateType.COMPLETED
|
lcmocc.operationState = fields.LcmOperationStateType.COMPLETED
|
||||||
# update inst and lcmocc at the same time
|
# update inst and lcmocc at the same time
|
||||||
@ -124,8 +114,70 @@ class ConductorV2(object):
|
|||||||
LOG.exception("PROCESSING %s failed", lcmocc.operation)
|
LOG.exception("PROCESSING %s failed", lcmocc.operation)
|
||||||
lcmocc.operationState = fields.LcmOperationStateType.FAILED_TEMP
|
lcmocc.operationState = fields.LcmOperationStateType.FAILED_TEMP
|
||||||
self._set_lcmocc_error(lcmocc, ex)
|
self._set_lcmocc_error(lcmocc, ex)
|
||||||
|
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)
|
lcmocc.update(context)
|
||||||
|
|
||||||
# send notification COMPLETED or FAILED_TEMP
|
# send notification COMPLETED or FAILED_TEMP
|
||||||
self.nfvo_client.send_lcmocc_notification(context, lcmocc, inst,
|
self.nfvo_client.send_lcmocc_notification(context, lcmocc, inst,
|
||||||
self.endpoint)
|
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,
|
||||||
|
self.endpoint)
|
||||||
|
@ -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.infra_drivers.openstack import openstack
|
||||||
from tacker.sol_refactored.nfvo import nfvo_client
|
from tacker.sol_refactored.nfvo import nfvo_client
|
||||||
from tacker.sol_refactored import objects
|
from tacker.sol_refactored import objects
|
||||||
|
from tacker.sol_refactored.objects.v2 import fields as v2fields
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -41,6 +42,68 @@ class VnfLcmDriverV2(object):
|
|||||||
self.endpoint = CONF.v2_vnfm.endpoint
|
self.endpoint = CONF.v2_vnfm.endpoint
|
||||||
self.nfvo_client = nfvo_client.NfvoClient()
|
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):
|
def _get_link_ports(self, inst_req):
|
||||||
names = []
|
names = []
|
||||||
if inst_req.obj_attr_is_set('extVirtualLinks'):
|
if inst_req.obj_attr_is_set('extVirtualLinks'):
|
||||||
@ -167,19 +230,24 @@ class VnfLcmDriverV2(object):
|
|||||||
href=inst_utils.inst_href(inst.id, self.endpoint)))
|
href=inst_utils.inst_href(inst.id, self.endpoint)))
|
||||||
|
|
||||||
# NOTE: if not granted, 403 error raised.
|
# 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
|
# set inst vimConnectionInfo
|
||||||
|
req = lcmocc.operationParams
|
||||||
vim_infos = {}
|
vim_infos = {}
|
||||||
if req.obj_attr_is_set('vimConnectionInfo'):
|
if req.obj_attr_is_set('vimConnectionInfo'):
|
||||||
vim_infos = req.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.
|
# if NFVO returns vimConnectionInfo use it.
|
||||||
# As the controller does for req.vimConnectionInfo, if accessInfo
|
# As the controller does for req.vimConnectionInfo, if accessInfo
|
||||||
# or interfaceInfo is not specified, get them from VIM DB.
|
# or interfaceInfo is not specified, get them from VIM DB.
|
||||||
# vimId must be in 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():
|
for key, res_vim_info in res_vim_infos.items():
|
||||||
if not (res_vim_info.obj_attr_is_set('accessInfo') and
|
if not (res_vim_info.obj_attr_is_set('accessInfo') and
|
||||||
res_vim_info.obj_attr_is_set('interfaceInfo')):
|
res_vim_info.obj_attr_is_set('interfaceInfo')):
|
||||||
@ -199,8 +267,6 @@ class VnfLcmDriverV2(object):
|
|||||||
|
|
||||||
inst.vimConnectionInfo = vim_infos
|
inst.vimConnectionInfo = vim_infos
|
||||||
|
|
||||||
return grant_req, grant_res
|
|
||||||
|
|
||||||
def instantiate_process(self, context, lcmocc, inst, grant_req,
|
def instantiate_process(self, context, lcmocc, inst, grant_req,
|
||||||
grant, vnfd):
|
grant, vnfd):
|
||||||
req = lcmocc.operationParams
|
req = lcmocc.operationParams
|
||||||
@ -215,49 +281,6 @@ class VnfLcmDriverV2(object):
|
|||||||
inst.instantiationState = 'INSTANTIATED'
|
inst.instantiationState = 'INSTANTIATED'
|
||||||
lcmocc_utils.make_instantiate_lcmocc(lcmocc, inst)
|
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):
|
def terminate_grant(self, context, lcmocc, inst, vnfd):
|
||||||
# grant exchange
|
# grant exchange
|
||||||
# NOTE: the api_version of NFVO supposes 1.4.0 at the moment.
|
# NOTE: the api_version of NFVO supposes 1.4.0 at the moment.
|
||||||
@ -381,17 +404,3 @@ class VnfLcmDriverV2(object):
|
|||||||
|
|
||||||
# reset vimConnectionInfo
|
# reset vimConnectionInfo
|
||||||
inst.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}')
|
@coordinate.lock_vnf_instance('{id}')
|
||||||
def delete(self, request, id):
|
def delete(self, request, id):
|
||||||
context = request.context
|
context = request.context
|
||||||
inst = inst_utils.get_inst(request.context, id)
|
inst = inst_utils.get_inst(context, id)
|
||||||
|
|
||||||
if inst.instantiationState != 'NOT_INSTANTIATED':
|
if inst.instantiationState != 'NOT_INSTANTIATED':
|
||||||
raise sol_ex.VnfInstanceIsInstantiated(inst_id=id)
|
raise sol_ex.VnfInstanceIsInstantiated(inst_id=id)
|
||||||
|
|
||||||
|
lcmocc_utils.check_lcmocc_in_progress(context, id)
|
||||||
|
|
||||||
inst.delete(context)
|
inst.delete(context)
|
||||||
|
|
||||||
# NOTE: inst record in DB deleted but inst object still
|
# NOTE: inst record in DB deleted but inst object still
|
||||||
@ -151,6 +153,8 @@ class VnfLcmControllerV2(sol_wsgi.SolAPIController):
|
|||||||
if inst.instantiationState != 'NOT_INSTANTIATED':
|
if inst.instantiationState != 'NOT_INSTANTIATED':
|
||||||
raise sol_ex.VnfInstanceIsInstantiated(inst_id=id)
|
raise sol_ex.VnfInstanceIsInstantiated(inst_id=id)
|
||||||
|
|
||||||
|
lcmocc_utils.check_lcmocc_in_progress(context, id)
|
||||||
|
|
||||||
now = datetime.utcnow()
|
now = datetime.utcnow()
|
||||||
lcmocc = objects.VnfLcmOpOccV2(
|
lcmocc = objects.VnfLcmOpOccV2(
|
||||||
id=uuidutils.generate_uuid(),
|
id=uuidutils.generate_uuid(),
|
||||||
@ -192,6 +196,8 @@ class VnfLcmControllerV2(sol_wsgi.SolAPIController):
|
|||||||
if inst.instantiationState != 'INSTANTIATED':
|
if inst.instantiationState != 'INSTANTIATED':
|
||||||
raise sol_ex.VnfInstanceIsNotInstantiated(inst_id=id)
|
raise sol_ex.VnfInstanceIsNotInstantiated(inst_id=id)
|
||||||
|
|
||||||
|
lcmocc_utils.check_lcmocc_in_progress(context, id)
|
||||||
|
|
||||||
now = datetime.utcnow()
|
now = datetime.utcnow()
|
||||||
lcmocc = objects.VnfLcmOpOccV2(
|
lcmocc = objects.VnfLcmOpOccV2(
|
||||||
id=uuidutils.generate_uuid(),
|
id=uuidutils.generate_uuid(),
|
||||||
@ -323,6 +329,21 @@ class VnfLcmControllerV2(sol_wsgi.SolAPIController):
|
|||||||
|
|
||||||
return sol_wsgi.SolResponse(200, resp_body)
|
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):
|
def lcm_op_occ_delete(self, request, id):
|
||||||
# not allowed to delete on the specification
|
# not allowed to delete on the specification
|
||||||
if not CONF.v2_vnfm.test_enable_lcm_op_occ_delete:
|
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)
|
changedExtConnectivity = sa.Column(sa.JSON(), nullable=True)
|
||||||
modificationsTriggeredByVnfPkgChange = sa.Column(sa.JSON(), nullable=True)
|
modificationsTriggeredByVnfPkgChange = sa.Column(sa.JSON(), nullable=True)
|
||||||
vnfSnapshotInfoId = sa.Column(sa.String(255), 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,
|
self.client = http_client.HttpClient(auth,
|
||||||
service_type='orchestration')
|
service_type='orchestration')
|
||||||
|
|
||||||
def create_stack(self, fields):
|
def create_stack(self, fields, wait=True):
|
||||||
path = "stacks"
|
path = "stacks"
|
||||||
resp, body = self.client.do_request(path, "POST",
|
resp, body = self.client.do_request(path, "POST",
|
||||||
expected_status=[201], body=fields)
|
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)
|
path = "stacks/{}".format(stack_name)
|
||||||
resp, body = self.client.do_request(path, "DELETE",
|
resp, body = self.client.do_request(path, "DELETE",
|
||||||
expected_status=[204, 404])
|
expected_status=[204, 404])
|
||||||
|
|
||||||
|
if wait:
|
||||||
|
self.wait_stack_delete(stack_name)
|
||||||
|
|
||||||
def get_status(self, stack_name):
|
def get_status(self, stack_name):
|
||||||
path = "stacks/{}".format(stack_name)
|
path = "stacks/{}".format(stack_name)
|
||||||
resp, body = self.client.do_request(path, "GET",
|
resp, body = self.client.do_request(path, "GET",
|
||||||
@ -100,6 +114,10 @@ class HeatClient(object):
|
|||||||
self._wait_completion(stack_name, "Stack create",
|
self._wait_completion(stack_name, "Stack create",
|
||||||
"CREATE_COMPLETE", "CREATE_IN_PROGRESS", "CREATE_FAILED")
|
"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):
|
def wait_stack_delete(self, stack_name):
|
||||||
self._wait_completion(stack_name, "Stack delete",
|
self._wait_completion(stack_name, "Stack delete",
|
||||||
"DELETE_COMPLETE", "DELETE_IN_PROGRESS", "DELETE_FAILED",
|
"DELETE_COMPLETE", "DELETE_IN_PROGRESS", "DELETE_FAILED",
|
||||||
|
@ -46,14 +46,16 @@ class Openstack(object):
|
|||||||
|
|
||||||
LOG.debug("stack fields: %s", fields)
|
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)
|
vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
|
||||||
heat_client = heat_utils.HeatClient(vim_info)
|
heat_client = heat_utils.HeatClient(vim_info)
|
||||||
|
status, _ = heat_client.get_status(stack_name)
|
||||||
|
if status is None:
|
||||||
heat_client.create_stack(fields)
|
heat_client.create_stack(fields)
|
||||||
|
else:
|
||||||
# wait stack created
|
heat_client.update_stack(stack_name, fields)
|
||||||
stack_name = fields['stack_name']
|
|
||||||
heat_client.wait_stack_create(stack_name)
|
|
||||||
|
|
||||||
# get stack resource
|
# get stack resource
|
||||||
heat_reses = heat_client.get_resources(stack_name)
|
heat_reses = heat_client.get_resources(stack_name)
|
||||||
@ -532,4 +534,3 @@ class Openstack(object):
|
|||||||
heat_client = heat_utils.HeatClient(vim_info)
|
heat_client = heat_utils.HeatClient(vim_info)
|
||||||
stack_name = heat_utils.get_stack_name(inst)
|
stack_name = heat_utils.get_stack_name(inst)
|
||||||
heat_client.delete_stack(stack_name)
|
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
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from tacker.sol_refactored.db import api as db_api
|
|
||||||
from tacker.sol_refactored.objects.common import fields # noqa
|
from tacker.sol_refactored.objects.common import fields # noqa
|
||||||
|
|
||||||
# NOTE: You may scratch your head as you see code that imports
|
# 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.
|
# the object.
|
||||||
|
|
||||||
|
|
||||||
def register_all(init_db=True):
|
def register_all():
|
||||||
# NOTE: You must make sure your object gets imported in this
|
# NOTE: You must make sure your object gets imported in this
|
||||||
# function in order for it to be registered by services that may
|
# function in order for it to be registered by services that may
|
||||||
# need to receive it via RPC.
|
# 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_snapshot')
|
||||||
__import__(objects_root + '.v2.vnf_state_snapshot_info')
|
__import__(objects_root + '.v2.vnf_state_snapshot_info')
|
||||||
__import__(objects_root + '.v2.vnf_virtual_link_resource_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 base as ovoo_base
|
||||||
from oslo_versionedobjects import exception as ovoo_exc
|
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.db.sqlalchemy import models
|
||||||
from tacker.sol_refactored import objects
|
from tacker.sol_refactored import objects
|
||||||
from tacker.sol_refactored.objects import fields as obj_fields
|
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():
|
for name, field in self.fields.items():
|
||||||
if not self.obj_attr_is_set(name):
|
if not self.obj_attr_is_set(name):
|
||||||
continue
|
continue
|
||||||
|
if getattr(self, name) is None:
|
||||||
|
continue
|
||||||
if isinstance(field, obj_fields.ObjectField):
|
if isinstance(field, obj_fields.ObjectField):
|
||||||
obj[name] = getattr(self, name).to_dict()
|
obj[name] = getattr(self, name).to_dict()
|
||||||
elif isinstance(field, obj_fields.ListOfObjectsField):
|
elif isinstance(field, obj_fields.ListOfObjectsField):
|
||||||
@ -378,6 +380,14 @@ class TackerPersistentObject(TackerObject):
|
|||||||
result = query.all()
|
result = query.all()
|
||||||
return [cls.from_db_obj(item) for item in result]
|
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
|
@classmethod
|
||||||
def from_db_obj(cls, db_obj):
|
def from_db_obj(cls, db_obj):
|
||||||
inst = cls()
|
inst = cls()
|
||||||
@ -417,6 +427,9 @@ class TackerPersistentObject(TackerObject):
|
|||||||
name_ = get_model_field(name)
|
name_ = get_model_field(name)
|
||||||
if not self.obj_attr_is_set(name):
|
if not self.obj_attr_is_set(name):
|
||||||
continue
|
continue
|
||||||
|
if getattr(self, name) is None:
|
||||||
|
obj[name_] = None
|
||||||
|
continue
|
||||||
if isinstance(field, obj_fields.ObjectField):
|
if isinstance(field, obj_fields.ObjectField):
|
||||||
obj[name_] = getattr(self, name).to_json()
|
obj[name_] = getattr(self, name).to_json()
|
||||||
elif isinstance(field, obj_fields.ListOfObjectsField):
|
elif isinstance(field, obj_fields.ListOfObjectsField):
|
||||||
|
@ -21,7 +21,8 @@ from tacker.sol_refactored.objects.v1 import fields as v1fields
|
|||||||
# NFV-SOL 003
|
# NFV-SOL 003
|
||||||
# - v3.3.1 9.5.2.2 (API version: 1.4.0)
|
# - v3.3.1 9.5.2.2 (API version: 1.4.0)
|
||||||
@base.TackerObjectRegistry.register
|
@base.TackerObjectRegistry.register
|
||||||
class GrantRequestV1(base.TackerObject, base.TackerObjectDictCompat):
|
class GrantRequestV1(base.TackerPersistentObject,
|
||||||
|
base.TackerObjectDictCompat):
|
||||||
|
|
||||||
# Version 1.0: Initial version
|
# Version 1.0: Initial version
|
||||||
VERSION = '1.0'
|
VERSION = '1.0'
|
||||||
|
@ -89,6 +89,11 @@ class Client(object):
|
|||||||
path, "POST", body=req_body, version="2.0.0")
|
path, "POST", body=req_body, version="2.0.0")
|
||||||
self.print(resp, body)
|
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():
|
def usage():
|
||||||
print("usage: cli resource action [arg...]")
|
print("usage: cli resource action [arg...]")
|
||||||
@ -105,67 +110,64 @@ def usage():
|
|||||||
print(" lcmocc list [body(path of content)]")
|
print(" lcmocc list [body(path of content)]")
|
||||||
print(" lcmocc show {id}")
|
print(" lcmocc show {id}")
|
||||||
print(" lcmocc delete {id}")
|
print(" lcmocc delete {id}")
|
||||||
|
print(" lcmocc retry {id}")
|
||||||
os._exit(1)
|
os._exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def get_body(arg):
|
||||||
|
with open(arg) as fp:
|
||||||
|
return json.load(fp)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
if len(sys.argv) < 3:
|
if len(sys.argv) < 3:
|
||||||
usage()
|
usage()
|
||||||
resource = sys.argv[1]
|
resource = sys.argv[1]
|
||||||
action = sys.argv[2]
|
action = sys.argv[2]
|
||||||
if resource not in ["inst", "subsc", "lcmocc"]:
|
|
||||||
usage()
|
|
||||||
if resource == "inst":
|
if resource == "inst":
|
||||||
if action not in ["create", "list", "show", "delete", "inst", "term"]:
|
if action not in ["create", "list", "show", "delete", "inst", "term"]:
|
||||||
usage()
|
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")
|
client = Client("/vnflcm/v2/vnf_instances")
|
||||||
elif resource == "subsc":
|
elif resource == "subsc":
|
||||||
|
if action not in ["create", "list", "show", "delete"]:
|
||||||
|
usage()
|
||||||
client = Client("/vnflcm/v2/subscriptions")
|
client = Client("/vnflcm/v2/subscriptions")
|
||||||
elif resource == "lcmocc":
|
elif resource == "lcmocc":
|
||||||
|
if action not in ["list", "show", "delete", "retry"]:
|
||||||
|
usage()
|
||||||
client = Client("/vnflcm/v2/vnf_lcm_op_occs")
|
client = Client("/vnflcm/v2/vnf_lcm_op_occs")
|
||||||
|
else:
|
||||||
|
usage()
|
||||||
|
|
||||||
if action == "create":
|
if action == "create":
|
||||||
with open(arg1) as fp:
|
if len(sys.argv) != 4:
|
||||||
body = json.load(fp)
|
usage()
|
||||||
client.create(body)
|
client.create(get_body(sys.argv[3]))
|
||||||
elif action == "list":
|
elif action == "list":
|
||||||
body = None
|
body = None
|
||||||
if arg1 is not None:
|
if len(sys.argv) == 4:
|
||||||
with open(arg1) as fp:
|
body = get_body(sys.argv[3])
|
||||||
body = json.load(fp)
|
elif len(sys.argv) != 3:
|
||||||
|
usage()
|
||||||
client.list(body)
|
client.list(body)
|
||||||
elif action == "show":
|
elif action == "show":
|
||||||
client.show(arg1)
|
if len(sys.argv) != 4:
|
||||||
|
usage()
|
||||||
|
client.show(sys.argv[3])
|
||||||
elif action == "delete":
|
elif action == "delete":
|
||||||
client.delete(arg1)
|
if len(sys.argv) != 4:
|
||||||
|
usage()
|
||||||
|
client.delete(sys.argv[3])
|
||||||
elif action == "inst":
|
elif action == "inst":
|
||||||
with open(arg2) as fp:
|
if len(sys.argv) != 5:
|
||||||
body = json.load(fp)
|
usage()
|
||||||
client.inst(arg1, body)
|
client.inst(sys.argv[3], get_body(sys.argv[4]))
|
||||||
elif action == "term":
|
elif action == "term":
|
||||||
with open(arg2) as fp:
|
if len(sys.argv) != 5:
|
||||||
body = json.load(fp)
|
usage()
|
||||||
client.term(arg1, body)
|
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'],
|
cfg.CONF(args=['--config-file', '/etc/tacker/tacker.conf'],
|
||||||
project='tacker',
|
project='tacker',
|
||||||
version='%%prog %s' % version.version_info.release_string())
|
version='%%prog %s' % version.version_info.release_string())
|
||||||
objects.register_all(False)
|
objects.register_all()
|
||||||
|
|
||||||
vim_info = cls.get_vim_info()
|
vim_info = cls.get_vim_info()
|
||||||
cls.auth_url = vim_info.interfaceInfo['endpoint']
|
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
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from oslo_utils import uuidutils
|
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.controller import vnflcm_v2
|
||||||
from tacker.sol_refactored.nfvo import nfvo_client
|
from tacker.sol_refactored.nfvo import nfvo_client
|
||||||
from tacker.sol_refactored import objects
|
from tacker.sol_refactored import objects
|
||||||
|
from tacker.sol_refactored.objects.v2 import fields
|
||||||
from tacker.tests.unit.db import base as db_base
|
from tacker.tests.unit.db import base as db_base
|
||||||
|
|
||||||
|
|
||||||
@ -30,11 +32,42 @@ class TestVnflcmV2(db_base.SqlTestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestVnflcmV2, self).setUp()
|
super(TestVnflcmV2, self).setUp()
|
||||||
objects.register_all(False)
|
objects.register_all()
|
||||||
self.controller = vnflcm_v2.VnfLcmControllerV2()
|
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 = mock.Mock()
|
||||||
self.request.context = context.get_admin_context()
|
self.request.context = self.context
|
||||||
self.request.context.api_version = api_version.APIVersion("2.0.0")
|
|
||||||
|
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')
|
@mock.patch.object(nfvo_client.NfvoClient, 'get_vnf_package_info_vnfd')
|
||||||
def test_create_pkg_disabled(self, mocked_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.assertRaises(sol_ex.VnfdIdNotEnabled,
|
||||||
self.controller.create, request=self.request, body=body)
|
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):
|
def test_invalid_subscripion(self):
|
||||||
body = {
|
body = {
|
||||||
"callbackUri": "http://127.0.0.1:6789/notification",
|
"callbackUri": "http://127.0.0.1:6789/notification",
|
||||||
@ -92,3 +175,11 @@ class TestVnflcmV2(db_base.SqlTestCase):
|
|||||||
body=body)
|
body=body)
|
||||||
self.assertEqual("'TLS_CERT' is not supported at the moment.",
|
self.assertEqual("'TLS_CERT' is not supported at the moment.",
|
||||||
ex.detail)
|
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…
x
Reference in New Issue
Block a user