Support API enhancement for Create VNF
Supported/Enhanced the Create VNF API. Implements: blueprint support-vnfm-operations Spec: https://specs.openstack.org/openstack/tacker-specs/specs/victoria/support-sol003-vnfm-operations.html Change-Id: Ie602242474149fec3ee8dbe1b8745c1803ad7336
This commit is contained in:
parent
ff97127e99
commit
e925ddfa19
|
@ -471,6 +471,12 @@ vnf_instance_create_request_description:
|
|||
in: body
|
||||
required: false
|
||||
type: string
|
||||
vnf_instance_create_request_metadata:
|
||||
description: |
|
||||
This attribute provides values for the "metadata" attribute in "VnfInstance".
|
||||
in: body
|
||||
required: false
|
||||
type: array
|
||||
vnf_instance_create_request_name:
|
||||
description: |
|
||||
Human-readable name of the VNF instance to be created.
|
||||
|
|
|
@ -42,6 +42,7 @@ Request Parameters
|
|||
- vnfdId: vnf_instance_create_request_vnfd_id
|
||||
- vnfInstanceName: vnf_instance_create_request_name
|
||||
- vnfInstanceDescription: vnf_instance_create_request_description
|
||||
- metadata: vnf_instance_create_request_metadata
|
||||
|
||||
Request Example
|
||||
---------------
|
||||
|
@ -520,9 +521,9 @@ Response Parameters
|
|||
- resourceId: resource_handle_resource_id
|
||||
- vimLevelResourceType: resource_handle_vim_level_resource_type
|
||||
- _links: vnf_instance_links
|
||||
|
||||
|
||||
Response Example
|
||||
----------------
|
||||
|
||||
.. literalinclude:: samples/vnflcm/list-vnf-instance-response.json
|
||||
:language: javascript
|
||||
:language: javascript
|
||||
|
|
|
@ -181,6 +181,7 @@ create = {
|
|||
'vnfdId': parameter_types.uuid,
|
||||
'vnfInstanceName': parameter_types.name_allow_zero_min_length,
|
||||
'vnfInstanceDescription': parameter_types.description,
|
||||
'metadata': parameter_types.keyvalue_pairs,
|
||||
},
|
||||
'required': ['vnfdId'],
|
||||
'additionalProperties': False,
|
||||
|
|
|
@ -56,6 +56,10 @@ class ViewBuilder(object):
|
|||
|
||||
def _get_vnf_instance_info(self, vnf_instance):
|
||||
vnf_instance_dict = vnf_instance.to_dict()
|
||||
if 'vnf_metadata' in vnf_instance_dict:
|
||||
metadata_val = vnf_instance_dict.pop('vnf_metadata')
|
||||
vnf_instance_dict['metadata'] = metadata_val
|
||||
|
||||
vnf_instance_dict = utils.convert_snakecase_to_camelcase(
|
||||
vnf_instance_dict)
|
||||
|
||||
|
|
|
@ -186,7 +186,8 @@ class VnfLcmController(wsgi.Controller):
|
|||
vnf_software_version=vnfd.vnf_software_version,
|
||||
vnfd_version=vnfd.vnfd_version,
|
||||
vnf_pkg_id=vnfd.package_uuid,
|
||||
tenant_id=request.context.project_id)
|
||||
tenant_id=request.context.project_id,
|
||||
vnf_metadata=req_body.get('metadata'))
|
||||
|
||||
vnf_instance.create()
|
||||
result = self._view_builder.create(vnf_instance)
|
||||
|
|
|
@ -202,6 +202,7 @@ class VnfInstance(model_base.BASE, models.SoftDeleteMixin,
|
|||
vim_connection_info = sa.Column(sa.JSON(), nullable=True)
|
||||
vnf_pkg_id = sa.Column(types.Uuid, nullable=False)
|
||||
tenant_id = sa.Column('tenant_id', sa.String(length=64), nullable=False)
|
||||
vnf_metadata = sa.Column(sa.JSON(), nullable=True)
|
||||
|
||||
|
||||
class VnfInstantiatedInfo(model_base.BASE, models.SoftDeleteMixin,
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
# Copyright 2020 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.
|
||||
#
|
||||
|
||||
# flake8: noqa: E402
|
||||
|
||||
"""add_vnf_metadata_to_vnflcm_db
|
||||
|
||||
Revision ID: 745e3e9fe5e2
|
||||
Revises: f9bc96967462
|
||||
Create Date: 2020-08-28 20:21:04.604343
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '745e3e9fe5e2'
|
||||
down_revision = 'f9bc96967462'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
from tacker.db import migration
|
||||
|
||||
|
||||
def upgrade(active_plugins=None, options=None):
|
||||
op.add_column('vnf_instances',
|
||||
sa.Column('vnf_metadata', sa.JSON(), nullable=True))
|
|
@ -1 +1 @@
|
|||
f9bc96967462
|
||||
745e3e9fe5e2
|
||||
|
|
|
@ -139,7 +139,8 @@ class VnfInstance(base.TackerObject, base.TackerPersistentObject,
|
|||
'tenant_id': fields.StringField(nullable=False),
|
||||
'instantiated_vnf_info': fields.ObjectField('InstantiatedVnfInfo',
|
||||
nullable=True, default=None),
|
||||
'vnf_pkg_id': fields.StringField(nullable=False)
|
||||
'vnf_pkg_id': fields.StringField(nullable=False),
|
||||
'vnf_metadata': fields.DictOfStringsField(nullable=True, default={})
|
||||
}
|
||||
|
||||
def __init__(self, context=None, **kwargs):
|
||||
|
@ -245,7 +246,8 @@ class VnfInstance(base.TackerObject, base.TackerPersistentObject,
|
|||
'vnf_product_name': self.vnf_product_name,
|
||||
'vnf_software_version': self.vnf_software_version,
|
||||
'vnf_pkg_id': self.vnf_pkg_id,
|
||||
'vnfd_version': self.vnfd_version}
|
||||
'vnfd_version': self.vnfd_version,
|
||||
'vnf_metadata': self.vnf_metadata}
|
||||
|
||||
if (self.instantiation_state == fields.VnfInstanceState.INSTANTIATED
|
||||
and self.instantiated_vnf_info):
|
||||
|
|
|
@ -112,7 +112,8 @@ def get_vnf_instance_data(vnfd_id):
|
|||
"vnfd_id": vnfd_id,
|
||||
"vnfd_version": "1.0",
|
||||
"tenant_id": uuidsentinel.tenant_id,
|
||||
"vnf_pkg_id": uuidsentinel.vnf_pkg_id
|
||||
"vnf_pkg_id": uuidsentinel.vnf_pkg_id,
|
||||
"vnf_metadata": {"key": "value"}
|
||||
}
|
||||
|
||||
|
||||
|
@ -128,7 +129,8 @@ def get_vnf_instance_data_with_id(vnfd_id):
|
|||
"vnfd_id": vnfd_id,
|
||||
"vnfd_version": "1.0",
|
||||
"tenant_id": uuidsentinel.tenant_id,
|
||||
"vnf_pkg_id": uuidsentinel.vnf_pkg_id
|
||||
"vnf_pkg_id": uuidsentinel.vnf_pkg_id,
|
||||
"vnf_metadata": {"key": "value"}
|
||||
}
|
||||
|
||||
|
||||
|
@ -150,7 +152,8 @@ def fake_vnf_instance_model_dict(**updates):
|
|||
'vim_connection_info': [],
|
||||
'tenant_id': '33f8dbdae36142eebf214c1869eb4e4c',
|
||||
'id': constants.UUID,
|
||||
'vnf_pkg_id': uuidsentinel.vnf_pkg_id
|
||||
'vnf_pkg_id': uuidsentinel.vnf_pkg_id,
|
||||
'vnf_metadata': {'key': 'value'}
|
||||
}
|
||||
|
||||
if updates:
|
||||
|
@ -346,7 +349,8 @@ def vnf_instance_model_object(vnf_instance):
|
|||
'vim_connection_info': vnf_instance.vim_connection_info,
|
||||
'tenant_id': vnf_instance.tenant_id,
|
||||
'created_at': vnf_instance.created_at,
|
||||
'vnf_pkg_id': vnf_instance.vnf_pkg_id
|
||||
'vnf_pkg_id': vnf_instance.vnf_pkg_id,
|
||||
'vnf_metadata': vnf_instance.vnf_metadata
|
||||
}
|
||||
|
||||
vnf_instance_db_obj = models.VnfInstance()
|
||||
|
|
|
@ -80,7 +80,8 @@ def _model_non_instantiated_vnf_instance(**updates):
|
|||
'tenant_id': uuidsentinel.tenant_id,
|
||||
'vnfd_id': uuidsentinel.vnfd_id,
|
||||
'vnf_pkg_id': uuidsentinel.vnf_pkg_id,
|
||||
'vnfd_version': '1.0'}
|
||||
'vnfd_version': '1.0',
|
||||
'vnf_metadata': {"key": "value"}}
|
||||
|
||||
if updates:
|
||||
vnf_instance.update(**updates)
|
||||
|
@ -159,7 +160,8 @@ def _fake_vnf_instance_not_instantiated_response(
|
|||
'vnfdVersion': '1.0',
|
||||
'vnfSoftwareVersion': '1.0',
|
||||
'vnfPkgId': uuidsentinel.vnf_pkg_id,
|
||||
'id': uuidsentinel.vnf_instance_id
|
||||
'id': uuidsentinel.vnf_instance_id,
|
||||
'metadata': {'key': 'value'}
|
||||
}
|
||||
|
||||
if updates:
|
||||
|
|
|
@ -34,6 +34,26 @@ from tacker.tests import uuidsentinel
|
|||
from tacker.vnfm import vim_client
|
||||
|
||||
|
||||
class FakeVNFMPlugin(mock.Mock):
|
||||
|
||||
def __init__(self):
|
||||
super(FakeVNFMPlugin, self).__init__()
|
||||
self.vnf1_vnfd_id = 'eb094833-995e-49f0-a047-dfb56aaf7c4e'
|
||||
self.vnf1_vnf_id = '91e32c20-6d1f-47a4-9ba7-08f5e5effe07'
|
||||
self.vnf1_update_vnf_id = '91e32c20-6d1f-47a4-9ba7-08f5e5effaf6'
|
||||
self.vnf2_vnfd_id = 'e4015e9f-1ef2-49fb-adb6-070791ad3c45'
|
||||
self.vnf3_vnfd_id = 'e4015e9f-1ef2-49fb-adb6-070791ad3c45'
|
||||
self.vnf3_vnf_id = '7168062e-9fa1-4203-8cb7-f5c99ff3ee1b'
|
||||
self.vnf3_update_vnf_id = '10f66bc5-b2f1-45b7-a7cd-6dd6ad0017f5'
|
||||
|
||||
self.cp11_id = 'd18c8bae-898a-4932-bff8-d5eac981a9c9'
|
||||
self.cp11_update_id = 'a18c8bae-898a-4932-bff8-d5eac981a9b8'
|
||||
self.cp12_id = 'c8906342-3e30-4b2a-9401-a251a7a9b5dd'
|
||||
self.cp12_update_id = 'b8906342-3e30-4b2a-9401-a251a7a9b5cc'
|
||||
self.cp32_id = '3d1bd2a2-bf0e-44d1-87af-a2c6b2cad3ed'
|
||||
self.cp32_update_id = '064c0d99-5a61-4711-9597-2a44dc5da14b'
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestController(base.TestCase):
|
||||
|
||||
|
@ -71,13 +91,15 @@ class TestController(base.TestCase):
|
|||
updates = {'vnfd_id': uuidsentinel.vnfd_id,
|
||||
'vnf_instance_description': None,
|
||||
'vnf_instance_name': None,
|
||||
'vnf_pkg_id': uuidsentinel.vnf_pkg_id}
|
||||
'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}
|
||||
body = {'vnfdId': uuidsentinel.vnfd_id,
|
||||
'metadata': {"key": "value"}}
|
||||
req.body = jsonutils.dump_as_bytes(body)
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
req.headers['Version'] = '2.6.1'
|
||||
|
@ -115,14 +137,16 @@ class TestController(base.TestCase):
|
|||
updates = {'vnfd_id': uuidsentinel.vnfd_id,
|
||||
'vnf_instance_description': 'SampleVnf Description',
|
||||
'vnf_instance_name': 'SampleVnf',
|
||||
'vnf_pkg_id': uuidsentinel.vnf_pkg_id}
|
||||
'vnf_pkg_id': uuidsentinel.vnf_pkg_id,
|
||||
'vnf_metadata': {"key": "value"}}
|
||||
|
||||
mock_vnf_instance_create.return_value =\
|
||||
fakes.return_vnf_instance_model(**updates)
|
||||
|
||||
body = {'vnfdId': uuidsentinel.vnfd_id,
|
||||
"vnfInstanceName": "SampleVnf",
|
||||
"vnfInstanceDescription": "SampleVnf Description"}
|
||||
"vnfInstanceDescription": "SampleVnf Description",
|
||||
'metadata': {"key": "value"}}
|
||||
req = fake_request.HTTPRequest.blank('/vnf_instances')
|
||||
req.body = jsonutils.dump_as_bytes(body)
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
|
@ -161,7 +185,8 @@ class TestController(base.TestCase):
|
|||
updates = {'vnfd_id': uuidsentinel.vnfd_id,
|
||||
'vnf_instance_description': None,
|
||||
'vnf_instance_name': None,
|
||||
'vnf_pkg_id': uuidsentinel.vnf_pkg_id}
|
||||
'vnf_pkg_id': uuidsentinel.vnf_pkg_id,
|
||||
'metadata': {'key': 'value'}}
|
||||
|
||||
mock_vnf_instance_create.return_value =\
|
||||
fakes.return_vnf_instance_model(**updates)
|
||||
|
@ -196,6 +221,12 @@ class TestController(base.TestCase):
|
|||
'expected_type': 'description'},
|
||||
{'attribute': 'vnfInstanceDescription', 'value': 123,
|
||||
'expected_type': 'description'},
|
||||
{'attribute': 'metadata', 'value': ['val1', 'val2'],
|
||||
'expected_type': 'object'},
|
||||
{'attribute': 'metadata', 'value': True,
|
||||
'expected_type': 'object'},
|
||||
{'attribute': 'metadata', 'value': 123,
|
||||
'expected_type': 'object'},
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_create_with_invalid_request_body(
|
||||
|
@ -203,7 +234,8 @@ class TestController(base.TestCase):
|
|||
"""value of attribute in body is of invalid type"""
|
||||
body = {"vnfInstanceName": "SampleVnf",
|
||||
"vnfdId": "29c770a3-02bc-4dfc-b4be-eb173ac00567",
|
||||
"vnfInstanceDescription": "VNF Description"}
|
||||
"vnfInstanceDescription": "VNF Description",
|
||||
"metadata": {"key": "value"}}
|
||||
req = fake_request.HTTPRequest.blank('/vnf_instances')
|
||||
body.update({attribute: value})
|
||||
req.body = jsonutils.dump_as_bytes(body)
|
||||
|
@ -223,13 +255,20 @@ class TestController(base.TestCase):
|
|||
"{attribute}. " "Value: {value}. {value} is "
|
||||
"not of type 'string'".
|
||||
format(value=value, attribute=attribute))
|
||||
elif expected_type == 'object':
|
||||
expected_message = ("Invalid input for field/attribute "
|
||||
"{attribute}. " "Value: {value}. {value} is "
|
||||
"not of type 'object'".
|
||||
format(value=value, attribute=attribute,
|
||||
expected_type=expected_type))
|
||||
|
||||
self.assertEqual(expected_message, exception.msg)
|
||||
|
||||
@mock.patch.object(objects.VnfPackageVnfd, 'get_by_id')
|
||||
@mock.patch.object(objects.vnf_package_vnfd.VnfPackageVnfd, 'get_by_id')
|
||||
def test_create_non_existing_vnf_package_vnfd(self, mock_vnf_by_id):
|
||||
mock_vnf_by_id.side_effect = exceptions.VnfPackageVnfdNotFound
|
||||
body = {'vnfdId': uuidsentinel.vnfd_id}
|
||||
body = {'vnfdId': uuidsentinel.vnfd_id,
|
||||
'metadata': {"key": "value"}}
|
||||
req = fake_request.HTTPRequest.blank('/vnf_instances')
|
||||
req.body = jsonutils.dump_as_bytes(body)
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
|
@ -239,7 +278,8 @@ class TestController(base.TestCase):
|
|||
body=body)
|
||||
|
||||
def test_create_without_vnfd_id(self):
|
||||
body = {"vnfInstanceName": "SampleVnfInstance"}
|
||||
body = {"vnfInstanceName": "SampleVnfInstance",
|
||||
'metadata': {"key": "value"}}
|
||||
req = fake_request.HTTPRequest.blank(
|
||||
'/vnf_instances')
|
||||
req.body = jsonutils.dump_as_bytes(body)
|
||||
|
@ -259,16 +299,21 @@ class TestController(base.TestCase):
|
|||
resp = req.get_response(self.app)
|
||||
self.assertEqual(http_client.METHOD_NOT_ALLOWED, resp.status_code)
|
||||
|
||||
@ddt.data({'name': "A" * 256, 'description': "VNF Description"},
|
||||
{'name': 'Fake-VNF', 'description': "A" * 1025})
|
||||
@ddt.data({'name': "A" * 256, 'description': "VNF Description",
|
||||
'meta': {"key": "value"}},
|
||||
{'name': 'Fake-VNF', 'description': "A" * 1025,
|
||||
'meta': {"key": "value"}},
|
||||
{'name': 'Fake-VNF', 'description': "VNF Description",
|
||||
'meta': {"key": "v" * 256}})
|
||||
@ddt.unpack
|
||||
def test_create_max_length_exceeded_for_vnf_name_and_description(
|
||||
self, name, description):
|
||||
self, name, description, meta):
|
||||
# vnf instance_name and description with length greater than max
|
||||
# length defined
|
||||
body = {"vnfInstanceName": name,
|
||||
"vnfdId": uuidsentinel.vnfd_id,
|
||||
"vnfInstanceDescription": description}
|
||||
"vnfInstanceDescription": description,
|
||||
"metadata": meta}
|
||||
req = fake_request.HTTPRequest.blank(
|
||||
'/vnf_instances')
|
||||
req.body = jsonutils.dump_as_bytes(body)
|
||||
|
|
Loading…
Reference in New Issue