Support VNF update operations based on ETSI NFV

Supported ETSI SOL003 Modify VNF function.
Added processing for receiving Individual VNF Instances(PATCH).

* PATCH /vnflcm/v1/vnf_instances/{vnfInstanceId}

Implements: blueprint support-etsi-nfv-specs
Spec: https://specs.openstack.org/openstack/tacker-specs/specs/victoria/support-vnf-update-api-based-on-etsi-nfv-sol.html

Change-Id: If8c37bcf2fcd9094f3be4d212bdf79c322ae2705
This commit is contained in:
Aldinson Esto 2020-08-21 21:11:24 +09:00 committed by Aldinson C. Esto
parent 172095279b
commit 797554a378
23 changed files with 1530 additions and 56 deletions

View File

@ -1189,6 +1189,50 @@ vnf_instance_metadata:
in: body
required: false
type: object
vnf_instance_modify_request_description:
description: |
New value of the "vnfInstanceDescription" attribute in
"VnfInstance", or "null" to remove the attribute.
in: body
required: false
type: string
vnf_instance_modify_request_metadata:
description: |
Modifications of the "metadata" attribute in
"VnfInstance". If present, these modifications shall be
applied according to the rules of JSON Merge PATCH
in: body
required: false
type: string
vnf_instance_modify_request_name:
description: |
New value of the "vnfInstanceName" attribute in
"VnfInstance", or "null" to remove the attribute.
in: body
required: false
type: string
vnf_instance_modify_request_vim_connection_info:
description: |
New content of certain entries in the
"vimConnectionInfo" attribute array in "VnfInstance", as
defined below this table.
in: body
required: false
type: string
vnf_instance_modify_request_vnf_pkg_id:
description: |
New value of the "vnfPkgId" attribute in "VnfInstance". The
value "null" is not permitted.
in: body
required: false
type: string
vnf_instance_modify_request_vnfd_id:
description: |
New value of the "vnfdId" attribute in "VnfInstance". The
value "null" is not permitted.
in: body
required: false
type: string
vnf_instance_name:
description: |
Name of the VNF instance.

View File

@ -0,0 +1,3 @@
{
"vnfdId": "093c38b5-a731-4593-a578-d12e42596b3e"
}

View File

@ -570,6 +570,66 @@ Response Example
.. literalinclude:: samples/vnflcm/list-vnf-instance-response.json
:language: javascript
Modify a VNF instance
========================
.. rest_method:: POST /vnflcm/v1/vnf_instances/{vnfInstanceId}
This method modifies an "Individual VNF instance" resource.
Changes to the VNF configurable properties are applied to the configuration in the VNF instance, and are reflected in
the representation of this resource. Other changes are applied to the VNF instance information managed by the VNFM,
and are reflected in the representation of this resource.
According to the ETSI NFV SOL document, there is no API request/response
specification for Etag yet, and transactions using Etag are not defined
by standardization. Therefore, the Victoria release does not support
`Error Code: 412 Precondition Failed`. Once a standard specification
for this is established, it will be installed on the tacker.
Response Codes
--------------
.. rest_status_code:: success status.yaml
- 202
.. rest_status_code:: error status.yaml
- 400
- 401
- 403
- 404
- 409
Request Parameters
------------------
.. rest_parameters:: parameters_vnflcm.yaml
- vnfInstanceId: vnf_instance_id
- vnfInstanceName: vnf_instance_modify_request_name
- vnfInstanceDescription: vnf_instance_modify_request_description
- vnfdId: vnf_instance_modify_request_vnfd_id
- metadata: vnf_instance_modify_request_metadata
- vimConnectionInfo: vnf_instance_modify_request_vim_connection_info
- id: vim_connection_info_id
- vimId: vim_connection_info_vim_id
- vimType: vim_connection_info_vim_type
- interfaceInfo: vim_connection_info_interface_info
- endpoint: vim_connection_info_interface_info_endpoint
- accessInfo: vim_connection_info_access_info
- username: vim_connection_info_access_info_username
- region: vim_connection_info_access_info_region
- password: vim_connection_info_access_info_password
- tenant: vim_connection_info_access_info_tenant
Request Example
---------------
.. literalinclude:: samples/vnflcm/modify-vnf-instance-request.json
:language: javascript
Show VNF LCM operation occurrence
=================================

View File

@ -239,3 +239,16 @@ register_subscription = {
'required': ['callbackUri'],
'additionalProperties': False,
}
update = {
'type': 'object',
'properties': {
'vnfdId': parameter_types.uuid,
'vnfInstanceName': parameter_types.name_allow_zero_min_length,
'vnfInstanceDescription': parameter_types.description,
'vnfPkgId': parameter_types.uuid,
'metadata': parameter_types.keyvalue_pairs,
'vimConnectionInfo': _vimConnectionInfo,
},
'additionalProperties': False,
}

View File

@ -695,6 +695,100 @@ class VnfLcmController(wsgi.Controller):
return self._view_builder.show_lcm_op_occs(vnf_lcm_op_occs)
@wsgi.response(http_client.OK)
@wsgi.expected_errors((http_client.FORBIDDEN, http_client.NOT_FOUND))
def update(self, request, id, body):
context = request.environ['tacker.context']
context.can(vnf_lcm_policies.VNFLCM % 'update_vnf')
# get body
req_body = utils.convert_camelcase_to_snakecase(body)
# According to the ETSI NFV SOL document,
# there is no API request/response
# specification for Etag yet,
# and transactions using Etag are not defined
# by standardization. Therefore, the Victoria release does not support
# `Error Code: 412 Precondition Failed`. Once a standard specification
# for this is established, it will be installed on the tacker.
# Confirmation of update target
try:
vnf_data = objects.VNF.vnf_index_list(id, context)
if not vnf_data:
msg = _("Can not find requested vnf data: %s") % id
return self._make_problem_detail(msg, 404, title='Not Found')
except Exception as e:
return self._make_problem_detail(
str(e), 500, 'Internal Server Error')
if (vnf_data.get("status") != fields.VnfStatus.ACTIVE and
vnf_data.get("status") != fields.VnfStatus.INACTIVE):
msg = _("VNF %(id)s status is %(state)s")
return self._make_problem_detail(msg % {"id": id,
"state": vnf_data.get("status")}, 409, 'Conflict')
try:
vnf_instance_data = objects.VnfInstanceList.vnf_instance_list(
vnf_data.get('vnfd_id'), context)
if not vnf_instance_data:
msg = _("Can not find requested vnf instance data: %s") \
% vnf_data.get('vnfd_id')
return self._make_problem_detail(msg, 404, title='Not Found')
except Exception as e:
return self._make_problem_detail(
str(e), 500, 'Internal Server Error')
if req_body['vnfd_id']:
try:
pkg_obj = objects.VnfPackageVnfd(context=context)
vnfd_pkg = pkg_obj.get_vnf_package_vnfd(req_body['vnfd_id'])
if not vnfd_pkg:
msg = _(
"Can not find requested vnf package vnfd: %s") %\
req_body['vnfd_id']
return self._make_problem_detail(msg, 400, 'Bad Request')
except Exception as e:
return self._make_problem_detail(
str(e), 500, 'Internal Server Error')
vnfd_pkg_data = {}
vnfd_pkg_data['vnf_provider'] = vnfd_pkg.get('vnf_provider')
vnfd_pkg_data['vnf_product_name'] = vnfd_pkg.get(
'vnf_product_name')
vnfd_pkg_data['vnf_software_version'] = vnfd_pkg.get(
'vnf_software_version')
vnfd_pkg_data['vnfd_version'] = vnfd_pkg.get('vnfd_version')
vnfd_pkg_data['package_uuid'] = vnfd_pkg.get('package_uuid')
# make op_occs_uuid
op_occs_uuid = uuidutils.generate_uuid()
# process vnf
if not req_body['vnfd_id']:
vnfd_pkg_data = ""
vnf_lcm_opoccs = {
'vnf_instance_id': id,
'id': op_occs_uuid,
'state_entered_time': vnf_data.get('updated_at'),
'operationParams': str(body)}
self.rpc_api.update(
context,
vnf_lcm_opoccs,
req_body,
vnfd_pkg_data,
vnf_data.get('vnfd_id'))
# make response
res = webob.Response(content_type='application/json')
res.status_int = 202
loc_url = CONF.vnf_lcm.endpoint_url + \
'/vnflcm/v1/vnf_lcm_op_occs/' + op_occs_uuid
location = ('Location', loc_url)
res.headerlist.append(location)
return res
@wsgi.response(http_client.CREATED)
@validation.schema(vnf_lcm.register_subscription)
def register_subscription(self, request, body):

