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,