Add VNF package update API
Added API to update information of VNF package. Implemented below API:- * PATCH /vnf_packages/{vnfPkgId} Co-authored-by: Shubham Potale <shubham.potale@nttdata.com> Change-Id: I6d60e87b48a6703362dcd30975f300f524f8ca7a Implements: bp enhance-vnf-package-support-part1
This commit is contained in:
parent
067d00371b
commit
c74cad521c
@ -498,6 +498,19 @@ tenant_id_opt:
|
||||
in: body
|
||||
required: false
|
||||
type: string
|
||||
update_operational_state:
|
||||
description: |
|
||||
New value of the operational state of the on-boarded instance of the VNF
|
||||
package. Valid values are "ENABLED" and "DISABLED". See note.
|
||||
in: body
|
||||
required: false
|
||||
type: string
|
||||
update_user_defined_data:
|
||||
description: |
|
||||
User defined data to be updated. For existing keys, the value is replaced.
|
||||
in: body
|
||||
required: false
|
||||
type: object
|
||||
updated_at:
|
||||
description: |
|
||||
The date and time when the resource was updated.
|
||||
@ -506,6 +519,19 @@ updated_at:
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
updated_operational_state:
|
||||
description: |
|
||||
Updated value of the operational state of the on-boarded instance of
|
||||
the VNF package.
|
||||
in: body
|
||||
required: false
|
||||
type: string
|
||||
updated_user_defined_data:
|
||||
description: |
|
||||
Updated value of user defined data.
|
||||
in: body
|
||||
required: false
|
||||
type: object
|
||||
usageState:
|
||||
description: |
|
||||
Usage state of the VNF package.
|
||||
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"operationalState": "DISABLED",
|
||||
"userDefinedData": {
|
||||
"key1": "value1",
|
||||
"key2": "value2"
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"operationalState":"DISABLED",
|
||||
"userDefinedData":{
|
||||
"abc":"xyz"
|
||||
}
|
||||
}
|
@ -247,3 +247,61 @@ Request Parameters
|
||||
- addressInformation: addressInformation
|
||||
- userName: userName
|
||||
- password: password
|
||||
|
||||
Update VNF Package Information
|
||||
==============================
|
||||
|
||||
.. rest_method:: PATCH /vnfpkgm/v1/vnf_packages/{vnf_package_id}
|
||||
|
||||
Updates the information of a VNF package.
|
||||
|
||||
Response Codes
|
||||
--------------
|
||||
|
||||
.. rest_status_code:: success status.yaml
|
||||
|
||||
- 200
|
||||
|
||||
.. rest_status_code:: error status.yaml
|
||||
|
||||
- 400
|
||||
- 401
|
||||
- 403
|
||||
- 404
|
||||
- 409
|
||||
|
||||
Request Parameters
|
||||
------------------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- vnf_package_id: vnf_package_id_path
|
||||
- operationalState: update_operational_state
|
||||
- userDefinedData: update_user_defined_data
|
||||
|
||||
.. note::
|
||||
At least one of the "operationalState" or "userDefinedData" parameters
|
||||
shall be present. If the VNF package is not on-boarded, the operation is
|
||||
used only to update existing or add additional user defined data using the
|
||||
"userDefinedData" attribute. If user passes existing user defined data
|
||||
with exact same key/values pairs, then it would return 400 error.
|
||||
|
||||
Request Example
|
||||
---------------
|
||||
|
||||
.. literalinclude:: samples/vnf_packages/vnf-packages-patch-request.json
|
||||
:language: javascript
|
||||
|
||||
Response Parameters
|
||||
-------------------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- operationalState: updated_operational_state
|
||||
- userDefinedData: updated_user_defined_data
|
||||
|
||||
Response Example
|
||||
----------------
|
||||
|
||||
.. literalinclude:: samples/vnf_packages/vnf-packages-patch-response.json
|
||||
:language: javascript
|
||||
|
@ -19,6 +19,7 @@ Schema for vnf packages create API.
|
||||
"""
|
||||
|
||||
from tacker.api.validation import parameter_types
|
||||
from tacker.objects.fields import PackageOperationalStateType
|
||||
|
||||
create = {
|
||||
'type': 'object',
|
||||
@ -48,3 +49,21 @@ upload_from_uri = {
|
||||
'required': ['addressInformation'],
|
||||
'additionalProperties': False,
|
||||
}
|
||||
|
||||
"""
|
||||
Schema for vnf packages update API.
|
||||
|
||||
"""
|
||||
patch = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'operationalState': {
|
||||
'type': 'string',
|
||||
'enum': list(PackageOperationalStateType.ALL),
|
||||
},
|
||||
'userDefinedData': parameter_types.keyvalue_pairs,
|
||||
},
|
||||
'anyOf': [{'required': ['operationalState']},
|
||||
{'required': ['userDefinedData']}],
|
||||
'additionalProperties': False
|
||||
}
|
||||
|
@ -92,6 +92,19 @@ class ViewBuilder(object):
|
||||
|
||||
return vnf_package_response
|
||||
|
||||
def _get_modified_user_data(self, old_user_data, new_user_data):
|
||||
# Checking for the new keys
|
||||
user_data_response = {k: new_user_data[k] for k
|
||||
in set(new_user_data) - set(old_user_data)}
|
||||
|
||||
# Checking for updation in values of existing keys
|
||||
for old_key, old_value in old_user_data.items():
|
||||
if old_key in new_user_data.keys() and \
|
||||
new_user_data[old_key] != old_user_data[old_key]:
|
||||
user_data_response[old_key] = new_user_data[old_key]
|
||||
|
||||
return user_data_response
|
||||
|
||||
def create(self, request, vnf_package):
|
||||
|
||||
return self._get_vnf_package(vnf_package)
|
||||
@ -103,3 +116,14 @@ class ViewBuilder(object):
|
||||
def index(self, request, vnf_packages):
|
||||
return {'vnf_packages': [self._get_vnf_package(
|
||||
vnf_package) for vnf_package in vnf_packages]}
|
||||
|
||||
def patch(self, vnf_package, new_vnf_package):
|
||||
response = {}
|
||||
if vnf_package.operational_state != new_vnf_package.operational_state:
|
||||
response['operationalState'] = new_vnf_package.operational_state
|
||||
if vnf_package.user_data != new_vnf_package.user_data:
|
||||
updated_user_data = self._get_modified_user_data(
|
||||
vnf_package.user_data, new_vnf_package.user_data)
|
||||
response['userDefinedData'] = updated_user_data
|
||||
|
||||
return response
|
||||
|
@ -48,6 +48,20 @@ class VnfPkgmController(wsgi.Controller):
|
||||
self.rpc_api = vnf_pkgm_rpc.VNFPackageRPCAPI()
|
||||
glance_store.initialize_glance_store()
|
||||
|
||||
def _get_vnf_package(self, id, request):
|
||||
# check if id is of type uuid format
|
||||
if not uuidutils.is_uuid_like(id):
|
||||
msg = _("Can not find requested vnf package: %s") % id
|
||||
raise webob.exc.HTTPNotFound(explanation=msg)
|
||||
|
||||
try:
|
||||
vnf_package = vnf_package_obj.VnfPackage.get_by_id(
|
||||
request.context, id)
|
||||
except exceptions.VnfPackageNotFound:
|
||||
msg = _("Can not find requested vnf package: %s") % id
|
||||
raise webob.exc.HTTPNotFound(explanation=msg)
|
||||
return vnf_package
|
||||
|
||||
@wsgi.response(http_client.CREATED)
|
||||
@wsgi.expected_errors((http_client.BAD_REQUEST, http_client.FORBIDDEN))
|
||||
@validation.schema(vnf_packages.create)
|
||||
@ -108,17 +122,7 @@ class VnfPkgmController(wsgi.Controller):
|
||||
context = request.environ['tacker.context']
|
||||
context.can(vnf_package_policies.VNFPKGM % 'delete')
|
||||
|
||||
# check if id is of type uuid format
|
||||
if not uuidutils.is_uuid_like(id):
|
||||
msg = _("Can not find requested vnf package: %s") % id
|
||||
raise webob.exc.HTTPNotFound(explanation=msg)
|
||||
|
||||
try:
|
||||
vnf_package = vnf_package_obj.VnfPackage.get_by_id(
|
||||
request.context, id)
|
||||
except exceptions.VnfPackageNotFound:
|
||||
msg = _("Can not find requested vnf package: %s") % id
|
||||
raise webob.exc.HTTPNotFound(explanation=msg)
|
||||
vnf_package = self._get_vnf_package(id, request)
|
||||
|
||||
if (vnf_package.operational_state ==
|
||||
fields.PackageOperationalStateType.ENABLED or
|
||||
@ -143,17 +147,7 @@ class VnfPkgmController(wsgi.Controller):
|
||||
context = request.environ['tacker.context']
|
||||
context.can(vnf_package_policies.VNFPKGM % 'upload_package_content')
|
||||
|
||||
# check if id is of type uuid format
|
||||
if not uuidutils.is_uuid_like(id):
|
||||
msg = _("Can not find requested vnf package: %s") % id
|
||||
raise webob.exc.HTTPNotFound(explanation=msg)
|
||||
|
||||
try:
|
||||
vnf_package = vnf_package_obj.VnfPackage.get_by_id(
|
||||
request.context, id)
|
||||
except exceptions.VnfPackageNotFound:
|
||||
msg = _("Can not find requested vnf package: %s") % id
|
||||
raise webob.exc.HTTPNotFound(explanation=msg)
|
||||
vnf_package = self._get_vnf_package(id, request)
|
||||
|
||||
if vnf_package.onboarding_state != \
|
||||
fields.PackageOnboardingStateType.CREATED:
|
||||
@ -197,10 +191,7 @@ class VnfPkgmController(wsgi.Controller):
|
||||
context = request.environ['tacker.context']
|
||||
context.can(vnf_package_policies.VNFPKGM % 'upload_from_uri')
|
||||
|
||||
# check if id is of type uuid format
|
||||
if not uuidutils.is_uuid_like(id):
|
||||
msg = _("Can not find requested vnf package: %s") % id
|
||||
raise webob.exc.HTTPNotFound(explanation=msg)
|
||||
vnf_package = self._get_vnf_package(id, request)
|
||||
|
||||
url = body['addressInformation']
|
||||
try:
|
||||
@ -213,13 +204,6 @@ class VnfPkgmController(wsgi.Controller):
|
||||
if hasattr(data_iter, 'close'):
|
||||
data_iter.close()
|
||||
|
||||
try:
|
||||
vnf_package = vnf_package_obj.VnfPackage.get_by_id(
|
||||
request.context, id)
|
||||
except exceptions.VnfPackageNotFound:
|
||||
msg = _("Can not find requested vnf package: %s") % id
|
||||
raise webob.exc.HTTPNotFound(explanation=msg)
|
||||
|
||||
if vnf_package.onboarding_state != \
|
||||
fields.PackageOnboardingStateType.CREATED:
|
||||
msg = _("VNF Package %(id)s onboarding state is not "
|
||||
@ -238,6 +222,59 @@ class VnfPkgmController(wsgi.Controller):
|
||||
user_name=body.get('userName'),
|
||||
password=body.get('password'))
|
||||
|
||||
@wsgi.response(http_client.OK)
|
||||
@wsgi.expected_errors((http_client.BAD_REQUEST, http_client.FORBIDDEN,
|
||||
http_client.NOT_FOUND, http_client.CONFLICT))
|
||||
@validation.schema(vnf_packages.patch)
|
||||
def patch(self, request, id, body):
|
||||
context = request.environ['tacker.context']
|
||||
context.can(vnf_package_policies.VNFPKGM % 'patch')
|
||||
|
||||
old_vnf_package = self._get_vnf_package(id, request)
|
||||
vnf_package = old_vnf_package.obj_clone()
|
||||
|
||||
user_data = body.get('userDefinedData')
|
||||
operational_state = body.get('operationalState')
|
||||
|
||||
if operational_state:
|
||||
if vnf_package.onboarding_state == \
|
||||
fields.PackageOnboardingStateType.ONBOARDED:
|
||||
if vnf_package.operational_state == operational_state:
|
||||
msg = _("VNF Package %(id)s is already in "
|
||||
"%(operationState)s operational state") % {
|
||||
"id": id,
|
||||
"operationState": vnf_package.operational_state}
|
||||
raise webob.exc.HTTPConflict(explanation=msg)
|
||||
else:
|
||||
# update vnf_package operational state,
|
||||
# if vnf_package Onboarding State is ONBOARDED
|
||||
vnf_package.operational_state = operational_state
|
||||
else:
|
||||
if not user_data:
|
||||
msg = _("Updating operational state is not allowed for VNF"
|
||||
" Package %(id)s when onboarding state is not "
|
||||
"%(onboarded)s")
|
||||
raise webob.exc.HTTPBadRequest(
|
||||
explanation=msg % {"id": id, "onboarded": fields.
|
||||
PackageOnboardingStateType.ONBOARDED})
|
||||
# update user data
|
||||
if user_data:
|
||||
for key, value in list(user_data.items()):
|
||||
if vnf_package.user_data.get(key) == value:
|
||||
del user_data[key]
|
||||
|
||||
if not user_data:
|
||||
msg = _("The userDefinedData provided in update request is as"
|
||||
" the existing userDefinedData of vnf package %(id)s."
|
||||
" Nothing to update.")
|
||||
raise webob.exc.HTTPConflict(
|
||||
explanation=msg % {"id": id})
|
||||
vnf_package.user_data = user_data
|
||||
|
||||
vnf_package.save()
|
||||
|
||||
return self._view_builder.patch(old_vnf_package, vnf_package)
|
||||
|
||||
|
||||
def create_resource():
|
||||
body_deserializers = {
|
||||
|
@ -58,7 +58,8 @@ class VnfpkgmAPIRouter(wsgi.Router):
|
||||
methods, controller, default_resource)
|
||||
|
||||
# Allowed methods on /vnf_packages/{id} resource
|
||||
methods = {"DELETE": "delete", "GET": "show"}
|
||||
methods = {"DELETE": "delete", "GET": "show",
|
||||
"PATCH": "patch"}
|
||||
self._setup_route(mapper, "/vnf_packages/{id}",
|
||||
methods, controller, default_resource)
|
||||
|
||||
|
@ -249,3 +249,8 @@ class LimitExceeded(TackerException):
|
||||
self.retry_after = (int(kwargs['retry']) if kwargs.get('retry')
|
||||
else None)
|
||||
super(LimitExceeded, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class UserDataUpdateCreateFailed(TackerException):
|
||||
msg_fmt = _("User data for VNF package %(id)s cannot be updated "
|
||||
"or created after %(retries)d retries.")
|
||||
|
@ -1 +1 @@
|
||||
9d425296f2c3
|
||||
abbef484b34c
|
@ -0,0 +1,40 @@
|
||||
# Copyright 2019 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.
|
||||
#
|
||||
|
||||
"""modify_unique_constraint_on_vnf_packages_user_data
|
||||
|
||||
Revision ID: abbef484b34c
|
||||
Revises: 9d425296f2c3
|
||||
Create Date: 2019-11-18 19:34:26.853715
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'abbef484b34c'
|
||||
down_revision = '9d425296f2c3'
|
||||
|
||||
from alembic import op
|
||||
|
||||
|
||||
def upgrade(active_plugins=None, options=None):
|
||||
op.drop_constraint(
|
||||
constraint_name='uniq_vnf_packages_user_data0idid0key0deleted',
|
||||
table_name='vnf_packages_user_data',
|
||||
type_='unique')
|
||||
|
||||
op.create_unique_constraint(
|
||||
constraint_name='uniq_vnf_packages_user_data0package_uuid0key0deleted',
|
||||
table_name='vnf_packages_user_data',
|
||||
columns=['package_uuid', 'key', 'deleted'])
|
@ -12,7 +12,9 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_db import exception as db_exc
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
from oslo_utils import timeutils
|
||||
from oslo_utils import uuidutils
|
||||
from oslo_versionedobjects import base as ovoo_base
|
||||
@ -37,8 +39,8 @@ LOG = logging.getLogger(__name__)
|
||||
def _add_user_defined_data(context, package_uuid, user_data,
|
||||
max_retries=10):
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
with db_api.context_manager.writer.using(context):
|
||||
|
||||
new_entries = []
|
||||
for key, value in user_data.items():
|
||||
new_entries.append({"key": key,
|
||||
@ -49,6 +51,58 @@ def _add_user_defined_data(context, package_uuid, user_data,
|
||||
models.VnfPackageUserData.__table__.insert(None),
|
||||
new_entries)
|
||||
|
||||
return user_data
|
||||
except db_exc.DBDuplicateEntry:
|
||||
# a concurrent transaction has been committed,
|
||||
# try again unless this was the last attempt
|
||||
with excutils.save_and_reraise_exception() as context:
|
||||
if attempt < max_retries - 1:
|
||||
context.reraise = False
|
||||
else:
|
||||
raise exceptions.UserDataUpdateCreateFailed(
|
||||
id=package_uuid, retries=max_retries)
|
||||
|
||||
|
||||
def _vnf_package_user_data_get_query(context, package_uuid, model):
|
||||
return api.model_query(context, model, read_deleted="no", project_only=True).\
|
||||
filter_by(package_uuid=package_uuid)
|
||||
|
||||
|
||||
@db_api.context_manager.writer
|
||||
def _update_user_defined_data(context, package_uuid, user_data):
|
||||
model = models.VnfPackageUserData
|
||||
user_data = user_data.copy()
|
||||
session = context.session
|
||||
with session.begin(subtransactions=True):
|
||||
# Get existing user_data
|
||||
db_user_data = _vnf_package_user_data_get_query(context, package_uuid,
|
||||
model).all()
|
||||
save = []
|
||||
skip = []
|
||||
# We only want to send changed user_data.
|
||||
for row in db_user_data:
|
||||
if row.key in user_data:
|
||||
value = user_data.pop(row.key)
|
||||
if row.value != value:
|
||||
# ORM objects will not be saved until we do the bulk save
|
||||
row.value = value
|
||||
save.append(row)
|
||||
continue
|
||||
skip.append(row)
|
||||
|
||||
# We also want to save non-existent user_data
|
||||
save.extend(model(key=key, value=value, package_uuid=package_uuid)
|
||||
for key, value in user_data.items())
|
||||
# Do a bulk save
|
||||
if save:
|
||||
session.bulk_save_objects(save, update_changed_only=True)
|
||||
|
||||
# Construct result dictionary with current user_data
|
||||
save.extend(skip)
|
||||
result = {row['key']: row['value'] for row in save}
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@db_api.context_manager.reader
|
||||
def _vnf_package_get_by_id(context, package_uuid, columns_to_join=None):
|
||||
@ -111,13 +165,21 @@ def _vnf_package_list_by_filters(context, read_deleted=None, **filters):
|
||||
|
||||
|
||||
@db_api.context_manager.writer
|
||||
def _vnf_package_update(context, package_uuid, values, columns_to_join=None):
|
||||
|
||||
vnf_package = _vnf_package_get_by_id(context, package_uuid,
|
||||
columns_to_join=columns_to_join)
|
||||
vnf_package.update(values)
|
||||
def _update_vnf_package_except_user_data(context, vnf_package):
|
||||
vnf_package.save(session=context.session)
|
||||
|
||||
|
||||
def _vnf_package_update(context, package_uuid, values, columns_to_join=None):
|
||||
user_data = values.pop('user_data', None)
|
||||
if user_data:
|
||||
_update_user_defined_data(context, package_uuid, user_data)
|
||||
|
||||
vnf_package = _vnf_package_get_by_id(
|
||||
context, package_uuid, columns_to_join=columns_to_join)
|
||||
if values:
|
||||
vnf_package.update(values)
|
||||
_update_vnf_package_except_user_data(context, vnf_package)
|
||||
|
||||
return vnf_package
|
||||
|
||||
|
||||
|
@ -84,6 +84,17 @@ rules = [
|
||||
'upload_from_uri'
|
||||
}
|
||||
]),
|
||||
policy.DocumentedRuleDefault(
|
||||
name=VNFPKGM % 'patch',
|
||||
check_str=base.RULE_ADMIN_OR_OWNER,
|
||||
description="update information of vnf package.",
|
||||
operations=[
|
||||
{
|
||||
'method': 'PATCH',
|
||||
'path': '/vnf_packages/{vnf_package_id}'
|
||||
}
|
||||
]),
|
||||
|
||||
]
|
||||
|
||||
|
||||
|
@ -119,7 +119,7 @@ class VnfPackageTest(base.BaseTackerTest):
|
||||
body = jsonutils.dumps({"userDefinedData": {"foo": "bar"}})
|
||||
vnf_package = self._create_vnf_package(body)
|
||||
file_path = self._get_csar_file_path("sample_vnf_package_csar.zip")
|
||||
with open(file_path, 'r') as file_object:
|
||||
with open(file_path, 'rb') as file_object:
|
||||
resp, resp_body = self.http_client.do_request(
|
||||
'{base_path}/{id}/package_content'.format(
|
||||
id=vnf_package['id'],
|
||||
@ -132,3 +132,40 @@ class VnfPackageTest(base.BaseTackerTest):
|
||||
|
||||
self._delete_vnf_package(vnf_package['id'])
|
||||
self._wait_for_delete(vnf_package['id'])
|
||||
|
||||
def test_patch_in_onboarded_state(self):
|
||||
user_data = jsonutils.dumps(
|
||||
{"userDefinedData": {"key1": "val1", "key2": "val2",
|
||||
"key3": "val3"}})
|
||||
vnf_package = self._create_vnf_package(user_data)
|
||||
|
||||
update_req_body = jsonutils.dumps(
|
||||
{"operationalState": "DISABLED",
|
||||
"userDefinedData": {"key1": "changed_val1",
|
||||
"key2": "val2", "new_key": "new_val"}})
|
||||
|
||||
expected_result = {"operationalState": "DISABLED",
|
||||
"userDefinedData": {
|
||||
"key1": "changed_val1", "new_key": "new_val"}}
|
||||
|
||||
file_path = self._get_csar_file_path("sample_vnf_package_csar.zip")
|
||||
with open(file_path, 'rb') as file_object:
|
||||
resp, resp_body = self.http_client.do_request(
|
||||
'{base_path}/{id}/package_content'.format(
|
||||
id=vnf_package['id'],
|
||||
base_path=self.base_url),
|
||||
"PUT", body=file_object, content_type='application/zip')
|
||||
|
||||
self.assertEqual(202, resp.status_code)
|
||||
self._wait_for_onboard(vnf_package['id'])
|
||||
|
||||
# Update vnf package which is onboarded
|
||||
resp, resp_body = self.http_client.do_request(
|
||||
'{base_path}/{id}'.format(id=vnf_package['id'],
|
||||
base_path=self.base_url),
|
||||
"PATCH", content_type='application/json', body=update_req_body)
|
||||
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual(expected_result, resp_body)
|
||||
self._delete_vnf_package(vnf_package['id'])
|
||||
self._wait_for_delete(vnf_package['id'])
|
||||
|
@ -49,9 +49,10 @@ class TestVnfPackage(SqlTestCase):
|
||||
vnf_package_db = models.VnfPackage()
|
||||
vnf_package_db.update(fakes.fake_vnf_package())
|
||||
vnf_package_db.save(self.context.session)
|
||||
expected_result = {'abc': 'xyz'}
|
||||
result = vnf_package._add_user_defined_data(
|
||||
self.context, vnf_package_db.id, vnf_package_db.user_data)
|
||||
self.assertEqual(None, result)
|
||||
self.assertEqual(expected_result, result)
|
||||
|
||||
def test_vnf_package_get_by_id(self):
|
||||
result = vnf_package._vnf_package_get_by_id(
|
||||
@ -75,7 +76,7 @@ class TestVnfPackage(SqlTestCase):
|
||||
update = {'user_data': {'test': 'xyz'}}
|
||||
result = vnf_package._vnf_package_update(
|
||||
self.context, self.vnf_package.id, update)
|
||||
self.assertEqual({'test': 'xyz'}, result.user_data)
|
||||
self.assertEqual({'test': 'xyz', 'abc': 'xyz'}, result.metadetails)
|
||||
|
||||
def test_destroy_vnf_package(self):
|
||||
vnf_package._destroy_vnf_package(self.context,
|
||||
@ -92,3 +93,27 @@ class TestVnfPackage(SqlTestCase):
|
||||
self.context, vnf_pack_list_obj, response, None)
|
||||
self.assertIsInstance(result, objects.VnfPackagesList)
|
||||
self.assertTrue(result.objects[0].id)
|
||||
|
||||
def test_patch_user_data_existing_key_with_same_value(self):
|
||||
update = {'user_data': {'abc': 'xyz'}}
|
||||
result = vnf_package._vnf_package_update(
|
||||
self.context, self.vnf_package.id, update)
|
||||
self.assertEqual({'abc': 'xyz'}, result.metadetails)
|
||||
|
||||
def test_patch_user_data_existing_key_new_value(self):
|
||||
update = {'user_data': {'abc': 'val1'}}
|
||||
result = vnf_package._vnf_package_update(
|
||||
self.context, self.vnf_package.id, update)
|
||||
self.assertEqual({'abc': 'val1'}, result.metadetails)
|
||||
|
||||
def test_patch_user_data_with_new_key_value(self):
|
||||
update = {'user_data': {'test': '123'}}
|
||||
result = vnf_package._vnf_package_update(
|
||||
self.context, self.vnf_package.id, update)
|
||||
self.assertEqual({'test': '123', 'abc': 'xyz'}, result.metadetails)
|
||||
|
||||
def test_patch_user_data_add_key_value_and_modify_value(self):
|
||||
update = {'user_data': {'test': 'val1', 'abc': 'val2'}}
|
||||
result = vnf_package._vnf_package_update(
|
||||
self.context, self.vnf_package.id, update)
|
||||
self.assertEqual({'test': 'val1', 'abc': 'val2'}, result.metadetails)
|
||||
|
@ -97,16 +97,43 @@ class InjectContext(wsgi.Middleware):
|
||||
return self.application
|
||||
|
||||
|
||||
def return_vnf_package():
|
||||
model_obj = models.VnfPackage()
|
||||
model_obj.update(fake_vnf_package())
|
||||
def fake_vnf_package_user_data(**updates):
|
||||
vnf_package_user_data = {
|
||||
'key': 'key',
|
||||
'value': 'value',
|
||||
'package_uuid': constants.UUID,
|
||||
'id': constants.UUID,
|
||||
}
|
||||
|
||||
if updates:
|
||||
vnf_package_user_data.update(updates)
|
||||
|
||||
return vnf_package_user_data
|
||||
|
||||
|
||||
def return_vnf_package_user_data(**updates):
|
||||
model_obj = models.VnfPackageUserData()
|
||||
model_obj.update(fake_vnf_package_user_data(**updates))
|
||||
return model_obj
|
||||
|
||||
|
||||
def return_vnfpkg_obj():
|
||||
def return_vnf_package(**updates):
|
||||
model_obj = models.VnfPackage()
|
||||
if 'user_data' in updates:
|
||||
metadata = []
|
||||
for key, value in updates.pop('user_data').items():
|
||||
vnf_package_user_data = return_vnf_package_user_data(
|
||||
**{'key': key, 'value': value})
|
||||
metadata.extend([vnf_package_user_data])
|
||||
model_obj._metadata = metadata
|
||||
model_obj.update(fake_vnf_package(**updates))
|
||||
return model_obj
|
||||
|
||||
|
||||
def return_vnfpkg_obj(**updates):
|
||||
vnf_package = vnf_package_obj.VnfPackage._from_db_object(
|
||||
context, vnf_package_obj.VnfPackage(),
|
||||
return_vnf_package(), expected_attrs=None)
|
||||
return_vnf_package(**updates), expected_attrs=None)
|
||||
return vnf_package
|
||||
|
||||
|
||||
|
@ -158,7 +158,6 @@ class TestController(base.TestCase):
|
||||
file_path = "tacker/tests/etc/samples/test_data.zip"
|
||||
file_obj = open(file_path, "rb")
|
||||
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj()
|
||||
mock_vnf_pack_save.return_value = fakes.return_vnfpkg_obj()
|
||||
mock_glance_store.return_value = 'location', 'size', 'checksum',\
|
||||
'multihash', 'loc_meta'
|
||||
req = fake_request.HTTPRequest.blank(
|
||||
@ -224,7 +223,6 @@ class TestController(base.TestCase):
|
||||
mock_url_open):
|
||||
body = {"addressInformation": "http://test_data.zip"}
|
||||
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj()
|
||||
mock_vnf_pack_save.return_value = fakes.return_vnfpkg_obj()
|
||||
req = fake_request.HTTPRequest.blank(
|
||||
'/vnf_packages/%s/package_content/upload_from_uri'
|
||||
% constants.UUID)
|
||||
@ -274,7 +272,10 @@ class TestController(base.TestCase):
|
||||
self.controller.upload_vnf_package_from_uri,
|
||||
req, constants.UUID, body=body)
|
||||
|
||||
def test_upload_vnf_package_from_uri_with_invalid_url(self):
|
||||
@mock.patch.object(vnf_package.VnfPackage, "get_by_id")
|
||||
def test_upload_vnf_package_from_uri_with_invalid_url(
|
||||
self, mock_vnf_by_id):
|
||||
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj()
|
||||
body = {"addressInformation": "http://test_data.zip"}
|
||||
req = fake_request.HTTPRequest.blank(
|
||||
'/vnf_packages/%s/package_content/upload_from_uri'
|
||||
@ -282,3 +283,133 @@ class TestController(base.TestCase):
|
||||
self.assertRaises(exc.HTTPBadRequest,
|
||||
self.controller.upload_vnf_package_from_uri,
|
||||
req, constants.UUID, body=body)
|
||||
|
||||
@mock.patch.object(vnf_package.VnfPackage, "get_by_id")
|
||||
@mock.patch.object(vnf_package.VnfPackage, "save")
|
||||
def test_patch(self, mock_save, mock_vnf_by_id):
|
||||
update_onboarding_state = {'onboarding_state': 'ONBOARDED'}
|
||||
mock_vnf_by_id.return_value = \
|
||||
fakes.return_vnfpkg_obj(**update_onboarding_state)
|
||||
|
||||
req_body = {"operationalState": "ENABLED",
|
||||
"userDefinedData": {"testKey1": "val01",
|
||||
"testKey2": "val02", "testkey3": "val03"}}
|
||||
|
||||
req = fake_request.HTTPRequest.blank(
|
||||
'/vnf_packages/%s'
|
||||
% constants.UUID)
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
req.method = 'PATCH'
|
||||
req.body = jsonutils.dump_as_bytes(req_body)
|
||||
resp = req.get_response(self.app)
|
||||
|
||||
self.assertEqual(http_client.OK, resp.status_code)
|
||||
self.assertEqual(req_body, jsonutils.loads(resp.body))
|
||||
|
||||
def test_patch_with_empty_body(self):
|
||||
body = {}
|
||||
req = fake_request.HTTPRequest.blank(
|
||||
'/vnf_packages/%s'
|
||||
% constants.UUID)
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
req.method = 'PATCH'
|
||||
req.body = jsonutils.dump_as_bytes(body)
|
||||
resp = req.get_response(self.app)
|
||||
self.assertEqual(http_client.BAD_REQUEST, resp.status_code)
|
||||
|
||||
def test_patch_with_invalid_operational_state(self):
|
||||
body = {"operationalState": "DISABLE"}
|
||||
req = fake_request.HTTPRequest.blank(
|
||||
'/vnf_packages/%s'
|
||||
% constants.UUID)
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
req.method = 'PATCH'
|
||||
req.body = jsonutils.dump_as_bytes(body)
|
||||
resp = req.get_response(self.app)
|
||||
self.assertEqual(http_client.BAD_REQUEST, resp.status_code)
|
||||
|
||||
@mock.patch.object(vnf_package.VnfPackage, "get_by_id")
|
||||
@mock.patch.object(vnf_package.VnfPackage, "save")
|
||||
def test_patch_update_existing_user_data(self, mock_save, mock_vnf_by_id):
|
||||
fake_obj = fakes.return_vnfpkg_obj(
|
||||
**{'user_data': {"testKey1": "val01", "testKey2": "val02",
|
||||
"testKey3": "val03"}})
|
||||
mock_vnf_by_id.return_value = fake_obj
|
||||
req_body = {"userDefinedData": {"testKey1": "changed_val01",
|
||||
"testKey2": "changed_val02",
|
||||
"testKey3": "changed_val03"}}
|
||||
req = fake_request.HTTPRequest.blank(
|
||||
'/vnf_packages/%s'
|
||||
% constants.UUID)
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
req.method = 'PATCH'
|
||||
req.body = jsonutils.dump_as_bytes(req_body)
|
||||
resp = req.get_response(self.app)
|
||||
self.assertEqual(http_client.OK, resp.status_code)
|
||||
self.assertEqual(req_body, jsonutils.loads(resp.body))
|
||||
|
||||
@mock.patch.object(vnf_package.VnfPackage, "get_by_id")
|
||||
@mock.patch.object(vnf_package.VnfPackage, "save")
|
||||
def test_patch_failed_with_same_user_data(self, mock_save,
|
||||
mock_vnf_by_id):
|
||||
body = {"userDefinedData": {"testKey1": "val01",
|
||||
"testKey2": "val02", "testkey3": "val03"}}
|
||||
fake_obj = fakes.return_vnfpkg_obj(
|
||||
**{'user_data': body["userDefinedData"]})
|
||||
mock_vnf_by_id.return_value = fake_obj
|
||||
|
||||
req = fake_request.HTTPRequest.blank('/vnf_packages/%s'
|
||||
% constants.UUID)
|
||||
self.assertRaises(exc.HTTPConflict,
|
||||
self.controller.patch,
|
||||
req, constants.UUID, body=body)
|
||||
|
||||
def test_patch_with_invalid_uuid(self):
|
||||
body = {"operationalState": "ENABLED"}
|
||||
req = fake_request.HTTPRequest.blank(
|
||||
'/vnf_packages/%s'
|
||||
% constants.INVALID_UUID)
|
||||
exception = self.assertRaises(exc.HTTPNotFound,
|
||||
self.controller.patch,
|
||||
req, constants.INVALID_UUID, body=body)
|
||||
self.assertEqual(
|
||||
"Can not find requested vnf package: %s" % constants.INVALID_UUID,
|
||||
exception.explanation)
|
||||
|
||||
@mock.patch.object(vnf_package.VnfPackage, "get_by_id")
|
||||
def test_patch_with_non_existing_vnf_package(self, mock_vnf_by_id):
|
||||
body = {"operationalState": "ENABLED"}
|
||||
msg = _("Can not find requested vnf package: %s") % constants.UUID
|
||||
mock_vnf_by_id.side_effect = exc.HTTPNotFound(explanation=msg)
|
||||
req = fake_request.HTTPRequest.blank(
|
||||
'/vnf_packages/%s' % constants.UUID)
|
||||
exception = self.assertRaises(
|
||||
exc.HTTPNotFound, self.controller.patch,
|
||||
req, constants.UUID, body=body)
|
||||
self.assertEqual(
|
||||
"Can not find requested vnf package: %s" % constants.UUID,
|
||||
exception.explanation)
|
||||
|
||||
@mock.patch.object(vnf_package.VnfPackage, "get_by_id")
|
||||
def test_patch_failed_with_same_operational_state(self, mock_vnf_by_id):
|
||||
update_operational_state = {'onboarding_state': 'ONBOARDED'}
|
||||
vnf_obj = fakes.return_vnfpkg_obj(**update_operational_state)
|
||||
mock_vnf_by_id.return_value = vnf_obj
|
||||
body = {"operationalState": "DISABLED",
|
||||
"userDefinedData": {"testKey1": "val01",
|
||||
"testKey2": "val02", "testkey3": "val03"}}
|
||||
req = fake_request.HTTPRequest.blank('/vnf_packages/%s'
|
||||
% constants.UUID)
|
||||
self.assertRaises(exc.HTTPConflict,
|
||||
self.controller.patch,
|
||||
req, constants.UUID, body=body)
|
||||
|
||||
@mock.patch.object(vnf_package.VnfPackage, "get_by_id")
|
||||
def test_patch_not_in_onboarded_state(self, mock_vnf_by_id):
|
||||
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj()
|
||||
body = {"operationalState": "DISABLED"}
|
||||
req = fake_request.HTTPRequest.blank('/vnf_packages/%s'
|
||||
% constants.UUID)
|
||||
self.assertRaises(exc.HTTPBadRequest,
|
||||
self.controller.patch,
|
||||
req, constants.UUID, body=body)
|
||||
|
Loading…
Reference in New Issue
Block a user