View File

@ -59,7 +59,7 @@ class VnflcmAPIRouter(wsgi.Router):
# Allowed methods on
# /vnflcm/v1/vnf_instances/{vnfInstanceId} resource
methods = {"DELETE": "delete", "GET": "show"}
methods = {"DELETE": "delete", "GET": "show", "PATCH": "update"}
self._setup_route(mapper, "/vnf_instances/{id}",
methods, controller, default_resource)

View File

@ -157,6 +157,79 @@ def revert_upload_vnf_package(function):
return decorated_function
@utils.expects_func_args('vnf_lcm_opoccs')
def revert_update_lcm(function):
"""Decorator to revert update_lcm on failure."""
@functools.wraps(function)
def decorated_function(self, context, *args, **kwargs):
try:
return function(self, context, *args, **kwargs)
except Exception as exp:
LOG.error("update vnf_instance failed %s" % exp)
with excutils.save_and_reraise_exception():
wrapped_func = safe_utils.get_wrapped_function(function)
keyed_args = inspect.getcallargs(wrapped_func, self, context,
*args, **kwargs)
context = keyed_args['context']
vnf_lcm_opoccs = keyed_args['vnf_lcm_opoccs']
try:
# update vnf
vnf_now = timeutils.utcnow()
vnf_obj = objects.vnf.VNF(context=context)
vnf_obj.id = vnf_lcm_opoccs.get('vnf_instance_id')
vnf_obj.status = 'ERROR'
vnf_obj.updated_at = vnf_now
vnf_obj.save()
e_msg = str(exp)
# update lcm_op_occs
problem_obj = objects.vnf_lcm_op_occs.ProblemDetails()
problem_obj.status = '500'
problem_obj.detail = e_msg
lcm_op_obj = objects.vnf_lcm_op_occs.VnfLcmOpOcc(
context=context)
lcm_op_obj.id = vnf_lcm_opoccs.get('id')
lcm_op_obj.operation_state =\
fields.LcmOccsOperationState.FAILED_TEMP
lcm_op_obj.error = problem_obj
lcm_op_obj.state_entered_time = vnf_now
lcm_op_obj.updated_at = vnf_now
lcm_op_obj.save()
# Notification
notification = {}
notification['notificationType'] = \
'VnfLcmOperationOccurrenceNotification'
notification['notificationStatus'] = 'RESULT'
notification['operationState'] = \
fields.LcmOccsOperationState.FAILED_TEMP
notification['vnfInstanceId'] = vnf_lcm_opoccs.get(
'vnf_instance_id')
notification['operation'] = 'MODIFY_INFO'
notification['isAutomaticInvocation'] = 'False'
notification['vnfLcmOpOccId'] = vnf_lcm_opoccs.get('id')
notification['error'] = jsonutils.dumps(
problem_obj.to_dict())
instance_url = self._get_vnf_instance_href(
vnf_lcm_opoccs.get('vnf_instance_id'))
lcm_url = self._get_vnf_lcm_op_occs_href(
vnf_lcm_opoccs.get('id'))
notification['_links'] = {
'vnfInstance': {
'href': instance_url},
'vnfLcmOpOcc': {
'href': lcm_url}}
self.send_notification(context, notification)
except Exception as msg:
LOG.error("revert_update_lcm failed %s" % str(msg))
return decorated_function
class Conductor(manager.Manager):
def __init__(self, host, conf=None):
if conf:
@ -180,6 +253,12 @@ class Conductor(manager.Manager):
glance_store.initialize_glance_store()
self._basic_config_check()
def _get_vnf_instance_href(self, vnf_instance_id):
return '/vnflcm/v1/vnf_instances/%s' % vnf_instance_id
def _get_vnf_lcm_op_occs_href(self, vnf_lcm_op_occs_id):
return '/vnflcm/v1/vnf_lcm_op_occs/%s' % vnf_lcm_op_occs_id
def _basic_config_check(self):
if not os.path.isdir(CONF.vnf_package.vnf_package_csar_path):
LOG.error("Config option 'vnf_package_csar_path' is not "
@ -1100,6 +1179,100 @@ class Conductor(manager.Manager):
auth_type=auth_type,
auth_params=auth_params)
@revert_update_lcm
def update(
self,
context,
vnf_lcm_opoccs,
body_data,
vnfd_pkg_data,
vnfd_id):
# input vnf_lcm_op_occs
now = timeutils.utcnow()
lcm_op_obj = objects.vnf_lcm_op_occs.VnfLcmOpOcc(context=context)
lcm_op_obj.id = vnf_lcm_opoccs.get('id')
lcm_op_obj.operation_state = fields.LcmOccsOperationState.PROCESSING
lcm_op_obj.state_entered_time = vnf_lcm_opoccs.get(
'state_entered_time')
lcm_op_obj.start_time = now
lcm_op_obj.vnf_instance_id = vnf_lcm_opoccs.get('vnf_instance_id')
lcm_op_obj.operation = fields.InstanceOperation.MODIFY_INFO
lcm_op_obj.is_automatic_invocation = 0
lcm_op_obj.is_cancel_pending = 0
lcm_op_obj.operationParams = vnf_lcm_opoccs.get('operationParams')
try:
lcm_op_obj.create()
except Exception as msg:
raise Exception(str(msg))
# Notification
instance_url = self._get_vnf_instance_href(
vnf_lcm_opoccs.get('vnf_instance_id'))
lcm_url = self._get_vnf_lcm_op_occs_href(vnf_lcm_opoccs.get('id'))
notification_data = {
'notificationType':
fields.LcmOccsNotificationType.VNF_OP_OCC_NOTIFICATION,
'notificationStatus': fields.LcmOccsNotificationStatus.START,
'operationState': fields.LcmOccsOperationState.PROCESSING,
'vnfInstanceId': vnf_lcm_opoccs.get('vnf_instance_id'),
'operation': fields.InstanceOperation.MODIFY_INFO,
'isAutomaticInvocation': False,
'vnfLcmOpOccId': vnf_lcm_opoccs.get('id'),
'_links': {
'vnfInstance': {
'href': instance_url},
'vnfLcmOpOcc': {
'href': lcm_url}}}
self.send_notification(context, notification_data)
# update vnf_instances
try:
ins_obj = objects.vnf_instance.VnfInstance(context=context)
result = ins_obj.update(
context,
vnf_lcm_opoccs,
body_data,
vnfd_pkg_data,
vnfd_id)
except Exception as msg:
raise Exception(str(msg))
# update lcm_op_occs
changed_info = objects.vnf_lcm_op_occs.VnfInfoModifications()
changed_info.vnf_instance_name = body_data.get('vnf_instance_name')
changed_info.vnf_instance_description = body_data.get(
'vnf_instance_description')
if body_data.get('vnfd_id'):
changed_info.vnfd_id = body_data.get('vnfd_id')
changed_info.vnf_provider = vnfd_pkg_data.get('vnf_provider')
changed_info.vnf_product_name = vnfd_pkg_data.get(
'vnf_product_name')
changed_info.vnf_software_version = vnfd_pkg_data.get(
'vnf_software_version')
changed_info.vnfd_version = vnfd_pkg_data.get('vnfd_version')
# update vnf_lcm_op_occs
now = timeutils.utcnow()
lcm_op_obj.id = vnf_lcm_opoccs.get('id')
lcm_op_obj.operation_state = fields.LcmOccsOperationState.COMPLETED
lcm_op_obj.state_entered_time = result
lcm_op_obj.updated_at = now
lcm_op_obj.changed_info = changed_info
try:
lcm_op_obj.save()
except Exception as msg:
raise Exception(str(msg))
# Notification
notification_data['notificationStatus'] = 'RESULT'
notification_data['operationState'] = 'COMPLETED'
notification_data['changed_info'] = changed_info.to_dict()
self.send_notification(context, notification_data)
def init(args, **kwargs):
CONF(args=args, project='tacker',

View File

@ -71,6 +71,20 @@ class VNFLcmRPCAPI(object):
heal_vnf_request=heal_vnf_request,
vnf_lcm_op_occs_id=vnf_lcm_op_occs_id)
def update(self, context, vnf_lcm_opoccs, body_data,
vnfd_pkg_data, vnfd_id, cast=True):
serializer = objects_base.TackerObjectSerializer()
client = rpc.get_client(self.target, version_cap=None,
serializer=serializer)
cctxt = client.prepare()
rpc_method = cctxt.cast if cast else cctxt.call
return rpc_method(context, 'update',
vnf_lcm_opoccs=vnf_lcm_opoccs,
body_data=body_data,
vnfd_pkg_data=vnfd_pkg_data,
vnfd_id=vnfd_id)
def send_notification(self, context, notification, cast=True):
serializer = objects_base.TackerObjectSerializer()

