Support attribute filtering for GET /vnf_packages API

Certain resource representations can become quite big, in particular
``GET /vnfpackages`` API which is a container for multiple sub-resources.
In these cases, it can be desired to reduce the amount of data exchanged
over the interface and processed by the API consumer application.

An attribute selector i.e filter, allows the API consumer to choose
which attributes it wants to be contained in the response. Only
attributes that are not required to be present, i.e. those with
a lower bound of zero on their cardinality (e.g. 0..1, 0..N)
and that are not conditionally mandatory, are allowed to be omitted
as part of the selection process.

This patch implements attribute filtering for ``GET /vnfpackages`` API.

It will support following query parameters:-
* filter: Filter criterion built with attributes of VNF Package and
REST operators.
* all_fields: Return all fields
* fields=<list> : Return fields specified in the <list>
* exclude_fields=<list>: Return fields not specified in the <list>
* exclude_default parameters: Return all fields except default fields
* exclude_default and fields=<list>: Return all fields except default
fields and fields in the <list>
* When no parameter is specified it is treated as exclude_default.

You can find more details about supported query parameters in document:
ETSI GS NFV-SOL 013 V2.6.1 (2019-03 in section 5.2 and 5.3

Co-Authored-By: Prashant Bhole <prashant.bhole@nttdata.com>

Change-Id: I1513d7d87967de5248787cd298c3bec65c2dde9d
Implements: bp/enhance-vnf-package-support-part1
This commit is contained in:
tpatil 2020-03-30 05:49:11 +00:00 committed by Prashant Bhole
parent cfaa39d73f
commit 3f5c81604e
19 changed files with 1896 additions and 243 deletions

View File

@ -0,0 +1,92 @@
# Copyright (C) 2020 NTT DATA
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from tacker.api.common import attribute_filter
from tacker.common import exceptions as exception
class BaseViewBuilder(object):
@classmethod
def validate_filter(cls, filters=None):
if not filters:
return
return attribute_filter.parse_filter_rule(filters,
target=cls.FLATTEN_ATTRIBUTES)
@classmethod
def validate_attribute_fields(cls, all_fields=None, fields=None,
exclude_fields=None, exclude_default=None):
if all_fields and (fields or exclude_fields or exclude_default):
msg = ("Invalid query parameter combination: 'all_fields' "
"cannot be combined with 'fields' or 'exclude_fields' "
"or 'exclude_default'")
raise exception.ValidationError(msg)
if fields and (all_fields or exclude_fields):
msg = ("Invalid query parameter combination: 'fields' "
"cannot be combined with 'all_fields' or 'exclude_fields' ")
raise exception.ValidationError(msg)
if exclude_fields and (all_fields or fields or exclude_default):
msg = ("Invalid query parameter combination: 'exclude_fields' "
"cannot be combined with 'all_fields' or 'fields' "
"or 'exclude_default'")
raise exception.ValidationError(msg)
if exclude_default and (all_fields or exclude_fields):
msg = ("Invalid query parameter combination: 'exclude_default' "
"cannot be combined with 'all_fields' or 'exclude_fields' ")
raise exception.ValidationError(msg)
def _validate_complex_attributes(query_parameter, fields):
msg = ("Invalid query parameter '%(query_parameter)s'. "
"Value: %(field)s")
for field in fields:
if field in cls.COMPLEX_ATTRIBUTES:
continue
elif '*' in field:
# Field should never contain '*' as it's reserved for
# special purpose for handling key-value pairs.
raise exception.ValidationError(msg %
{"query_parameter": query_parameter,
"field": field})
elif field not in cls.FLATTEN_COMPLEX_ATTRIBUTES:
# Special case for field with key-value pairs.
# In this particular case, key will act as an attribute
# in structure so you need to treat it differently than
# other fields. All key-value pair field will be post-fix
# with '*' in FLATTEN_COMPLEX_ATTRIBUTES. Request
# with field which contains '*' will be treated as an
# error.
special_field = False
for attribute in cls.FLATTEN_COMPLEX_ATTRIBUTES:
if '*' in attribute and field.startswith(
attribute.split('*')[0]):
special_field = True
if not special_field:
raise exception.ValidationError(msg %
{"query_parameter": query_parameter,
"field": field})
if fields:
_validate_complex_attributes("fields", fields.split(','))
elif exclude_fields:
_validate_complex_attributes("exclude_fields",
exclude_fields.split(","))

View File

@ -13,10 +13,16 @@
# License for the specific language governing permissions and limitations
# under the License.
from tacker.objects import fields
from tacker.api import views as base
from tacker.objects import vnf_package as _vnf_package
class ViewBuilder(object):
class ViewBuilder(base.BaseViewBuilder):
FLATTEN_ATTRIBUTES = _vnf_package.VnfPackage.FLATTEN_ATTRIBUTES
COMPLEX_ATTRIBUTES = _vnf_package.VnfPackage.COMPLEX_ATTRIBUTES
FLATTEN_COMPLEX_ATTRIBUTES = [key for key in FLATTEN_ATTRIBUTES.keys()
if '/' in key]
def _get_links(self, vnf_package):
return {
@ -32,64 +38,13 @@ class ViewBuilder(object):
}
}
def _get_software_images(self, vnf_deployment_flavours):
software_images = list()
for vnf_deployment_flavour in vnf_deployment_flavours:
for sw_image in vnf_deployment_flavour.software_images:
software_images.append({
"id": sw_image.software_image_id,
"name": sw_image.name,
"provider": "provider",
"version": sw_image.version,
"checksum": {
"algorithm": sw_image.algorithm,
"hash": sw_image.hash
},
"containerFormat": sw_image.container_format,
"diskFormat": sw_image.disk_format,
"minDisk": sw_image.min_disk,
"minRam": sw_image.min_ram,
"size": sw_image.size,
"imagePath": sw_image.image_path,
"userMetadata": sw_image.metadata
})
return {'softwareImages': software_images}
def _get_vnfd(self, vnf_package):
vnfd = vnf_package.vnfd
return {
'vnfdId': vnfd.vnfd_id,
'vnfProvider': vnfd.vnf_provider,
'vnfProductName': vnfd.vnf_product_name,
'vnfSoftwareVersion': vnfd.vnf_software_version,
'vnfdVersion': vnfd.vnfd_version
}
def _basic_vnf_package_info(self, vnf_package):
return {
'id': vnf_package.id,
'onboardingState': vnf_package.onboarding_state,
'operationalState': vnf_package.operational_state,
'usageState': vnf_package.usage_state,
'userDefinedData': vnf_package.user_data,
}
def _get_vnf_package(self, vnf_package):
vnf_package_response = self._basic_vnf_package_info(vnf_package)
def _get_vnf_package(self, vnf_package, include_fields=None):
vnf_package_response = vnf_package.to_dict(
include_fields=include_fields)
links = self._get_links(vnf_package)
vnf_package_response.update(links)
if (vnf_package.onboarding_state ==
fields.PackageOnboardingStateType.ONBOARDED):
# add software images
vnf_deployment_flavours = vnf_package.vnf_deployment_flavours
vnf_package_response.update(self._get_software_images(
vnf_deployment_flavours))
vnf_package_response.update(self._get_vnfd(vnf_package))
return vnf_package_response
def _get_modified_user_data(self, old_user_data, new_user_data):
@ -106,17 +61,11 @@ class ViewBuilder(object):
return user_data_response
def create(self, request, vnf_package):
return self._get_vnf_package(vnf_package)
def show(self, request, vnf_package):
return self._get_vnf_package(vnf_package)
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:
@ -127,3 +76,47 @@ class ViewBuilder(object):
response['userDefinedData'] = updated_user_data
return response
def index(self, request, vnf_packages, all_fields=True,
exclude_fields=None, fields=None, exclude_default=False):
# Find out which fields are to be returned in the response.
if all_fields:
include_fields = set(self.FLATTEN_ATTRIBUTES.keys())
if exclude_default and fields or fields:
# Note(tpatil): If fields contains any of the complex attributes
# base name, it should include all attributes from that complex
# attribute. For example, if softwareImages is passed in the
# fields, it should include all attributes matching
# softwareImages/*.
fields = set(fields.split(','))
attributes = set(self.COMPLEX_ATTRIBUTES).intersection(fields)
for attribute in attributes:
add_fields = set([key for key in self.FLATTEN_ATTRIBUTES.
keys() if key.startswith(attribute)])
fields = fields.union(add_fields)
include_fields = set(_vnf_package.VnfPackage.simple_attributes +
_vnf_package.VnfPackage.simple_instantiated_attributes). \
union(fields)
elif exclude_default:
include_fields = set(_vnf_package.VnfPackage.simple_attributes +
_vnf_package.VnfPackage.simple_instantiated_attributes)
elif exclude_fields:
# Note(tpatil): If exclude_fields contains any of the complex
# attributes base name, it should exclude all attributes from
# that complex attribute. For example, if softwareImages is passed
# in the excluded_fields, it should exclude all attributes
# matching softwareImages/*.
exclude_fields = set(exclude_fields.split(','))
exclude_additional_attributes = set(
self.COMPLEX_ATTRIBUTES).intersection(exclude_fields)
for attribute in exclude_additional_attributes:
fields = set([key for key in self.FLATTEN_ATTRIBUTES.keys()
if key.startswith(attribute)])
exclude_fields = exclude_fields.union(fields)
include_fields = set(self.FLATTEN_ATTRIBUTES.keys()) - \
exclude_fields
return {'vnf_packages': [self._get_vnf_package(vnf_package,
include_fields=include_fields)for vnf_package in vnf_packages]}

View File

