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:
parent
cfaa39d73f
commit
3f5c81604e
@ -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(","))
|
@ -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]}
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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 ]
|
@ -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
|
@ -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
|
@ -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
|
@ -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 ]
|
@ -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
|
@ -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
|
@ -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
|
@ -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, |