View File

@ -201,7 +201,6 @@ class VnfInstance(model_base.BASE, models.SoftDeleteMixin,
task_state = sa.Column(sa.String(255), nullable=True)
vim_connection_info = sa.Column(sa.JSON(), nullable=True)
tenant_id = sa.Column('tenant_id', sa.String(length=64), nullable=False)
vnf_pkg_id = sa.Column(types.Uuid, nullable=False)
vnf_metadata = sa.Column(sa.JSON(), nullable=True)

View File

@ -25,6 +25,7 @@ def register_all():
# function in order for it to be registered by services that may
# need to receive it via RPC.
__import__('tacker.objects.heal_vnf_request')
__import__('tacker.objects.vnfd')
__import__('tacker.objects.vnf_package')
__import__('tacker.objects.vnf_package_vnfd')
__import__('tacker.objects.vnf_deployment_flavour')
@ -34,6 +35,7 @@ def register_all():
__import__('tacker.objects.vim_connection')
__import__('tacker.objects.instantiate_vnf_req')
__import__('tacker.objects.vnf_resources')
__import__('tacker.objects.vnf')
__import__('tacker.objects.vnfd')
__import__('tacker.objects.vnf_lcm_op_occs')
__import__('tacker.objects.terminate_vnf_req')

View File

@ -255,3 +255,14 @@ class LcmOccsNotificationType(BaseTackerEnum):
VNF_ID_CREATION_NOTIFICATION = 'VnfIdentifierCreationNotification'
ALL = (VNF_OP_OCC_NOTIFICATION)
class VnfStatus(BaseTackerEnum):
ACTIVE = 'ACTIVE'
INACTIVE = 'INACTIVE'
ALL = (ACTIVE, INACTIVE)
class InstanceOperation(BaseTackerEnum):
MODIFY_INFO = 'MODIFY_INFO'

View File

@ -26,6 +26,8 @@ class VimConnectionInfo(base.TackerObject, base.TackerPersistentObject):
'id': fields.StringField(nullable=False),
'vim_id': fields.StringField(nullable=True, default=None),
'vim_type': fields.StringField(nullable=False),
'interface_info': fields.DictOfNullableStringsField(nullable=True,
default={}),
'access_info': fields.DictOfNullableStringsField(nullable=True,
default={}),
}
@ -36,7 +38,11 @@ class VimConnectionInfo(base.TackerObject, base.TackerPersistentObject):
vim_id = data_dict.get('vim_id')
vim_type = data_dict.get('vim_type')
access_info = data_dict.get('access_info', {})
obj = cls(id=id, vim_id=vim_id, vim_type=vim_type,
interface_info = data_dict.get('interface_info', {})
obj = cls(id=id,
vim_id=vim_id,
vim_type=vim_type,
interface_info=interface_info,
access_info=access_info)
return obj
@ -55,4 +61,5 @@ class VimConnectionInfo(base.TackerObject, base.TackerPersistentObject):
return {'id': self.id,
'vim_id': self.vim_id,
'vim_type': self.vim_type,
'interface_info': self.interface_info,
'access_info': self.access_info}

111
tacker/objects/vnf.py Normal file
View File

@ -0,0 +1,111 @@
# 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_log import log as logging
from oslo_versionedobjects import base as ovoo_base
from tacker.db import api as db_api
from tacker.db.db_sqlalchemy import api
from tacker.db.vnfm import vnfm_db
from tacker.objects import base
from tacker.objects import fields
LOG = logging.getLogger(__name__)
def _vnf_update(context, values):
update = {'status': values.status,
'updated_at': values.updated_at}
api.model_query(context, vnfm_db.VNF). \
filter_by(id=values.id). \
update(update, synchronize_session=False)
@db_api.context_manager.reader
def _vnf_get(context, id, columns_to_join=None):
vnf_data = api.model_query(
context,
vnfm_db.VNF).filter_by(
id=id).filter_by(
deleted_at='0001-01-01 00:00:00').first()
if vnf_data:
vnf_data = vnf_data.__dict__
vnf_attribute_data = api.model_query(
context, vnfm_db.VNFAttribute).filter_by(
vnf_id=vnf_data.get('id')).first()
vnf_data['vnf_attribute'] = vnf_attribute_data.__dict__
vnfd_data = api.model_query(
context, vnfm_db.VNFD).filter_by(
id=vnf_data.get('vnfd_id')).first()
vnf_data['vnfd'] = vnfd_data.__dict__
vnfd_attribute_data = api.model_query(
context, vnfm_db.VNFDAttribute).filter_by(
vnfd_id=vnf_data.get('vnfd_id')).first()
vnf_data['vnfd_attribute'] = vnfd_attribute_data.__dict__
else:
vnf_data = ""
return vnf_data
@base.TackerObjectRegistry.register
class VNF(base.TackerObject, base.TackerObjectDictCompat,
base.TackerPersistentObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'id': fields.UUIDField(nullable=False),
'tenant_id': fields.UUIDField(nullable=False),
'name': fields.StringField(nullable=False),
'vnfd_id': fields.UUIDField(nullable=False),
'instance_id': fields.StringField(nullable=True),
'mgmt_ip_address': fields.StringField(nullable=True),
'status': fields.StringField(nullable=True),
'description': fields.StringField(nullable=True),
'placement_attr': fields.StringField(nullable=True),
'vim_id': fields.StringField(nullable=False),
'error_reason': fields.StringField(nullable=True),
'vnf_attribute': fields.ObjectField(
'VNFAttribute', nullable=True),
'vnfd': fields.ObjectField('VNFD', nullable=True),
}
@base.remotable
def save(self):
updates = self.obj_clone()
_vnf_update(self._context, updates)
@base.remotable_classmethod
def vnf_index_list(cls, id, context):
# get vnf_instance data
expected_attrs = ["vnf_attribute", "vnfd"]
db_vnf = _vnf_get(context, id, columns_to_join=expected_attrs)
return db_vnf
@base.TackerObjectRegistry.register
class VnfList(ovoo_base.ObjectListBase, base.TackerObject):
VERSION = '1.0'
fields = {
'objects': fields.ListOfObjectsField('VNF')
}
@base.remotable_classmethod
def vnf_index_list(cls, id, context):
# get vnf_instance data
expected_attrs = ["vnf_attribute", "vnfd"]
db_vnf = _vnf_get(context, id, columns_to_join=expected_attrs)
return db_vnf

View File

@ -28,10 +28,13 @@ from tacker.common import utils
from tacker.db import api as db_api
from tacker.db.db_sqlalchemy import api
from tacker.db.db_sqlalchemy import models
from tacker.db.vnfm import vnfm_db
from tacker import objects
from tacker.objects import base
from tacker.objects import fields
from tacker.objects import vnf_instantiated_info
from tacker.objects import vnf_package as vnf_package_obj
from tacker.objects import vnf_package_vnfd as vnf_package_vnfd
LOG = logging.getLogger(__name__)
@ -149,6 +152,151 @@ def _wrap_object_error(method):
return wrapper
@db_api.context_manager.reader
def _get_vnf_instance(context, id):
vnf_instance = api.model_query(
context, models.VnfInstance).filter_by(
vnfd_id=id).first()
return vnf_instance
@db_api.context_manager.reader
def _vnf_instance_get(context, vnfd_id, columns_to_join=None):
query = api.model_query(context, models.VnfInstance, read_deleted="no",
project_only=True).filter_by(vnfd_id=vnfd_id)
if columns_to_join:
for column in columns_to_join:
query = query.options(joinedload(column))
return query.first()
def _merge_vim_connection_info(
pre_vim_connection_info_list,
update_vim_connection_info_list):
def update_nested_element(pre_data, update_data):
for key, val in update_data.items():
if not isinstance(val, dict):
pre_data[key] = val
continue
if key in pre_data:
pre_data[key].update(val)
else:
pre_data.update({key: val})
result = []
clone_pre_list = copy.deepcopy(pre_vim_connection_info_list)
for update_vim_connection in update_vim_connection_info_list:
pre_data = None
for i in range(0, len(clone_pre_list) - 1):
if clone_pre_list[i].id == update_vim_connection.get('id'):
pre_data = clone_pre_list.pop(i)
if pre_data is None:
# new elm.
result.append(objects.VimConnectionInfo._from_dict(
update_vim_connection))
continue
convert_dict = pre_data.to_dict()
update_nested_element(convert_dict, update_vim_connection)
result.append(objects.VimConnectionInfo._from_dict(
convert_dict))
# Reflecting unupdated data
result.extend(clone_pre_list)
return result
@db_api.context_manager.writer
def _update_vnf_instances(
context,
vnf_lcm_opoccs,
body_data,
vnfd_pkg_data,
vnfd_id):
updated_values = {}
updated_values['vnf_instance_name'] = body_data.get('vnf_instance_name')
updated_values['vnf_instance_description'] = body_data.get(
'vnf_instance_description')
# get vnf_instances
vnf_instance = _get_vnf_instance(context, vnfd_id)
if body_data.get('metadata'):
vnf_instance.vnf_metadata.update(body_data.get('metadata'))
updated_values['vnf_metadata'] = vnf_instance.vnf_metadata
if body_data.get('vim_connection_info'):
merge_vim_connection_info = _merge_vim_connection_info(
vnf_instance.vim_connection_info,
body_data.get('vim_connection_info'))
updated_values['vim_connection_info'] = merge_vim_connection_info
if body_data.get('vnfd_id'):
updated_values['vnfd_id'] = body_data.get('vnfd_id')
updated_values['vnf_provider'] = vnfd_pkg_data.get('vnf_provider')
updated_values['vnf_product_name'] = vnfd_pkg_data.get(
'vnf_product_name')
updated_values['vnf_software_version'] = vnfd_pkg_data.get(
'vnf_software_version')
api.model_query(context, models.VnfInstance). \
filter_by(id=vnf_lcm_opoccs.get('vnf_instance_id')). \
update(updated_values, synchronize_session=False)
vnf_now = timeutils.utcnow()
if body_data.get('vnfd_id'):
# update vnf
updated_values = {'vnfd_id': body_data.get('vnfd_id'),
'updated_at': vnf_now
}
api.model_query(context, vnfm_db.VNF).\
filter_by(id=vnf_lcm_opoccs.get('vnf_instance_id')). \
update(updated_values, synchronize_session=False)
# get vnf_packages
id = vnfd_pkg_data.get('package_uuid')
try:
vnf_package = vnf_package_obj.VnfPackage.get_by_id(context, id)
except exceptions.VnfPackageNotFound:
raise exceptions.VnfPackageNotFound(id=id)
if vnf_package.usage_state == 'NOT_IN_USE':
# update vnf_packages
now = timeutils.utcnow()
updated_values = {'usage_state': 'IN_USE',
'updated_at': now
}
api.model_query(context, models.VnfPackage).\
filter_by(id=id). \
update(updated_values, synchronize_session=False)
# get vnf_instances
vnf_instance = _get_vnf_instance(context, vnfd_id)
if not vnf_instance:
# get vnf_package_vnfd
vnfd_data = vnf_package_vnfd.VnfPackageVnfd.get_by_vnfdId(
context, vnfd_id)
# update vnf_packages
now = timeutils.utcnow()
updated_values = {'usage_state': 'NOT_IN_USE',
'updated_at': now
}
api.model_query(context, models.VnfPackage).\
filter_by(id=vnfd_data.package_uuid). \
update(updated_values, synchronize_session=False)
return vnf_now
@base.TackerObjectRegistry.register
class VnfInstance(base.TackerObject, base.TackerPersistentObject,
base.TackerObjectDictCompat):
@ -171,7 +319,6 @@ class VnfInstance(base.TackerObject, base.TackerPersistentObject,
'vim_connection_info': fields.ListOfObjectsField(
'VimConnectionInfo', nullable=True, default=[]),
'tenant_id': fields.StringField(nullable=False),
'vnf_pkg_id': fields.StringField(nullable=False),
'vnf_metadata': fields.DictOfStringsField(nullable=True, default={}),
'instantiated_vnf_info': fields.ObjectField('InstantiatedVnfInfo',
nullable=True, default=None)
@ -329,7 +476,6 @@ class VnfInstance(base.TackerObject, base.TackerPersistentObject,
'vnf_product_name': self.vnf_product_name,
'vnf_software_version': self.vnf_software_version,
'vnfd_version': self.vnfd_version,
'vnf_pkg_id': self.vnf_pkg_id,
'vnf_metadata': self.vnf_metadata}
if (self.instantiation_state == fields.VnfInstanceState.INSTANTIATED
@ -344,6 +490,23 @@ class VnfInstance(base.TackerObject, base.TackerPersistentObject,
return data
@base.remotable
def update(
self,
context,
vnf_lcm_opoccs,
body_data,
vnfd_pkg_data,
vnfd_id):
# update vnf_instances
return _update_vnf_instances(
context,
vnf_lcm_opoccs,
body_data,
vnfd_pkg_data,
vnfd_id)
@base.remotable_classmethod
def get_by_id(cls, context, id):
expected_attrs = ["instantiated_vnf_info"]
@ -380,3 +543,19 @@ class VnfInstanceList(ovoo_base.ObjectListBase, base.TackerObject):
return _make_vnf_instance_list(context, cls(), db_vnf_instances,
expected_attrs)
@base.remotable_classmethod
def vnf_instance_list(cls, vnfd_id, context):
# get vnf_instance data
expected_attrs = ["instantiated_vnf_info"]
db_vnf_instances = _vnf_instance_get(context, vnfd_id,
columns_to_join=expected_attrs)
vnf_instance_cls = VnfInstance
vnf_instance_data = ""
vnf_instance_obj = vnf_instance_cls._from_db_object(
context, vnf_instance_cls(context), db_vnf_instances,
expected_attrs=expected_attrs)
vnf_instance_data = vnf_instance_obj
return vnf_instance_data

View File

@ -13,6 +13,7 @@
# under the License.
from oslo_db import exception as db_exc
from oslo_log import log as logging
from oslo_utils import uuidutils
from tacker.common import exceptions
@ -23,6 +24,8 @@ from tacker.db.db_sqlalchemy import models
from tacker.objects import base
from tacker.objects import fields
LOG = logging.getLogger(__name__)
@db_api.context_manager.writer
def _vnf_package_vnfd_create(context, values):
@ -39,6 +42,49 @@ def _vnf_package_vnfd_create(context, values):
return vnf_package_vnfd
@db_api.context_manager.reader
def _get_vnf_package_vnfd(context, id, package_uuid=None, del_flg=None):
if package_uuid and not del_flg:
query = api.model_query(
context,
models.VnfPackageVnfd).filter_by(
package_uuid=id).filter_by(
deleted=0)
elif package_uuid and del_flg:
query = api.model_query(
context, models.VnfPackageVnfd).filter_by(
package_uuid=id)
else:
query = api.model_query(
context,
models.VnfPackageVnfd).filter_by(
vnfd_id=id).filter_by(
deleted=0)
try:
result = query.all()
result_line = ""
for line in result:
result_line = line
except Exception:
LOG.error("select vnf_package_vnfd failed")
if result_line:
return result_line
else:
return None
@db_api.context_manager.writer
def _vnf_package_vnfd_delete(context, id):
try:
api.model_query(
context, models.VnfPackageVnfd).filter_by(
package_uuid=id).delete()
except Exception:
LOG.error("delete vnf_package_vnfd failed")
@db_api.context_manager.reader
def _vnf_package_vnfd_get_by_id(context, vnfd_id):
@ -59,6 +105,38 @@ def _vnf_package_vnfd_get_by_id(context, vnfd_id):
return result
def _vnf_package_vnfd_get_by_packageId(context, packageId):
query = api.model_query(
context,
models.VnfPackageVnfd,
read_deleted="no",
project_only=True).filter_by(
package_uuid=packageId)
result = query.first()
if not result:
return None
return result
@db_api.context_manager.reader
def _vnf_package_vnfd_get_by_vnfdId(context, vnfdId):
query = api.model_query(context,
models.VnfPackageVnfd,
read_deleted="no",
project_only=True).filter_by(vnfd_id=vnfdId)
result = query.first()
if not result:
return None
return result
@db_api.context_manager.reader
def _get_vnf_package_vnfd_by_vnfid(context, vnfpkgid):
@ -127,10 +205,18 @@ class VnfPackageVnfd(base.TackerObject, base.TackerObjectDictCompat,
self._context, updates)
self._from_db_object(self._context, self, db_vnf_package_vnfd)
@base.remotable
def get_vnf_package_vnfd(self, id, package_uuid=None, del_flg=None):
return _get_vnf_package_vnfd(self._context, id, package_uuid, del_flg)
@base.remotable_classmethod
def get_vnf_package_vnfd_by_vnfid(self, context, vnfid):
return _get_vnf_package_vnfd_by_vnfid(context, vnfid)
@base.remotable
def delete(self, id):
_vnf_package_vnfd_delete(self._context, id)
@classmethod
def obj_from_db_obj(cls, context, db_obj):
return cls._from_db_object(context, cls(), db_obj)
@ -139,3 +225,19 @@ class VnfPackageVnfd(base.TackerObject, base.TackerObjectDictCompat,
def get_by_id(cls, context, id):
db_vnf_package_vnfd = _vnf_package_vnfd_get_by_id(context, id)
return cls._from_db_object(context, cls(), db_vnf_package_vnfd)
@base.remotable_classmethod
def get_by_vnfdId(cls, context, id):
db_vnf_package_vnfd = _vnf_package_vnfd_get_by_vnfdId(
context, id)
if not db_vnf_package_vnfd:
return db_vnf_package_vnfd
return cls._from_db_object(context, cls(), db_vnf_package_vnfd)
@base.remotable_classmethod
def get_by_packageId(cls, context, id):
db_vnf_package_vnfd = _vnf_package_vnfd_get_by_packageId(
context, id)
if not db_vnf_package_vnfd:
return db_vnf_package_vnfd
return cls._from_db_object(context, cls(), db_vnf_package_vnfd)

View File

@ -110,6 +110,17 @@ rules = [
}
]
),
policy.DocumentedRuleDefault(
name=VNFLCM % 'update_vnf',
check_str=base.RULE_ADMIN_OR_OWNER,
description="Update an Individual VNF instance.",
operations=[
{
'method': 'PATCH',
'path': '/vnflcm/v1/vnf_instances/{vnfInstanceId}'
}
]
),
]

View File

@ -15,9 +15,11 @@
import datetime
import iso8601
import os
import shutil
from tacker import objects
from tacker.objects import fields
from tacker.tests import constants
import tempfile
import uuid
import yaml
@ -25,6 +27,7 @@ import zipfile
from oslo_config import cfg
from tacker.db.db_sqlalchemy import models
from tacker.tests import utils
from tacker.tests import uuidsentinel
@ -150,3 +153,137 @@ def get_expected_vnfd_data(zip_file=None):
shutil.rmtree(csar_temp_dir)
return file_path_and_data
def fake_vnf_package_vnfd_model_dict(**updates):
vnfd = {
'package_uuid': uuidsentinel.package_uuid,
'deleted': False,
'deleted_at': None,
'updated_at': None,
'created_at': datetime.datetime(2020, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
'vnf_product_name': 'Sample VNF',
'vnf_provider': 'test vnf provider',
'vnf_software_version': '1.0',
'vnfd_id': uuidsentinel.vnfd_id,
'vnfd_version': '1.0',
'id': constants.UUID,
}
if updates:
vnfd.update(updates)
return vnfd
def return_vnf_package_vnfd():
model_obj = models.VnfPackageVnfd()
model_obj.update(fake_vnf_package_vnfd_model_dict())
return model_obj
def _model_non_instantiated_vnf_instance(**updates):
vnf_instance = {
'created_at': datetime.datetime(2020, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
'deleted': False,
'deleted_at': None,
'id': uuidsentinel.vnf_instance_id,
'instantiated_vnf_info': None,
'instantiation_state': fields.VnfInstanceState.NOT_INSTANTIATED,
'updated_at': None,
'vim_connection_info': [],
'vnf_instance_description': 'Vnf instance description',
'vnf_instance_name': 'Vnf instance name',
'vnf_product_name': 'Sample VNF',
'vnf_provider': 'Vnf provider',
'vnf_software_version': '1.0',
'tenant_id': uuidsentinel.tenant_id,
'vnfd_id': uuidsentinel.vnfd_id,
'vnfd_version': '1.0'}
if updates:
vnf_instance.update(**updates)
return vnf_instance
def return_vnf_instance(
instantiated_state=fields.VnfInstanceState.NOT_INSTANTIATED,
scale_status=None,
**updates):
if instantiated_state == fields.VnfInstanceState.NOT_INSTANTIATED:
data = _model_non_instantiated_vnf_instance(**updates)
data['instantiation_state'] = instantiated_state
vnf_instance_obj = objects.VnfInstance(**data)
elif scale_status:
data = _model_non_instantiated_vnf_instance(**updates)
data['instantiation_state'] = instantiated_state
vnf_instance_obj = objects.VnfInstance(**data)
get_instantiated_vnf_info = {
'flavour_id': uuidsentinel.flavour_id,
'vnf_state': 'STARTED',
'instance_id': uuidsentinel.instance_id
}
instantiated_vnf_info = get_instantiated_vnf_info
s_status = {"aspect_id": "SP1", "scale_level": 1}
scale_status = objects.ScaleInfo(**s_status)
instantiated_vnf_info.update(
{"ext_cp_info": [],
'ext_virtual_link_info': [],
'ext_managed_virtual_link_info': [],
'vnfc_resource_info': [],
'vnf_virtual_link_resource_info': [],
'virtual_storage_resource_info': [],
"flavour_id": "simple",
"scale_status": [scale_status],
"vnf_instance_id": "171f3af2-a753-468a-b5a7-e3e048160a79",
"additional_params": {"key": "value"},
'vnf_state': "STARTED"})
info_data = objects.InstantiatedVnfInfo(**instantiated_vnf_info)
vnf_instance_obj.instantiated_vnf_info = info_data
else:
data = _model_non_instantiated_vnf_instance(**updates)
data['instantiation_state'] = instantiated_state
vnf_instance_obj = objects.VnfInstance(**data)
inst_vnf_info = objects.InstantiatedVnfInfo.obj_from_primitive({
"ext_cp_info": [],
'ext_virtual_link_info': [],
'ext_managed_virtual_link_info': [],
'vnfc_resource_info': [],
'vnf_virtual_link_resource_info': [],
'virtual_storage_resource_info': [],
"flavour_id": "simple",
"additional_params": {"key": "value"},
'vnf_state': "STARTED"}, None)
vnf_instance_obj.instantiated_vnf_info = inst_vnf_info
return vnf_instance_obj
def _get_vnf(**updates):
vnf_data = {
'tenant_id': uuidsentinel.tenant_id,
'name': "fake_name",
'vnfd_id': uuidsentinel.vnfd_id,
'vnf_instance_id': uuidsentinel.instance_id,
'mgmt_ip_address': "fake_mgmt_ip_address",
'status': 'ACTIVE',
'description': 'fake_description',
'placement_attr': 'fake_placement_attr',
'vim_id': 'uuidsentinel.vim_id',
'error_reason': 'fake_error_reason',
}
if updates:
vnf_data.update(**updates)
return vnf_data

View File

@ -14,7 +14,9 @@
# limitations under the License.
import base64
import datetime
import fixtures
import iso8601
import json
import os
import requests
@ -33,6 +35,7 @@ from tacker.common import csar_utils
from tacker.common import exceptions
from tacker.conductor import conductor_server
from tacker import context
from tacker import context as t_context
from tacker.glance_store import store as glance_store
from tacker import objects
from tacker.objects import fields
@ -78,6 +81,9 @@ class TestConductor(SqlTestCase, unit_base.FixturedTestCase):
self.vnf_package = self._create_vnf_package()
self.instance_uuid = uuidsentinel.instance_id
self.temp_dir = self.useFixture(fixtures.TempDir()).path
self.body_data = self._create_body_data()
self.vnf_lcm_opoccs = self._create_vnf_lcm_opoccs()
self.vnfd_pkg_data = self._create_vnfd_pkg_data()
def _mock_vnfm_plugin(self):
self.vnfm_plugin = mock.Mock(wraps=FakeVNFMPlugin())
@ -120,6 +126,40 @@ class TestConductor(SqlTestCase, unit_base.FixturedTestCase):
return [DummyLcmSubscription(auth_params)]
def _create_body_data(self):
body_data = {}
body_data['vnf_instance_name'] = "new_instance_name"
body_data['vnf_instance_description'] = "new_instance_discription"
body_data['vnfd_id'] = "2c69a161-0000-4b0f-bcf8-391f8fc76600"
body_data['vnf_configurable_properties'] = {"test": "test_value"}
body_data['vnfc_info_modifications_delete_ids'] = ["test1"]
return body_data
def _create_vnf_lcm_opoccs(self):
vnf_lcm_opoccs = {
'vnf_instance_id': uuidsentinel.vnf_instance_id,
'id': uuidsentinel.id,
'state_entered_time': datetime.datetime(
1900, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
'operationParams': {
"key": "value"}}
return vnf_lcm_opoccs
def _create_vnfd_pkg_data(self):
vnfd_pkg_data = {}
vnfd_pkg_data['vnf_provider'] = fakes.return_vnf_package_vnfd().get(
'vnf_provider')
vnfd_pkg_data['vnf_product_name'] =\
fakes.return_vnf_package_vnfd().get('vnf_product_name')
vnfd_pkg_data['vnf_software_version'] =\
fakes.return_vnf_package_vnfd().get('vnf_software_version')
vnfd_pkg_data['vnfd_version'] = fakes.return_vnf_package_vnfd().get(
'vnfd_version')
vnfd_pkg_data['package_uuid'] = fakes.return_vnf_package_vnfd().get(
'package_uuid')
return vnfd_pkg_data
def assert_auth_basic(
self,
acutual_request,
@ -958,3 +998,24 @@ class TestConductor(SqlTestCase, unit_base.FixturedTestCase):
self.assertEqual(result, 99)
mock_subscriptions_get.assert_called()
@mock.patch.object(conductor_server, 'revert_update_lcm')
@mock.patch.object(t_context.get_admin_context().session, "add")
@mock.patch.object(objects.vnf_lcm_op_occs.VnfLcmOpOcc, "save")
@mock.patch.object(objects.VnfInstance, "update")
@mock.patch.object(objects.vnf_lcm_op_occs.VnfLcmOpOcc, "create")
def test_update(self, mock_create, mock_update, mock_save, mock_add,
mock_revert):
mock_create.return_value = "OK"
mock_update.return_value = datetime.datetime(
1900, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC)
mock_add.return_value = "OK"
mock_save.return_value = "OK"
vnfd_id = "2c69a161-0000-4b0f-bcf8-391f8fc76600"
self.conductor.update(
self.context,
self.vnf_lcm_opoccs,
self.body_data,
self.vnfd_pkg_data,
vnfd_id)

View File

@ -45,6 +45,14 @@ software_image = {
'metadata': {'key1': 'value1'}
}
artifact_data = {
'file_name': 'test', 'type': 'test',
'algorithm': 'sha-256',
'hash': 'b9c3036539fd7a5f87a1bf38eb05fdde8b556a1'
'a7e664dbeda90ed3cd74b4f9d',
'metadata': {'key1': 'value1'}
}
artifacts = {
'json_data': 'test data',
'type': 'tosca.artifacts.nfv.SwImage',
@ -94,6 +102,42 @@ subscription_data = {
'created_at': "2020-06-11 09:39:58"
}
vnfd_data = {
"tenant_id": uuidsentinel.tenant_id,
'name': 'test',
'description': 'test_description',
'mgmt_driver': 'test_mgmt_driver'
}
vnfd_attribute = {
'key': 'test_key',
'value': 'test_value',
}
lcm_op_occs_data = {
"tenant_id": uuidsentinel.tenant_id,
'operation_state': 'PROCESSING',
'state_entered_time': datetime.datetime(1900, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
'start_time': datetime.datetime(1900, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
'operation': 'MODIFY_INFO',
'is_automatic_invocation': 0,
'is_cancel_pending': 0,
}
vim_data = {
'id': uuidsentinel.vim_id,
'type': 'test_type',
"tenant_id": uuidsentinel.tenant_id,
'name': "test_name",
'description': "test_description",
'placement_attr': "test_placement_attr",
'shared': 0,
'status': "REACHABLE",
'is_default': 0
}
fake_vnf_package_response = copy.deepcopy(vnf_package_data)
fake_vnf_package_response.pop('user_data')
fake_vnf_package_response.update({'id': uuidsentinel.package_uuid})
@ -430,3 +474,16 @@ def get_changed_info_data():
"vnf_software_version": "1.0",
"vnfd_version": "MME_1.0"
}
def get_vnf(vnfd_id, vim_id):
return {
'tenant_id': uuidsentinel.tenant_id,
'name': "test_name",
'vnfd_id': vnfd_id,
'mgmt_ip_address': "test_mgmt_ip_address",
'status': "ACTIVE",
'description': "test_description",
'placement_attr': "test_placement_attr",
'vim_id': vim_id
}

View File

@ -0,0 +1,69 @@
# 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 unittest import mock
import datetime
import iso8601
from tacker import context
from tacker.db.nfvo import nfvo_db
from tacker import objects
from tacker.tests.unit.db.base import SqlTestCase
from tacker.tests.unit.objects import fakes
from tacker.tests import uuidsentinel
class TestVnf(SqlTestCase):
def setUp(self):
super(TestVnf, self).setUp()
self.context = context.get_admin_context()
self.vnfd = self._create_vnfd()
self.vims = self._create_vims()
def _create_vnfd(self):
vnfd_obj = objects.Vnfd(context=self.context, **fakes.vnfd_data)
vnfd_obj.create()
return vnfd_obj
def _create_vims(self):
vim_obj = nfvo_db.Vim(**fakes.vim_data)
return vim_obj
def test_save(self):
vnf_data = fakes.get_vnf(self.vnfd.id, self.vims.id)
vnf_obj = objects.vnf.VNF(context=self.context, **vnf_data)
vnf_obj.id = uuidsentinel.instance_id
vnf_obj.status = 'ERROR'
vnf_obj.updated_at = datetime.datetime(
1900, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC)
vnf_obj.save()
self.assertEqual('ERROR', vnf_obj.status)
@mock.patch('tacker.objects.vnf._vnf_get')
def test_vnf_index_list(self, mock_vnf_get):
vnf_data = fakes.get_vnf(self.vnfd.id, self.vims.id)
vnf_obj = objects.vnf.VNF(context=self.context, **vnf_data)
vnf_obj.id = uuidsentinel.instance_id
vnf_obj.updated_at = datetime.datetime(
1900, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC)
vnf_obj.save()
mock_vnf_get.return_value = vnf_obj
vnf_data_result = vnf_obj.vnf_index_list(vnf_obj.id, self.context)
self.assertEqual('ACTIVE', vnf_data_result.status)

View File

@ -18,6 +18,8 @@ from unittest import mock
from tacker.common import exceptions
from tacker import context
from tacker.db import api as sqlalchemy_api
from tacker.db.db_sqlalchemy import api
from tacker.db.nfvo import nfvo_db
from tacker import objects
from tacker.tests.unit.db.base import SqlTestCase
from tacker.tests.unit.objects import fakes
@ -26,12 +28,41 @@ from tacker.tests import uuidsentinel
get_engine = sqlalchemy_api.get_engine
class FakeApiModelQuery:
def __init__(
self,
callback_filter_by=None,
callback_update=None,
callback_options=None):
self.callback_filter_by = callback_filter_by
self.callback_update = callback_update
self.callback_options = callback_options
def options(self, *args, **kwargs):
if self.callback_options:
self.callback_options(*args, **kwargs)
def filter_by(self, *args, **kwargs):
if self.callback_filter_by:
self.callback_filter_by(*args, **kwargs)
return self
def update(self, *args, **kwargs):
if self.callback_update:
self.callback_update(*args, **kwargs)
return self
class TestVnfInstance(SqlTestCase):
maxDiff = None
def setUp(self):
super(TestVnfInstance, self).setUp()
self.context = context.get_admin_context()
self.vnf_package = self._create_and_upload_vnf_package()
self.vims = nfvo_db.Vim(**fakes.vim_data)
self.engine = get_engine()
self.conn = self.engine.connect()
@ -157,3 +188,156 @@ class TestVnfInstance(SqlTestCase):
vnf_instance_obj = objects.VnfInstance(context=self.context)
self.assertRaises(exceptions.ObjectActionError,
vnf_instance_obj.destroy, self.context)
@mock.patch('tacker.objects.vnf_instance._get_vnf_instance')
@mock.patch('tacker.objects.vnf_package.VnfPackage.get_by_id')
@mock.patch.object(api, 'model_query')
def test_update_vnf_instances(
self,
mock_model_query,
mock_get_vnf_package,
mock_get_vnf):
vnf_instance_data = fakes.fake_vnf_instance_model_dict(**{
"vim_connection_info": [
objects.VimConnectionInfo._from_dict({
"id": "testid",
"vim_id": "aaa",
"vim_type": "openstack-1",
"interface_info": {"endpoint": "endpoint"},
"access_info": {"username": "xxxxx",
"region": "region",
"password": "password",
"tenant": "tenant"}}),
objects.VimConnectionInfo._from_dict({
"id": "testid3",
"vim_id": "ccc",
"vim_type": "openstack-2",
"interface_info": {"endpoint": "endpoint22"},
"access_info": {"username": "xxxxx",
"region": "region",
"password": "password"}}),
objects.VimConnectionInfo._from_dict({
"id": "testid5",
"vim_id": "eee",
"vim_type": "openstack-4"})
],
"vnf_metadata": {"testkey": "test_value"}})
vnf_instance = objects.VnfInstance(
context=self.context, **vnf_instance_data)
mock_get_vnf.return_value = \
fakes.vnf_instance_model_object(vnf_instance)
def mock_filter(id=None):
print('### mock_filter ###', id)
def mock_update(updated_values, synchronize_session=False):
print('### mock_update ###', updated_values)
if 'vim_connection_info' not in updated_values:
return
compar_updated_values = {}
compar_updated_values['vnf_instance_name'] = "new_instance_name"
compar_updated_values['vnf_instance_description'] = \
"new_instance_discription"
compar_updated_values['vnf_metadata'] = {
"testkey": "test_value1", "testkey2": "test_value2"}
compar_updated_values['vim_connection_info'] = [
objects.VimConnectionInfo._from_dict({
"id": "testid",
"vim_id": "bbb",
"vim_type": "openstack-1A",
"interface_info": {"endpoint": "endpoint11"},
"access_info": {"username": "xxxxx1",
"region": "region1",
"password": "password1",
"tenant": "tenant1"}}),
objects.VimConnectionInfo._from_dict({
"id": "testid3",
"vim_id": "ccc",
"vim_type": "openstack-2",
"interface_info": {"endpoint": "endpoint22"},
"access_info": {"username": "xxxxx",
"region": "region",
"password": "password2",
"tenant": "tenant2"}}),
objects.VimConnectionInfo._from_dict({
"id": "testid5",
"vim_id": "eee",
"vim_type": "openstack-4"}),
objects.VimConnectionInfo._from_dict({
"id": "testid7",
"vim_id": "fff",
"vim_type": "openstack-5A",
"interface_info": {"endpoint": "endpoint55"},
"access_info": {"username": "xxxxx5",
"region": "region5",
"password": "password5",
"tenant": "tenant5"}})
]
compar_updated_values['vnfd_id'] = \
"2c69a161-0000-4b0f-bcf8-391f8fc76600"
compar_updated_values['vnf_provider'] = \
self.vnf_package.get('vnf_provider')
compar_updated_values['vnf_product_name'] = \
self.vnf_package.get('vnf_product_name')
compar_updated_values['vnf_software_version'] = \
self.vnf_package.get('vnf_software_version')
expected_vci = sorted(compar_updated_values.pop(
'vim_connection_info'), key=lambda x: x.id)
actual_vci = sorted(
updated_values.pop('vim_connection_info'),
key=lambda x: x.id)
for e, a in zip(expected_vci, actual_vci):
self.assertDictEqual(
e.to_dict(),
a.to_dict())
self.assertDictEqual(
compar_updated_values,
updated_values)
fake_api_model_query = FakeApiModelQuery(
callback_filter_by=mock_filter, callback_update=mock_update)
mock_model_query.return_value = fake_api_model_query
vnf_lcm_opoccs = {}
body = {"vnf_instance_name": "new_instance_name",
"vnf_instance_description": "new_instance_discription",
"vnfd_id": "2c69a161-0000-4b0f-bcf8-391f8fc76600",
"vnf_configurable_properties": {"test": "test_value1"},
"vnfc_info_modifications_delete_ids": ["test1"],
"metadata": {"testkey": "test_value1",
"testkey2": "test_value2"},
"vim_connection_info": [
{"id": "testid",
"vim_id": "bbb",
"vim_type": "openstack-1A",
"interface_info": {"endpoint": "endpoint11"},
"access_info": {"username": "xxxxx1",
"region": "region1",
"password": "password1",
"tenant": "tenant1"}},
{"id": "testid3",
"vim_type": "openstack-2",
"access_info": {"password": "password2",
"tenant": "tenant2"}},
{"id": "testid7",
"vim_id": "fff",
"vim_type": "openstack-5A",
"interface_info": {"endpoint": "endpoint55"},
"access_info": {"username": "xxxxx5",
"region": "region5",
"password": "password5",
"tenant": "tenant5"}},
]}
vnf_instance.update(
self.context,
vnf_lcm_opoccs,
body,
self.vnf_package,
self.vnf_package.id)

View File

@ -120,7 +120,6 @@ def _model_non_instantiated_vnf_instance(**updates):
'tenant_id': uuidsentinel.tenant_id,
'vnfd_id': uuidsentinel.vnfd_id,
'vnfd_version': '1.0',
'vnf_pkg_id': uuidsentinel.vnf_pkg_id,
'vnf_metadata': {"key": "value"}}
if updates:
@ -231,7 +230,6 @@ def _fake_vnf_instance_not_instantiated_response(
'vnfdId': uuidsentinel.vnfd_id,
'vnfdVersion': '1.0',
'vnfSoftwareVersion': '1.0',
'vnfPkgId': uuidsentinel.vnf_pkg_id,
'id': uuidsentinel.vnf_instance_id,
'metadata': {'key': 'value'}
}

View File

@ -244,6 +244,7 @@ class TestController(base.TestCase):
@mock.patch.object(objects.VnfPackage, 'get_by_id')
@mock.patch('tacker.api.vnflcm.v1.controller.'
'VnfLcmController._create_vnf')
@mock.patch.object(objects.vnf_package.VnfPackage, 'save')
@mock.patch.object(TackerManager, 'get_service_plugins',
return_value={'VNFM': FakeVNFMPlugin()})
@mock.patch.object(objects.vnf_instance, '_vnf_instance_create')
@ -252,57 +253,14 @@ class TestController(base.TestCase):
self, mock_get_by_id,
mock_vnf_instance_create,
mock_get_service_plugins,
mock_package_save,
mock_private_create_vnf,
mock_vnf_package_get_by_id,
mock_update_package_usage_state,
mock_get_vim):
mock_get_by_id.return_value = fakes.return_vnf_package_vnfd()
updates = {'vnfd_id': uuidsentinel.vnfd_id,
'vnf_instance_description': None,
'vnf_instance_name': None,
'vnf_pkg_id': uuidsentinel.vnf_pkg_id,
'vnf_metadata': {"key": "value"}}
mock_vnf_instance_create.return_value =\
fakes.return_vnf_instance_model(**updates)
req = fake_request.HTTPRequest.blank('/vnf_instances')
body = {'vnfdId': uuidsentinel.vnfd_id,
'metadata': {"key": "value"}}
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
# Call create API
resp = req.get_response(self.app)
self.assertEqual(http_client.CREATED, resp.status_code)
updates = {'vnfInstanceDescription': None, 'vnfInstanceName': None}
expected_vnf = fakes.fake_vnf_instance_response(**updates)
location_header = ('http://localhost/vnflcm/v1/vnf_instances/%s'
% resp.json['id'])
self.assertEqual(expected_vnf, resp.json)
self.assertEqual(location_header, resp.headers['location'])
@mock.patch('tacker.api.vnflcm.v1.controller.'
'VnfLcmController._create_vnf')
@mock.patch.object(objects.VnfInstance, 'save')
@mock.patch.object(vim_client.VimClient, "get_vim")
@mock.patch.object(objects.vnf_package.VnfPackage, 'get_by_id')
@mock.patch.object(objects.vnf_package.VnfPackage, 'save')
@mock.patch.object(objects.vnf_instance, '_vnf_instance_create')
@mock.patch.object(objects.vnf_package_vnfd.VnfPackageVnfd, 'get_by_id')
def test_create_with_name_and_description(
self, mock_get_by_id_package_vnfd,
mock_vnf_instance_create, mock_package_save,
mock_get_by_id_package, mock_get_vim,
mock_save, mock_create_vnf):
mock_get_vim.return_value = self.vim_info
mock_get_by_id_package_vnfd.return_value = \
fakes.return_vnf_package_vnfd()
mock_get_by_id_package.return_value = \
mock_get_by_id.return_value = fakes.return_vnf_package_vnfd()
mock_vnf_package_get_by_id.return_value = \
fakes.return_vnf_package_with_deployment_flavour()
updates = {'vnfd_id': uuidsentinel.vnfd_id,
@ -1199,8 +1157,11 @@ class TestController(base.TestCase):
constants.INVALID_UUID,
resp.json['itemNotFound']['message'])
@ddt.data('PATCH', 'HEAD', 'PUT', 'POST')
def test_show_invalid_http_method(self, http_method):
@mock.patch.object(TackerManager, 'get_service_plugins',
return_value={'VNFM': FakeVNFMPlugin()})
@ddt.data('HEAD', 'PUT', 'POST')
def test_show_invalid_http_method(self, http_method,
mock_get_service_plugins):
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s' % constants.UUID)
req.headers['Content-Type'] = 'application/json'
@ -1888,3 +1849,187 @@ class TestController(base.TestCase):
resp = req.get_response(self.app)
self.assertEqual(http_client.NOT_FOUND, resp.status_code)
@mock.patch.object(TackerManager, 'get_service_plugins',
return_value={'VNFM': FakeVNFMPlugin()})
@mock.patch.object(objects.VNF, "vnf_index_list")
@mock.patch.object(objects.VnfInstanceList, "vnf_instance_list")
@mock.patch.object(objects.VnfPackageVnfd, 'get_vnf_package_vnfd')
@mock.patch.object(VNFLcmRPCAPI, "update")
def test_update_vnf(
self,
mock_update,
mock_vnf_package_vnf_get_vnf_package_vnfd,
mock_vnf_instance_list,
mock_vnf_index_list,
mock_get_service_plugins):
mock_vnf_index_list.return_value = fakes._get_vnf()
mock_vnf_instance_list.return_value = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED)
mock_vnf_package_vnf_get_vnf_package_vnfd.return_value =\
fakes.return_vnf_package_vnfd()
body = {"vnfInstanceName": "new_instance_name",
"vnfInstanceDescription": "new_instance_discription",
"vnfdId": "2c69a161-0000-4b0f-bcf8-391f8fc76600",
"vnfConfigurableProperties": {
"test": "test_value"
},
"vnfcInfoModificationsDeleteIds": ["test1"],
"metadata": {"testkey": "test_value"},
"vimConnectionInfo": {"id": "testid"}}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s' % constants.UUID)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'PATCH'
# Call Instantiate API
resp = req.get_response(self.app)
print(resp)
self.assertEqual(http_client.ACCEPTED, resp.status_code)
mock_update.assert_called_once()
@mock.patch.object(TackerManager, 'get_service_plugins',
return_value={'VNFM': FakeVNFMPlugin()})
@mock.patch.object(objects.VNF, "vnf_index_list")
def test_update_vnf_none_vnf_data(
self,
mock_vnf_index_list,
mock_get_service_plugins):
mock_vnf_index_list.return_value = ""
body = {"vnfInstanceName": "new_instance_name",
"vnfInstanceDescription": "new_instance_discription",
"vnfdId": "2c69a161-0000-4b0f-bcf8-391f8fc76600",
"vnfConfigurableProperties": {
"test": "test_value"
},
"vnfcInfoModificationsDeleteIds": ["test1"],
"metadata": {"testkey": "test_value"},
"vimConnectionInfo": {"id": "testid"}}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s' % constants.UUID)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'PATCH'
msg = _("Can not find requested vnf data: %s") % constants.UUID
res = self._make_problem_detail(msg, 404, title='Not Found')
resp = req.get_response(self.app)
self.assertEqual(res.text, resp.text)
@mock.patch.object(TackerManager, 'get_service_plugins',
return_value={'VNFM': FakeVNFMPlugin()})
@mock.patch.object(objects.VNF, "vnf_index_list")
def test_update_vnf_status_err(
self,
mock_vnf_index_list,
mock_get_service_plugins):
updates = {'status': 'ERROR'}
mock_vnf_index_list.return_value = fakes._get_vnf(**updates)
body = {"vnfInstanceName": "new_instance_name",
"vnfInstanceDescription": "new_instance_discription",
"vnfdId": "2c69a161-0000-4b0f-bcf8-391f8fc76600",
"vnfConfigurableProperties": {
"test": "test_value"
},
"vnfcInfoModificationsDeleteIds": ["test1"],
"metadata": {"testkey": "test_value"},
"vimConnectionInfo": {"id": "testid"}}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s' % constants.UUID)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'PATCH'
msg = _("VNF %(id)s status is %(state)s") % {
"id": constants.UUID, "state": "ERROR"}
res = self._make_problem_detail(msg %
{"state": "ERROR"}, 409, 'Conflict')
resp = req.get_response(self.app)
self.assertEqual(res.text, resp.text)
@mock.patch.object(TackerManager, 'get_service_plugins',
return_value={'VNFM': FakeVNFMPlugin()})
@mock.patch.object(objects.VNF, "vnf_index_list")
@mock.patch.object(objects.VnfInstanceList, "vnf_instance_list")
def test_update_vnf_none_instance_data(
self,
mock_vnf_instance_list,
mock_vnf_index_list,
mock_get_service_plugins):
mock_vnf_index_list.return_value = fakes._get_vnf()
mock_vnf_instance_list.return_value = ""
body = {"vnfInstanceName": "new_instance_name",
"vnfInstanceDescription": "new_instance_discription",
"vnfdId": "2c69a161-0000-4b0f-bcf8-391f8fc76600",
"vnfConfigurableProperties": {
"test": "test_value"
},
"vnfcInfoModificationsDeleteIds": ["test1"],
"metadata": {"testkey": "test_value"},
"vimConnectionInfo": {"id": "testid"}}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s' % constants.UUID)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'PATCH'
vnf_data = fakes._get_vnf()
msg = _("Can not find requested vnf instance data: %s") % vnf_data.get(
'vnfd_id')
res = self._make_problem_detail(msg, 404, title='Not Found')
resp = req.get_response(self.app)
self.assertEqual(res.text, resp.text)
@mock.patch.object(TackerManager, 'get_service_plugins',
return_value={'VNFM': FakeVNFMPlugin()})
@mock.patch.object(objects.VNF, "vnf_index_list")
@mock.patch.object(objects.VnfInstanceList, "vnf_instance_list")
@mock.patch.object(objects.VnfPackageVnfd, 'get_vnf_package_vnfd')
@mock.patch.object(VNFLcmRPCAPI, "update")
def test_update_vnf_none_vnfd_data(
self,
mock_update,
mock_vnf_package_vnf_get_vnf_package_vnfd,
mock_vnf_instance_list,
mock_vnf_index_list,
mock_get_service_plugins):
mock_vnf_index_list.return_value = fakes._get_vnf()
mock_vnf_instance_list.return_value = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED)
mock_vnf_package_vnf_get_vnf_package_vnfd.return_value = ""
body = {"vnfInstanceName": "new_instance_name",
"vnfInstanceDescription": "new_instance_discription",
"vnfdId": "2c69a161-0000-4b0f-bcf8-391f8fc76600",
"vnfConfigurableProperties": {
"test": "test_value"
},
"vnfcInfoModificationsDeleteIds": ["test1"],
"metadata": {"testkey": "test_value"},
"vimConnectionInfo": {"id": "testid"}}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s' % constants.UUID)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'PATCH'
fakes._get_vnf()
msg = _("Can not find requested vnf package vnfd: %s") %\
body.get('vnfdId')
res = self._make_problem_detail(msg, 400, 'Bad Request')
resp = req.get_response(self.app)
self.assertEqual(res.text, resp.text)