@ -114,11 +114,37 @@ class VnfPkgmController(wsgi.Controller):
context = request.environ['tacker.context']
context.can(vnf_package_policies.VNFPKGM % 'index')
vnf_packages = vnf_package_obj.VnfPackagesList.get_all(
request.context,
expected_attrs=["vnf_deployment_flavours", "vnfd"])
search_opts = {}
search_opts.update(request.GET)
return self._view_builder.index(request, vnf_packages)
def _key_exists(key, validate_value=True):
try:
request.GET[key]
except KeyError:
return False
return True
all_fields = _key_exists('all_fields')
exclude_default = _key_exists('exclude_default')
fields = request.GET.get('fields')
exclude_fields = request.GET.get('exclude_fields')
filters = request.GET.get('filter')
if not (all_fields or fields or exclude_fields):
exclude_default = True
self._view_builder.validate_attribute_fields(all_fields=all_fields,
fields=fields, exclude_fields=exclude_fields,
exclude_default=exclude_default)
filters = self._view_builder.validate_filter(filters)
vnf_packages = vnf_package_obj.VnfPackagesList.get_by_filters(
request.context, read_deleted='no', filters=filters)
return self._view_builder.index(request, vnf_packages,
all_fields=all_fields, exclude_fields=exclude_fields,
fields=fields, exclude_default=exclude_default)
@wsgi.response(http_client.NO_CONTENT)
@wsgi.expected_errors((http_client.FORBIDDEN, http_client.NOT_FOUND,

View File

@ -19,6 +19,7 @@
"""Utilities and helper functions."""
import functools
from functools import reduce
import inspect
import logging as std_logging
import math
@ -402,6 +403,22 @@ def is_url(url):
return False
def flatten_dict(data, prefix=''):
ret = {}
for key, val in data.items():
key = prefix + key
if isinstance(val, dict):
ret.update(flatten_dict(val, key + '/'))
else:
ret[key] = val
return ret
def deepgetattr(obj, attr):
"""Recurses through an attribute chain to get the ultimate value."""
return reduce(getattr, attr.split('.'), obj)
class CooperativeReader(object):
"""An eventlet thread friendly class for reading in image data.

View File

@ -239,6 +239,16 @@ class VnfDeploymentFlavour(base.TackerObject, base.TackerPersistentObject):
objects.VnfSoftwareImage, db_sw_images)
self.obj_reset_changes(['software_images'])
def to_dict(self, include_fields=None):
software_images = list()
for software_image in self.software_images:
sw_image_dict = software_image.to_dict(
include_fields=include_fields)
if sw_image_dict:
software_images.append(sw_image_dict)
return software_images
@base.TackerObjectRegistry.register
class VnfDeploymentFlavoursList(ovoo_base.ObjectListBase, base.TackerObject):
@ -248,3 +258,12 @@ class VnfDeploymentFlavoursList(ovoo_base.ObjectListBase, base.TackerObject):
fields = {
'objects': fields.ListOfObjectsField('VnfDeploymentFlavour')
}
def to_dict(self, include_fields=None):
software_images = list()
for deployment_flavour in self.objects:
images = deployment_flavour.to_dict(include_fields=include_fields)
if images:
software_images.extend(images)
return software_images

View File

@ -25,12 +25,14 @@ from sqlalchemy_filters import apply_filters
from tacker._i18n import _
from tacker.common import exceptions
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 import objects
from tacker.objects import base
from tacker.objects import fields
from tacker.objects import vnf_software_image
_NO_DATA_SENTINEL = object()
@ -256,14 +258,62 @@ def _make_vnf_packages_list(context, vnf_package_list, db_vnf_package_list,
class VnfPackage(base.TackerObject, base.TackerPersistentObject,
base.TackerObjectDictCompat):
# Key corresponds to the name of the parameter as defined
# in type VnfPkgInfo of SOL003 document and value will contain tuple
# of following values:-
# 1. Parameter that is mapped to version object
# 2. Data type of the field as per the data types supported by
# attribute-based filtering
# 3. DB model that's mapped to the version object.
# 4. Valid values for a given data type if any. This value is set
# especially for 'enum' data type.
ALL_ATTRIBUTES = {
'id': ('id', "string", 'VnfPackage'),
'onboardingState': ('onboarding_state', "enum", 'VnfPackage',
fields.PackageOnboardingStateTypeField().valid_values),
'operationalState': ('operational_state', 'enum', 'VnfPackage',
fields.PackageOperationalStateTypeField().valid_values),
'usageState': ('usage_state', 'enum', 'VnfPackage',
fields.PackageUsageStateTypeField().valid_values),
'vnfProvider': ('vnfd.vnf_provider', 'string', 'VnfPackageVnfd'),
'vnfProductName': ('vnfd.vnf_product_name', 'string',
'VnfPackageVnfd'),
'vnfdId': ('vnfd.vnfd_id', 'string', 'VnfPackageVnfd'),
'vnfSoftwareVersion': ('vnfd.vnf_software_version', 'string',
'VnfPackageVnfd'),
'vnfdVersion': ('vnfd.vnfd_version', 'string', 'VnfPackageVnfd'),
'userDefinedData/*': ('user_data', 'key_value_pair',
{"key_column": "key", "value_column": "value",
"model": "VnfPackageUserData"}),
"checksum": {
'algorithm': ('algorithm', 'string', 'VnfPackage'),
'hash': ('hash', 'string', 'VnfPackage'),
}
}
ALL_ATTRIBUTES.update(vnf_software_image.VnfSoftwareImage.ALL_ATTRIBUTES)
FLATTEN_ATTRIBUTES = utils.flatten_dict(ALL_ATTRIBUTES.copy())
simple_attributes = ['id', 'onboardingState', 'operationalState',
'usageState']
simple_instantiated_attributes = ['vnfProvider', 'vnfProductName',
'vnfdId', 'vnfSoftwareVersion', 'vnfdVersion']
COMPLEX_ATTRIBUTES = ["checksum", "userDefinedData"]
COMPLEX_ATTRIBUTES.extend(
vnf_software_image.VnfSoftwareImage.COMPLEX_ATTRIBUTES)
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'id': fields.UUIDField(nullable=False),
'onboarding_state': fields.StringField(nullable=False),
'operational_state': fields.StringField(nullable=False),
'usage_state': fields.StringField(nullable=False),
'onboarding_state': fields.PackageOnboardingStateTypeField(
nullable=False),
'operational_state': fields.PackageOperationalStateTypeField(
nullable=False),
'usage_state': fields.PackageUsageStateTypeField(nullable=False),
'user_data': fields.DictOfStringsField(),
'tenant_id': fields.StringField(nullable=False),
'algorithm': fields.StringField(nullable=True),
@ -277,8 +327,7 @@ class VnfPackage(base.TackerObject, base.TackerPersistentObject,
@staticmethod
def _from_db_object(context, vnf_package, db_vnf_package,
expected_attrs=None):
if expected_attrs is None:
expected_attrs = []
expected_attrs = expected_attrs or []
vnf_package._context = context
@ -303,7 +352,8 @@ class VnfPackage(base.TackerObject, base.TackerPersistentObject,
expected_attrs=None):
"""Method to help with migration of extra attributes to objects."""
expected_attrs = expected_attrs or []
if expected_attrs is None:
expected_attrs = []
if 'vnf_deployment_flavours' in expected_attrs:
vnf_package._load_vnf_deployment_flavours(
@ -460,6 +510,92 @@ class VnfPackage(base.TackerObject, base.TackerPersistentObject,
else:
return False
def _get_vnfd(self, include_fields=None):
response = dict()
to_fields = set(self.simple_instantiated_attributes).intersection(
include_fields)
for field in to_fields:
response[field] = utils.deepgetattr(self,
self.FLATTEN_ATTRIBUTES[field][0])
return response
def _get_checksum(self, include_fields=None):
response = dict()
to_fields = set([key for key in self.FLATTEN_ATTRIBUTES.keys()
if key.startswith('checksum')])
to_fields = to_fields.intersection(include_fields)
for field in to_fields:
display_field = field.split("/")[-1]
response[display_field] = getattr(self,
self.FLATTEN_ATTRIBUTES[field][0])
return {'checksum': response} if response else None
def _get_user_defined_data(self, include_fields=None):
# Need special handling for field containing key-value pair.
# If user requests userDefined/key1 and if userDefineData contains
# key1=value1, key2-value2, it should return only keys that are
# requested in include_fields. If user requests only userDefinedData,
# then in that case,it should return all key/value pairs. In case,
# if any of the requested key is not present, then it should
# siliently ignore it.
key = 'userDefinedData'
if key in include_fields or 'userDefinedData/*' in include_fields:
return {key: self.user_data}
else:
# Check if user has requested specified keys from
# userDefinedData.
data_resp = dict()
key_list = []
special_key = 'userDefinedData/'
for field in include_fields:
if field.startswith(special_key):
key_list.append(field[len(special_key):])
for key_req in key_list:
if key_req in self.user_data:
data_resp[key_req] = self.user_data[key_req]
if data_resp:
return {key: data_resp}
def _basic_vnf_package_info(self, include_fields=None):
response = dict()
to_fields = set(self.simple_attributes).intersection(include_fields)
for field in to_fields:
response[field] = getattr(self, self.FLATTEN_ATTRIBUTES[field][0])
return response
def to_dict(self, include_fields=None):
if not include_fields:
include_fields = set(self.FLATTEN_ATTRIBUTES.keys())
vnf_package_response = self._basic_vnf_package_info(
include_fields=include_fields)
user_defined_data = self._get_user_defined_data(
include_fields=include_fields)
if user_defined_data:
vnf_package_response.update(user_defined_data)
if (self.onboarding_state ==
fields.PackageOnboardingStateType.ONBOARDED):
software_images = self.vnf_deployment_flavours.to_dict(
include_fields=include_fields)
if software_images:
vnf_package_response.update(
{'softwareImages': software_images})
vnf_package_response.update(self._get_vnfd(
include_fields=include_fields))
checksum = self._get_checksum(include_fields=include_fields)
if checksum:
vnf_package_response.update(checksum)
return vnf_package_response
@base.TackerObjectRegistry.register
class VnfPackagesList(ovoo_base.ObjectListBase, base.TackerObject):

View File

@ -18,6 +18,7 @@ from oslo_versionedobjects import base as ovoo_base
from sqlalchemy.orm import joinedload
from tacker.common import exceptions
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
@ -80,6 +81,37 @@ def _vnf_sw_image_get_by_id(context, id):
@base.TackerObjectRegistry.register
class VnfSoftwareImage(base.TackerObject, base.TackerPersistentObject):
ALL_ATTRIBUTES = {
"softwareImages": {
'id': ('software_image_id', 'string', 'VnfSoftwareImage'),
'imagePath': ('image_path', 'string', 'VnfSoftwareImage'),
'diskFormat': ('disk_format', 'string', 'VnfSoftwareImage'),
'userMetadata/*': ('metadata', 'key_value_pair',
{"key_column": "key", "value_column": "value",
"model": "VnfSoftwareImageMetadata"}),
'size': ('size', 'number', 'VnfSoftwareImage'),
'createdAt': ('created_at', 'datetime', 'VnfSoftwareImage'),
'name': ('name', 'string', 'VnfSoftwareImage'),
'minDisk': ('min_disk', 'number', 'VnfSoftwareImage'),
'version': ('version', 'string', 'VnfSoftwareImage'),
'provider': ('provider', 'string', 'VnfSoftwareImage'),
'minRam': ('min_ram', 'number', 'VnfSoftwareImage'),
'containerFormat': ('container_format', 'string',
'VnfSoftwareImage'),
"checksum": {
'hash': ('hash', 'string', 'VnfSoftwareImage'),
'algorithm': ('algorithm', 'string', 'VnfSoftwareImage')
}
}
}
FLATTEN_ATTRIBUTES = utils.flatten_dict(ALL_ATTRIBUTES.copy())
SIMPLE_ATTRIBUTES = ['id', 'imagePath', 'diskFormat', 'size',
'createdAt', 'name', 'minDisk', 'version', 'provider', 'minRam',
'containerFormat']
COMPLEX_ATTRIBUTES = ['softwareImages', 'softwareImages/userMetadata',
'softwareImages/checksum']
# Version 1.0: Initial version
VERSION = '1.0'
@ -199,6 +231,64 @@ class VnfSoftwareImage(base.TackerObject, base.TackerPersistentObject):
return cls._from_db_object(context, cls(), db_sw_image,
expected_attrs=expected_attrs)
def _get_user_metadata(self, include_fields=None):
# Need special handling for field containing key-value pair.
# If user requests softwareImages/userMetadata/key1 and if
# softwareImages/userMetadata contains key1=value1, key2=value2,
# it should return only keys that are requested in include_fields.
# If user requests only softwareImages/userMetadata, then in that
# case, it should return all key/value pairs. If any of the requested
# key is not present, then it will siliently ignore it.
key = 'softwareImages/userMetadata'
if key in include_fields or '%s/*' % key in \
include_fields:
return self.metadata
else:
# Check if user has requested specified keys from
# softwareImages/userMetadata.
key_list = []
special_key = '%s/' % key
for field in include_fields:
if field.startswith(special_key):
key_list.append(field[len(special_key):])
data_resp = dict()
for key_req in key_list:
if key_req in self.metadata:
data_resp[key_req] = self.metadata[key_req]
if len(key_list) > 0:
return data_resp
def to_dict(self, include_fields=None):
response = dict()
fields = ['softwareImages/%s' % attribute for attribute in
self.SIMPLE_ATTRIBUTES]
to_fields = set(fields).intersection(include_fields)
for field in to_fields:
display_field = field.split("/")[-1]
response[display_field] = getattr(self,
self.FLATTEN_ATTRIBUTES[field][0])
# add checksum
to_fields = set([key for key in self.FLATTEN_ATTRIBUTES.keys()
if key.startswith('softwareImages/checksum')])
checksum = dict()
to_fields = to_fields.intersection(include_fields)
for field in to_fields:
display_field = field.split("/")[-1]
checksum[display_field] = getattr(self,
self.FLATTEN_ATTRIBUTES[field][0])
if checksum:
response.update({"checksum": checksum})
user_metadata = self._get_user_metadata(include_fields)
if user_metadata is not None:
response.update({"userMetadata": user_metadata})
return response
@base.TackerObjectRegistry.register
class VnfSoftwareImagesList(ovoo_base.ObjectListBase, base.TackerObject):

View File

@ -0,0 +1,275 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: Simple deployment flavour for Sample VNF
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
- helloworld3_types.yaml
topology_template:
inputs:
descriptor_id:
type: string
descriptor_version:
type: string
provider:
type: string
product_name:
type: string
software_version:
type: string
vnfm_info:
type: list
entry_schema:
type: string
flavour_id:
type: string
flavour_description:
type: string
substitution_mappings:
node_type: company.provider.VNF
properties:
flavour_id: simple
requirements:
virtual_link_external: [ CP1, virtual_link ]
node_templates:
VNF:
type: company.provider.VNF
properties:
flavour_description: A simple flavour
interfaces:
Vnflcm:
# supporting only 'instantiate', 'terminate', 'modify'
# not supporting LCM script, supporting only default LCM
instantiate: []
instantiate_start: []
instantiate_end: []
terminate: []
terminate_start: []
terminate_end: []
modify_information: []
modify_information_start: []
modify_information_end: []
# change_flavour: []
# change_flavour_start: []
# change_flavour_end: []
# change_external_connectivity: []
# change_external_connectivity_start: []
# change_external_connectivity_end: []
# operate: []
# operate_start: []
# operate_end: []
# heal: []
# heal_start: []
# heal_end: []
# scale: []
# scale_start: []
# scale_end: []
# scale_to_level: []
# scale_to_level_start: []
# scale_to_level_end: []
VDU1:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: VDU1
description: VDU1 compute node
vdu_profile:
min_number_of_instances: 1
max_number_of_instances: 1
sw_image_data:
name: Software of VDU1
version: '0.4.0'
checksum:
algorithm: sha-256
hash: b9c3036539fd7a5f87a1bf38eb05fdde8b556a1a7e664dbeda90ed3cd74b4f9d
container_format: bare
disk_format: qcow2
min_disk: 1 GB
size: 1 GB
artifacts:
sw_image:
type: tosca.artifacts.nfv.SwImage
file: ../Files/images/cirros-0.4.0-x86_64-disk.img
capabilities:
virtual_compute:
properties:
virtual_memory:
virtual_mem_size: 512 MB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 1 GB
VDU2:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: VDU2
description: VDU2 compute node
vdu_profile:
min_number_of_instances: 1
max_number_of_instances: 3
capabilities:
virtual_compute:
properties:
virtual_memory:
virtual_mem_size: 512 MB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 1 GB
requirements:
- virtual_storage: VirtualStorage
VirtualStorage:
type: tosca.nodes.nfv.Vdu.VirtualBlockStorage
properties:
virtual_block_storage_data:
size_of_storage: 30 GB
rdma_enabled: true
sw_image_data:
name: VrtualStorage
version: '0.4.0'
checksum:
algorithm: sha-256
hash: b9c3036539fd7a5f87a1bf38eb05fdde8b556a1a7e664dbeda90ed3cd74b4f9d
container_format: bare
disk_format: qcow2
min_disk: 2 GB
min_ram: 8192 MB
size: 2 GB
artifacts:
sw_image:
type: tosca.artifacts.nfv.SwImage
file: ../Files/images/cirros-0.4.0-x86_64-disk.img
CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
vnic_type: direct-physical
requirements:
- virtual_binding: VDU1
#- virtual_link: # the target node is determined in the NSD
CP2:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 1
requirements:
- virtual_binding: VDU1
- virtual_link: internalVL2
CP3:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 2
requirements:
- virtual_binding: VDU2
- virtual_link: internalVL2
internalVL2:
type: tosca.nodes.nfv.VnfVirtualLink
properties:
connectivity_type:
layer_protocols: [ ipv4 ]
description: Internal Virtual link in the VNF
vl_profile:
max_bitrate_requirements:
root: 1048576
leaf: 1048576
min_bitrate_requirements:
root: 1048576
leaf: 1048576
virtual_link_protocol_data:
- associated_layer_protocol: ipv4
l3_protocol_data:
ip_version: ipv4
cidr: 11.11.0.0/24
policies:
- scaling_aspects:
type: tosca.policies.nfv.ScalingAspects
properties:
aspects:
worker_instance:
name: worker_instance_aspect
description: worker_instance scaling aspect
max_scale_level: 2
step_deltas:
- delta_1
- VDU2_initial_delta:
type: tosca.policies.nfv.VduInitialDelta
properties:
initial_delta:
number_of_instances: 1
targets: [ VDU2 ]
- VDU2_scaling_aspect_deltas:
type: tosca.policies.nfv.VduScalingAspectDeltas
properties:
aspect: worker_instance
deltas:
delta_2:
number_of_instances: 1
targets: [ VDU2 ]
- instantiation_levels:
type: tosca.policies.nfv.InstantiationLevels
properties:
levels:
instantiation_level_1:
description: Smallest size
scale_info:
worker_instance:
scale_level: 0
instantiation_level_2:
description: Largest size
scale_info:
worker_instance:
scale_level: 2
default_level: instantiation_level_1
- VDU1_instantiation_levels:
type: tosca.policies.nfv.VduInstantiationLevels
properties:
levels:
instantiation_level_1:
number_of_instances: 1
instantiation_level_2:
number_of_instances: 3
targets: [ VDU1 ]
- VDU2_instantiation_levels:
type: tosca.policies.nfv.VduInstantiationLevels
properties:
levels:
instantiation_level_1:
number_of_instances: 1
instantiation_level_2:
number_of_instances: 1
targets: [ VDU2 ]
- internalVL2_instantiation_levels:
type: tosca.policies.nfv.VirtualLinkInstantiationLevels
properties:
levels:
instantiation_level_1:
bitrate_requirements:
root: 1048576
leaf: 1048576
instantiation_level_2:
bitrate_requirements:
root: 1048576
leaf: 1048576
targets: [ internalVL2 ]

View File

@ -0,0 +1,31 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: Sample VNF
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
- helloworld3_types.yaml
- helloworld3_df_simple.yaml
topology_template:
inputs:
selected_flavour:
type: string
description: VNF deployment flavour selected by the consumer. It is provided in the API
node_templates:
VNF:
type: company.provider.VNF
properties:
flavour_id: { get_input: selected_flavour }
descriptor_id: b1bb0ce7-ebca-4fa7-95ed-4840d70a1177
provider: Company
product_name: Sample VNF
software_version: '1.0'
descriptor_version: '1.0'
vnfm_info:
- Tacker
requirements:
#- virtual_link_external # mapped in lower-level templates
#- virtual_link_internal # mapped in lower-level templates

View File

@ -0,0 +1,53 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: VNF type definition
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
node_types:
company.provider.VNF:
derived_from: tosca.nodes.nfv.VNF
properties:
descriptor_id:
type: string
constraints: [ valid_values: [ b1bb0ce7-ebca-4fa7-95ed-4840d70a1177 ] ]
default: b1bb0ce7-ebca-4fa7-95ed-4840d70a1177
descriptor_version:
type: string
constraints: [ valid_values: [ '1.0' ] ]
default: '1.0'
provider:
type: string
constraints: [ valid_values: [ 'Company' ] ]
default: 'Company'
product_name:
type: string
constraints: [ valid_values: [ 'Sample VNF' ] ]
default: 'Sample VNF'
software_version:
type: string
constraints: [ valid_values: [ '1.0' ] ]
default: '1.0'
vnfm_info:
type: list
entry_schema:
type: string
constraints: [ valid_values: [ Tacker ] ]
default: [ Tacker ]
flavour_id:
type: string
constraints: [ valid_values: [ simple ] ]
default: simple
flavour_description:
type: string
default: ""
requirements:
- virtual_link_external:
capability: tosca.capabilities.nfv.VirtualLinkable
- virtual_link_internal:
capability: tosca.capabilities.nfv.VirtualLinkable
interfaces:
Vnflcm:
type: tosca.interfaces.nfv.Vnflcm

View File

@ -0,0 +1,7 @@
TOSCA-Meta-File-Version: 1.0
Created-by: Hiroyuki JO
CSAR-Version: 1.1
Entry-Definitions: Definitions/helloworld3_top.vnfd.yaml
Name: Files/images/cirros-0.4.0-x86_64-disk.img
Content-type: application/x-iso9066-image

View File

@ -0,0 +1,275 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: Simple deployment flavour for Demo VNF
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
- helloworld3_types.yaml
topology_template:
inputs:
descriptor_id:
type: string
descriptor_version:
type: string
provider:
type: string
product_name:
type: string
software_version:
type: string
vnfm_info:
type: list
entry_schema:
type: string
flavour_id:
type: string
flavour_description:
type: string
substitution_mappings:
node_type: company.provider.VNF
properties:
flavour_id: simple
requirements:
virtual_link_external: [ CP1, virtual_link ]
node_templates:
VNF:
type: company.provider.VNF
properties:
flavour_description: A simple flavour
interfaces:
Vnflcm:
# supporting only 'instantiate', 'terminate', 'modify'
# not supporting LCM script, supporting only default LCM
instantiate: []
instantiate_start: []
instantiate_end: []
terminate: []
terminate_start: []
terminate_end: []
modify_information: []
modify_information_start: []
modify_information_end: []
# change_flavour: []
# change_flavour_start: []
# change_flavour_end: []
# change_external_connectivity: []
# change_external_connectivity_start: []
# change_external_connectivity_end: []
# operate: []
# operate_start: []
# operate_end: []
# heal: []
# heal_start: []
# heal_end: []
# scale: []
# scale_start: []
# scale_end: []
# scale_to_level: []
# scale_to_level_start: []
# scale_to_level_end: []
VDU1:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: VDU1
description: VDU1 compute node
vdu_profile:
min_number_of_instances: 1
max_number_of_instances: 1
sw_image_data:
name: Demo software of VDU1
version: '0.4.0'
checksum:
algorithm: sha-512
hash: 9a318acd9d049bbd6a8c662b18be95414b3b1f2e3d27e38e59bf99347193ac2831220aa54296ee35aee8f53c399000f095c8eca7eb611c5b2c83eeb7c30834a8
container_format: bare
disk_format: qcow2
min_disk: 4 GB
size: 4 GB
artifacts:
sw_image:
type: tosca.artifacts.nfv.SwImage
file: ../Files/images/cirros-0.4.0-x86_64-disk.img
capabilities:
virtual_compute:
properties:
virtual_memory:
virtual_mem_size: 512 MB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 1 GB
VDU2:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: VDU2
description: VDU2 compute node
vdu_profile:
min_number_of_instances: 1
max_number_of_instances: 3
capabilities:
virtual_compute:
properties:
virtual_memory:
virtual_mem_size: 512 MB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 1 GB
requirements:
- virtual_storage: VirtualStorage
VirtualStorage:
type: tosca.nodes.nfv.Vdu.VirtualBlockStorage
properties:
virtual_block_storage_data:
size_of_storage: 30 GB
rdma_enabled: true
sw_image_data:
name: DemoVirtualStorage
version: '0.4.0'
checksum:
algorithm: sha-512
hash: 9a318acd9d049bbd6a8c662b18be95414b3b1f2e3d27e38e59bf99347193ac2831220aa54296ee35aee8f53c399000f095c8eca7eb611c5b2c83eeb7c30834a8
container_format: bare
disk_format: qcow2
min_disk: 8 GB
min_ram: 8192 MB
size: 8 GB
artifacts:
sw_image:
type: tosca.artifacts.nfv.SwImage
file: ../Files/images/cirros-0.4.0-x86_64-disk.img
CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
vnic_type: direct-physical
requirements:
- virtual_binding: VDU1
#- virtual_link: # the target node is determined in the NSD
CP2:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 1
requirements:
- virtual_binding: VDU1
- virtual_link: internalVL2
CP3:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 2
requirements:
- virtual_binding: VDU2
- virtual_link: internalVL2
internalVL2:
type: tosca.nodes.nfv.VnfVirtualLink
properties:
connectivity_type:
layer_protocols: [ ipv4 ]
description: Internal Virtual link in the VNF
vl_profile:
max_bitrate_requirements:
root: 1048576
leaf: 1048576
min_bitrate_requirements:
root: 1048576
leaf: 1048576
virtual_link_protocol_data:
- associated_layer_protocol: ipv4
l3_protocol_data:
ip_version: ipv4
cidr: 11.11.0.0/24
policies:
- scaling_aspects:
type: tosca.policies.nfv.ScalingAspects
properties:
aspects:
worker_instance:
name: worker_instance_aspect
description: worker_instance scaling aspect
max_scale_level: 2
step_deltas:
- delta_1
- VDU2_initial_delta:
type: tosca.policies.nfv.VduInitialDelta
properties:
initial_delta:
number_of_instances: 1
targets: [ VDU2 ]
- VDU2_scaling_aspect_deltas:
type: tosca.policies.nfv.VduScalingAspectDeltas
properties:
aspect: worker_instance
deltas:
delta_2:
number_of_instances: 1
targets: [ VDU2 ]
- instantiation_levels:
type: tosca.policies.nfv.InstantiationLevels
properties:
levels:
instantiation_level_1:
description: Smallest size
scale_info:
worker_instance:
scale_level: 0
instantiation_level_2:
description: Largest size
scale_info:
worker_instance:
scale_level: 2
default_level: instantiation_level_1
- VDU1_instantiation_levels:
type: tosca.policies.nfv.VduInstantiationLevels
properties:
levels:
instantiation_level_1:
number_of_instances: 1
instantiation_level_2:
number_of_instances: 3
targets: [ VDU1 ]
- VDU2_instantiation_levels:
type: tosca.policies.nfv.VduInstantiationLevels
properties:
levels:
instantiation_level_1:
number_of_instances: 1
instantiation_level_2:
number_of_instances: 1
targets: [ VDU2 ]
- internalVL2_instantiation_levels:
type: tosca.policies.nfv.VirtualLinkInstantiationLevels
properties:
levels:
instantiation_level_1:
bitrate_requirements:
root: 1048576
leaf: 1048576
instantiation_level_2:
bitrate_requirements:
root: 1048576
leaf: 1048576
targets: [ internalVL2 ]

View File

@ -0,0 +1,31 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: Demo VNF
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
- helloworld3_types.yaml
- helloworld3_df_simple.yaml
topology_template:
inputs:
selected_flavour:
type: string
description: VNF deployment flavour selected by the consumer. It is provided in the API
node_templates:
VNF:
type: company.provider.VNF
properties:
flavour_id: { get_input: selected_flavour }
descriptor_id: 5189df9e-7018-11ea-b97a-000c292ec2ea
provider: DemoLabs
product_name: Demo VNF
software_version: '1.0'
descriptor_version: '1.0'
vnfm_info:
- Tacker
requirements:
#- virtual_link_external # mapped in lower-level templates
#- virtual_link_internal # mapped in lower-level templates

View File

@ -0,0 +1,53 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: VNF type definition
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
node_types:
company.provider.VNF:
derived_from: tosca.nodes.nfv.VNF
properties:
descriptor_id:
type: string
constraints: [ valid_values: [ 5189df9e-7018-11ea-b97a-000c292ec2ea ] ]
default: 5189df9e-7018-11ea-b97a-000c292ec2ea
descriptor_version:
type: string
constraints: [ valid_values: [ '1.0' ] ]
default: '1.0'
provider:
type: string
constraints: [ valid_values: [ 'DemoLabs' ] ]
default: 'DemoLabs'
product_name:
type: string
constraints: [ valid_values: [ 'Demo VNF' ] ]
default: 'Demo VNF'
software_version:
type: string
constraints: [ valid_values: [ '1.0' ] ]
default: '1.0'
vnfm_info:
type: list
entry_schema:
type: string
constraints: [ valid_values: [ Tacker ] ]
default: [ Tacker ]
flavour_id:
type: string
constraints: [ valid_values: [ simple ] ]
default: simple
flavour_description:
type: string
default: ""
requirements:
- virtual_link_external:
capability: tosca.capabilities.nfv.VirtualLinkable
- virtual_link_internal:
capability: tosca.capabilities.nfv.VirtualLinkable
interfaces:
Vnflcm:
type: tosca.interfaces.nfv.Vnflcm

View File

@ -0,0 +1,7 @@
TOSCA-Meta-File-Version: 1.0
Created-by: Hiroyuki JO
CSAR-Version: 1.1
Entry-Definitions: Definitions/helloworld3_top.vnfd.yaml
Name: Files/images/cirros-0.4.0-x86_64-disk.img
Content-type: application/x-iso9066-image

View File

@ -13,8 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
from copy import deepcopy
import ddt
import os
from six.moves import urllib
import tempfile
import time
import zipfile
@ -23,6 +25,7 @@ from oslo_serialization import jsonutils
import tacker.conf
from tacker.tests.functional import base
from tacker.tests import utils
CONF = tacker.conf.CONF
@ -37,6 +40,25 @@ class VnfPackageTest(base.BaseTackerTest):
def setUp(self):
super(VnfPackageTest, self).setUp()
self.base_url = "/vnfpkgm/v1/vnf_packages"
# Here we create and upload vnf package. Also get 'show' api response
# as reference data for attribute filter tests
self.package_id1 = self._create_and_upload_vnf("vnfpkgm1")
show_url = self.base_url + "/" + self.package_id1
resp, self.package1 = self.http_client.do_request(show_url, "GET")
self.assertEqual(200, resp.status_code)
self.package_id2 = self._create_and_upload_vnf("vnfpkgm2")
show_url = self.base_url + "/" + self.package_id2
resp, self.package2 = self.http_client.do_request(show_url, "GET")
self.assertEqual(200, resp.status_code)
def tearDown(self):
for package_id in [self.package_id1, self.package_id2]:
self._disable_operational_state(package_id)
self._delete_vnf_package(package_id)
self._wait_for_delete(package_id)
super(VnfPackageTest, self).tearDown()
def _wait_for_delete(self, package_uuid):
show_url = self.base_url + "/" + package_uuid
@ -128,43 +150,58 @@ class VnfPackageTest(base.BaseTackerTest):
self.assertIn(vnf_package_list[0], package_uuids)
self.assertIn(vnf_package_list[1], package_uuids)
def _get_csar_file_path(self, file_name):
file_path = os.path.abspath(os.path.join(os.path.dirname(__file__),
'../../etc/samples/' + file_name))
return file_path
def _get_csar_dir_path(self, csar_name):
csar_dir = os.path.abspath(os.path.join(os.path.dirname(__file__),
"../../etc/samples/etsi/nfv", csar_name))
return csar_dir
def test_upload_from_file_update_show_and_delete(self):
"""This method tests multiple operations on vnf package.
def _create_and_upload_vnf(self, sample_name):
body = jsonutils.dumps({"userDefinedData": {"foo": "bar"}})
vnf_package = self._create_vnf_package(body)
csar_dir = self._get_csar_dir_path(sample_name)
file_path, vnfd_id = utils.create_csar_with_unique_vnfd_id(csar_dir)
self.addCleanup(os.remove, file_path)
- upload package from file
- update the package state to DISABLED.
- show the package
- delete the package
"""
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'])
return 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)
file_path = self._get_csar_file_path("sample_vnf_package_csar.zip")
upload_url = "{base_path}/{id}/package_content".format(
base_path=self.base_url, id=vnf_package['id'])
with open(file_path, 'rb') as file_object:
resp, resp_body = self.http_client.do_request(upload_url,
"PUT", body=file_object, content_type='application/zip')
self.assertEqual(202, resp.status_code)
self._wait_for_onboard(vnf_package['id'])
update_req_body = jsonutils.dumps(
{"operationalState": "DISABLED",
"userDefinedData": {"key1": "changed_val1",
"key2": "val2", "new_key": "new_val"}})
operational_state = "DISABLED"
user_defined_data = {"key1": "changed_val1", "new_key": "new_val"}
expected_result = {"operationalState": operational_state,
"userDefinedData": user_defined_data}
expected_result = {"operationalState": "DISABLED",
"userDefinedData": {
"key1": "changed_val1", "new_key": "new_val"}}
csar_dir = self._get_csar_dir_path("vnfpkgm1")
file_path, vnfd_id = utils.create_csar_with_unique_vnfd_id(csar_dir)
self.addCleanup(os.remove, file_path)
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(
@ -174,26 +211,91 @@ class VnfPackageTest(base.BaseTackerTest):
self.assertEqual(200, resp.status_code)
self.assertEqual(expected_result, resp_body)
# Show vnf package and verify whether the userDefinedData
# and operationalState parameters are updated correctly.
show_url = self.base_url + "/" + vnf_package['id']
resp, body = self.http_client.do_request(show_url, "GET")
self.assertEqual(200, resp.status_code)
self.assertEqual(operational_state, body['operationalState'])
expected_user_defined_data = {'key1': 'changed_val1', 'key2': 'val2',
'key3': 'val3', 'new_key': 'new_val'}
self.assertEqual(expected_user_defined_data, body['userDefinedData'])
self._delete_vnf_package(vnf_package['id'])
self._wait_for_delete(vnf_package['id'])
def test_index_attribute_filter(self):
filter_expr = {
'filter': "(gt,softwareImages/minDisk,7);"
"(eq,onboardingState,ONBOARDED);"
"(eq,softwareImages/checksum/algorithm,'sha-512')"
}
filter_url = self.base_url + "?" + urllib.parse.urlencode(filter_expr)
resp, body = self.http_client.do_request(filter_url, "GET")
package = deepcopy(self.package2)
for attr in ['softwareImages', 'checksum', 'userDefinedData']:
package.pop(attr, None)
expected_result = {'vnf_packages': [package]}
self.assertEqual(expected_result, body)
def test_index_attribute_selector_all_fields(self):
"""Test for attribute selector 'all_fields'
We intentionally use attribute filter along with attribute selector.
It is because when these tests run with concurrency > 1, there will
be multiple sample packages present at a time. Hence attribute
selector will be applied on all of them. It will be difficult to
predict the expected result. Hence we are limiting the result set by
filtering one of the vnf package which was created for this speific
test.
"""
filter_expr = {'filter': '(eq,id,%s)' % self.package_id1,
'all_fields': ''}
filter_url = self.base_url + "?" + urllib.parse.urlencode(filter_expr)
resp, body = self.http_client.do_request(filter_url, "GET")
expected_result = {'vnf_packages': [self.package1]}
self.assertEqual(expected_result, body)
def test_index_attribute_selector_exclude_default(self):
filter_expr = {'filter': '(eq,id,%s)' % self.package_id2,
'exclude_default': ''}
filter_url = self.base_url + "?" + urllib.parse.urlencode(filter_expr)
resp, body = self.http_client.do_request(filter_url, "GET")
package2 = deepcopy(self.package2)
for attr in ['softwareImages', 'checksum', 'userDefinedData']:
package2.pop(attr, None)
expected_result = {'vnf_packages': [package2]}
self.assertEqual(expected_result, body)
def test_index_attribute_selector_exclude_fields(self):
filter_expr = {'filter': '(eq,id,%s)' % self.package_id2,
'exclude_fields': 'checksum,softwareImages/checksum'}
filter_url = self.base_url + "?" + urllib.parse.urlencode(filter_expr)
resp, body = self.http_client.do_request(filter_url, "GET")
package2 = deepcopy(self.package2)
for software_image in package2['softwareImages']:
software_image.pop('checksum', None)
package2.pop('checksum', None)
expected_result = {'vnf_packages': [package2]}
self.assertEqual(expected_result, body)
def test_index_attribute_selector_fields(self):
filter_expr = {'filter': '(eq,id,%s)' % self.package_id1,
'fields': 'softwareImages/checksum/hash,'
'softwareImages/containerFormat,softwareImages/name,'
'userDefinedData'}
filter_url = self.base_url + "?" + urllib.parse.urlencode(filter_expr)
resp, body = self.http_client.do_request(filter_url, "GET")
package1 = deepcopy(self.package1)
# Prepare expected result
for software_image in package1['softwareImages']:
software_image['checksum'].pop('algorithm', None)
for attr in ['createdAt', 'diskFormat', 'id', 'imagePath',
'minDisk', 'minRam', 'provider', 'size', 'userMetadata',
'version']:
software_image.pop(attr, None)
package1.pop('checksum', None)
expected_result = {'vnf_packages': [package1]}
self.assertEqual(expected_result, body)
def _create_and_onboard_vnf_package(self, file_name=None):
body = jsonutils.dumps({"userDefinedData": {"foo": "bar"}})
vnf_package = self._create_vnf_package(body)
if file_name is None:
file_name = "sample_vnf_package_csar.zip"
file_path = self._get_csar_file_path(file_name)
file_path = os.path.abspath(os.path.join(os.path.dirname(__file__),
'../../etc/samples/' + file_name))
with open(file_path, 'rb') as file_object:
resp, resp_body = self.http_client.do_request(
'{base_path}/{id}/package_content'.format(

View File

@ -49,7 +49,7 @@ 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'}
expected_result = {'key1': 'value1', 'key2': 'value2'}
result = vnf_package._add_user_defined_data(
self.context, vnf_package_db.id, vnf_package_db.user_data)
self.assertEqual(expected_result, result)

View File

@ -14,6 +14,7 @@
# under the License.
from copy import deepcopy
import datetime
import io
import iso8601
@ -26,70 +27,188 @@ import zipfile
from tacker.api.vnfpkgm.v1.router import VnfpkgmAPIRouter
from tacker import context
from tacker.db.db_sqlalchemy import models
from tacker.objects import vnf_deployment_flavour as vnf_deployment_flavour_obj
from tacker.objects import vnf_package as vnf_package_obj
from tacker.objects import vnf_package_vnfd as vnf_package_vnfd_obj
from tacker.objects import vnf_software_image as vnf_software_image_obj
from tacker.tests import constants
from tacker.tests import uuidsentinel
from tacker import wsgi
VNFPACKAGE_RESPONSE = {'_links': {
'packageContent': {
'href':
'/vnfpkgm/v1/vnf_packages/'
'f26f181d-7891-4720-b022-b074ec1733ef/package_content'},
'self': {
'href':
'/vnfpkgm/v1/vnf_packages/'
'f26f181d-7891-4720-b022-b074ec1733ef'},
},
VNFPACKAGE_RESPONSE = {
'_links': {
'packageContent': {
'href': '/vnfpkgm/v1/vnf_packages/'
'f26f181d-7891-4720-b022-b074ec1733ef/package_content'},
'self': {
'href': '/vnfpkgm/v1/vnf_packages/'
'f26f181d-7891-4720-b022-b074ec1733ef'}
},
'checksum': {
'algorithm': 'fake vnf package algorithm',
'hash': 'fake vnf package hash'
},
'id': 'f26f181d-7891-4720-b022-b074ec1733ef',
'onboardingState': 'CREATED',
'operationalState': 'DISABLED',
'onboardingState': 'ONBOARDED',
'operationalState': 'ENABLED',
'usageState': 'NOT_IN_USE',
'userDefinedData': {'abc': 'xyz'}
'vnfProductName': 'fake vnf product name',
'vnfProvider': 'fake vnf provider',
'vnfSoftwareVersion': 'fake vnf software version',
'vnfdId': uuidsentinel.vnfd_id,
'vnfdVersion': 'fake vnfd version',
'userDefinedData': {'key1': 'value1', 'key2': 'value2'},
'softwareImages': [{
'checksum': {'algorithm': 'fake-algorithm',
'hash': 'fake software image hash'},
'containerFormat': 'bare',
'createdAt': datetime.datetime(1900, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
'diskFormat': 'qcow2',
'id': 'fake_software_image_id',
'imagePath': 'fake image path',
'minDisk': 1,
'minRam': 0,
'name': 'fake software image name',
'provider': 'fake provider',
'size': 1,
'userMetadata': {'key3': 'value3', 'key4': 'value4'},
'version': '11.22.33'
}],
}
VNFPACKAGE_INDEX_RESPONSE = {'vnf_packages': [{'_links': {
'packageContent': {
'href':
'/vnfpkgm/v1/vnf_packages/'
'f26f181d-7891-4720-b022-b074ec1733ef/package_content'},
'self': {
'href': '/vnfpkgm/v1/vnf_packages/'
'f26f181d-7891-4720-b022-b074ec1733ef'}},
'id': 'f26f181d-7891-4720-b022-b074ec1733ef',
'onboardingState': 'CREATED',
'operationalState': 'DISABLED',
'usageState': 'NOT_IN_USE',
'userDefinedData': {}}]
VNFPACKAGE_INDEX_RESPONSE = {
'vnf_packages': [VNFPACKAGE_RESPONSE]
}
def fake_vnf_package(**updates):
vnf_package = {
'algorithm': None,
'deleted': False,
'deleted_at': None,
'updated_at': None,
def index_response(remove_attrs=None, vnf_package_updates=None):
# Returns VNFPACKAGE_INDEX_RESPONSE
# parameter remove_attrs is a list of attribute names
# to be removed before returning the response
if not remove_attrs:
return VNFPACKAGE_INDEX_RESPONSE
vnf_package = deepcopy(VNFPACKAGE_RESPONSE)
for attr in remove_attrs:
vnf_package.pop(attr, None)
if vnf_package_updates:
vnf_package.update(vnf_package_updates)
return {'vnf_packages': [vnf_package]}
def _fake_software_image(updates=None):
software_image = {
'id': uuidsentinel.software_image_id,
'disk_format': 'qcow2',
'min_ram': 0,
'min_disk': 1,
'container_format': 'bare',
'provider': 'fake provider',
'image_path': 'fake image path',
'software_image_id': 'fake_software_image_id',
'size': 1,
'name': 'fake software image name',
'hash': 'fake software image hash',
'version': '11.22.33',
'algorithm': 'fake-algorithm',
'metadata': {'key3': 'value3', 'key4': 'value4'},
'created_at': datetime.datetime(1900, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
'hash': None,
'location_glance_store': None,
'onboarding_state': 'CREATED',
'operational_state': 'DISABLED',
}
if updates:
software_image.update(updates)
return software_image
def return_software_image(updates=None):
software_image = _fake_software_image(updates)
obj = vnf_software_image_obj.VnfSoftwareImage(**software_image)
return obj
def _fake_deployment_flavour(updates=None):
deployment_flavour = {
'id': uuidsentinel.deployment_flavour_id,
'package_uuid': 'f26f181d-7891-4720-b022-b074ec1733ef',
'flavour_id': 'fake flavour id',
'flavour_description': 'fake flavour description',
'instantiation_levels': {"level1": 1, "level2": 2}
}
if updates:
deployment_flavour.update(updates)
return deployment_flavour
def _return_deployment_flavour(deployment_flavour_updates=None,
software_image_updates=None):
flavour = _fake_deployment_flavour(deployment_flavour_updates)
obj = vnf_deployment_flavour_obj.VnfDeploymentFlavour(**flavour)
software_image = return_software_image(software_image_updates)
software_image_list = vnf_software_image_obj.VnfSoftwareImagesList()
software_image_list.objects = [software_image]
obj.software_images = software_image_list
return obj
def _fake_vnfd(updates=None):
vnfd = {
'id': uuidsentinel.vnfd_unused_id,
'package_uuid': 'f26f181d-7891-4720-b022-b074ec1733ef',
'vnfd_id': uuidsentinel.vnfd_id,
'vnf_provider': 'fake vnf provider',
'vnf_product_name': 'fake vnf product name',
'vnfd_version': 'fake vnfd version',
'vnf_software_version': 'fake vnf software version'
}
if updates:
vnfd.update(updates)
return vnfd
def _return_vnfd(updates=None):
vnfd = _fake_vnfd(updates)
return vnf_package_vnfd_obj.VnfPackageVnfd(**vnfd)
def fake_vnf_package(updates=None):
vnf_package = {
'id': constants.UUID,
'hash': 'fake vnf package hash',
'algorithm': 'fake vnf package algorithm',
'location_glance_store': 'fake location',
'onboarding_state': 'ONBOARDED',
'operational_state': 'ENABLED',
'tenant_id': uuidsentinel.tenant_id,
'usage_state': 'NOT_IN_USE',
'user_data': {'abc': 'xyz'},
'id': constants.UUID,
'user_data': {'key1': 'value1', 'key2': 'value2'},
}
if updates:
vnf_package.update(updates)
return vnf_package
def return_vnfpkg_obj(vnf_package_updates=None, vnfd_updates=None,
deployment_flavour_updates=None, software_image_updates=None):
vnf_package = fake_vnf_package(vnf_package_updates)
obj = vnf_package_obj.VnfPackage(**vnf_package)
obj.vnfd = _return_vnfd(vnfd_updates)
deployment_flavour = _return_deployment_flavour(
deployment_flavour_updates, software_image_updates)
flavour_list = vnf_deployment_flavour_obj.VnfDeploymentFlavoursList()
flavour_list.objects = [deployment_flavour]
obj.vnf_deployment_flavours = flavour_list
return obj
def return_vnf_package_list():
vnf_package = return_vnfpkg_obj()
return [vnf_package]
class InjectContext(wsgi.Middleware):
"""Add a 'tacker.context' to WSGI environ."""
@ -103,64 +222,6 @@ class InjectContext(wsgi.Middleware):
return self.application
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_vnf_package(onboarded=False, **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
if onboarded:
updates = {'onboarding_state': 'ONBOARDED',
'operational_state': 'ENABLED',
'algorithm': 'test',
'hash': 'test',
'location_glance_store': 'file:test/path/pkg-uuid',
'updated_at': datetime.datetime(
1900, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC)}
model_obj.update(fake_vnf_package(**updates))
else:
model_obj.update(fake_vnf_package(**updates))
return model_obj
def return_vnfpkg_obj(onboarded=False, **updates):
vnf_package = vnf_package_obj.VnfPackage._from_db_object(
context, vnf_package_obj.VnfPackage(),
return_vnf_package(onboarded=onboarded, **updates),
expected_attrs=None)
return vnf_package
def return_vnf_package_list():
vnf_package = return_vnfpkg_obj()
return [vnf_package]
def wsgi_app_v1(fake_auth_context=None):
inner_app_v1 = VnfpkgmAPIRouter()
if fake_auth_context is not None:

View File

@ -12,7 +12,6 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import ddt
import mock
from oslo_serialization import jsonutils
@ -21,13 +20,14 @@ from six.moves import urllib
from webob import exc
from tacker.api.vnfpkgm.v1 import controller
from tacker.common import exceptions
from tacker.common import exceptions as tacker_exc
from tacker.conductor.conductorrpc.vnf_pkgm_rpc import VNFPackageRPCAPI
from tacker.glance_store import store as glance_store
from tacker import objects
from tacker.objects import fields
from tacker.objects import vnf_package
from tacker.objects.vnf_package import VnfPackagesList
from tacker.objects.vnf_software_image import VnfSoftwareImage
from tacker.tests import constants
from tacker.tests.unit import base
from tacker.tests.unit import fake_request
@ -68,11 +68,13 @@ class TestController(base.TestCase):
resp = req.get_response(self.app)
self.assertEqual(http_client.CREATED, resp.status_code)
@mock.patch.object(VnfSoftwareImage, 'get_by_id')
@mock.patch.object(vnf_package.VnfPackage, "get_by_id")
def test_show(self, mock_vnf_by_id):
def test_show(self, mock_vnf_by_id, mock_sw_image_by_id):
req = fake_request.HTTPRequest.blank(
'/vnfpkgm/v1/vnf_packages/%s' % constants.UUID)
mock_vnf_by_id.return_value = fakes.return_vnf_package()
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj()
mock_sw_image_by_id.return_value = fakes.return_software_image()
expected_result = fakes.VNFPACKAGE_RESPONSE
res_dict = self.controller.show(req, constants.UUID)
self.assertEqual(expected_result, res_dict)
@ -92,18 +94,376 @@ class TestController(base.TestCase):
self.assertRaises(exc.HTTPNotFound, self.controller.show,
req, constants.UUID)
@mock.patch.object(VnfPackagesList, "get_all")
def test_index(self, mock_vnf_list):
req = fake_request.HTTPRequest.blank('/vnfpkgm/v1/vnf_packages/')
@mock.patch.object(VnfPackagesList, "get_by_filters")
@ddt.data('/vnfpkgm/v1/vnf_packages')
def test_index(self, path, mock_vnf_list):
req = fake_request.HTTPRequest.blank(path)
mock_vnf_list.return_value = fakes.return_vnf_package_list()
res_dict = self.controller.index(req)
expected_result = fakes.VNFPACKAGE_INDEX_RESPONSE
expected_result = fakes.index_response(remove_attrs=[
'softwareImages', 'checksum', 'userDefinedData'])
self.assertEqual(expected_result, res_dict)
@mock.patch.object(VnfPackagesList, "get_by_filters")
def test_index_attribute_selector_all_fields(self, mock_vnf_list):
params = {'all_fields': ''}
query = urllib.parse.urlencode(params)
req = fake_request.HTTPRequest.blank('/vnfpkgm/v1/vnf_packages?' +
query)
mock_vnf_list.return_value = fakes.return_vnf_package_list()
res_dict = self.controller.index(req)
expected_result = fakes.index_response()
self.assertEqual(expected_result, res_dict)
@mock.patch.object(VnfPackagesList, "get_by_filters")
def test_index_attribute_selector_exclude_default(self, mock_vnf_list):
params = {'exclude_default': ''}
query = urllib.parse.urlencode(params)
req = fake_request.HTTPRequest.blank('/vnfpkgm/v1/vnf_packages?' +
query)
mock_vnf_list.return_value = fakes.return_vnf_package_list()
res_dict = self.controller.index(req)
expected_result = fakes.index_response(remove_attrs=[
'softwareImages', 'checksum', 'userDefinedData'])
self.assertEqual(expected_result, res_dict)
@mock.patch.object(VnfPackagesList, "get_by_filters")
@ddt.data(
{'exclude_fields': 'softwareImages'},
{'exclude_fields': 'checksum'},
{'exclude_fields': 'userDefinedData'},
)
def test_index_attribute_selector_exclude_fields(self, params,
mock_vnf_list):
query = urllib.parse.urlencode(params)
req = fake_request.HTTPRequest.blank('/vnfpkgm/v1/vnf_packages?' +
query)
mock_vnf_list.return_value = fakes.return_vnf_package_list()
res_dict = self.controller.index(req)
remove_attrs = [params['exclude_fields']]
expected_result = fakes.index_response(remove_attrs=remove_attrs)
self.assertEqual(expected_result, res_dict)
@mock.patch.object(VnfPackagesList, "get_by_filters")
@ddt.data(
{'fields': 'softwareImages'},
{'fields': 'checksum'},
{'fields': 'userDefinedData'},
)
def test_index_attribute_selector_fields(self, params, mock_vnf_list):
"""Test valid attribute names with fields parameter
We can specify complex attributes in fields. Hence the data only
contains such attributes.
"""
complex_attrs = ['softwareImages', 'checksum', 'userDefinedData']
query = urllib.parse.urlencode(params)
req = fake_request.HTTPRequest.blank('/vnfpkgm/v1/vnf_packages?' +
query)
mock_vnf_list.return_value = fakes.return_vnf_package_list()
res_dict = self.controller.index(req)
remove_attrs = [x for x in complex_attrs if x != params['fields']]
expected_result = fakes.index_response(remove_attrs=remove_attrs)
self.assertEqual(expected_result, res_dict)
@mock.patch.object(VnfPackagesList, "get_by_filters")
def test_index_attribute_selector_user_defined_data_combination(self,
mock_vnf_list):
"""Query user defined data with fields parameter
This test queries combination of user defined data. i.e. fields of
different complex attributes.
"""
params = {
'fields': 'userDefinedData/key1,softwareImages/userMetadata/key3',
}
query = urllib.parse.urlencode(params)
req = fake_request.HTTPRequest.blank('/vnfpkgm/v1/vnf_packages?' +
query)
mock_vnf_list.return_value = fakes.return_vnf_package_list()
res_dict = self.controller.index(req)
vnf_package_updates = {
'userDefinedData': {'key1': 'value1'},
'softwareImages': [{'userMetadata': {'key3': 'value3'}}]
}
expected_result = fakes.index_response(remove_attrs=[
'checksum'], vnf_package_updates=vnf_package_updates)
self.assertEqual(expected_result, res_dict)
@mock.patch.object(VnfPackagesList, "get_by_filters")
def test_index_attribute_selector_user_defined_data(self, mock_vnf_list):
params = {'fields': 'userDefinedData/key1,userDefinedData/key2'}
query = urllib.parse.urlencode(params)
req = fake_request.HTTPRequest.blank('/vnfpkgm/v1/vnf_packages?' +
query)
mock_vnf_list.return_value = fakes.return_vnf_package_list()
res_dict = self.controller.index(req)
expected_result = fakes.index_response(remove_attrs=[
'checksum', 'softwareImages'])
self.assertEqual(expected_result, res_dict)
@mock.patch.object(VnfPackagesList, "get_by_filters")
def test_index_attribute_selector_nested_complex_attribute(self,
mock_vnf_list):
params = {'fields': 'softwareImages/checksum/algorithm,'
'softwareImages/minRam'}
query = urllib.parse.urlencode(params)
req = fake_request.HTTPRequest.blank('/vnfpkgm/v1/vnf_packages?' +
query)
mock_vnf_list.return_value = fakes.return_vnf_package_list()
res_dict = self.controller.index(req)
vnf_package_updates = {
'softwareImages': [{
'minRam': 0,
'checksum': {'algorithm': 'fake-algorithm'}
}]
}
expected_result = fakes.index_response(remove_attrs=[
'checksum', 'userDefinedData'],
vnf_package_updates=vnf_package_updates)
self.assertEqual(expected_result, res_dict)
@mock.patch.object(VnfPackagesList, "get_by_filters")
@ddt.data(
{'filter': '(eq,vnfdId,dummy_vnfd_id)'},
{'filter': '(in,vnfdId,dummy_vnfd_id)'},
{'filter': '(cont,vnfdId,dummy_vnfd_id)'},
{'filter': '(neq,vnfdId,dummy_vnfd_id)'},
{'filter': '(nin,vnfdId,dummy_vnfd_id)'},
{'filter': '(ncont,vnfdId,dummy_vnfd_id)'},
{'filter': '(gt,softwareImages/createdAt,2020-03-11 04:10:15+00:00)'},
{'filter': '(gte,softwareImages/createdAt,2020-03-14 04:10:15+00:00)'},
{'filter': '(lt,softwareImages/createdAt,2020-03-20 04:10:15+00:00)'},
{'filter': '(lte,softwareImages/createdAt,2020-03-11 04:10:15+00:00)'},
)
def test_index_filter_operator(self, filter_params, mock_vnf_list):
"""Tests all supported operators in filter expression """
query = urllib.parse.urlencode(filter_params)
req = fake_request.HTTPRequest.blank('/vnfpkgm/v1/vnf_packages?' +
query)
mock_vnf_list.return_value = fakes.return_vnf_package_list()
res_dict = self.controller.index(req)
expected_result = fakes.index_response(remove_attrs=[
'softwareImages', 'checksum', 'userDefinedData'])
self.assertEqual(expected_result, res_dict)
@mock.patch.object(VnfPackagesList, "get_by_filters")
def test_index_filter_combination(self, mock_vnf_list):
"""Test multiple filter parameters separated by semicolon """
params = {'filter': '(eq,vnfdId,dummy_vnfd_id);(eq,id,dummy_id)'}
query = urllib.parse.urlencode(params)
req = fake_request.HTTPRequest.blank('/vnfpkgm/v1/vnf_packages?' +
query)
mock_vnf_list.return_value = fakes.return_vnf_package_list()
res_dict = self.controller.index(req)
expected_result = fakes.index_response(remove_attrs=[
'softwareImages', 'checksum', 'userDefinedData'])
self.assertEqual(expected_result, res_dict)
@mock.patch.object(VnfPackagesList, "get_by_filters")
@ddt.data(
{'filter': '(eq,id,dummy_value)'},
{'filter': '(eq,vnfdId,dummy_value)'},
{'filter': '(eq,onboardingState,ONBOARDED)'},
{'filter': '(eq,operationalState,ENABLED)'},
{'filter': '(eq,usageState,NOT_IN_USE)'},
{'filter': '(eq,vnfProvider,dummy_value)'},
{'filter': '(eq,vnfProductName,dummy_value)'},
{'filter': '(eq,vnfSoftwareVersion,dummy_value)'},
{'filter': '(eq,vnfdVersion,dummy_value)'},
{'filter': '(eq,userDefinedData/key1,dummy_value)'},
{'filter': '(eq,checksum/algorithm,dummy_value)'},
{'filter': '(eq,checksum/hash,dummy_value)'},
{'filter': '(eq,softwareImages/id,dummy_value)'},
{'filter': '(eq,softwareImages/imagePath,dummy_value)'},
{'filter': '(eq,softwareImages/diskFormat,dummy_value)'},
{'filter': '(eq,softwareImages/userMetadata/key3,dummy_value)'},
{'filter': '(eq,softwareImages/size,0)'},
{'filter': '(gt,softwareImages/createdAt,2020-03-14 04:10:15+00:00)'},
{'filter': '(eq,softwareImages/name,dummy_value)'},
{'filter': '(eq,softwareImages/minDisk,0)'},
{'filter': '(eq,softwareImages/version,dummy_value)'},
{'filter': '(eq,softwareImages/provider,dummy_value)'},
{'filter': '(eq,softwareImages/minRam,0)'},
{'filter': '(eq,softwareImages/containerFormat,dummy_value)'},
{'filter': '(eq,softwareImages/checksum/hash,dummy_value)'},
{'filter': '(eq,softwareImages/checksum/algorithm,dummy_value)'},
)
def test_index_filter_attributes(self, filter_params, mock_vnf_list):
"""Test various attributes supported for filter parameter """
query = urllib.parse.urlencode(filter_params)
req = fake_request.HTTPRequest.blank('/vnfpkgm/v1/vnf_packages?' +
query)
mock_vnf_list.return_value = fakes.return_vnf_package_list()
res_dict = self.controller.index(req)
expected_result = fakes.index_response(remove_attrs=[
'softwareImages', 'checksum', 'userDefinedData'])
self.assertEqual(expected_result, res_dict)
@mock.patch.object(VnfPackagesList, "get_by_filters")
@ddt.data(
{'filter': "(eq,vnfProductName,dummy_value)"},
{'filter': "(eq,vnfProductName,dummy value)"},
{'filter': "(eq,vnfProductName,'dummy value')"},
{'filter': "(eq,vnfProductName,'dummy (hi) value')"},
{'filter': "(eq,vnfProductName,'dummy ''hi'' value')"},
{'filter': "(eq,vnfProductName,'''dummy ''hi'' value''')"},
)
def test_index_filter_valid_string_values(self, filter_params,
mock_vnf_list):
"""Tests all the supported string values.
For example:
- values which are not enclosed in single quotes
- values which are enclosed in single quotes
- values having single quotes within them
- values having round brackets in them
"""
query = urllib.parse.urlencode(filter_params)
req = fake_request.HTTPRequest.blank('/vnfpkgm/v1/vnf_packages?' +
query)
mock_vnf_list.return_value = fakes.return_vnf_package_list()
res_dict = self.controller.index(req)
expected_result = fakes.index_response(remove_attrs=[
'softwareImages', 'checksum', 'userDefinedData'])
self.assertEqual(expected_result, res_dict)
@mock.patch.object(VnfPackagesList, "get_by_filters")
@ddt.data(
{'filter': "(eq,vnfProductName,value"},
{'filter': "eq,vnfProductName,value)"},
{'filter': "(eq,vnfProductName,value);"},
{'filter': "(eq , vnfProductName ,value)"},
)
def test_index_filter_invalid_expression(self, filter_params,
mock_vnf_list):
"""Test invalid filter expression """
query = urllib.parse.urlencode(filter_params)
req = fake_request.HTTPRequest.blank('/vnfpkgm/v1/vnf_packages?' +
query)
mock_vnf_list.return_value = fakes.return_vnf_package_list()
mock_vnf_list.return_value = fakes.return_vnf_package_list()
self.assertRaises(tacker_exc.ValidationError, self.controller.index,
req)
@mock.patch.object(VnfPackagesList, "get_by_filters")
@ddt.data(
{'filter': "(eq,vnfProductName,singl'quote)"},
{'filter': "(eq,vnfProductName,three''' quotes)"},
{'filter': "(eq,vnfProductName,round ) bracket)"},
{'filter': "(eq,vnfProductName,'dummy 'hi' value')"},
{'filter': "(eq,vnfProductName,'dummy's value')"},
{'filter': "(eq,vnfProductName,'three ''' quotes')"},
)
def test_index_filter_invalid_string_values(self, filter_params,
mock_vnf_list):
"""Test invalid string values as per ETSI NFV SOL013 5.2.2 """
query = urllib.parse.urlencode(filter_params)
req = fake_request.HTTPRequest.blank('/vnfpkgm/v1/vnf_packages?' +
query)
mock_vnf_list.return_value = fakes.return_vnf_package_list()
mock_vnf_list.return_value = fakes.return_vnf_package_list()
self.assertRaises(tacker_exc.ValidationError, self.controller.index,
req)
@mock.patch.object(VnfPackagesList, "get_by_filters")
@ddt.data(
{'filter': '(eq,vnfdId,value1,value2)'},
{'filter': '(fake,vnfdId,dummy_vnfd_id)'},
{'filter': '(,vnfdId,dummy_vnfd_id)'},
)
def test_index_filter_invalid_operator(self, params, mock_vnf_list):
"""Test invalid operator in filter expression """
query = urllib.parse.urlencode(params)
req = fake_request.HTTPRequest.blank('/vnfpkgm/v1/vnf_packages?' +
query)
mock_vnf_list.return_value = fakes.return_vnf_package_list()
self.assertRaises(tacker_exc.ValidationError, self.controller.index,
req)
@mock.patch.object(VnfPackagesList, "get_by_filters")
@ddt.data(
{'filter': '(eq,fakeattr,fakevalue)'},
{'filter': '(eq,,fakevalue)'},
)
def test_index_filter_invalid_attribute(self, params, mock_vnf_list):
"""Test invalid attribute in filter expression """
query = urllib.parse.urlencode(params)
req = fake_request.HTTPRequest.blank('/vnfpkgm/v1/vnf_packages?' +
query)
mock_vnf_list.return_value = fakes.return_vnf_package_list()
self.assertRaises(tacker_exc.ValidationError, self.controller.index,
req)
@mock.patch.object(VnfPackagesList, "get_by_filters")
@ddt.data(
{'filter': '(eq,operationalState,fake_value)'},
{'filter': '(eq,softwareImages/size,fake_value)'},
{'filter': '(gt,softwareImages/createdAt,fake_value)'},
{'filter': '(eq,softwareImages/minDisk,fake_value)'},
{'filter': '(eq,softwareImages/minRam,fake_value)'},
)
def test_index_filter_invalid_value_type(self, filter_params,
mock_vnf_list):
"""Test values which doesn't match with attribute data type """
query = urllib.parse.urlencode(filter_params)
req = fake_request.HTTPRequest.blank('/vnfpkgm/v1/vnf_packages?' +
query)
mock_vnf_list.return_value = fakes.return_vnf_package_list()
self.assertRaises(tacker_exc.ValidationError, self.controller.index,
req)
@mock.patch.object(VnfPackagesList, "get_by_filters")
@ddt.data(
{'fields': 'nonExistentField'},
{'exclude_fields': 'nonExistentField'}
)
def test_index_attribute_selector_invalid_fields(self, params,
mock_vnf_list):
query = urllib.parse.urlencode(params)
req = fake_request.HTTPRequest.blank('/vnfpkgm/v1/vnf_packages?' +
query)
mock_vnf_list.return_value = fakes.return_vnf_package_list()
self.assertRaises(tacker_exc.ValidationError, self.controller.index,
req)
@mock.patch.object(VnfPackagesList, "get_by_filters")
@ddt.data(
{'fields': 'softwareImages', 'all_fields': ''},
{'exclude_fields': 'checksum', 'all_fields': ''},
{'fields': 'softwareImages', 'exclude_fields': 'checksum'}
)
def test_index_attribute_selector_invalid_combination(self, params,
mock_vnf_list):
"""Test invalid combination of attribute selector parameters """
query = urllib.parse.urlencode(params)
req = fake_request.HTTPRequest.blank('/vnfpkgm/v1/vnf_packages?' +
query)
mock_vnf_list.return_value = fakes.return_vnf_package_list()
self.assertRaises(tacker_exc.ValidationError, self.controller.index,
req)
@mock.patch.object(VnfPackagesList, "get_by_filters")
@ddt.data(
{'exclude_default': 'softwareImages'},
{'all_fields': 'checksum'},
)
def test_index_attribute_selector_unexpected_value(self, params,
mock_vnf_list):
"""Test values with the parameters which doesn't need values. """
query = urllib.parse.urlencode(params)
req = fake_request.HTTPRequest.blank('/vnfpkgm/v1/vnf_packages?' +
query)
mock_vnf_list.return_value = fakes.return_vnf_package_list()
self.assertRaises(tacker_exc.ValidationError, self.controller.index,
req)
@mock.patch.object(vnf_package.VnfPackage, "get_by_id")
@mock.patch.object(VNFPackageRPCAPI, "delete_vnf_package")
def test_delete_with_204_status(self, mock_delete_rpc, mock_vnf_by_id):
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj()
vnfpkg_updates = {'operational_state': 'DISABLED'}
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj(
vnf_package_updates=vnfpkg_updates)
req = fake_request.HTTPRequest.blank(
'/vnf_packages/%s' % constants.UUID)
req.headers['Content-Type'] = 'application/json'
@ -160,7 +520,12 @@ class TestController(base.TestCase):
mock_glance_store):
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()
updates = {'onboarding_state': 'CREATED',
'operational_state': 'DISABLED'}
vnf_package_dict = fakes.fake_vnf_package(updates)
vnf_package_obj = objects.VnfPackage(**vnf_package_dict)
mock_vnf_by_id.return_value = vnf_package_obj
mock_vnf_pack_save.return_value = vnf_package_obj
mock_glance_store.return_value = 'location', 'size', 'checksum',\
'multihash', 'loc_meta'
req = fake_request.HTTPRequest.blank(
@ -208,7 +573,7 @@ class TestController(base.TestCase):
file_path = "tacker/tests/etc/samples/test_data.zip"
file_obj = open(file_path, "rb")
vnf_obj = fakes.return_vnfpkg_obj()
vnf_obj.__setattr__('onboarding_state', 'test')
vnf_obj.__setattr__('onboarding_state', 'ONBOARDED')
mock_vnf_by_id.return_value = vnf_obj
req = fake_request.HTTPRequest.blank(
'/vnf_packages/%s/package_content' % constants.UUID)
@ -225,7 +590,12 @@ class TestController(base.TestCase):
mock_upload_vnf_package_from_uri,
mock_url_open):
body = {"addressInformation": "http://test_data.zip"}
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj()
updates = {'onboarding_state': 'CREATED',
'operational_state': 'DISABLED'}
vnf_package_dict = fakes.fake_vnf_package(updates)
vnf_package_obj = objects.VnfPackage(**vnf_package_dict)
mock_vnf_by_id.return_value = vnf_package_obj
mock_vnf_pack_save.return_value = vnf_package_obj
req = fake_request.HTTPRequest.blank(
'/vnf_packages/%s/package_content/upload_from_uri'
% constants.UUID)
@ -266,7 +636,7 @@ class TestController(base.TestCase):
mock_url_open):
body = {"addressInformation": "http://test.zip"}
vnf_obj = fakes.return_vnfpkg_obj()
vnf_obj.__setattr__('onboarding_state', 'test')
vnf_obj.__setattr__('onboarding_state', 'ONBOARDED')
mock_vnf_by_id.return_value = vnf_obj
req = fake_request.HTTPRequest.blank(
'/vnf_packages/%s/package_content/upload_from_uri'
@ -290,9 +660,9 @@ class TestController(base.TestCase):
@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'}
vnf_package_updates = {'operational_state': 'DISABLED'}
mock_vnf_by_id.return_value = \
fakes.return_vnfpkg_obj(**update_onboarding_state)
fakes.return_vnfpkg_obj(vnf_package_updates=vnf_package_updates)
req_body = {"operationalState": "ENABLED",
"userDefinedData": {"testKey1": "val01",
@ -334,9 +704,10 @@ class TestController(base.TestCase):
@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"}})
fake_obj = fakes.return_vnfpkg_obj(vnf_package_updates={
"operational_state": "DISABLED", "onboarding_state": "CREATED",
"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",
@ -355,17 +726,23 @@ class TestController(base.TestCase):
@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"}}
vnf_package_updates = {"operational_state": "DISABLED",
"onboarding_state": "CREATED",
"user_data": {"testKey1": "val01",
"testKey2": "val02",
"testkey3": "val03"}}
req_body = {"userDefinedData": {"testKey1": "val01",
"testKey2": "val02",
"testkey3": "val03"}}
fake_obj = fakes.return_vnfpkg_obj(
**{'user_data': body["userDefinedData"]})
vnf_package_updates=vnf_package_updates)
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)
req, constants.UUID, body=req_body)
def test_patch_with_invalid_uuid(self):
body = {"operationalState": "ENABLED"}
@ -395,9 +772,9 @@ class TestController(base.TestCase):
@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
vnf_package_updates = {'operational_state': 'DISABLED'}
mock_vnf_by_id.return_value = \
fakes.return_vnfpkg_obj(vnf_package_updates=vnf_package_updates)
body = {"operationalState": "DISABLED",
"userDefinedData": {"testKey1": "val01",
"testKey2": "val02", "testkey3": "val03"}}
@ -409,7 +786,10 @@ class TestController(base.TestCase):
@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()
vnf_package_updates = {'onboarding_state': 'CREATED',
'operational_state': 'DISABLED'}
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj(
vnf_package_updates=vnf_package_updates)
body = {"operationalState": "DISABLED"}
req = fake_request.HTTPRequest.blank('/vnf_packages/%s'
% constants.UUID)
@ -423,7 +803,7 @@ class TestController(base.TestCase):
'application/zip,text/plain')
def test_get_vnf_package_vnfd_with_valid_accept_headers(
self, accept_headers, mock_vnf_by_id, mock_get_vnf_package_vnfd):
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj(onboarded=True)
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj()
mock_get_vnf_package_vnfd.return_value = fakes.return_vnfd_data()
req = fake_request.HTTPRequest.blank(
'/vnf_packages/%s/vnfd'
@ -436,7 +816,7 @@ class TestController(base.TestCase):
@mock.patch.object(vnf_package.VnfPackage, "get_by_id")
def test_get_vnf_package_vnfd_with_invalid_accept_header(
self, mock_vnf_by_id):
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj(onboarded=True)
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj()
req = fake_request.HTTPRequest.blank(
'/vnf_packages/%s/vnfd'
% constants.UUID)
@ -450,7 +830,7 @@ class TestController(base.TestCase):
@mock.patch.object(vnf_package.VnfPackage, "get_by_id")
def test_get_vnf_package_vnfd_failed_with_bad_request(
self, mock_vnf_by_id, mock_get_vnf_package_vnfd):
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj(onboarded=True)
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj()
mock_get_vnf_package_vnfd.return_value = fakes.return_vnfd_data()
req = fake_request.HTTPRequest.blank(
'/vnf_packages/%s/vnfd'
@ -466,7 +846,7 @@ class TestController(base.TestCase):
def test_get_vnf_package_vnfd_for_content_type_text_plain(self,
mock_vnf_by_id,
mock_get_vnf_package_vnfd):
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj(onboarded=True)
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj()
fake_vnfd_data = fakes.return_vnfd_data(multiple_yaml_files=False)
mock_get_vnf_package_vnfd.return_value = fake_vnfd_data
req = fake_request.HTTPRequest.blank(
@ -483,7 +863,12 @@ class TestController(base.TestCase):
@mock.patch.object(vnf_package.VnfPackage, "get_by_id")
def test_get_vnf_package_vnfd_failed_with_invalid_status(
self, mock_vnf_by_id):
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj(onboarded=False)
vnf_package_updates = {
'onboarding_state': 'CREATED',
'operational_state': 'DISABLED'
}
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj(
vnf_package_updates=vnf_package_updates)
req = fake_request.HTTPRequest.blank(
'/vnf_packages/%s/vnfd'
% constants.UUID)
@ -513,7 +898,7 @@ class TestController(base.TestCase):
% constants.UUID)
req.headers['Accept'] = 'application/zip'
req.method = 'GET'
mock_vnf_by_id.side_effect = exceptions.VnfPackageNotFound
mock_vnf_by_id.side_effect = tacker_exc.VnfPackageNotFound
self.assertRaises(exc.HTTPNotFound,
self.controller.get_vnf_package_vnfd, req,
constants.UUID)
@ -522,8 +907,8 @@ class TestController(base.TestCase):
@mock.patch.object(vnf_package.VnfPackage, "get_by_id")
def test_get_vnf_package_vnfd_failed_with_internal_server_error(
self, mock_vnf_by_id, mock_get_vnf_package_vnfd):
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj(onboarded=True)
mock_get_vnf_package_vnfd.side_effect = exceptions.FailedToGetVnfdData
mock_vnf_by_id.return_value = fakes.return_vnfpkg_obj()
mock_get_vnf_package_vnfd.side_effect = tacker_exc.FailedToGetVnfdData
req = fake_request.HTTPRequest.blank(
'/vnf_packages/%s/vnfd'
% constants.UUID)