Add instantiate vnf instance API

Implemented instantiate vnf instance API.

* GET /vnflcm/v1/vnf_instances/{vnf_instance_id}/instantiate

Co-authored-By: Nitin Uikey <nitin.uikey@nttdata.com>
Co-authored-By: tpatil <tushar.vitthal.patil@gmail.com>
Co-authored-By: Ajay Parja <ajay.parja@nttdata.com>
Co-authored-By: Shubham Potale <shubham.potale@nttdata.com>
Co-authored-By: Sameer Thakur <sameer.thakur@nttdata.com>

Updated lower-constraints openstacksdk version to 0.44.0 as it
includes fix[1] which is required to create images from filename
which further requires to upgrade os-service-types, keystoneauth1
and decorato to higher versions.

[1] : https://review.opendev.org/#/c/710368

Change-Id: Ic30f8d730d54e3af1345816ffa1bfb702cd00694
Blueprint: support-etsi-nfv-specs
This commit is contained in:
Niraj 2019-12-05 07:47:47 +00:00 committed by tpatil
parent 7fe80b0e8b
commit a14590620b
62 changed files with 8140 additions and 102 deletions

View File

@ -19,7 +19,7 @@ coverage==4.0
cryptography==2.1
ddt===1.0.1
debtcollector==1.19.0
decorator==4.2.1
decorator==4.4.1
deprecation==2.0
doc8==0.6.0
docutils==0.14
@ -46,8 +46,8 @@ Jinja2==2.10
jmespath==0.9.3
jsonpatch==1.21
jsonpointer==2.0
jsonschema==2.6.0
keystoneauth1==3.11.0
jsonschema==3.0.0
keystoneauth1==3.15.0
keystonemiddleware==4.17.0
kombu==4.0.0
kubernetes==5.0.0
@ -64,10 +64,10 @@ netaddr==0.7.18
netifaces==0.10.6
oauthlib==2.0.7
openstackdocstheme==1.20.0
openstacksdk==0.12.0
openstacksdk==0.44.0
os-api-ref==1.5.0
os-client-config==1.29.0
os-service-types==1.2.0
os-service-types==1.7.0
osc-lib==1.10.0
oslo.cache==1.29.0
oslo.concurrency==3.26.0

View File

@ -10,7 +10,7 @@ anyjson>=0.3.3 # BSD
Babel!=2.4.0,>=2.3.4 # BSD
eventlet!=0.23.0,!=0.25.0,>=0.22.0 # MIT
requests>=2.14.2 # Apache-2.0
jsonschema>=2.6.0 # MIT
jsonschema>=3.0.0 # MIT
keystonemiddleware>=4.17.0 # Apache-2.0
kombu!=4.0.2,>=4.0.0 # BSD
netaddr>=0.7.18 # BSD
@ -37,6 +37,7 @@ oslo.upgradecheck>=0.1.0 # Apache-2.0
oslo.utils>=3.33.0 # Apache-2.0
oslo.versionedobjects>=1.33.3 # Apache-2.0
openstackdocstheme>=1.20.0 # Apache-2.0
openstacksdk>=0.44.0 # Apache-2.0
python-neutronclient>=6.7.0 # Apache-2.0
python-novaclient>=9.1.0 # Apache-2.0
tosca-parser>=1.6.0 # Apache-2.0

View File

@ -19,7 +19,161 @@ Schema for vnf lcm APIs.
"""
from tacker.api.validation import parameter_types
from tacker.objects import fields
_extManagedVirtualLinkData = {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'id': parameter_types.identifier,
'vnfVirtualLinkDescId': parameter_types.identifier_in_vnfd,
'resourceId': parameter_types.identifier_in_vim
},
'required': ['id', 'vnfVirtualLinkDescId', 'resourceId'],
'additionalProperties': False,
},
}
_ipaddresses = {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'type': {'enum': fields.IpAddressType.ALL},
'subnetId': parameter_types.identifier_in_vim,
'fixedAddresses': {'type': 'array'}
},
'if': {'properties': {'type': {'const': fields.IpAddressType.IPV4}}},
'then': {
'properties': {
'fixedAddresses': {
'type': 'array',
'items': {'type': 'string', 'format': 'ipv4'}
}
}
},
'else': {
'properties': {
'fixedAddresses': {
'type': 'array',
'items': {'type': 'string', 'format': 'ipv6'}
}
}
},
'required': ['type', 'fixedAddresses'],
'additionalProperties': False
}
}
_ipOverEthernetAddressData = {
'type': 'object',
'properties': {
'macAddress': parameter_types.mac_address_or_none,
'ipAddresses': _ipaddresses,
},
'anyOf': [
{'required': ['macAddress']},
{'required': ['ipAddresses']}
],
'additionalProperties': False
}
_cpProtocolData = {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'layerProtocol': {'type': 'string',
'enum': 'IP_OVER_ETHERNET'
},
'ipOverEthernet': _ipOverEthernetAddressData,
},
'required': ['layerProtocol'],
'additionalProperties': False,
}
}
_vnfExtCpConfig = {
'type': 'array', 'minItems': 1, 'maxItems': 1,
'items': {
'type': 'object',
'properties': {
'cpInstanceId': parameter_types.identifier_in_vnf,
'linkPortId': parameter_types.identifier,
'cpProtocolData': _cpProtocolData,
},
'additionalProperties': False,
}
}
_vnfExtCpData = {
'type': 'array', 'minItems': 1,
'items': {
'type': 'object',
'properties': {
'cpdId': parameter_types.identifier_in_vnfd,
'cpConfig': _vnfExtCpConfig,
},
'required': ['cpdId', 'cpConfig'],
'additionalProperties': False,
},
}
_resourceHandle = {
'type': 'object',
'properties': {
'resourceId': parameter_types.identifier_in_vim,
'vimLevelResourceType': {'type': 'string', 'maxLength': 255},
},
'required': ['resourceId'],
'additionalProperties': False,
}
_extLinkPortData = {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'id': parameter_types.identifier,
'resourceHandle': _resourceHandle,
},
'required': ['id', 'resourceHandle'],
'additionalProperties': False,
}
}
_extVirtualLinkData = {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'id': parameter_types.identifier,
'resourceId': parameter_types.identifier_in_vim,
'extCps': _vnfExtCpData,
'extLinkPorts': _extLinkPortData,
},
'required': ['id', 'resourceId', 'extCps'],
'additionalProperties': False,
}
}
_vimConnectionInfo = {
'type': 'array',
'maxItems': 1,
'items': {
'type': 'object',
'properties': {
'id': parameter_types.identifier,
'vimId': parameter_types.identifier,
'vimType': {'type': 'string', 'minLength': 1, 'maxLength': 255},
'accessInfo': parameter_types.keyvalue_pairs,
},
'required': ['id', 'vimType'],
'additionalProperties': False,
}
}
create = {
'type': 'object',
@ -31,3 +185,17 @@ create = {
'required': ['vnfdId'],
'additionalProperties': False,
}
instantiate = {
'type': 'object',
'properties': {
'flavourId': {'type': 'string', 'maxLength': 255},
'instantiationLevelId': {'type': 'string', 'maxLength': 255},
'extVirtualLinks': _extVirtualLinkData,
'extManagedVirtualLinks': _extManagedVirtualLinkData,
'vimConnectionInfo': _vimConnectionInfo,
'additionalParams': parameter_types.keyvalue_pairs,
},
'required': ['flavourId'],
'additionalProperties': False,
}

View File

@ -138,3 +138,36 @@ uuid = {
name_allow_zero_min_length = {
'type': 'string', 'minLength': 0, 'maxLength': 255
}
ip_address = {
'type': 'string',
'oneOf': [
{'format': 'ipv4'},
{'format': 'ipv6'}
]
}
positive_integer = {
'type': ['integer', 'string'],
'pattern': '^[0-9]*$', 'minimum': 1, 'minLength': 1
}
identifier = {
'type': 'string', 'minLength': 1, 'maxLength': 255
}
identifier_in_vim = {
'type': 'string', 'minLength': 1, 'maxLength': 255
}
identifier_in_vnfd = {
'type': 'string', 'minLength': 1, 'maxLength': 255
}
identifier_in_vnf = {
'type': 'string', 'minLength': 1, 'maxLength': 255
}
mac_address_or_none = {
'type': 'string', 'format': 'mac_address_or_none'
}

View File

@ -20,9 +20,11 @@ Internal implementation of request Body validating middleware.
import jsonschema
from jsonschema import exceptions as jsonschema_exc
import netaddr
from oslo_utils import uuidutils
import rfc3986
import six
import webob
from tacker.common import exceptions as exception
@ -38,6 +40,21 @@ def _validate_uuid_format(instance):
return uuidutils.is_uuid_like(instance)
@jsonschema.FormatChecker.cls_checks('mac_address_or_none',
webob.exc.HTTPBadRequest)
def validate_mac_address_or_none(instance):
"""Validate instance is a MAC address"""
if instance is None:
return
if not netaddr.valid_mac(instance):
msg = _("'%s' is not a valid mac address")
raise webob.exc.HTTPBadRequest(explanation=msg % instance)
return True
class FormatChecker(jsonschema.FormatChecker):
"""A FormatChecker can output the message from cause exception
@ -76,14 +93,14 @@ class FormatChecker(jsonschema.FormatChecker):
class _SchemaValidator(object):
"""A validator class
This class is changed from Draft4Validator to validate minimum/maximum
This class is changed from Draft7Validator to validate minimum/maximum
value of a string number(e.g. '10'). This changes can be removed when
we tighten up the API definition and the XML conversion.
Also FormatCheckers are added for checking data formats which would be
passed through cinder api commonly.
"""
validator_org = jsonschema.Draft4Validator
validator_org = jsonschema.Draft7Validator
def __init__(self, schema):
validator_cls = jsonschema.validators.extend(self.validator_org,
@ -95,7 +112,9 @@ class _SchemaValidator(object):
try:
self.validator.validate(*args, **kwargs)
except jsonschema.ValidationError as ex:
if len(ex.path) > 0:
if isinstance(ex.cause, webob.exc.HTTPBadRequest):
detail = str(ex.cause)
elif len(ex.path) > 0:
detail = _("Invalid input for field/attribute %(path)s."
" Value: %(value)s. %(message)s") % {
'path': ex.path.pop(), 'value': ex.instance,

View File

@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_utils import uuidutils
import six
from six.moves import http_client
import webob
@ -22,19 +24,141 @@ from tacker.api import validation
from tacker.api.views import vnf_lcm as vnf_lcm_view
from tacker.common import exceptions
from tacker.common import utils
from tacker.conductor.conductorrpc import vnf_lcm_rpc
from tacker.extensions import nfvo
from tacker import objects
from tacker.objects import fields
from tacker.policies import vnf_lcm as vnf_lcm_policies
from tacker.vnfm import vim_client
from tacker import wsgi
def check_vnf_state(action, instantiation_state=None, task_state=(None,)):
"""Decorator to check vnf states are valid for particular action.
If the vnf is in the wrong state, it will raise conflict exception.
"""
if instantiation_state is not None and not \
isinstance(instantiation_state, set):
instantiation_state = set(instantiation_state)
if task_state is not None and not isinstance(task_state, set):
task_state = set(task_state)
def outer(f):
@six.wraps(f)
def inner(self, context, vnf_instance, *args, **kw):
if instantiation_state is not None and \
vnf_instance.instantiation_state not in \
instantiation_state:
raise exceptions.VnfInstanceConflictState(
attr='instantiation_state',
uuid=vnf_instance.id,
state=vnf_instance.instantiation_state,
action=action)
if (task_state is not None and
vnf_instance.task_state not in task_state):
raise exceptions.VnfInstanceConflictState(
attr='task_state',
uuid=vnf_instance.id,
state=vnf_instance.task_state,
action=action)
return f(self, context, vnf_instance, *args, **kw)
return inner
return outer
class VnfLcmController(wsgi.Controller):
_view_builder_class = vnf_lcm_view.ViewBuilder
def __init__(self):
super(VnfLcmController, self).__init__()
self.rpc_api = vnf_lcm_rpc.VNFLcmRPCAPI()
def _get_vnf_instance_href(self, vnf_instance):
return '/vnflcm/v1/vnf_instances/%s' % vnf_instance.id
def _get_vnf_instance(self, context, id):
# check if id is of type uuid format
if not uuidutils.is_uuid_like(id):
msg = _("Can not find requested vnf instance: %s") % id
raise webob.exc.HTTPNotFound(explanation=msg)
try:
vnf_instance = objects.VnfInstance.get_by_id(context, id)
except exceptions.VnfInstanceNotFound:
msg = _("Can not find requested vnf instance: %s") % id
raise webob.exc.HTTPNotFound(explanation=msg)
return vnf_instance
def _validate_flavour_and_inst_level(self, context, req_body,
vnf_instance):
inst_levels = {}
flavour_list = []
vnf_package_vnfd = objects.VnfPackageVnfd.get_by_id(
context, vnf_instance.vnfd_id)
vnf_package = objects.VnfPackage.get_by_id(
context, vnf_package_vnfd.package_uuid)
deployment_flavour = vnf_package.vnf_deployment_flavours
for dep_flavour in deployment_flavour.objects:
flavour_list.append(dep_flavour.flavour_id)
if dep_flavour.instantiation_levels:
inst_levels.update({
dep_flavour.flavour_id: dep_flavour.instantiation_levels})
if req_body['flavour_id'] not in flavour_list:
raise exceptions.FlavourNotFound(flavour_id=req_body['flavour_id'])
req_inst_level_id = req_body.get('instantiation_level_id')
if req_inst_level_id is None:
return
if not inst_levels:
raise exceptions.InstantiationLevelNotFound(
inst_level_id=req_inst_level_id)
for flavour, inst_level in inst_levels.items():
if flavour != req_body['flavour_id']:
continue
if req_inst_level_id in inst_level.get('levels').keys():
# Found instantiation level
return
raise exceptions.InstantiationLevelNotFound(
inst_level_id=req_body['instantiation_level_id'])
def _validate_vim_connection(self, context, instantiate_vnf_request):
if instantiate_vnf_request.vim_connection_info:
vim_id = instantiate_vnf_request.vim_connection_info[0].vim_id
access_info = \
instantiate_vnf_request.vim_connection_info[0].access_info
if access_info:
region_name = access_info.get('region')
else:
region_name = None
else:
vim_id = None
region_name = None
vim_client_obj = vim_client.VimClient()
try:
vim_client_obj.get_vim(
context, vim_id, region_name=region_name)
except nfvo.VimDefaultNotDefined as exp:
raise webob.exc.HTTPBadRequest(explanation=exp.message)
except nfvo.VimNotFoundException:
msg = _("VimConnection id is not found: %s")\
% vim_id
raise webob.exc.HTTPBadRequest(explanation=msg)
except nfvo.VimRegionNotFoundException:
msg = _("Region not found for the VimConnection: %s")\
% vim_id
raise webob.exc.HTTPBadRequest(explanation=msg)
@wsgi.response(http_client.CREATED)
@wsgi.expected_errors((http_client.BAD_REQUEST, http_client.FORBIDDEN))
@validation.schema(vnf_lcm.create)
@ -77,8 +201,42 @@ class VnfLcmController(wsgi.Controller):
def delete(self, request, id):
raise webob.exc.HTTPNotImplemented()
@check_vnf_state(action="instantiate",
instantiation_state=[fields.VnfInstanceState.NOT_INSTANTIATED],
task_state=[None])
def _instantiate(self, context, vnf_instance, request_body):
req_body = utils.convert_camelcase_to_snakecase(request_body)
try:
self._validate_flavour_and_inst_level(context, req_body,
vnf_instance)
except exceptions.NotFound as ex:
raise webob.exc.HTTPBadRequest(explanation=six.text_type(ex))
instantiate_vnf_request = \
objects.InstantiateVnfRequest.obj_from_primitive(
req_body, context=context)
# validate the vim connection id passed through request is exist or not
self._validate_vim_connection(context, instantiate_vnf_request)
vnf_instance.task_state = fields.VnfInstanceTaskState.INSTANTIATING
vnf_instance.save()
self.rpc_api.instantiate(context, vnf_instance,
instantiate_vnf_request)
@wsgi.response(http_client.ACCEPTED)
@wsgi.expected_errors((http_client.FORBIDDEN, http_client.NOT_FOUND,
http_client.CONFLICT, http_client.BAD_REQUEST))
@validation.schema(vnf_lcm.instantiate)
def instantiate(self, request, id, body):
raise webob.exc.HTTPNotImplemented()
context = request.environ['tacker.context']
context.can(vnf_lcm_policies.VNFLCM % 'instantiate')
vnf_instance = self._get_vnf_instance(context, id)
self._instantiate(context, vnf_instance, body)
def terminate(self, request, id, body):
raise webob.exc.HTTPNotImplemented()

View File

@ -10,7 +10,11 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
from heatclient import client as heatclient
from openstack import connection
from tacker.vnfm import keystone
@ -23,6 +27,13 @@ class OpenstackClients(object):
self.mistral_client = None
self.keystone_client = None
self.region_name = region_name
if auth_attr:
# Note(tpatil): In vnflcm, auth_attr contains region information
# which should be popped before creating the keystoneclient.
auth_attr = copy.deepcopy(auth_attr)
auth_attr.pop('region', None)
self.auth_attr = auth_attr
def _keystone_client(self):
@ -49,3 +60,31 @@ class OpenstackClients(object):
if not self.heat_client:
self.heat_client = self._heat_client()
return self.heat_client
class OpenstackSdkConnection(object):
def __init__(self, vim_connection_info, version=None):
super(OpenstackSdkConnection, self).__init__()
self.keystone_plugin = keystone.Keystone()
self.connection = self.openstack_connection(vim_connection_info,
version)
def openstack_connection(self, vim_connection_info, version):
access_info = vim_connection_info.access_info
auth = dict(auth_url=access_info['auth_url'],
username=access_info['username'],
password=access_info['password'],
project_name=access_info['project_name'],
user_domain_name=access_info['user_domain_name'],
project_domain_name=access_info['project_domain_name'])
session = self.keystone_plugin.initialize_client(**auth).session
conn = connection.Connection(
region_name=access_info.get('region'),
session=session,
identity_interface='internal',
image_api_version=version)
return conn

View File

@ -43,7 +43,6 @@ class DriverManager(object):
LOG.error(msg)
raise SystemExit(msg)
drivers[type_] = ext
self._drivers = dict((type_, ext.obj)
for (type_, ext) in drivers.items())
LOG.info("Registered drivers from %(namespace)s: %(keys)s",

View File

@ -215,6 +215,23 @@ class VnfInstanceNotFound(NotFound):
message = _("No vnf instance with id %(id)s.")
class VnfInstanceConflictState(Conflict):
message = _("Vnf instance %(uuid)s in %(attr)s %(state)s. Cannot "
"%(action)s while the vnf instance is in this state.")
class FlavourNotFound(NotFound):
message = _("No flavour with id '%(flavour_id)s'.")
class InstantiationLevelNotFound(NotFound):
message = _("No instantiation level with id '%(inst_level_id)s'.")
class VimConnectionNotFound(NotFound):
message = _("No vim found with id '%(vim_id)s'.")
class VnfResourceNotFound(NotFound):
message = _("No vnf resource with id %(id)s.")
@ -235,6 +252,20 @@ class VnfInstantiatedInfoNotFound(NotFound):
message = _("No vnf instantiated info for vnf id %(vnf_instance_id)s.")
class VnfInstantiationFailed(TackerException):
message = _("Vnf instantiation failed for vnf %(id)s, error: %(error)s")
class VnfInstantiationWaitFailed(TackerException):
message = _("Vnf instantiation wait failed for vnf %(id)s, "
"error: %(error)s")
class VnfPreInstantiationFailed(TackerException):
message = _("Vnf '%(id)s' failed during pre-instantiation due to error: "
"%(error)s")
class OrphanedObjectError(TackerException):
msg_fmt = _('Cannot call %(method)s on orphaned %(objtype)s object')

View File

@ -21,6 +21,7 @@
import functools
import inspect
import logging as std_logging
import math
import os
import random
import re
@ -36,6 +37,7 @@ from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import importutils
from six.moves import urllib
from stevedore import driver
try:
from eventlet import sleep
@ -392,6 +394,14 @@ def convert_snakecase_to_camelcase(request_data):
return request_data
def is_url(url):
try:
urllib.request.urlopen(url)
return True
except Exception:
return False
class CooperativeReader(object):
"""An eventlet thread friendly class for reading in image data.
@ -516,3 +526,71 @@ class LimitingReader(object):
if self.bytes_read > self.limit:
raise self.exception_class()
return result
class MemoryUnit(object):
UNIT_SIZE_DEFAULT = 'B'
UNIT_SIZE_DICT = {'B': 1, 'kB': 1000, 'KiB': 1024, 'MB': 1000000,
'MiB': 1048576, 'GB': 1000000000,
'GiB': 1073741824, 'TB': 1000000000000,
'TiB': 1099511627776}
@staticmethod
def convert_unit_size_to_num(size, unit=None):
"""Convert given size to a number representing given unit.
If unit is None, convert to a number representing UNIT_SIZE_DEFAULT
:param size: unit size e.g. 1 TB
:param unit: unit to be converted to e.g GB
:return: converted number e.g. 1000 for 1 TB size and unit GB
"""
if unit:
unit = MemoryUnit.validate_unit(unit)
else:
unit = MemoryUnit.UNIT_SIZE_DEFAULT
LOG.info(_('A memory unit is not provided for size; using the '
'default unit %(default)s.') % {'default': 'B'})
regex = re.compile('(\d*)\s*(\w*)')
result = regex.match(str(size)).groups()
if result[1]:
unit_size = MemoryUnit.validate_unit(result[1])
converted = int(str_to_num(result[0]) *
MemoryUnit.UNIT_SIZE_DICT[unit_size] *
math.pow(MemoryUnit.UNIT_SIZE_DICT
[unit], -1))
LOG.info(_('Given size %(size)s is converted to %(num)s '
'%(unit)s.') % {'size': size,
'num': converted, 'unit': unit})
else:
converted = (str_to_num(result[0]))
return converted
@staticmethod
def validate_unit(unit):
if unit in MemoryUnit.UNIT_SIZE_DICT.keys():
return unit
else:
for key in MemoryUnit.UNIT_SIZE_DICT.keys():
if key.upper() == unit.upper():
return key
msg = _('Provided unit "{0}" is not valid. The valid units are '
'{1}').format(unit, MemoryUnit.UNIT_SIZE_DICT.keys())
LOG.error(msg)
raise ValueError(msg)
def str_to_num(value):
"""Convert a string representation of a number into a numeric type."""
if (isinstance(value, int) or
isinstance(value, float)):
return value
try:
return int(value)
except ValueError:
try:
return float(value)
except ValueError:
return None

View File

@ -22,6 +22,7 @@ import shutil
import sys
from glance_store import exceptions as store_exceptions
from oslo_config import cfg
from oslo_log import log as logging
import oslo_messaging
from oslo_service import periodic_task
@ -37,7 +38,6 @@ from tacker.common import exceptions
from tacker.common import safe_utils
from tacker.common import topics
from tacker.common import utils
import tacker.conf
from tacker import context as t_context
from tacker.db.common_services import common_services_db
from tacker.db.nfvo import nfvo_db
@ -50,9 +50,41 @@ from tacker.objects.vnf_package import VnfPackagesList
from tacker.plugins.common import constants
from tacker import service as tacker_service
from tacker import version
from tacker.vnflcm import vnflcm_driver
from tacker.vnfm import plugin
CONF = cfg.CONF
# NOTE(tpatil): keystone_authtoken opts registered explicitly as conductor
# service doesn't use the keystonemiddleware.authtoken middleware as it's
# used by the tacker.service in the api-paste.ini
OPTS = [cfg.StrOpt('user_domain_id',
default='default',
help='User Domain Id'),
cfg.StrOpt('project_domain_id',
default='default',
help='Project Domain Id'),
cfg.StrOpt('password',
default='default',
help='User Password'),
cfg.StrOpt('username',
default='default',
help='User Name'),
cfg.StrOpt('user_domain_name',
default='default',
help='Use Domain Name'),
cfg.StrOpt('project_name',
default='default',
help='Project Name'),
cfg.StrOpt('project_domain_name',
default='default',
help='Project Domain Name'),
cfg.StrOpt('auth_url',
default='http://localhost/identity/v3',
help='Keystone endpoint')]
cfg.CONF.register_opts(OPTS, 'keystone_authtoken')
CONF = tacker.conf.CONF
LOG = logging.getLogger(__name__)
@ -109,6 +141,8 @@ class Conductor(manager.Manager):
else:
self.conf = CONF
super(Conductor, self).__init__(host=self.conf.host)
self.vnfm_plugin = plugin.VNFMPlugin()
self.vnflcm_driver = vnflcm_driver.VnfLcmDriver()
def init_host(self):
glance_store.initialize_glance_store()
@ -361,6 +395,35 @@ class Conductor(manager.Manager):
{'zip': csar_path, 'folder': csar_zip_temp_path,
'uuid': vnf_pack.id})
def instantiate(self, context, vnf_instance, instantiate_vnf):
self.vnflcm_driver.instantiate_vnf(context, vnf_instance,
instantiate_vnf)
vnf_package_vnfd = objects.VnfPackageVnfd.get_by_id(context,
vnf_instance.vnfd_id)
vnf_package = objects.VnfPackage.get_by_id(context,
vnf_package_vnfd.package_uuid, expected_attrs=['vnfd'])
try:
self._update_package_usage_state(context, vnf_package)
except Exception:
LOG.error("Failed to update usage_state of vnf package %s",
vnf_package.id)
def _update_package_usage_state(self, context, vnf_package):
"""Update vnf package usage state to IN_USE/NOT_IN_USE
If vnf package is not used by any of the vnf instances, it's usage
state should be set to NOT_IN_USE otherwise it should be set to
IN_USE.
"""
result = vnf_package.is_package_in_use(context)
if result:
vnf_package.usage_state = fields.PackageUsageStateType.IN_USE
else:
vnf_package.usage_state = fields.PackageUsageStateType.NOT_IN_USE
vnf_package.save()
def init(args, **kwargs):
CONF(args=args, project='tacker',

View File

@ -0,0 +1,40 @@
# 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.
import oslo_messaging
from tacker.common import rpc
from tacker.common import topics
from tacker.objects import base as objects_base
class VNFLcmRPCAPI(object):
target = oslo_messaging.Target(
exchange='tacker',
topic=topics.TOPIC_CONDUCTOR,
fanout=False,
version='1.0')
def instantiate(self, context, vnf_instance, instantiate_vnf, cast=True):
serializer = objects_base.TackerObjectSerializer()
client = rpc.get_client(self.target, version_cap=None,
serializer=serializer)
cctxt = client.prepare()
rpc_method = cctxt.cast if cast else cctxt.call
return rpc_method(context, 'instantiate',
vnf_instance=vnf_instance,
instantiate_vnf=instantiate_vnf)

View File

@ -19,8 +19,8 @@ from oslo_config import cfg
from tacker.conf import conductor
from tacker.conf import vnf_package
CONF = cfg.CONF
CONF.import_group('keystone_authtoken', 'keystonemiddleware.auth_token')
vnf_package.register_opts(CONF)
conductor.register_opts(CONF)

View File

@ -0,0 +1,31 @@
# 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 oslo_log import log as logging
from tacker._i18n import _
from tacker.common import exceptions
LOG = logging.getLogger(__name__)
class GlanceClientException(exceptions.TackerException):
message = _("%(msg)s")
class ImageCreateWaitFailed(exceptions.TackerException):
message = _('Create image failed %(reason)s')

View File

@ -199,6 +199,11 @@ class InvalidKubernetesInputParameter(exceptions.InvalidInput):
message = _("Found unsupported keys for %(found_keys)s ")
class InvalidInstReqInfoForScaling(exceptions.InvalidInput):
message = _("Scaling resource cannot be set to "
"fixed ip_address or mac_address.")
def _validate_service_type_list(data, valid_values=None):
if not isinstance(data, list):
msg = _("Invalid data format for service list: '%s'") % data

View File

@ -32,4 +32,5 @@ def register_all():
__import__('tacker.objects.vnf_instance')
__import__('tacker.objects.vnf_instantiated_info')
__import__('tacker.objects.vim_connection')
__import__('tacker.objects.instantiate_vnf_req')
__import__('tacker.objects.vnf_resources')

View File

@ -0,0 +1,368 @@
# 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 oslo_log import log as logging
from tacker import objects
from tacker.objects import base
from tacker.objects import fields
LOG = logging.getLogger(__name__)
@base.TackerObjectRegistry.register
class InstantiateVnfRequest(base.TackerObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'flavour_id': fields.StringField(nullable=False),
'instantiation_level_id': fields.StringField(nullable=True,
default=None),
'ext_managed_virtual_links': fields.ListOfObjectsField(
'ExtManagedVirtualLinkData', nullable=True, default=[]),
'vim_connection_info': fields.ListOfObjectsField(
'VimConnectionInfo', nullable=True, default=[]),
'ext_virtual_links': fields.ListOfObjectsField(
'ExtVirtualLinkData', nullable=True, default=[]),
'additional_params': fields.DictOfStringsField(nullable=True,
default={}),
}
@classmethod
def obj_from_primitive(cls, primitive, context):
if 'tacker_object.name' in primitive:
obj_instantiate_vnf_req = super(
InstantiateVnfRequest, cls).obj_from_primitive(
primitive, context)
else:
if 'ext_managed_virtual_links' in primitive.keys():
obj_data = [ExtManagedVirtualLinkData._from_dict(
ext_manage) for ext_manage in primitive.get(
'ext_managed_virtual_links', [])]
primitive.update({'ext_managed_virtual_links': obj_data})
if 'vim_connection_info' in primitive.keys():
obj_data = [objects.VimConnectionInfo._from_dict(
vim_conn) for vim_conn in primitive.get(
'vim_connection_info', [])]
primitive.update({'vim_connection_info': obj_data})
if 'ext_virtual_links' in primitive.keys():
obj_data = [ExtVirtualLinkData.obj_from_primitive(
ext_vir_link, context) for ext_vir_link in primitive.get(
'ext_virtual_links', [])]
primitive.update({'ext_virtual_links': obj_data})
obj_instantiate_vnf_req = InstantiateVnfRequest._from_dict(
primitive)
return obj_instantiate_vnf_req
@classmethod
def _from_dict(cls, data_dict):
flavour_id = data_dict.get('flavour_id')
instantiation_level_id = data_dict.get('instantiation_level_id')
ext_managed_virtual_links = data_dict.get('ext_managed_virtual_links',
[])
vim_connection_info = data_dict.get('vim_connection_info', [])
ext_virtual_links = data_dict.get('ext_virtual_links', [])
additional_params = data_dict.get('additional_params', {})
return cls(flavour_id=flavour_id,
instantiation_level_id=instantiation_level_id,
ext_managed_virtual_links=ext_managed_virtual_links,
vim_connection_info=vim_connection_info,
ext_virtual_links=ext_virtual_links,
additional_params=additional_params)
@base.TackerObjectRegistry.register
class ExtManagedVirtualLinkData(base.TackerObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'id': fields.StringField(nullable=False),
'vnf_virtual_link_desc_id': fields.StringField(nullable=False),
'resource_id': fields.StringField(nullable=False),
}
@classmethod
def _from_dict(cls, data_dict):
id = data_dict.get('id')
vnf_virtual_link_desc_id = data_dict.get(
'vnf_virtual_link_desc_id')
resource_id = data_dict.get('resource_id')
obj = cls(id=id, vnf_virtual_link_desc_id=vnf_virtual_link_desc_id,
resource_id=resource_id)
return obj
@base.TackerObjectRegistry.register
class ExtVirtualLinkData(base.TackerObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'id': fields.StringField(nullable=False),
'resource_id': fields.StringField(nullable=False),
'ext_cps': fields.ListOfObjectsField(
'VnfExtCpData', nullable=True, default=[]),
'ext_link_ports': fields.ListOfObjectsField(
'ExtLinkPortData', nullable=True, default=[]),
}
@classmethod
def obj_from_primitive(cls, primitive, context):
if 'tacker_object.name' in primitive:
obj_ext_virt_link = super(
ExtVirtualLinkData, cls).obj_from_primitive(
primitive, context)
else:
if 'ext_cps' in primitive.keys():
obj_data = [VnfExtCpData.obj_from_primitive(
ext_cp, context) for ext_cp in primitive.get(
'ext_cps', [])]
primitive.update({'ext_cps': obj_data})
if 'ext_link_ports' in primitive.keys():
obj_data = [ExtLinkPortData.obj_from_primitive(
ext_link_port_data, context)
for ext_link_port_data in primitive.get(
'ext_link_ports', [])]
primitive.update({'ext_link_ports': obj_data})
obj_ext_virt_link = ExtVirtualLinkData._from_dict(primitive)
return obj_ext_virt_link
@classmethod
def _from_dict(cls, data_dict):
id = data_dict.get('id')
resource_id = data_dict.get('resource_id')
ext_cps = data_dict.get('ext_cps', [])
ext_link_ports = data_dict.get('ext_link_ports', [])
obj = cls(id=id, resource_id=resource_id, ext_cps=ext_cps,
ext_link_ports=ext_link_ports)
return obj
@base.TackerObjectRegistry.register
class VnfExtCpData(base.TackerObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'cpd_id': fields.StringField(nullable=False),
'cp_config': fields.ListOfObjectsField(
'VnfExtCpConfig', nullable=True, default=[]),
}
@classmethod
def obj_from_primitive(cls, primitive, context):
if 'tacker_object.name' in primitive:
obj_vnf_ext_cp_data = super(VnfExtCpData, cls).obj_from_primitive(
primitive, context)
else:
if 'cp_config' in primitive.keys():
obj_data = [VnfExtCpConfig.obj_from_primitive(
vnf_ext_cp_conf, context)
for vnf_ext_cp_conf in primitive.get('cp_config', [])]
primitive.update({'cp_config': obj_data})
obj_vnf_ext_cp_data = VnfExtCpData._from_dict(primitive)
return obj_vnf_ext_cp_data
@classmethod
def _from_dict(cls, data_dict):
cpd_id = data_dict.get('cpd_id')
cp_config = data_dict.get('cp_config', [])
obj = cls(cpd_id=cpd_id, cp_config=cp_config)
return obj
@base.TackerObjectRegistry.register
class VnfExtCpConfig(base.TackerObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'cp_instance_id': fields.StringField(nullable=True, default=None),
'link_port_id': fields.StringField(nullable=True, default=None),
'cp_protocol_data': fields.ListOfObjectsField(
'CpProtocolData', nullable=True, default=[]),
}
@classmethod
def obj_from_primitive(cls, primitive, context):
if 'tacker_object.name' in primitive:
obj_ext_cp_config = super(VnfExtCpConfig, cls).obj_from_primitive(
primitive, context)
else:
if 'cp_protocol_data' in primitive.keys():
obj_data = [CpProtocolData.obj_from_primitive(
cp_protocol, context) for cp_protocol in primitive.get(
'cp_protocol_data', [])]
primitive.update({'cp_protocol_data': obj_data})
obj_ext_cp_config = VnfExtCpConfig._from_dict(primitive)
return obj_ext_cp_config
@classmethod
def _from_dict(cls, data_dict):
cp_instance_id = data_dict.get('cp_instance_id')
link_port_id = data_dict.get('link_port_id')
cp_protocol_data = data_dict.get('cp_protocol_data', [])
obj = cls(cp_instance_id=cp_instance_id,
link_port_id=link_port_id, cp_protocol_data=cp_protocol_data)
return obj
@base.TackerObjectRegistry.register
class CpProtocolData(base.TackerObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'layer_protocol': fields.StringField(nullable=False),
'ip_over_ethernet': fields.ObjectField(
'IpOverEthernetAddressData', nullable=True, default=None),
}
@classmethod
def obj_from_primitive(cls, primitive, context):
if 'tacker_object.name' in primitive:
obj_cp_protocal = super(CpProtocolData, cls).obj_from_primitive(
primitive, context)
else:
if 'ip_over_ethernet' in primitive.keys():
obj_data = IpOverEthernetAddressData.obj_from_primitive(
primitive.get('ip_over_ethernet', {}), context)
primitive.update({'ip_over_ethernet': obj_data})
obj_cp_protocal = CpProtocolData._from_dict(primitive)
return obj_cp_protocal
@classmethod
def _from_dict(cls, data_dict):
layer_protocol = data_dict.get('layer_protocol')
ip_over_ethernet = data_dict.get('ip_over_ethernet')
obj = cls(layer_protocol=layer_protocol,
ip_over_ethernet=ip_over_ethernet)
return obj
@base.TackerObjectRegistry.register
class IpOverEthernetAddressData(base.TackerObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'mac_address': fields.StringField(nullable=True, default=None),
'ip_addresses': fields.ListOfObjectsField('IpAddress', nullable=True,
default=[]),
}
@classmethod
def obj_from_primitive(cls, primitive, context):
if 'tacker_object.name' in primitive:
ip_over_ethernet = super(
IpOverEthernetAddressData, cls).obj_from_primitive(
primitive, context)
else:
if 'ip_addresses' in primitive.keys():
obj_data = [IpAddress._from_dict(
ip_address) for ip_address in primitive.get(
'ip_addresses', [])]
primitive.update({'ip_addresses': obj_data})
ip_over_ethernet = IpOverEthernetAddressData._from_dict(primitive)
return ip_over_ethernet
@classmethod
def _from_dict(cls, data_dict):
mac_address = data_dict.get('mac_address')
ip_addresses = data_dict.get('ip_addresses', [])
obj = cls(mac_address=mac_address, ip_addresses=ip_addresses)
return obj
@base.TackerObjectRegistry.register
class IpAddress(base.TackerObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'type': fields.IpAddressTypeField(nullable=False),
'subnet_id': fields.StringField(nullable=True, default=None),
'fixed_addresses': fields.ListOfStringsField(nullable=True,
default=[])
}
@classmethod
def _from_dict(cls, data_dict):
type = data_dict.get('type')
subnet_id = data_dict.get('subnet_id')
fixed_addresses = data_dict.get('fixed_addresses', [])
obj = cls(type=type, subnet_id=subnet_id,
fixed_addresses=fixed_addresses)
return obj
@base.TackerObjectRegistry.register
class ExtLinkPortData(base.TackerObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'id': fields.UUIDField(nullable=False),
'resource_handle': fields.ObjectField(
'ResourceHandle', nullable=False),
}
@classmethod
def obj_from_primitive(cls, primitive, context):
if 'tacker_object.name' in primitive:
obj_link_port_data = super(
ExtLinkPortData, cls).obj_from_primitive(primitive, context)
else:
if 'resource_handle' in primitive.keys():
obj_data = objects.ResourceHandle._from_dict(primitive.get(
'resource_handle', []))
primitive.update({'resource_handle': obj_data})
obj_link_port_data = ExtLinkPortData._from_dict(primitive)
return obj_link_port_data
@classmethod
def _from_dict(cls, data_dict):
id = data_dict.get('id')
resource_handle = data_dict.get('resource_handle')
obj = cls(id=id, resource_handle=resource_handle)
return obj

View File

@ -19,6 +19,7 @@ from oslo_utils import timeutils
from oslo_utils import uuidutils
from oslo_versionedobjects import base as ovoo_base
from sqlalchemy.orm import joinedload
from sqlalchemy.sql import func
from tacker._i18n import _
from tacker.common import exceptions
@ -238,7 +239,8 @@ def _make_vnf_packages_list(context, vnf_package_list, db_vnf_package_list,
@base.TackerObjectRegistry.register
class VnfPackage(base.TackerObject, base.TackerPersistentObject):
class VnfPackage(base.TackerObject, base.TackerPersistentObject,
base.TackerObjectDictCompat):
# Version 1.0: Initial version
VERSION = '1.0'
@ -428,6 +430,23 @@ class VnfPackage(base.TackerObject, base.TackerPersistentObject):
self.id, updates)
self._from_db_object(self._context, self, db_vnf_package)
@base.remotable
def is_package_in_use(self, context):
if self.onboarding_state == \
fields.PackageOnboardingStateType.ONBOARDED:
# check if vnf package is used by any vnf instances.
query = context.session.query(
func.count(models.VnfInstance.id)).\
filter_by(
instantiation_state=fields.VnfInstanceState.INSTANTIATED).\
filter_by(tenant_id=self.tenant_id).\
filter_by(vnfd_id=self.vnfd.vnfd_id).\
filter_by(deleted=False)
result = query.scalar()
return True if result > 0 else False
else:
return False
@base.TackerObjectRegistry.register
class VnfPackagesList(ovoo_base.ObjectListBase, base.TackerObject):

View File

@ -33,6 +33,17 @@ rules = [
}
]
),
policy.DocumentedRuleDefault(
name=VNFLCM % 'instantiate',
check_str=base.RULE_ADMIN_OR_OWNER,
description="Instantiate vnf instance.",
operations=[
{
'method': 'POST',
'path': '/vnflcm/v1/vnf_instances/{vnfInstanceId}/instantiate'
}
]
)
]

View File

@ -56,11 +56,17 @@ class TestCase(base.BaseTestCase):
class FixturedTestCase(TestCase):
client_fixture_class = None
sdk_connection_fixure_class = None
def setUp(self):
super(FixturedTestCase, self).setUp()
if self.client_fixture_class:
if self.client_fixture_class or self.sdk_connection_fixure_class:
self.requests_mock = self.useFixture(requests_mock_fixture.
Fixture())
fix = self.client_fixture_class(self.requests_mock)
self.cs = self.useFixture(fix).client
if self.client_fixture_class:
hc_fix = self.client_fixture_class(self.requests_mock)
self.cs = self.useFixture(hc_fix).client
if self.sdk_connection_fixure_class:
sdk_conn_fix = self.sdk_connection_fixure_class(self.requests_mock)
self.sdk_conn = self.useFixture(sdk_conn_fix).client

View File

@ -32,19 +32,46 @@ from tacker import objects
from tacker.objects import vnf_package
from tacker.tests.unit.conductor import fakes
from tacker.tests.unit.db.base import SqlTestCase
from tacker.tests.unit.objects import fakes as fake_obj
from tacker.tests.unit.vnflcm import fakes as vnflcm_fakes
from tacker.tests import uuidsentinel
CONF = tacker.conf.CONF
class FakeVnfLcmDriver(mock.Mock):
pass
class FakeVNFMPlugin(mock.Mock):
pass
class TestConductor(SqlTestCase):
def setUp(self):
super(TestConductor, self).setUp()
self.addCleanup(mock.patch.stopall)
self.context = context.get_admin_context()
self._mock_vnflcm_driver()
self._mock_vnfm_plugin()
self.conductor = conductor_server.Conductor('host')
self.vnf_package = self._create_vnf_package()
def _mock_vnfm_plugin(self):
self.vnfm_plugin = mock.Mock(wraps=FakeVNFMPlugin())
fake_vnfm_plugin = mock.Mock()
fake_vnfm_plugin.return_value = self.vnfm_plugin
self._mock(
'tacker.vnfm.plugin.VNFMPlugin', fake_vnfm_plugin)
def _mock_vnflcm_driver(self):
self.vnflcm_driver = mock.Mock(wraps=FakeVnfLcmDriver())
fake_vnflcm_driver = mock.Mock()
fake_vnflcm_driver.return_value = self.vnflcm_driver
self._mock(
'tacker.vnflcm.vnflcm_driver.VnfLcmDriver', fake_vnflcm_driver)
def _create_vnf_package(self):
vnfpkgm = vnf_package.VnfPackage(context=self.context,
**fakes.VNF_PACKAGE_DATA)
@ -153,6 +180,77 @@ class TestConductor(SqlTestCase):
self.vnf_package)
shutil.rmtree(fake_csar)
def _create_and_upload_vnf_package(self):
vnf_package = objects.VnfPackage(context=self.context,
**fake_obj.vnf_package_data)
vnf_package.create()
vnf_pack_vnfd = fake_obj.get_vnf_package_vnfd_data(
vnf_package.id, uuidsentinel.vnfd_id)
vnf_pack_vnfd_obj = objects.VnfPackageVnfd(
context=self.context, **vnf_pack_vnfd)
vnf_pack_vnfd_obj.create()
vnf_package.onboarding_state = "ONBOARDED"
vnf_package.save()
return vnf_pack_vnfd_obj
@mock.patch.object(objects.VnfPackage, 'is_package_in_use')
def test_instantiate_vnf_instance(self, mock_package_in_use):
vnf_package_vnfd = self._create_and_upload_vnf_package()
vnf_instance_data = fake_obj.get_vnf_instance_data(
vnf_package_vnfd.vnfd_id)
mock_package_in_use.return_value = False
vnf_instance = objects.VnfInstance(context=self.context,
**vnf_instance_data)
vnf_instance.create()
instantiate_vnf_req = vnflcm_fakes.get_instantiate_vnf_request_obj()
self.conductor.instantiate(self.context, vnf_instance,
instantiate_vnf_req)
self.vnflcm_driver.instantiate_vnf.assert_called_once_with(
self.context, vnf_instance, instantiate_vnf_req)
mock_package_in_use.assert_called_once()
@mock.patch.object(objects.VnfPackage, 'is_package_in_use')
def test_instantiate_vnf_instance_with_vnf_package_in_use(self,
mock_vnf_package_in_use):
vnf_package_vnfd = self._create_and_upload_vnf_package()
vnf_instance_data = fake_obj.get_vnf_instance_data(
vnf_package_vnfd.vnfd_id)
mock_vnf_package_in_use.return_value = True
vnf_instance = objects.VnfInstance(context=self.context,
**vnf_instance_data)
vnf_instance.create()
instantiate_vnf_req = vnflcm_fakes.get_instantiate_vnf_request_obj()
self.conductor.instantiate(self.context, vnf_instance,
instantiate_vnf_req)
self.vnflcm_driver.instantiate_vnf.assert_called_once_with(
self.context, vnf_instance, instantiate_vnf_req)
mock_vnf_package_in_use.assert_called_once()
@mock.patch.object(objects.VnfPackage, 'is_package_in_use')
@mock.patch('tacker.conductor.conductor_server.LOG')
def test_instantiate_vnf_instance_failed_with_exception(
self, mock_log, mock_is_package_in_use):
vnf_package_vnfd = self._create_and_upload_vnf_package()
vnf_instance_data = fake_obj.get_vnf_instance_data(
vnf_package_vnfd.vnfd_id)
vnf_instance = objects.VnfInstance(context=self.context,
**vnf_instance_data)
vnf_instance.create()
instantiate_vnf_req = vnflcm_fakes.get_instantiate_vnf_request_obj()
mock_is_package_in_use.side_effect = Exception
self.conductor.instantiate(self.context, vnf_instance,
instantiate_vnf_req)
self.vnflcm_driver.instantiate_vnf.assert_called_once_with(
self.context, vnf_instance, instantiate_vnf_req)
mock_is_package_in_use.assert_called_once()
expected_log = 'Failed to update usage_state of vnf package %s'
mock_log.error.assert_called_once_with(expected_log,
vnf_package_vnfd.package_uuid)
@mock.patch.object(os, 'remove')
@mock.patch.object(shutil, 'rmtree')
@mock.patch.object(os.path, 'exists')

View File

@ -446,3 +446,28 @@ def get_dummy_ns_obj_2():
'attributes': {
'param_values': {'nsd': {'vl1_name': 'net_mgmt',
'vl2_name': 'net0'}}}}}
def get_dummy_vnf_instance():
connection_info = get_dummy_vim_connection_info()
return {'created_at': '', 'deleted': False, 'deleted_at': None,
'id': 'fake_id', 'instantiated_vnf_info': None,
'instantiation_state': 'NOT_INSTANTIATED',
'tenant_id': 'fake_tenant_id', 'updated_at': '',
'vim_connection_info': [connection_info],
'vnf_instance_description': 'VNF Description',
'vnf_instance_name': 'test', 'vnf_product_name': 'Sample VNF',
'vnf_provider': 'Company', 'vnf_software_version': '1.0',
'vnfd_id': 'fake_vnfd_id', 'vnfd_version': '1.0'}
def get_dummy_vim_connection_info():
return {'access_info': {
'auth_url': 'fake/url',
'cert_verify': 'False', 'password': 'admin',
'project_domain_name': 'Default',
'project_id': None, 'project_name': 'admin',
'user_domain_name': 'Default', 'username': 'admin'},
'created_at': '', 'deleted': False, 'deleted_at': '',
'id': 'fake_id', 'updated_at': '',
'vim_id': 'fake_vim_id', 'vim_type': 'openstack'}

View File

@ -220,8 +220,19 @@ ext_managed_virtual_link_info = {
'vnf_link_ports': [vnf_link_ports],
}
vnfc_resource_info = {
'id': uuidsentinel.resource_info_id,
'vdu_id': 'vdu1',
'compute_resource': None,
'storage_resource_ids': [uuidsentinel.id1, uuidsentinel.id2],
'reservation_id': uuidsentinel.reservation_id,
'vnfc_cp_info': None,
'metadata': {'key': 'value'}
}
vnfc_cp_info = {
'id': uuidsentinel.cp_info,
'id': uuidsentinel.cp_instance_id,
'cpd_id': uuidsentinel.cpd_id,
'vnf_ext_cp_id': uuidsentinel.vnf_ext_cp_id,
'cp_protocol_info': [cp_protocol_info],

View File

@ -22,7 +22,12 @@ import webob
from tacker.api.vnflcm.v1.router import VnflcmAPIRouter
from tacker import context
from tacker.db.db_sqlalchemy import models
from tacker import objects
from tacker.objects import fields
from tacker.objects.instantiate_vnf_req import ExtManagedVirtualLinkData
from tacker.objects.instantiate_vnf_req import ExtVirtualLinkData
from tacker.objects.instantiate_vnf_req import InstantiateVnfRequest
from tacker.objects.vim_connection import VimConnectionInfo
from tacker.tests import constants
from tacker.tests import uuidsentinel
from tacker import wsgi
@ -94,7 +99,47 @@ def return_vnf_instance_model(
return model_obj
def fake_vnf_instance_response(**updates):
def return_vnf_instance(
instantiated_state=fields.VnfInstanceState.NOT_INSTANTIATED,
**updates):
if instantiated_state == fields.VnfInstanceState.NOT_INSTANTIATED:
data = _model_non_instantiated_vnf_instance(**updates)
data['instantiation_state'] = instantiated_state
vnf_instance_obj = objects.VnfInstance(**data)
else:
data = _model_non_instantiated_vnf_instance(**updates)
data['instantiation_state'] = instantiated_state
vnf_instance_obj = objects.VnfInstance(**data)
inst_vnf_info = objects.InstantiatedVnfInfo.obj_from_primitive({
"ext_cp_info": [],
'ext_virtual_link_info': [],
'ext_managed_virtual_link_info': [],
'vnfc_resource_info': [],
'vnf_virtual_link_resource_info': [],
'virtual_storage_resource_info': [],
"flavour_id": "simple",
"additional_params": {"key": "value"},
'vnf_state': "STARTED"}, None)
vnf_instance_obj.instantiated_vnf_info = inst_vnf_info
return vnf_instance_obj
def _instantiated_vnf_links(vnf_instance_id):
links = {
"self": {"href": "/vnflcm/v1/vnf_instances/%s" % vnf_instance_id},
"terminate": {"href": "/vnflcm/v1/vnf_instances/%s/terminate" %
vnf_instance_id},
"heal": {"href": "/vnflcm/v1/vnf_instances/%s/heal" %
vnf_instance_id}}
return links
def _fake_vnf_instance_not_instantiated_response(
**updates):
vnf_instance = {
'vnfInstanceDescription': 'Vnf instance description',
'vnfInstanceName': 'Vnf instance name',
@ -121,6 +166,530 @@ def fake_vnf_instance_response(**updates):
return vnf_instance
def fake_vnf_instance_response(
instantiated_state=fields.VnfInstanceState.NOT_INSTANTIATED,
**updates):
if instantiated_state == fields.VnfInstanceState.NOT_INSTANTIATED:
data = _fake_vnf_instance_not_instantiated_response(**updates)
else:
data = _fake_vnf_instance_not_instantiated_response(**updates)
data['_links'] = _instantiated_vnf_links(uuidsentinel.vnf_instance_id)
data['instantiationState'] = instantiated_state
data['vimConnectionInfo'] = []
def _instantiated_vnf_info():
inst_vnf_info = {}
inst_vnf_info['extCpInfo'] = []
inst_vnf_info['flavourId'] = 'simple'
inst_vnf_info['vnfState'] = 'STARTED'
inst_vnf_info['additionalParams'] = {"key": "value"}
return inst_vnf_info
data['instantiatedVnfInfo'] = _instantiated_vnf_info()
return data
def fake_vnf_package(**updates):
vnf_package = {
'algorithm': None,
'deleted': False,
'deleted_at': None,
'updated_at': None,
'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',
'tenant_id': uuidsentinel.tenant_id,
'usage_state': 'NOT_IN_USE',
'user_data': {'abc': 'xyz'},
'id': constants.UUID,
}
if updates:
vnf_package.update(updates)
return vnf_package
def fake_vnf_package_deployment_flavour(**updates):
vnf_package_deployment_data = {
'flavour_description': 'flavour_description',
'instantiation_levels': ('{"levels": {'
'"instantiation_level_1":'
'{"description": "Smallest size",'
' "scale_info": {"worker_instance":'
'{"scale_level": 0}}}},'
' "default_level": '
'"instantiation_level_1"}'),
'package_uuid': constants.UUID,
'flavour_id': 'simple',
}
if updates:
vnf_package_deployment_data.update(updates)
return vnf_package_deployment_data
def fake_vnf_package_software_image(**updates):
vnf_package_software_image_data = {
'id': constants.UUID,
'name': 'name',
'provider': 'provider',
'version': 'version',
'algorithm': 'algorithm',
'hash': 'hash',
'container_format': 'container_format',
'disk_format': 'disk_format',
'min_disk': 2,
'min_ram': 10,
'size': 5,
'image_path': 'image/path',
'flavour_uuid': constants.UUID,
'software_image_id': constants.UUID,
'created_at': datetime.datetime(1900, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC)
}
if updates:
vnf_package_software_image_data.update(updates)
return vnf_package_software_image_data
def return_vnf_deployment_flavour():
model_obj = models.VnfDeploymentFlavour()
model_obj.update(fake_vnf_package_deployment_flavour())
return model_obj
def return_vnf_software_image():
model_obj = models.VnfSoftwareImage()
model_obj.update(fake_vnf_package_software_image())
return model_obj
def return_vnf_package():
model_obj = models.VnfPackage()
model_obj.update(fake_vnf_package())
return model_obj
def return_vnf_package_with_deployment_flavour():
vnf_package = objects.VnfPackage._from_db_object(
context, objects.VnfPackage(), return_vnf_package(),
expected_attrs=None)
vnf_package_deployment_flavour = \
objects.VnfDeploymentFlavour._from_db_object(
context, objects.VnfDeploymentFlavour(),
return_vnf_deployment_flavour(), expected_attrs=None)
vnf_software_image = objects.VnfSoftwareImage._from_db_object(
context, objects.VnfSoftwareImage(), return_vnf_software_image(),
expected_attrs=None)
vnf_software_image_list = objects.VnfSoftwareImagesList()
vnf_software_image_list.objects = [vnf_software_image]
vnf_package_deployment_flavour.software_images = vnf_software_image_list
vnf_package_deployment_flavour_list = objects.VnfDeploymentFlavoursList()
vnf_package_deployment_flavour_list.objects = \
[vnf_package_deployment_flavour]
vnf_package.vnf_deployment_flavours = vnf_package_deployment_flavour_list
return vnf_package
def get_vnf_instantiation_request_body():
instantiation_req_body = {
"flavourId": "simple",
"instantiationLevelId": "instantiation_level_1",
"additionalParams": {"key1": 'value1', "key2": 'value2'},
"extVirtualLinks": [{
"id": 'f8c35bd0-4d67-4436-9f11-14b8a84c92aa',
"resourceId": 'f8c35bd0-4d67-4436-9f11-14b8a84c92aa',
"extCps": [{
"cpdId": 'f8c35bd0-4d67-4436-9f11-14b8a84c92aa',
"cpConfig": [{
"cpInstanceId": 'f8c35bd0-4d67-4436-9f11-14b8a84c92aa',
"linkPortId": 'f8c35bd0-4d67-4436-9f11-14b8a84c92aa',
"cpProtocolData": [
{
"layerProtocol": 'IP_OVER_ETHERNET',
"ipOverEthernet": {
"macAddress":
'fa:16:3e:11:11:11',
"ipAddresses": [
{
"type": "IPV4",
"fixedAddresses": [
'192.168.11.01',
'192.168.21.202'
],
"subnetId":
'actual-subnet-id'
}
]
}
}
]
}
]
}],
"extLinkPorts": [
{
"id": 'f8c35bd0-4d67-4436-9f11-14b8a84c92aa',
"resourceHandle": {
"resourceId":
'f8c35bd0-4d67-4436-9f11-14b8a84c92aa',
"vimLevelResourceType":
'f8c35bd0-4d67-4436-9f11-14b8a84c92aa',
}
},
{
"id": 'f8c35bd0-4d67-4436-9f11-14b8a84c92aa',
"resourceHandle": {
"resourceId":
'f8c35bd0-4d67-4436-9f11-14b8a84c92aa',
"vimLevelResourceType":
'f8c35bd0-4d67-4436-9f11-14b8a84c92aa',
}
}],
}],
"extManagedVirtualLinks": [
{"id": 'f8c35bd0-4d67-4436-9f11-14b8a84c92aa',
"vnfVirtualLinkDescId": 'f8c35bd0-4d67-4436-9f11-14b8a84c92aa',
"resourceId": 'f8c35bd0-4d67-4436-9f11-14b8a84c92aa'},
{"id": 'f8c35bd0-4d67-4436-9f11-14b8a84c92aa',
"vnfVirtualLinkDescId": 'f8c35bd0-4d67-4436-9f11-14b8a84c92aa',
"resourceId": 'f8c35bd0-4d67-4436-9f11-14b8a84c92aa'}],
"vimConnectionInfo": [
{"id": 'f8c35bd0-4d67-4436-9f11-14b8a84c92aa',
"vimId": 'f8c35bd0-4d67-4436-9f11-14b8a84c92aa',
"vimType": 'openstack',
"accessInfo": {"key1": 'value1', "key2": 'value2'}}],
}
return instantiation_req_body
def get_instantiate_vnf_request_obj():
instantiate_vnf_req = InstantiateVnfRequest()
ext_managed_virtual_link_data = ExtManagedVirtualLinkData()
vim_connection_info = VimConnectionInfo()
ext_virtual_link_data = ExtVirtualLinkData()
instantiate_vnf_req.additional_params = None
instantiate_vnf_req.deleted = 0
instantiate_vnf_req.ext_managed_virtual_links = \
[ext_managed_virtual_link_data]
instantiate_vnf_req.ext_virtual_link_data = [ext_virtual_link_data]
instantiate_vnf_req.flavour_id = 'test'
instantiate_vnf_req.instantiation_level_id = 'instantiation_level_1'
instantiate_vnf_req.vim_connection_info = [vim_connection_info]
return instantiate_vnf_req
def create_types_yaml_file():
yaml_str = ("""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: 1111
descriptor_version:
type: string
constraints: [ valid_values: [ '1.0' ] ]
default: 'fake desc version'
provider:
type: string
constraints: [ valid_values: [ 'Company' ] ]
default: 'Company'
product_name:
type: string
constraints: [ valid_values: [ 'Sample VNF' ] ]
default: 'fake product name'
software_version:
type: string
constraints: [ valid_values: [ '1.0' ] ]
default: 'fake software version'
vnfm_info:
type: list
entry_schema:
type: string
constraints: [ valid_values: [ Tacker ] ]
default: [ Tacker ]
flavour_id:
type: string
constraints: [ valid_values: [ simple ] ]
default: fake id
flavour_description:
type: string
default: "fake flavour"
requirements:
- virtual_link_external:
capability: tosca.capabilities.nfv.VirtualLinkable
- virtual_link_internal:
capability: tosca.capabilities.nfv.VirtualLinkable
interfaces:
Vnflcm:
type: tosca.interfaces.nfv.Vnflcm""")
file_path = os.path.abspath(
os.path.join(os.path.dirname(__file__), 'types.yaml'))
yaml_file = open(file_path, "w+")
yaml_file.write(yaml_str)
yaml_file.close()
def delete_types_yaml_file():
file_path = os.path.abspath(
os.path.join(os.path.dirname(__file__), 'types.yaml'))
if os.path.exists(file_path):
os.remove(file_path)
def create_vnfd_dict_file():
vnfd_dict_str = str(get_vnfd_dict())
file_path = os.path.abspath(
os.path.join(os.path.dirname(__file__), 'vnfd_dict.yaml'))
vnfd_dict_file = open(file_path, "w+")
vnfd_dict_file.write(vnfd_dict_str)
vnfd_dict_file.close()
def delete_vnfd_dict_yaml_file():
file_path = os.path.abspath(
os.path.join(os.path.dirname(__file__), 'vnfd_dict.yaml'))
if os.path.exists(file_path):
os.remove(file_path)
def get_vnfd_dict(image_path=None):
if image_path is None:
image_path = 'fake/image/path'
vnfd_dict = {
'description': 'Simple deployment flavour for Sample VNF',
'imports': ['/opt/stack/tacker/tacker/tests/unit/vnflcm/types.yaml'],
'topology_template':
{'inputs': {'descriptor_id': {'type': 'string'},
'descriptor_version': {'type': 'string'},
'flavour_description': {'type': 'string'},
'flavour_id': {'type': 'string'},
'product_name': {'type': 'string'},
'provider': {'type': 'string'},
'software_version': {'type': 'string'},
'vnfm_info': {
'entry_schema': {'type': 'string'},
'type': 'list'}},
'node_templates': {
'CP3': {'properties': {
'layer_protocols': ['ipv4'], 'order': 2},
'requirements': [{'virtual_binding': 'VDU1'},
{'virtual_link': 'VL3'}],
'type': 'tosca.nodes.nfv.VduCp'},
'CP4': {'properties': {'layer_protocols': ['ipv4'],
'order': 3},
'requirements': [{'virtual_binding': 'VDU1'},
{'virtual_link': 'VL4'}],
'type': 'tosca.nodes.nfv.VduCp'},
'VDU1': {'artifacts': {
'sw_image': {
'file': image_path,
'type': 'tosca.artifacts.nfv.SwImage'}},
'capabilities': {
'virtual_compute': {'properties': {
'virtual_cpu': {'num_virtual_cpu': 1},
'virtual_local_storage': [
{'size_of_storage': '1 ''GiB'}],
'virtual_memory': {
'virtual_mem_size': '512 ''MiB'}}}},
'properties': {
'description': 'VDU1 compute node',
'name': 'VDU1',
'sw_image_data': {
'checksum': {
'algorithm': 'fake algo',
'hash': 'fake hash'},
'container_format':
'fake container format',
'disk_format': 'fake disk format',
'min_disk': '1''GiB',
'name': 'fake name',
'size': 'fake size ' 'GiB',
'version': 'fake version'},
'vdu_profile': {
'max_number_of_instances': 1,
'min_number_of_instances': 1}},
'type': 'tosca.nodes.nfv.Vdu.Compute'},
'VL3': {'properties': {
'connectivity_type': {'layer_protocols': []},
'description': 'Internal virtual link in VNF',
'vl_profile': {
'max_bitrate_requirements': {
'leaf': 1048576,
'root': 1048576
},
'min_bitrate_requirements': {
'leaf': 1048576,
'root': 1048576
},
'virtual_link_protocol_data': [
{'layer_protocol': 'ipv4',
'l3_protocol_data': {}
}]}},
'type': 'tosca.nodes.nfv.VnfVirtualLink'},
'VL4': {'properties': {'connectivity_type': {
'layer_protocols': ['ipv4']},
'description': 'Internal virtual link in VNF',
'vl_profile': {}},
'type': 'tosca.nodes.nfv.VnfVirtualLink'},
'VNF': {'interfaces': {'Vnflcm': {
'instantiate': [],
'instantiate_end': [],
'instantiate_start': [],
'modify_information': [],
'modify_information_end': [],
'modify_information_start': [], 'terminate': [],
'terminate_end': [], 'terminate_start': []}},
'properties': {
'flavour_description': 'A simple flavor'},
'type': 'company.provider.VNF'}},
'substitution_mappings': {
'node_type': 'company.provider.VNF',
'properties': {'flavour_id': 'simple'},
'requirements': {
'virtual_link_external': [
'CP1', 'virtual_link']}}},
'tosca_definitions_version': 'tosca_simple_yaml_1_2'}
return vnfd_dict
def get_dummy_vnf_instance():
connection_info = get_dummy_vim_connection_info()
return {'created_at': '', 'deleted': False, 'deleted_at': None,
'id': 'fake_id', 'instantiated_vnf_info': None,
'instantiation_state': 'NOT_INSTANTIATED',
'tenant_id': 'fake_tenant_id', 'updated_at': '',
'vim_connection_info': [connection_info],
'vnf_instance_description': 'VNF Description',
'vnf_instance_name': 'test', 'vnf_product_name': 'Sample VNF',
'vnf_provider': 'Company', 'vnf_software_version': '1.0',
'vnfd_id': 'fake_vnfd_id', 'vnfd_version': '1.0'}
def get_dummy_vim_connection_info():
return {'access_info': {
'auth_url': 'fake/url',
'cert_verify': 'False', 'password': 'admin',
'project_domain_name': 'Default',
'project_id': None, 'project_name': 'admin',
'user_domain_name': 'Default', 'username': 'admin'},
'created_at': '', 'deleted': False, 'deleted_at': '',
'id': 'fake_id', 'updated_at': '',
'vim_id': 'fake_vim_id', 'vim_type': 'openstack'}
def get_dummy_instantiate_vnf_request(**updates):
instantiate_vnf_request = {
'additional_params': None, 'created_at': '', 'deleted': '',
'deleted_at': '', 'flavour_id': 'simple',
'instantiation_level_id': 'instantiation_level_1',
'updated_at': '', 'vim_connection_info': []}
if updates:
instantiate_vnf_request['vim_connection_info'].append(updates)
return instantiate_vnf_request
def get_instantiate_vnf_request_with_ext_virtual_links(**updates):
instantiate_vnf_request = \
{"flavourId": "simple",
"instantiationLevelId": "instantiation_level_1",
"extVirtualLinks": [{
"id": "ext-vl-uuid-VL1",
"vimConnectionId": "8a3adb69-0784-43c7-833e-aab0b6ab4470",
"resourceId": "f671ea41-bb4a-4b86-b6bd-b058f68f0498",
"extCps": [{
"cpdId": "CP1",
"cpConfig": [{
"linkPortId": "ee2982f6-8d0d-4649-9357-e527bcb68ed1",
"cpProtocolData": [{
"layerProtocol": "IP_OVER_ETHERNET",
"ipOverEthernet": {
"ipAddresses": [{
"type": "IPV4",
"fixedAddresses": ["192.168.120.95"],
"subnetId": "f577b050-b80a-baed-96db88cd529b"
}]}}]}]}]},
{
"id": "ext-vl-uuid-VL1",
"vimConnectionId": "8a3adb69-0784-43c7-833e-aab0b6ab4470",
"resourceId": "f671ea41-bb4a-4b86-b6bd-b058f68f0498",
"extCps": [{
"cpdId": "CP2",
"cpConfig": [{
"cpProtocolData": [{
"layerProtocol": "IP_OVER_ETHERNET",
"ipOverEthernet": {
"ipAddresses": [{
"type": "IPV4",
"fixedAddresses": ["192.168.120.96"],
"subnetId": "f577b050-b80a-96db88cd529b"
}]}}]}]}]}],
"extManagedVirtualLinks": [{
"id": "extMngVLnk-uuid_VL3",
"vnfVirtualLinkDescId": "VL3",
"vimConnectionId": "8a3adb69-0784-43c7-833e-aab0b6ab4470",
"resourceId": "f671ea41-bb4a-4b86-b6bd-b058f68f0498"
}],
"vimConnectionInfo": []
}
if updates:
instantiate_vnf_request.update(updates)
return instantiate_vnf_request
def get_dummy_grant_response():
return {'VDU1': {'checksum': {'algorithm': 'fake algo',
'hash': 'fake hash'},
'container_format': 'fake container format',
'disk_format': 'fake disk format',
'image_path': ('/var/lib/tacker/vnfpackages/' +
uuidsentinel.instance_id +
'/Files/images/path'),
'min_disk': 1,
'min_ram': 0,
'name': 'fake name',
'version': 'fake version'}}
def return_vnf_resource():
version_obj = objects.VnfResource(
created_at=datetime.datetime(1900, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC),
deleted=False,
deleted_at=None,
id=uuidsentinel.vnf_resource_id,
resource_identifier=uuidsentinel.resource_identifier,
resource_name='test-image',
resource_status='CREATED',
resource_type='image',
updated_at=None,
vnf_instance_id=uuidsentinel.vnf_instance_id
)
return version_obj
class InjectContext(wsgi.Middleware):
"""Add a 'tacker.context' to WSGI environ."""

View File

@ -21,11 +21,16 @@ from webob import exc
from tacker.api.vnflcm.v1 import controller
from tacker.common import exceptions
from tacker.conductor.conductorrpc.vnf_lcm_rpc import VNFLcmRPCAPI
from tacker.extensions import nfvo
from tacker import objects
from tacker.objects import fields
from tacker.tests import constants
from tacker.tests.unit import base
from tacker.tests.unit import fake_request
from tacker.tests.unit.vnflcm import fakes
from tacker.tests import uuidsentinel
from tacker.vnfm import vim_client
@ddt.ddt
@ -193,3 +198,448 @@ class TestController(base.TestCase):
req.method = 'POST'
resp = req.get_response(self.app)
self.assertEqual(http_client.BAD_REQUEST, resp.status_code)
@mock.patch.object(vim_client.VimClient, "get_vim")
@mock.patch.object(objects.vnf_instance, "_vnf_instance_get_by_id")
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch.object(objects.VnfPackageVnfd, 'get_by_id')
@mock.patch.object(objects.VnfPackage, "get_by_id")
@mock.patch.object(VNFLcmRPCAPI, "instantiate")
def test_instantiate_with_deployment_flavour(
self, mock_instantiate, mock_vnf_package_get_by_id,
mock_vnf_package_vnfd_get_by_id, mock_save,
mock_vnf_instance_get_by_id, mock_get_vim):
mock_vnf_instance_get_by_id.return_value =\
fakes.return_vnf_instance_model()
mock_vnf_package_vnfd_get_by_id.return_value = \
fakes.return_vnf_package_vnfd()
mock_vnf_package_get_by_id.return_value = \
fakes.return_vnf_package_with_deployment_flavour()
body = {"flavourId": "simple"}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/instantiate' % uuidsentinel.vnf_instance_id)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
# Call Instantiate API
resp = req.get_response(self.app)
self.assertEqual(http_client.ACCEPTED, resp.status_code)
mock_instantiate.assert_called_once()
@mock.patch.object(objects.vnf_instance, "_vnf_instance_get_by_id")
@mock.patch.object(objects.VnfPackageVnfd, 'get_by_id')
@mock.patch.object(objects.VnfPackage, "get_by_id")
def test_instantiate_with_non_existing_deployment_flavour(
self, mock_vnf_package_get_by_id, mock_vnf_package_vnfd_get_by_id,
mock_vnf_instance_get_by_id):
mock_vnf_instance_get_by_id.return_value =\
fakes.return_vnf_instance_model()
mock_vnf_package_vnfd_get_by_id.return_value = \
fakes.return_vnf_package_vnfd()
mock_vnf_package_get_by_id.return_value = \
fakes.return_vnf_package_with_deployment_flavour()
body = {"flavourId": "invalid"}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/instantiate' % uuidsentinel.vnf_instance_id)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
# Call Instantiate API
resp = req.get_response(self.app)
self.assertEqual(http_client.BAD_REQUEST, resp.status_code)
self.assertEqual("No flavour with id 'invalid'.",
resp.json['badRequest']['message'])
@mock.patch.object(vim_client.VimClient, "get_vim")
@mock.patch.object(objects.vnf_instance, "_vnf_instance_get_by_id")
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch.object(objects.VnfPackageVnfd, 'get_by_id')
@mock.patch.object(objects.VnfPackage, "get_by_id")
@mock.patch.object(VNFLcmRPCAPI, "instantiate")
def test_instantiate_with_instantiation_level(
self, mock_instantiate, mock_vnf_package_get_by_id,
mock_vnf_package_vnfd_get_by_id, mock_save,
mock_vnf_instance_get_by_id, mock_get_vim):
mock_vnf_instance_get_by_id.return_value =\
fakes.return_vnf_instance_model()
mock_vnf_package_vnfd_get_by_id.return_value = \
fakes.return_vnf_package_vnfd()
mock_vnf_package_get_by_id.return_value = \
fakes.return_vnf_package_with_deployment_flavour()
body = {"flavourId": "simple",
"instantiationLevelId": "instantiation_level_1"}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/instantiate' % uuidsentinel.vnf_instance_id)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
# Call Instantiate API
resp = req.get_response(self.app)
self.assertEqual(http_client.ACCEPTED, resp.status_code)
mock_instantiate.assert_called_once()
@mock.patch.object(vim_client.VimClient, "get_vim")
@mock.patch.object(objects.vnf_instance, "_vnf_instance_get_by_id")
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch.object(objects.VnfPackageVnfd, 'get_by_id')
@mock.patch.object(objects.VnfPackage, "get_by_id")
@mock.patch.object(VNFLcmRPCAPI, "instantiate")
def test_instantiate_with_no_inst_level_in_flavour(
self, mock_instantiate, mock_vnf_package_get_by_id,
mock_vnf_package_vnfd_get_by_id, mock_save,
mock_vnf_instance_get_by_id, mock_get_vim):
mock_vnf_instance_get_by_id.return_value =\
fakes.return_vnf_instance_model()
mock_vnf_package_vnfd_get_by_id.return_value = \
fakes.return_vnf_package_vnfd()
vnf_package = fakes.return_vnf_package_with_deployment_flavour()
vnf_package.vnf_deployment_flavours[0].instantiation_levels = None
mock_vnf_package_get_by_id.return_value = vnf_package
# No instantiation level in deployment flavour but it's passed in the
# request
body = {"flavourId": "simple",
"instantiationLevelId": "instantiation_level_1"}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/instantiate' % uuidsentinel.vnf_instance_id)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
# Call Instantiate API
resp = req.get_response(self.app)
self.assertEqual(http_client.BAD_REQUEST, resp.status_code)
self.assertEqual("No instantiation level with id "
"'instantiation_level_1'.", resp.json['badRequest']['message'])
@mock.patch.object(objects.vnf_instance, "_vnf_instance_get_by_id")
@mock.patch.object(objects.VnfPackageVnfd, 'get_by_id')
@mock.patch.object(objects.VnfPackage, "get_by_id")
@mock.patch.object(VNFLcmRPCAPI, "instantiate")
def test_instantiate_with_non_existing_instantiation_level(
self, mock_instantiate, mock_vnf_package_get_by_id,
mock_vnf_package_vnfd_get_by_id,
mock_vnf_instance_get_by_id):
mock_vnf_instance_get_by_id.return_value =\
fakes.return_vnf_instance_model()
mock_vnf_package_vnfd_get_by_id.return_value = \
fakes.return_vnf_package_vnfd()
mock_vnf_package_get_by_id.return_value = \
fakes.return_vnf_package_with_deployment_flavour()
body = {"flavourId": "simple",
"instantiationLevelId": "non-existing"}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/instantiate' % uuidsentinel.vnf_instance_id)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
# Call Instantiate API
resp = req.get_response(self.app)
self.assertEqual(http_client.BAD_REQUEST, resp.status_code)
self.assertEqual("No instantiation level with id 'non-existing'.",
resp.json['badRequest']['message'])
@mock.patch.object(vim_client.VimClient, "get_vim")
@mock.patch.object(objects.vnf_instance, "_vnf_instance_get_by_id")
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch.object(objects.VnfPackageVnfd, 'get_by_id')
@mock.patch.object(objects.VnfPackage, "get_by_id")
@mock.patch.object(VNFLcmRPCAPI, "instantiate")
def test_instantiate_with_vim_connection(
self, mock_instantiate, mock_vnf_package_get_by_id,
mock_vnf_package_vnfd_get_by_id, mock_save,
mock_vnf_instance_get_by_id, mock_get_vim):
mock_vnf_instance_get_by_id.return_value =\
fakes.return_vnf_instance_model()
mock_vnf_package_vnfd_get_by_id.return_value = \
fakes.return_vnf_package_vnfd()
mock_vnf_package_get_by_id.return_value = \
fakes.return_vnf_package_with_deployment_flavour()
body = {"flavourId": "simple",
"vimConnectionInfo": [
{"id": uuidsentinel.vim_connection_id,
"vimId": uuidsentinel.vim_id,
"vimType": 'openstack'}
]}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/instantiate' % uuidsentinel.vnf_instance_id)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
# Call Instantiate API
resp = req.get_response(self.app)
self.assertEqual(http_client.ACCEPTED, resp.status_code)
mock_instantiate.assert_called_once()
@mock.patch.object(vim_client.VimClient, "get_vim")
@mock.patch.object(objects.vnf_instance, "_vnf_instance_get_by_id")
@mock.patch.object(objects.VnfPackageVnfd, 'get_by_id')
@mock.patch.object(objects.VnfPackage, "get_by_id")
def test_instantiate_with_non_existing_vim(
self, mock_vnf_package_get_by_id, mock_vnf_package_vnfd_get_by_id,
mock_vnf_instance_get_by_id, mock_get_vim):
mock_vnf_instance_get_by_id.return_value =\
fakes.return_vnf_instance_model()
mock_vnf_package_vnfd_get_by_id.return_value = \
fakes.return_vnf_package_vnfd()
mock_vnf_package_get_by_id.return_value = \
fakes.return_vnf_package_with_deployment_flavour()
mock_get_vim.side_effect = nfvo.VimNotFoundException
body = {"flavourId": "simple",
"vimConnectionInfo": [
{"id": uuidsentinel.vim_connection_id,
"vimId": uuidsentinel.vim_id,
"vimType": 'openstack'}
]}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/instantiate' % uuidsentinel.vnf_instance_id)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
# Call Instantiate API
resp = req.get_response(self.app)
self.assertEqual(http_client.BAD_REQUEST, resp.status_code)
self.assertEqual("VimConnection id is not found: %s" %
uuidsentinel.vim_id, resp.json['badRequest']['message'])
@mock.patch.object(vim_client.VimClient, "get_vim")
@mock.patch.object(objects.vnf_instance, "_vnf_instance_get_by_id")
@mock.patch.object(objects.VnfPackageVnfd, 'get_by_id')
@mock.patch.object(objects.VnfPackage, "get_by_id")
def test_instantiate_with_non_existing_region_vim(
self, mock_vnf_package_get_by_id, mock_vnf_package_vnfd_get_by_id,
mock_vnf_instance_get_by_id, mock_get_vim):
mock_vnf_instance_get_by_id.return_value =\
fakes.return_vnf_instance_model()
mock_vnf_package_vnfd_get_by_id.return_value = \
fakes.return_vnf_package_vnfd()
mock_vnf_package_get_by_id.return_value = \
fakes.return_vnf_package_with_deployment_flavour()
mock_get_vim.side_effect = nfvo.VimRegionNotFoundException
body = {"flavourId": "simple",
"vimConnectionInfo": [
{'id': uuidsentinel.vim_connection_id,
'vimId': uuidsentinel.vim_id,
'vimType': 'openstack',
'accessInfo': {"region": 'region_non_existing'}}
]}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/instantiate' % uuidsentinel.vnf_instance_id)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
# Call Instantiate API
resp = req.get_response(self.app)
self.assertEqual(http_client.BAD_REQUEST, resp.status_code)
self.assertEqual("Region not found for the VimConnection: %s" %
uuidsentinel.vim_id, resp.json['badRequest']['message'])
@mock.patch.object(vim_client.VimClient, "get_vim")
@mock.patch.object(objects.vnf_instance, "_vnf_instance_get_by_id")
@mock.patch.object(objects.VnfPackageVnfd, 'get_by_id')
@mock.patch.object(objects.VnfPackage, "get_by_id")
def test_instantiate_with_default_vim_not_configured(
self, mock_vnf_package_get_by_id, mock_vnf_package_vnfd_get_by_id,
mock_vnf_instance_get_by_id, mock_get_vim):
mock_vnf_instance_get_by_id.return_value =\
fakes.return_vnf_instance_model()
mock_vnf_package_vnfd_get_by_id.return_value = \
fakes.return_vnf_package_vnfd()
mock_vnf_package_get_by_id.return_value = \
fakes.return_vnf_package_with_deployment_flavour()
mock_get_vim.side_effect = nfvo.VimDefaultNotDefined
body = {"flavourId": "simple"}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/instantiate' % uuidsentinel.vnf_instance_id)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
# Call Instantiate API
resp = req.get_response(self.app)
self.assertEqual(http_client.BAD_REQUEST, resp.status_code)
self.assertEqual("Default VIM is not defined.",
resp.json['badRequest']['message'])
@mock.patch.object(objects.vnf_instance, "_vnf_instance_get_by_id")
def test_instantiate_incorrect_instantiation_state(self, mock_vnf_by_id):
vnf_instance = fakes.return_vnf_instance_model()
vnf_instance.instantiation_state = 'INSTANTIATED'
mock_vnf_by_id.return_value = vnf_instance
body = {"flavourId": "simple"}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/instantiate' % uuidsentinel.vnf_instance_id)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
# Call Instantiate API
resp = req.get_response(self.app)
self.assertEqual(http_client.CONFLICT, resp.status_code)
@mock.patch.object(objects.vnf_instance, "_vnf_instance_get_by_id")
def test_instantiate_incorrect_task_state(self, mock_vnf_by_id):
vnf_instance = fakes.return_vnf_instance_model(
task_state=fields.VnfInstanceTaskState.INSTANTIATING)
mock_vnf_by_id.return_value = vnf_instance
body = {"flavourId": "simple"}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/instantiate' % uuidsentinel.vnf_instance_id)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
resp = req.get_response(self.app)
self.assertEqual(http_client.CONFLICT, resp.status_code)
expected_msg = ("Vnf instance %s in task_state INSTANTIATING. Cannot "
"instantiate while the vnf instance is in this state.")
self.assertEqual(expected_msg % uuidsentinel.vnf_instance_id,
resp.json['conflictingRequest']['message'])
@ddt.data({'attribute': 'flavourId', 'value': 123,
'expected_type': 'string'},
{'attribute': 'flavourId', 'value': True,
'expected_type': 'string'},
{'attribute': 'instantiationLevelId', 'value': 123,
'expected_type': 'string'},
{'attribute': 'instantiationLevelId', 'value': True,
'expected_type': 'string'},
{'attribute': 'additionalParams', 'value': ['val1', 'val2'],
'expected_type': 'object'},
{'attribute': 'additionalParams', 'value': True,
'expected_type': 'object'},
{'attribute': 'additionalParams', 'value': 123,
'expected_type': 'object'},
)
@ddt.unpack
def test_instantiate_with_invalid_request_body(
self, attribute, value, expected_type):
body = fakes.get_vnf_instantiation_request_body()
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/instantiate' % uuidsentinel.vnf_instance_id)
body.update({attribute: value})
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
exception = self.assertRaises(
exceptions.ValidationError, self.controller.instantiate,
req, body=body)
expected_message = \
("Invalid input for field/attribute {attribute}. Value: {value}. "
"{value} is not of type '{expected_type}'".
format(value=value, attribute=attribute,
expected_type=expected_type))
self.assertEqual(expected_message, exception.msg)
def test_instantiate_without_flavour_id(self):
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/instantiate' % uuidsentinel.vnf_instance_id)
req.body = jsonutils.dump_as_bytes({})
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
# Call Instantiate API
resp = req.get_response(self.app)
self.assertEqual(http_client.BAD_REQUEST, resp.status_code)
self.assertEqual("'flavourId' is a required property",
resp.json['badRequest']['message'])
def test_instantiate_invalid_request_parameter(self):
body = {"flavourId": "simple"}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/instantiate' % uuidsentinel.vnf_instance_id)
# Pass invalid request parameter
body = {"flavourId": "simple"}
body.update({'additional_property': 'test_value'})
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
# Call Instantiate API
resp = req.get_response(self.app)
self.assertEqual(http_client.BAD_REQUEST, resp.status_code)
self.assertEqual("Additional properties are not allowed "
"('additional_property' was unexpected)",
resp.json['badRequest']['message'])
def test_instantiate_with_invalid_uuid(self):
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/instantiate' % constants.INVALID_UUID)
body = {"flavourId": "simple"}
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
# Call Instantiate API
resp = req.get_response(self.app)
self.assertEqual(http_client.NOT_FOUND, resp.status_code)
self.assertEqual(
"Can not find requested vnf instance: %s" % constants.INVALID_UUID,
resp.json['itemNotFound']['message'])
@mock.patch.object(objects.VnfInstance, "get_by_id")
def test_instantiate_with_non_existing_vnf_instance(
self, mock_vnf_by_id):
mock_vnf_by_id.side_effect = exceptions.VnfInstanceNotFound
body = {"flavourId": "simple"}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/instantiate' % uuidsentinel.vnf_instance_id)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
# Call Instantiate API
resp = req.get_response(self.app)
self.assertEqual(http_client.NOT_FOUND, resp.status_code)
self.assertEqual("Can not find requested vnf instance: %s" %
uuidsentinel.vnf_instance_id,
resp.json['itemNotFound']['message'])
@ddt.data('HEAD', 'PUT', 'DELETE', 'PATCH', 'GET')
def test_instantiate_invalid_http_method(self, method):
# Wrong HTTP method
body = fakes.get_vnf_instantiation_request_body()
req = fake_request.HTTPRequest.blank(
'/vnf_instances/29c770a3-02bc-4dfc-b4be-eb173ac00567/instantiate')
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = method
resp = req.get_response(self.app)
self.assertEqual(http_client.METHOD_NOT_ALLOWED, resp.status_code)

View File

@ -0,0 +1,49 @@
# Copyright (c) 2020 NTT DATA
#
# 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.
import os
import ddt
from oslo_config import cfg
from tacker.tests.unit import base
from tacker.tests.unit.vnflcm import fakes
from tacker.tests import uuidsentinel
from tacker.vnflcm import utils as vnflcm_utils
@ddt.ddt
class VnfLcmUtilsTestCase(base.TestCase):
@ddt.data(
{'image_path': 'cirros-0.4.0-x86_64-disk.img',
'extracted_path': 'cirros-0.4.0-x86_64-disk.img'},
{'image_path': '../ImageFiles/image/cirros-0.4.0-x86_64-disk.img',
'extracted_path': 'ImageFiles/image/cirros-0.4.0-x86_64-disk.img'},
{'image_path': '../../Files/image/cirros-0.4.0-x86_64-disk.img',
'extracted_path': 'Files/image/cirros-0.4.0-x86_64-disk.img'}
)
@ddt.unpack
def test_create_grant_request_with_software_image_path(self, image_path,
extracted_path):
vnf_package_id = uuidsentinel.package_uuid
vnfd_dict = fakes.get_vnfd_dict(image_path=image_path)
vnf_software_images = vnflcm_utils._create_grant_request(
vnfd_dict, vnf_package_id)
vnf_package_path = cfg.CONF.vnf_package.vnf_package_csar_path
expected_image_path = os.path.join(vnf_package_path, vnf_package_id,
extracted_path)
self.assertEqual(expected_image_path,
vnf_software_images['VDU1'].image_path)

View File

@ -0,0 +1,336 @@
# Copyright (c) 2020 NTT DATA
#
# 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.
import os
import shutil
import zipfile
import fixtures
import mock
from oslo_config import cfg
from tacker.common import exceptions
from tacker.common import utils
from tacker import context
from tacker import objects
from tacker.tests.unit.db import base as db_base
from tacker.tests.unit.vnflcm import fakes
from tacker.tests import uuidsentinel
from tacker.vnflcm import vnflcm_driver
class InfraDriverException(Exception):
pass
class FakeDriverManager(mock.Mock):
def __init__(self, fail_method_name=None, vnf_resource_count=1):
super(FakeDriverManager, self).__init__()
self.fail_method_name = fail_method_name
self.vnf_resource_count = vnf_resource_count
def invoke(self, *args, **kwargs):
if 'pre_instantiation_vnf' in args:
vnf_resource_list = [fakes.return_vnf_resource() for index in
range(self.vnf_resource_count)]
return {'node_name': vnf_resource_list}
if 'instantiate_vnf' in args:
if self.fail_method_name and \
self.fail_method_name == 'instantiate_vnf':
raise InfraDriverException("instantiate_vnf failed")
instance_id = uuidsentinel.instance_id
vnfd_dict = kwargs.get('vnfd_dict')
vnfd_dict['instance_id'] = instance_id
return instance_id
if 'create_wait' in args:
if self.fail_method_name and \
self.fail_method_name == 'create_wait':
raise InfraDriverException("create_wait failed")
if 'post_vnf_instantiation' in args:
pass
if 'delete' in args:
if self.fail_method_name and \
self.fail_method_name == 'delete':
raise InfraDriverException("delete failed")
if 'delete_wait' in args:
if self.fail_method_name and \
self.fail_method_name == 'delete_wait':
raise InfraDriverException("delete_wait failed")
if 'delete_vnf_instance_resource' in args:
if self.fail_method_name and \
self.fail_method_name == 'delete_vnf_resource':
raise InfraDriverException("delete_vnf_resource failed")
class FakeVimClient(mock.Mock):
pass
class TestVnflcmDriver(db_base.SqlTestCase):
def setUp(self):
super(TestVnflcmDriver, self).setUp()
self.addCleanup(mock.patch.stopall)
self.context = context.get_admin_context()
self._mock_vim_client()
self._stub_get_vim()
self.temp_dir = self.useFixture(fixtures.TempDir()).path
def _mock_vnf_manager(self, fail_method_name=None, vnf_resource_count=1):
self._vnf_manager = mock.Mock(wraps=FakeDriverManager(
fail_method_name=fail_method_name,
vnf_resource_count=vnf_resource_count))
self._vnf_manager.__contains__ = mock.Mock(
return_value=True)
fake_vnf_manager = mock.Mock()
fake_vnf_manager.return_value = self._vnf_manager
self._mock(
'tacker.common.driver_manager.DriverManager', fake_vnf_manager)
def _mock_vim_client(self):
self.vim_client = mock.Mock(wraps=FakeVimClient())
fake_vim_client = mock.Mock()
fake_vim_client.return_value = self.vim_client
self._mock(
'tacker.vnfm.vim_client.VimClient', fake_vim_client)
def _stub_get_vim(self):
vim_obj = {'vim_id': '6261579e-d6f3-49ad-8bc3-a9cb974778ff',
'vim_name': 'fake_vim', 'vim_auth':
{'auth_url': 'http://localhost/identity', 'password':
'test_pw', 'username': 'test_user', 'project_name':
'test_project'}, 'vim_type': 'openstack'}
self.vim_client.get_vim.return_value = vim_obj
@mock.patch.object(objects.VnfResource, 'create')
@mock.patch.object(objects.VnfPackageVnfd, 'get_by_id')
@mock.patch.object(objects.VnfInstance, "save")
def test_instantiate_vnf(self, mock_vnf_instance_save,
mock_vnf_package_vnfd, mock_create):
vnf_package_vnfd = fakes.return_vnf_package_vnfd()
vnf_package_id = vnf_package_vnfd.package_uuid
mock_vnf_package_vnfd.return_value = vnf_package_vnfd
instantiate_vnf_req_dict = fakes.get_dummy_instantiate_vnf_request()
instantiate_vnf_req_obj = \
objects.InstantiateVnfRequest.obj_from_primitive(
instantiate_vnf_req_dict, self.context)
vnf_instance_obj = fakes.return_vnf_instance()
fake_csar = os.path.join(self.temp_dir, vnf_package_id)
cfg.CONF.set_override('vnf_package_csar_path', self.temp_dir,
group='vnf_package')
base_path = os.path.dirname(os.path.abspath(__file__))
sample_vnf_package_zip = os.path.join(
base_path, "../../etc/samples/sample_vnf_package_csar.zip")
extracted_zip_path = fake_csar
zipfile.ZipFile(sample_vnf_package_zip, 'r').extractall(
extracted_zip_path)
self._mock_vnf_manager()
driver = vnflcm_driver.VnfLcmDriver()
driver.instantiate_vnf(self.context, vnf_instance_obj,
instantiate_vnf_req_obj)
self.assertEqual("INSTANTIATED", vnf_instance_obj.instantiation_state)
self.assertEqual(2, mock_vnf_instance_save.call_count)
self.assertEqual(4, self._vnf_manager.invoke.call_count)
shutil.rmtree(fake_csar)
@mock.patch.object(objects.VnfResource, 'create')
@mock.patch.object(objects.VnfPackageVnfd, 'get_by_id')
@mock.patch.object(objects.VnfInstance, "save")
def test_instantiate_vnf_with_ext_virtual_links(
self, mock_vnf_instance_save, mock_vnf_package_vnfd, mock_create):
vnf_package_vnfd = fakes.return_vnf_package_vnfd()
vnf_package_id = vnf_package_vnfd.package_uuid
mock_vnf_package_vnfd.return_value = vnf_package_vnfd
req_body = fakes.get_instantiate_vnf_request_with_ext_virtual_links()
instantiate_vnf_req_dict = utils.convert_camelcase_to_snakecase(
req_body)
instantiate_vnf_req_obj = \
objects.InstantiateVnfRequest.obj_from_primitive(
instantiate_vnf_req_dict, self.context)
vnf_instance_obj = fakes.return_vnf_instance()
fake_csar = os.path.join(self.temp_dir, vnf_package_id)
cfg.CONF.set_override('vnf_package_csar_path', self.temp_dir,
group='vnf_package')
base_path = os.path.dirname(os.path.abspath(__file__))
sample_vnf_package_zip = os.path.join(
base_path, "../../etc/samples/sample_vnf_package_csar.zip")
extracted_zip_path = fake_csar
zipfile.ZipFile(sample_vnf_package_zip, 'r').extractall(
extracted_zip_path)
self._mock_vnf_manager()
driver = vnflcm_driver.VnfLcmDriver()
driver.instantiate_vnf(self.context, vnf_instance_obj,
instantiate_vnf_req_obj)
self.assertEqual("INSTANTIATED", vnf_instance_obj.instantiation_state)
self.assertEqual(2, mock_vnf_instance_save.call_count)
self.assertEqual(4, self._vnf_manager.invoke.call_count)
shutil.rmtree(fake_csar)
@mock.patch.object(objects.VnfResource, 'create')
@mock.patch.object(objects.VnfPackageVnfd, 'get_by_id')
@mock.patch.object(objects.VnfInstance, "save")
def test_instantiate_vnf_vim_connection_info(
self, mock_vnf_instance_save, mock_vnf_package_vnfd, mock_create):
vnf_package_vnfd = fakes.return_vnf_package_vnfd()
vnf_package_id = vnf_package_vnfd.package_uuid
mock_vnf_package_vnfd.return_value = vnf_package_vnfd
vim_connection_info = fakes.get_dummy_vim_connection_info()
instantiate_vnf_req_dict = \
fakes.get_dummy_instantiate_vnf_request(**vim_connection_info)
instantiate_vnf_req_obj = \
objects.InstantiateVnfRequest.obj_from_primitive(
instantiate_vnf_req_dict, self.context)
vnf_instance_obj = fakes.return_vnf_instance()
fake_csar = os.path.join(self.temp_dir, vnf_package_id)
cfg.CONF.set_override('vnf_package_csar_path', self.temp_dir,
group='vnf_package')
base_path = os.path.dirname(os.path.abspath(__file__))
sample_vnf_package_zip = os.path.join(
base_path, "../../etc/samples/sample_vnf_package_csar.zip")
extracted_zip_path = fake_csar
zipfile.ZipFile(sample_vnf_package_zip, 'r').extractall(
extracted_zip_path)
self._mock_vnf_manager()
driver = vnflcm_driver.VnfLcmDriver()
driver.instantiate_vnf(self.context, vnf_instance_obj,
instantiate_vnf_req_obj)
self.assertEqual("INSTANTIATED", vnf_instance_obj.instantiation_state)
self.assertEqual(2, mock_vnf_instance_save.call_count)
self.assertEqual(4, self._vnf_manager.invoke.call_count)
shutil.rmtree(fake_csar)
@mock.patch.object(objects.VnfResource, 'create')
@mock.patch.object(objects.VnfPackageVnfd, 'get_by_id')
@mock.patch.object(objects.VnfInstance, "save")
def test_instantiate_vnf_infra_fails_to_instantiate(
self, mock_vnf_instance_save, mock_vnf_package_vnfd, mock_create):
vnf_package_vnfd = fakes.return_vnf_package_vnfd()
vnf_package_id = vnf_package_vnfd.package_uuid
mock_vnf_package_vnfd.return_value = vnf_package_vnfd
vim_connection_info = fakes.get_dummy_vim_connection_info()
instantiate_vnf_req_dict = \
fakes.get_dummy_instantiate_vnf_request(**vim_connection_info)
instantiate_vnf_req_obj = \
objects.InstantiateVnfRequest.obj_from_primitive(
instantiate_vnf_req_dict, self.context)
vnf_instance_obj = fakes.return_vnf_instance()
fake_csar = os.path.join(self.temp_dir, vnf_package_id)
cfg.CONF.set_override('vnf_package_csar_path', self.temp_dir,
group='vnf_package')
base_path = os.path.dirname(os.path.abspath(__file__))
sample_vnf_package_zip = os.path.join(
base_path, "../../etc/samples/sample_vnf_package_csar.zip")
extracted_zip_path = fake_csar
zipfile.ZipFile(sample_vnf_package_zip, 'r').extractall(
extracted_zip_path)
self._mock_vnf_manager(fail_method_name="instantiate_vnf")
driver = vnflcm_driver.VnfLcmDriver()
error = self.assertRaises(exceptions.VnfInstantiationFailed,
driver.instantiate_vnf, self.context, vnf_instance_obj,
instantiate_vnf_req_obj)
expected_error = ("Vnf instantiation failed for vnf %s, error: "
"instantiate_vnf failed")
self.assertEqual(expected_error % vnf_instance_obj.id, str(error))
self.assertEqual("NOT_INSTANTIATED",
vnf_instance_obj.instantiation_state)
self.assertEqual(1, mock_vnf_instance_save.call_count)
self.assertEqual(2, self._vnf_manager.invoke.call_count)
shutil.rmtree(fake_csar)
@mock.patch.object(objects.VnfResource, 'create')
@mock.patch.object(objects.VnfPackageVnfd, 'get_by_id')
@mock.patch.object(objects.VnfInstance, "save")
def test_instantiate_vnf_infra_fails_to_wait_after_instantiate(
self, mock_vnf_instance_save, mock_vnf_package_vnfd, mock_create):
vnf_package_vnfd = fakes.return_vnf_package_vnfd()
vnf_package_id = vnf_package_vnfd.package_uuid
mock_vnf_package_vnfd.return_value = vnf_package_vnfd
vim_connection_info = fakes.get_dummy_vim_connection_info()
instantiate_vnf_req_dict = \
fakes.get_dummy_instantiate_vnf_request(**vim_connection_info)
instantiate_vnf_req_obj = \
objects.InstantiateVnfRequest.obj_from_primitive(
instantiate_vnf_req_dict, self.context)
vnf_instance_obj = fakes.return_vnf_instance()
fake_csar = os.path.join(self.temp_dir, vnf_package_id)
cfg.CONF.set_override('vnf_package_csar_path', self.temp_dir,
group='vnf_package')
base_path = os.path.dirname(os.path.abspath(__file__))
sample_vnf_package_zip = os.path.join(
base_path, "../../etc/samples/sample_vnf_package_csar.zip")
extracted_zip_path = fake_csar
zipfile.ZipFile(sample_vnf_package_zip, 'r').extractall(
extracted_zip_path)
self._mock_vnf_manager(fail_method_name='create_wait')
driver = vnflcm_driver.VnfLcmDriver()
error = self.assertRaises(exceptions.VnfInstantiationWaitFailed,
driver.instantiate_vnf, self.context, vnf_instance_obj,
instantiate_vnf_req_obj)
expected_error = ("Vnf instantiation wait failed for vnf %s, error: "
"create_wait failed")
self.assertEqual(expected_error % vnf_instance_obj.id, str(error))
self.assertEqual("NOT_INSTANTIATED",
vnf_instance_obj.instantiation_state)
self.assertEqual(1, mock_vnf_instance_save.call_count)
self.assertEqual(3, self._vnf_manager.invoke.call_count)
shutil.rmtree(fake_csar)
@mock.patch.object(objects.VnfResource, 'create')
@mock.patch.object(objects.VnfPackageVnfd, 'get_by_id')
@mock.patch.object(objects.VnfInstance, "save")
def test_instantiate_vnf_with_short_notation(self, mock_vnf_instance_save,
mock_vnf_package_vnfd, mock_create):
vnf_package_vnfd = fakes.return_vnf_package_vnfd()
vnf_package_id = vnf_package_vnfd.package_uuid
mock_vnf_package_vnfd.return_value = vnf_package_vnfd
instantiate_vnf_req_dict = fakes.get_dummy_instantiate_vnf_request()
instantiate_vnf_req_obj = \
objects.InstantiateVnfRequest.obj_from_primitive(
instantiate_vnf_req_dict, self.context)
vnf_instance_obj = fakes.return_vnf_instance()
fake_csar = os.path.join(self.temp_dir, vnf_package_id)
cfg.CONF.set_override('vnf_package_csar_path', self.temp_dir,
group='vnf_package')
base_path = os.path.dirname(os.path.abspath(__file__))
sample_vnf_package_zip = os.path.join(
base_path, "../../etc/samples/"
"sample_vnf_package_csar_with_short_notation.zip")
extracted_zip_path = fake_csar
zipfile.ZipFile(sample_vnf_package_zip, 'r').extractall(
extracted_zip_path)
self._mock_vnf_manager(vnf_resource_count=2)
driver = vnflcm_driver.VnfLcmDriver()
driver.instantiate_vnf(self.context, vnf_instance_obj,
instantiate_vnf_req_obj)
self.assertEqual(2, mock_create.call_count)
self.assertEqual("INSTANTIATED", vnf_instance_obj.instantiation_state)
shutil.rmtree(fake_csar)

View File

@ -0,0 +1,202 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: ETSI NFV SOL 001 common types definitions version 2.6.1
metadata:
template_name: etsi_nfv_sol001_common_types
template_author: ETSI_NFV
template_version: 2.6.1
data_types:
tosca.datatypes.nfv.L2AddressData:
derived_from: tosca.datatypes.Root
description: Describes the information on the MAC addresses to be assigned to a connection point.
properties:
mac_address_assignment:
type: boolean
description: Specifies if the address assignment is the responsibility of management and orchestration function or not. If it is set to True, it is the management and orchestration function responsibility
required: true
tosca.datatypes.nfv.L3AddressData:
derived_from: tosca.datatypes.Root
description: Provides information about Layer 3 level addressing scheme and parameters applicable to a CP
properties:
ip_address_assignment:
type: boolean
description: Specifies if the address assignment is the responsibility of management and orchestration function or not. If it is set to True, it is the management and orchestration function responsibility
required: true
floating_ip_activated:
type: boolean
description: Specifies if the floating IP scheme is activated on the Connection Point or not
required: true
ip_address_type:
type: string
description: Defines address type. The address type should be aligned with the address type supported by the layer_protocols properties of the parent VnfExtCp
required: false
constraints:
- valid_values: [ ipv4, ipv6 ]
number_of_ip_address:
type: integer
description: Minimum number of IP addresses to be assigned
required: false
constraints:
- greater_than: 0
tosca.datatypes.nfv.AddressData:
derived_from: tosca.datatypes.Root
description: Describes information about the addressing scheme and parameters applicable to a CP
properties:
address_type:
type: string
description: Describes the type of the address to be assigned to a connection point. The content type shall be aligned with the address type supported by the layerProtocol property of the connection point
required: true
constraints:
- valid_values: [ mac_address, ip_address ]
l2_address_data:
type: tosca.datatypes.nfv.L2AddressData
description: Provides the information on the MAC addresses to be assigned to a connection point.
required: false
l3_address_data:
type: tosca.datatypes.nfv.L3AddressData
description: Provides the information on the IP addresses to be assigned to a connection point
required: false
tosca.datatypes.nfv.ConnectivityType:
derived_from: tosca.datatypes.Root
description: describes additional connectivity information of a virtualLink
properties:
layer_protocols:
type: list
description: Identifies the protocol a virtualLink gives access to (ethernet, mpls, odu2, ipv4, ipv6, pseudo-wire).The top layer protocol of the virtualLink protocol stack shall always be provided. The lower layer protocols may be included when there are specific requirements on these layers.
required: true
entry_schema:
type: string
constraints:
- valid_values: [ ethernet, mpls, odu2, ipv4, ipv6, pseudo-wire ]
flow_pattern:
type: string
description: Identifies the flow pattern of the connectivity
required: false
constraints:
- valid_values: [ line, tree, mesh ]
tosca.datatypes.nfv.LinkBitrateRequirements:
derived_from: tosca.datatypes.Root
description: describes the requirements in terms of bitrate for a virtual link
properties:
root:
type: integer # in bits per second
description: Specifies the throughput requirement in bits per second of the link (e.g. bitrate of E-Line, root bitrate of E-Tree, aggregate capacity of E-LAN).
required: true
constraints:
- greater_or_equal: 0
leaf:
type: integer # in bits per second
description: Specifies the throughput requirement in bits per second of leaf connections to the link when applicable to the connectivity type (e.g. for E-Tree and E LAN branches).
required: false
constraints:
- greater_or_equal: 0
tosca.datatypes.nfv.CpProtocolData:
derived_from: tosca.datatypes.Root
description: Describes and associates the protocol layer that a CP uses together with other protocol and connection point information
properties:
associated_layer_protocol:
type: string
required: true
description: One of the values of the property layer_protocols of the CP
constraints:
- valid_values: [ ethernet, mpls, odu2, ipv4, ipv6, pseudo-wire ]
address_data:
type: list
description: Provides information on the addresses to be assigned to the CP
entry_schema:
type: tosca.datatypes.nfv.AddressData
required: false
tosca.datatypes.nfv.VnfProfile:
derived_from: tosca.datatypes.Root
description: describes a profile for instantiating VNFs of a particular NS DF according to a specific VNFD and VNF DF.
properties:
instantiation_level:
type: string
description: Identifier of the instantiation level of the VNF DF to be used for instantiation. If not present, the default instantiation level as declared in the VNFD shall be used.
required: false
min_number_of_instances:
type: integer
description: Minimum number of instances of the VNF based on this VNFD that is permitted to exist for this VnfProfile.
required: true
constraints:
- greater_or_equal: 0
max_number_of_instances:
type: integer
description: Maximum number of instances of the VNF based on this VNFD that is permitted to exist for this VnfProfile.
required: true
constraints:
- greater_or_equal: 0
tosca.datatypes.nfv.Qos:
derived_from: tosca.datatypes.Root
description: describes QoS data for a given VL used in a VNF deployment flavour
properties:
latency:
type: scalar-unit.time #Number
description: Specifies the maximum latency
required: true
constraints:
- greater_than: 0 s
packet_delay_variation:
type: scalar-unit.time #Number
description: Specifies the maximum jitter
required: true
constraints:
- greater_or_equal: 0 s
packet_loss_ratio:
type: float
description: Specifies the maximum packet loss ratio
required: false
constraints:
- in_range: [ 0.0, 1.0 ]
capability_types:
tosca.capabilities.nfv.VirtualLinkable:
derived_from: tosca.capabilities.Node
description: A node type that includes the VirtualLinkable capability indicates that it can be pointed by tosca.relationships.nfv.VirtualLinksTo relationship type
relationship_types:
tosca.relationships.nfv.VirtualLinksTo:
derived_from: tosca.relationships.DependsOn
description: Represents an association relationship between the VduCp and VnfVirtualLink node types
valid_target_types: [ tosca.capabilities.nfv.VirtualLinkable ]
node_types:
tosca.nodes.nfv.Cp:
derived_from: tosca.nodes.Root
description: Provides information regarding the purpose of the connection point
properties:
layer_protocols:
type: list
description: Identifies which protocol the connection point uses for connectivity purposes
required: true
entry_schema:
type: string
constraints:
- valid_values: [ ethernet, mpls, odu2, ipv4, ipv6, pseudo-wire ]
role: #Name in ETSI NFV IFA011 v0.7.3: cpRole
type: string
description: Identifies the role of the port in the context of the traffic flow patterns in the VNF or parent NS
required: false
constraints:
- valid_values: [ root, leaf ]
description:
type: string
description: Provides human-readable information on the purpose of the connection point
required: false
protocol:
type: list
description: Provides information on the addresses to be assigned to the connection point(s) instantiated from this Connection Point Descriptor
required: false
entry_schema:
type: tosca.datatypes.nfv.CpProtocolData
trunk_mode:
type: boolean
description: Provides information about whether the CP instantiated from this Cp is in Trunk mode (802.1Q or other), When operating in "trunk mode", the Cp is capable of carrying traffic for several VLANs. Absence of this property implies that trunkMode is not configured for the Cp i.e. It is equivalent to boolean value "false".
required: false

View File

@ -0,0 +1,65 @@
heat_template_version: 2013-05-23
description: 'Template for test _generate_hot_from_tosca().
'
parameters: {}
resources:
VDU1:
type: OS::Nova::Server
properties:
networks:
- port:
get_resource: CP1
- port: neutron-port-uuid_CP2
- port:
get_resource: CP3
- port:
get_resource: CP4
flavor:
get_resource: VDU1_flavor
name: VDU1
image: glance-image-uuid_VDU1
VDU1_flavor:
type: OS::Nova::Flavor
properties:
disk: 1
ram: 512
vcpus: 1
CP1:
type: OS::Neutron::Port
properties:
network: neutron-network-uuid_VL1
fixed_ips:
- subnet: neutron-subnet-uuid_CP1
ip_address: 1.1.1.1
mac_address: fa:16:3e:11:11:11
CP3:
type: OS::Neutron::Port
properties:
network: neutron-network-uuid_VL3
CP4:
type: OS::Neutron::Port
properties:
network:
get_resource: VL4
VL4:
type: OS::Neutron::Net
properties:
qos_policy:
get_resource: VL4_qospolicy
VL4_subnet:
type: OS::Neutron::Subnet
properties:
ip_version: 4
cidr: 44.44.0.0/24
network:
get_resource: VL4
VL4_qospolicy:
type: OS::Neutron::QoSPolicy
VL4_bandwidth:
type: OS::Neutron::QoSBandwidthLimitRule
properties:
policy:
get_resource: VL4_qospolicy
max_kbps: 1024.0
outputs: {}

View File

@ -0,0 +1,61 @@
heat_template_version: 2013-05-23
description: 'Template for test _generate_hot_from_tosca() with scaling.
'
parameters: {}
resources:
worker_instance:
type: OS::Heat::AutoScalingGroup
properties:
desired_capacity: 1
resource:
properties:
vdu1_flavor_id:
get_resource: VDU1_flavor
vl3_id: neutron-network-uuid_VL3
vl4_id:
get_resource: VL4
type: worker_instance.hot.yaml
min_size: 1
max_size: 3
worker_instance_scale_out:
type: OS::Heat::ScalingPolicy
properties:
scaling_adjustment: 1
adjustment_type: change_in_capacity
auto_scaling_group_id:
get_resource: worker_instance
worker_instance_scale_in:
type: OS::Heat::ScalingPolicy
properties:
scaling_adjustment: -1
adjustment_type: change_in_capacity
auto_scaling_group_id:
get_resource: worker_instance
VDU1_flavor:
type: OS::Nova::Flavor
properties:
disk: 1
ram: 512
vcpus: 1
VL4:
type: OS::Neutron::Net
properties:
qos_policy:
get_resource: VL4_qospolicy
VL4_subnet:
type: OS::Neutron::Subnet
properties:
ip_version: 4
network:
get_resource: VL4
cidr: 44.44.0.0/24
VL4_qospolicy:
type: OS::Neutron::QoSPolicy
VL4_bandwidth:
type: OS::Neutron::QoSBandwidthLimitRule
properties:
max_kbps: 1024.0
policy:
get_resource: VL4_qospolicy
outputs: {}

View File

@ -0,0 +1,41 @@
heat_template_version: 2013-05-23
description: Scaling template
parameters:
vdu1_flavor_id:
type: string
vl3_id:
type: string
vl4_id:
type: string
resources:
VDU1:
type: OS::Nova::Server
properties:
name: VDU1
networks:
- port:
get_resource: CP1
- port: neutron-port-uuid_CP2
- port:
get_resource: CP3
- port:
get_resource: CP4
flavor:
get_param: vdu1_flavor_id
image: glance-image-uuid_VDU1
CP1:
type: OS::Neutron::Port
properties:
network: neutron-network-uuid_VL1
fixed_ips:
- subnet: neutron-subnet-uuid_CP1
CP3:
type: OS::Neutron::Port
properties:
network:
get_param: vl3_id
CP4:
type: OS::Neutron::Port
properties:
network:
get_param: vl4_id

View File

@ -0,0 +1,115 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: >
Template for test _generate_hot_from_tosca().
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
node_types:
topology_template:
node_templates:
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 GiB
size: 1 GiB
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 MiB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 1 GiB
CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
requirements:
- virtual_binding: VDU1
CP2:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 1
requirements:
- virtual_binding: VDU1
CP3:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 2
requirements:
- virtual_binding: VDU1
- virtual_link: VL3
CP4:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 3
requirements:
- virtual_binding: VDU1
- virtual_link: VL4
VL3:
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: 33.33.0.0/24
VL4:
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: 44.44.0.0/24

View File

@ -0,0 +1,16 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: >
Template for test _generate_hot_from_tosca() the case of tosca-parser error.
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
node_types:
topology_template:
node_templates:
VDU1:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: VDU1

View File

@ -0,0 +1,48 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: >
Template for test _generate_hot_from_tosca() the case of heat-translator error.
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
node_types:
topology_template:
node_templates:
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 GiB
size: 1 GiB
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 MiB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 1 GiB
CP1:
type: tosca.nodes.nfv.VnfExtCp
properties:
layer_protocols: [ ipv4 ]

View File

@ -0,0 +1,197 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: >
Template for test _generate_hot_from_tosca().
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
node_types:
ntt.nslab.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: [ 'NTT NS lab' ] ]
default: 'NTT NS lab'
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
topology_template:
inputs:
selected_flavour:
type: string
default: simple
description: VNF deployment flavour selected by the consumer. It is provided in the API
substitution_mappings:
node_type: ntt.nslab.VNF
properties:
flavour_id: simple
requirements:
virtual_link_external: [ CP1, virtual_link ]
node_templates:
VNF:
type: ntt.nslab.VNF
properties:
flavour_id: { get_input: selected_flavour }
descriptor_id: b1bb0ce7-ebca-4fa7-95ed-4840d70a1177
provider: NTT NS lab
product_name: Sample VNF
software_version: '1.0'
descriptor_version: '1.0'
vnfm_info:
- Tacker
flavour_description: A simple flavour
interfaces:
Vnflcm:
instantiate: []
instantiate_start: []
instantiate_end: []
terminate: []
terminate_start: []
terminate_end: []
modify_information: []
modify_information_start: []
modify_information_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 GiB
size: 1 GiB
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 MiB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 1 GiB
CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
requirements:
- virtual_binding: VDU1
CP2:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 1
requirements:
- virtual_binding: VDU1
CP3:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 2
requirements:
- virtual_binding: VDU1
- virtual_link: VL3
CP4:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 3
requirements:
- virtual_binding: VDU1
- virtual_link: VL4
VL3:
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: 33.33.0.0/24
VL4:
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: 44.44.0.0/24

View File

@ -0,0 +1,183 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: >
Template for test _generate_hot_from_tosca() with scaling.
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
node_types:
topology_template:
node_templates:
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 GiB
size: 1 GiB
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 MiB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 1 GiB
CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
requirements:
- virtual_binding: VDU1
CP2:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 1
requirements:
- virtual_binding: VDU1
CP3:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 2
requirements:
- virtual_binding: VDU1
- virtual_link: VL3
CP4:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 3
requirements:
- virtual_binding: VDU1
- virtual_link: VL4
VL3:
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: 33.33.0.0/24
VL4:
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: 44.44.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
- VDU1_initial_delta:
type: tosca.policies.nfv.VduInitialDelta
properties:
initial_delta:
number_of_instances: 1
targets: [ VDU1 ]
- VDU1_scaling_aspect_deltas:
type: tosca.policies.nfv.VduScalingAspectDeltas
properties:
aspect: worker_instance
deltas:
delta_1:
number_of_instances: 1
targets: [ VDU1 ]
- 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 ]
- VL4_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: [ VL4 ]

View File

@ -0,0 +1,105 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: >
Template for test _generate_hot_from_tosca() with scaling the case of invalid inst_req_info.
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
node_types:
topology_template:
node_templates:
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 GiB
size: 1 GiB
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 MiB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 1 GiB
CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
requirements:
- virtual_binding: VDU1
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
- VDU1_initial_delta:
type: tosca.policies.nfv.VduInitialDelta
properties:
initial_delta:
number_of_instances: 1
targets: [ VDU1 ]
- VDU1_scaling_aspect_deltas:
type: tosca.policies.nfv.VduScalingAspectDeltas
properties:
aspect: worker_instance
deltas:
delta_1:
number_of_instances: 1
targets: [ VDU1 ]
- 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 ]

View File

@ -0,0 +1,197 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: >
Template for test _generate_hot_from_tosca().
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
node_types:
ntt.nslab.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: [ 'NTT NS lab' ] ]
default: 'NTT NS lab'
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
topology_template:
inputs:
selected_flavour:
type: string
default: simple
description: VNF deployment flavour selected by the consumer. It is provided in the API
substitution_mappings:
node_type: ntt.nslab.VNF
properties:
flavour_id: simple
requirements:
virtual_link_external: [ CP1, virtual_link ]
node_templates:
VNF:
type: ntt.nslab.VNF
properties:
flavour_id: { get_input: selected_flavour }
descriptor_id: b1bb0ce7-ebca-4fa7-95ed-4840d70a1177
provider: NTT NS lab
product_name: Sample VNF
software_version: '1.0'
descriptor_version: '1.0'
vnfm_info:
- Tacker
flavour_description: A simple flavour
interfaces:
Vnflcm:
instantiate: []
instantiate_start: []
instantiate_end: []
terminate: []
terminate_start: []
terminate_end: []
modify_information: []
modify_information_start: []
modify_information_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 GiB
size: 1 GiB
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 MiB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 1 GiB
CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
requirements:
- virtual_binding: VDU1
CP2:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 1
requirements:
- virtual_binding: VDU1
CP3:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 2
requirements:
- virtual_binding: VDU1
- virtual_link: VL3
CP4:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 3
requirements:
- virtual_binding: VDU1
- virtual_link: VL4
VL3:
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: 33.33.0.0/24
VL4:
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: 44.44.0.0/24

View File

@ -18,10 +18,11 @@ from heatclient import client
from keystoneauth1 import fixture
from keystoneauth1 import loading
from keystoneauth1 import session
from openstack import connection
IDENTITY_URL = 'http://identityserver:5000/v3'
HEAT_URL = 'http://heat-api'
GLANCE_URL = 'http://image-api/v2'
class ClientFixture(fixtures.Fixture):
@ -37,20 +38,44 @@ class ClientFixture(fixtures.Fixture):
self.discovery = fixture.V2Discovery(href=self.identity_url)
s = self.token.add_service('orchestration')
s.add_endpoint(heat_url)
self.auth_url = '%s/tokens' % self.identity_url
def setUp(self):
super(ClientFixture, self).setUp()
auth_url = '%s/tokens' % self.identity_url
headers = {'X-Content-Type': 'application/json'}
self.requests_mock.post(auth_url,
self.requests_mock.post(self.auth_url,
json=self.token, headers=headers)
self.requests_mock.get(self.identity_url,
json=self.discovery, headers=headers)
self.client = self.new_client()
def new_client(self):
def _set_session(self):
self.session = session.Session()
loader = loading.get_plugin_loader('password')
self.session.auth = loader.load_from_options(
auth_url=self.identity_url, username='xx', password='xx')
def new_client(self):
self._set_session()
return client.Client("1", session=self.session)
class SdkConnectionFixture(ClientFixture):
"""Fixture class to access the apis via openstacksdk's Connection object.
This class is mocking the requests of glance api.
"""
def __init__(self, requests_mock, glance_url=GLANCE_URL):
super(SdkConnectionFixture, self).__init__(requests_mock)
s = self.token.add_service('image')
s.add_endpoint(glance_url)
def new_client(self):
self._set_session()
conn = connection.Connection(
region_name=None,
session=self.session,
identity_interface='internal',
image_api_version='2')
return conn

View File

@ -12,10 +12,15 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
import yaml
from tacker import objects
from tacker.objects import fields
from tacker.tests import uuidsentinel
def get_dummy_stack(outputs=True, status='CREATE_COMPELETE'):
def get_dummy_stack(outputs=True, status='CREATE_COMPELETE', attrs=None):
outputs_value = [{}]
if outputs:
outputs_value = [{'output_value': '192.168.120.216',
@ -33,22 +38,26 @@ def get_dummy_stack(outputs=True, status='CREATE_COMPELETE'):
'stack_owner': None,
'updated_time': None,
'id': uuidsentinel.instance_id}
if attrs:
dummy_stack.update(attrs)
return dummy_stack
def get_dummy_resource(resource_status='CREATE_COMPLETE'):
return {'resource_name': 'SP1_group',
def get_dummy_resource(resource_status='CREATE_COMPLETE',
resource_name='SP1_group', physical_resource_id=uuidsentinel.stack_id,
resource_type='OS::Heat::AutoScalingGroup'):
return {'resource_name': resource_name,
'logical_resource_id': 'SP1_group',
'creation_time': '2019-03-06T08:57:47Z',
'resource_status_reason': 'state changed',
'updated_time': '2019-03-06T08:57:47Z',
'required_by': ['SP1_scale_out', 'SP1_scale_in'],
'resource_status': resource_status,
'physical_resource_id': uuidsentinel.stack_id,
'physical_resource_id': physical_resource_id,
'attributes': {'outputs_list': None, 'refs': None,
'refs_map': None, 'outputs': None,
'current_size': None, 'mgmt_ip-vdu1': 'test1'},
'resource_type': 'OS::Heat::AutoScalingGroup'}
'resource_type': resource_type}
def get_dummy_event(resource_status='CREATE_COMPLETE'):
@ -68,3 +77,256 @@ def get_dummy_policy_dict():
'action': 'out',
'type': 'tosca.policies.tacker.Scaling',
'properties': {}}
def get_vnf_instance_object(instantiated_vnf_info=None,
instantiation_state=fields.VnfInstanceState.NOT_INSTANTIATED):
inst_vnf_info = instantiated_vnf_info or get_vnf_instantiated_info()
vnf_instance = objects.VnfInstance(id=uuidsentinel.vnf_instance_id,
vnf_instance_name="Test-Vnf-Instance",
vnf_instance_description="vnf instance description",
instantiation_state=instantiation_state, vnfd_id=uuidsentinel.vnfd_id,
vnf_provider="sample provider", vnf_product_name="vnf product name",
vnf_software_version='1.0', vnfd_version="2",
instantiated_vnf_info=inst_vnf_info)
return vnf_instance
def get_virtual_storage_resource_info(desc_id="VirtualStorage",
set_resource_id=True):
if set_resource_id:
resource_id = uuidsentinel.storage_resource_id
else:
resource_id = ""
resource_handle = objects.ResourceHandle(
resource_id=resource_id,
vim_level_resource_type="OS::Cinder::Volume")
storage_resource_info = objects.VirtualStorageResourceInfo(
id=uuidsentinel.storage_id,
virtual_storage_desc_id=desc_id,
storage_resource=resource_handle)
return storage_resource_info
def _get_virtual_link_port(virtual_link_port_id, cp_instance_id,
set_resource_id=False):
if set_resource_id:
resource_id = uuidsentinel.virtual_link_port_resource_id
else:
resource_id = ""
resource_handle = objects.ResourceHandle(
resource_id=resource_id,
vim_level_resource_type="OS::Neutron::Port")
v_l_port = objects.VnfLinkPortInfo(
id=virtual_link_port_id, cp_instance_id=cp_instance_id,
resource_handle=resource_handle)
return v_l_port
def get_virtual_link_resource_info(virtual_link_port_id, cp_instance_id,
desc_id="internalVL1", set_resource_id=True):
network_resource = objects.ResourceHandle(
resource_id=uuidsentinel.virtual_link_resource_id,
vim_level_resource_type="OS::Neutron::Network")
v_l_link_port = _get_virtual_link_port(virtual_link_port_id,
cp_instance_id=cp_instance_id, set_resource_id=set_resource_id)
v_l_resource_info = objects.VnfVirtualLinkResourceInfo(
id=uuidsentinel.v_l_resource_info_id,
vnf_virtual_link_desc_id=desc_id,
network_resource=network_resource, vnf_link_ports=[v_l_link_port])
return v_l_resource_info
def _get_ext_virtual_link_port(ext_v_l_port_id, cp_instance_id,
set_resource_id=False):
if set_resource_id:
resource_id = uuidsentinel.ext_virtual_link_port_resource_id
else:
resource_id = ""
resource_handle = objects.ResourceHandle(
resource_id=resource_id,
vim_level_resource_type="OS::Neutron::Port")
ext_v_l_port = objects.VnfLinkPortInfo(
id=ext_v_l_port_id, cp_instance_id=cp_instance_id,
resource_handle=resource_handle)
return ext_v_l_port
def get_ext_managed_virtual_link_resource_info(virtual_link_port_id,
cp_instance_id, desc_id="externalVL1", set_resource_id=True):
network_resource = objects.ResourceHandle(
resource_id=uuidsentinel.ext_managed_virtual_link_resource_id)
ext_v_l_link_port = _get_ext_virtual_link_port(virtual_link_port_id,
cp_instance_id=cp_instance_id, set_resource_id=set_resource_id)
ext_managed_v_l_resource_info = objects.ExtManagedVirtualLinkInfo(
id=uuidsentinel.v_l_resource_info_id,
vnf_virtual_link_desc_id=desc_id,
network_resource=network_resource,
vnf_link_ports=[ext_v_l_link_port])
return ext_managed_v_l_resource_info
def _get_vnfc_cp_info(virtual_link_port_id, cpd_id="CP1"):
vnfc_cp_info = objects.VnfcCpInfo(
id=uuidsentinel.vnfc_cp_info_id,
cpd_id=cpd_id,
cp_protocol_info=[],
vnf_link_port_id=virtual_link_port_id)
return vnfc_cp_info
def get_vnfc_resource_info(vdu_id="VDU1", storage_resource_ids=None,
set_resource_id=True):
storage_resource_ids = storage_resource_ids or []
if set_resource_id:
resource_id = uuidsentinel.vdu_resource_id
else:
resource_id = ""
resource_handle = objects.ResourceHandle(
resource_id=resource_id,
vim_level_resource_type="OS::Nova::Server")
vnfc_cp_info = _get_vnfc_cp_info(uuidsentinel.virtual_link_port_id)
vnfc_resource_info = objects.VnfcResourceInfo(
id=uuidsentinel.vnfc_resource_id, vdu_id=vdu_id,
compute_resource=resource_handle, vnfc_cp_info=[vnfc_cp_info],
storage_resource_ids=storage_resource_ids)
return vnfc_resource_info
def get_vnf_instantiated_info(flavour_id='simple',
instantiation_level_id=None, vnfc_resource_info=None,
virtual_storage_resource_info=None,
vnf_virtual_link_resource_info=None,
ext_managed_virtual_link_info=None):
vnfc_resource_info = vnfc_resource_info or []
vnf_virtual_link_resource_info = vnf_virtual_link_resource_info or []
virtual_storage_resource_info = virtual_storage_resource_info or []
ext_managed_virtual_link_info = ext_managed_virtual_link_info or []
inst_vnf_info = objects.InstantiatedVnfInfo(flavour_id=flavour_id,
instantiation_level_id=instantiation_level_id,
instance_id=uuidsentinel.instance_id,
vnfc_resource_info=vnfc_resource_info,
vnf_virtual_link_resource_info=vnf_virtual_link_resource_info,
virtual_storage_resource_info=virtual_storage_resource_info,
ext_managed_virtual_link_info=ext_managed_virtual_link_info)
return inst_vnf_info
def get_vnf_software_image_object(image_path=None):
image_path = image_path or ("http://download.cirros-cloud.net/0.4.0/"
"cirros-0.4.0-x86_64-disk.img")
vnf_software_image = objects.VnfSoftwareImage(
name='test-image', image_path=image_path,
min_disk=10, min_ram=4, disk_format="qcow2",
container_format="bare", hash="hash")
return vnf_software_image
def get_fake_glance_image_dict(image_path=None, status='pending_create',
hash_value='hash'):
"""Create a fake glance image.
:return:
Glance image dict with id, name, etc.
"""
if not image_path:
image_path = "http://localhost/cirros.img"
image_attrs = {"name": 'test-image', "image_path": image_path,
"id": uuidsentinel.image_id,
"min_disk": "fake_description",
"min_ram": "0",
"disk_format": "qcow2",
"container_format": "bare",
"hash_value": hash_value,
"status": status}
return image_attrs
def get_vnf_resource_object(resource_name="VDU1",
resource_type="OS::Nova::Server"):
vnf_resource = objects.VnfResource(
resource_identifier=uuidsentinel.resource_identifier,
id=uuidsentinel.vnf_resource_id,
resource_name=resource_name,
resource_type=resource_type,
vnf_instance_id=uuidsentinel.vnf_instance_id)
return vnf_resource
def get_vim_connection_info_object():
access_info = {'auth_url': 'http://127.0.1.0/identity/v3',
'cert_verify': True,
'password': 'devstack',
'project_name': 'nfv',
'username': 'nfv_user'}
vim_connection = objects.VimConnectionInfo(
id=uuidsentinel.vim_connection_id, vim_id=uuidsentinel.vim_id,
vim_type='openstack', access_info=access_info)
return vim_connection
def get_vnfd_dict():
filename = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "../data/",
'test_tosca_image.yaml')
with open(filename) as f:
vnfd_dict = {'vnfd': {'attributes': {'vnfd': str(yaml.safe_load(f))}}}
vnfd_dict.update({'id': '7ed39362-c551-4ce7-9ad2-17a98a6cee3d',
'name': None, 'attributes': {'param_values': "",
'stack_name': 'vnflcm_7ed39362-c551-4ce7-9ad2-17a98a6cee3d'},
'placement_attr': {'region_name': None}})
return vnfd_dict
def get_instantiate_vnf_request():
inst_vnf_req = objects.InstantiateVnfRequest(
flavour_id='simple')
return inst_vnf_req
def get_grant_response_dict():
grant_response_dict = {
'VDU1': [get_vnf_resource_object(resource_name='VDU1')],
'VirtualStorage': [get_vnf_resource_object(
resource_name='VirtualStorage')]}
return grant_response_dict

View File

@ -0,0 +1,297 @@
# Copyright 2015 Brocade Communications System, Inc.
# 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.
import codecs
import os
import yaml
from tacker import context
from tacker.extensions import vnfm
from tacker import objects
from tacker.tests.unit import base
from tacker.tests import uuidsentinel
from tacker.vnfm.infra_drivers.openstack.translate_template import TOSCAToHOT
class TestEtsiTranslateTemplate(base.TestCase):
def setUp(self):
super(TestEtsiTranslateTemplate, self).setUp()
self.tth = TOSCAToHOT(None, None)
self.tth.fields = {}
self.tth.vnf = {}
self.tth.vnf['attributes'] = {}
def _get_template(self, name):
filename = os.path.join(
os.path.dirname(os.path.abspath(__file__)), name)
with codecs.open(filename, encoding='utf-8', errors='strict') as f:
return f.read()
def _load_yaml(self, yaml_name, update_import=False):
filename = os.path.join(
os.path.dirname(os.path.abspath(__file__)), yaml_name)
file_abspath = os.path.dirname(os.path.abspath(filename))
with open(filename, 'r') as f:
heat_yaml = f.read()
heat_dict = yaml.safe_load(heat_yaml)
if update_import:
self._update_imports(heat_dict, file_abspath)
return heat_dict
def _update_imports(self, yaml_dict, file_abspath):
imports = yaml_dict['imports']
new_imports = []
for i in imports:
new_imports.append(file_abspath + '/' + i)
yaml_dict['imports'] = new_imports
def test_generate_hot_from_tosca(self):
tosca_file = './data/etsi_nfv/' \
'tosca_generate_hot_from_tosca.yaml'
hot_file = './data/etsi_nfv/hot/' \
'hot_generate_hot_from_tosca.yaml'
vnfd_dict = self._load_yaml(tosca_file, update_import=True)
# Input params
dev_attrs = {}
data = [{
"id": 'VL1',
"resource_id": 'neutron-network-uuid_VL1',
"ext_cps": [{
"cpd_id": "CP1",
"cp_config": [{
"cp_protocol_data": [{
"layer_protocol": "IP_OVER_ETHERNET",
"ip_over_ethernet": {
"mac_address": 'fa:16:3e:11:11:11',
"ip_addresses": [{
'type': 'IPV4',
'fixed_addresses': ['1.1.1.1'],
'subnet_id': 'neutron-subnet-uuid_CP1'}]}
}]
}]}]},
{
"id": 'VL2',
"resource_id": 'neutron-network-uuid_VL2',
"ext_cps": [{
"cpd_id": 'CP2',
"cp_config": [{
"link_port_id": uuidsentinel.link_port_id,
"cp_protocol_data": [{
"layer_protocol": "IP_OVER_ETHERNET"}]}]
}],
"ext_link_ports": [{
"id": uuidsentinel.link_port_id,
"resource_handle": {
"resource_id": 'neutron-port-uuid_CP2'}
}]}]
ext_mg_vl = [{'id': 'VL3', 'vnf_virtual_link_desc_id': 'VL3',
'resource_id': 'neutron-network-uuid_VL3'}]
request = {'ext_managed_virtual_links': ext_mg_vl,
'ext_virtual_links': data, 'flavour_id': 'simple'}
ctxt = context.get_admin_context()
inst_req_info = objects.InstantiateVnfRequest.obj_from_primitive(
request, ctxt)
# image and info
grant_info = {
'VDU1': [objects.VnfResource(id=uuidsentinel.id,
vnf_instance_id=uuidsentinel.vnf_instance_id,
resource_type='image',
resource_identifier='glance-image-uuid_VDU1')]}
self.tth._generate_hot_from_tosca(vnfd_dict, dev_attrs,
inst_req_info, grant_info)
expected_hot_tpl = self._load_yaml(hot_file)
actual_hot_tpl = yaml.safe_load(self.tth.heat_template_yaml)
self.assertEqual(expected_hot_tpl, actual_hot_tpl)
def test_generate_hot_from_tosca_with_scaling(self):
tosca_file = './data/etsi_nfv/' \
'tosca_generate_hot_from_tosca_with_scaling.yaml'
hot_file = './data/etsi_nfv/hot/' \
'scaling/' \
'hot_generate_hot_from_tosca_with_scaling.yaml'
hot_aspect_file = './data/etsi_nfv/hot/' \
'scaling/' \
'worker_instance.hot.yaml'
vnfd_dict = self._load_yaml(tosca_file, update_import=True)
# Input params
dev_attrs = {}
data = [{
"id": 'VL1',
"resource_id": 'neutron-network-uuid_VL1',
"ext_cps": [{
"cpd_id": "CP1",
"cp_config": [{
"cp_protocol_data": [{
"layer_protocol": "IP_OVER_ETHERNET",
"ip_over_ethernet": {
"ip_addresses": [{
'type': 'IPV4',
'subnet_id': 'neutron-subnet-uuid_CP1'}]}}]
}]}]},
{
"id": 'VL2',
"resource_id": 'neutron-network-uuid_VL2',
"ext_cps": [{
"cpd_id": 'CP2',
"cp_config": [{
"link_port_id": uuidsentinel.link_port_id,
"cp_protocol_data": [{
"layer_protocol": "IP_OVER_ETHERNET"}]}]
}],
"ext_link_ports": [{
"id": uuidsentinel.link_port_id,
"resource_handle": {
"resource_id": 'neutron-port-uuid_CP2'}
}]}]
ext_mg_vl = [{'id': 'VL3', 'vnf_virtual_link_desc_id': 'VL3',
'resource_id': 'neutron-network-uuid_VL3'}]
request = {'ext_managed_virtual_links': ext_mg_vl,
'ext_virtual_links': data, 'flavour_id': 'simple',
'instantiation_level_id': 'instantiation_level_1'}
ctxt = context.get_admin_context()
inst_req_info = objects.InstantiateVnfRequest.obj_from_primitive(
request, ctxt)
# image and info
grant_info = {
'VDU1': [objects.VnfResource(id=uuidsentinel.id,
vnf_instance_id=uuidsentinel.vnf_instance_id,
resource_type='image',
resource_identifier='glance-image-uuid_VDU1')]}
self.tth._generate_hot_from_tosca(vnfd_dict, dev_attrs,
inst_req_info, grant_info)
expected_hot_tpl = self._load_yaml(hot_file)
actual_hot_tpl = yaml.safe_load(self.tth.heat_template_yaml)
self.assertEqual(expected_hot_tpl, actual_hot_tpl)
expected_hot_aspect_tpl = self._load_yaml(hot_aspect_file)
actual_hot_aspect_tpl = \
yaml.safe_load(
self.tth.nested_resources['worker_instance.hot.yaml'])
self.assertEqual(expected_hot_aspect_tpl, actual_hot_aspect_tpl)
def test_generate_hot_from_tosca_with_substitution_mappings_error(self):
tosca_file = './data/etsi_nfv/' \
'tosca_generate_hot_from_tosca_' \
'with_substitution_mappings_error.yaml'
vnfd_dict = self._load_yaml(tosca_file, update_import=True)
dev_attrs = {}
self.assertRaises(vnfm.InvalidParamsForSM,
self.tth._generate_hot_from_tosca,
vnfd_dict,
dev_attrs,
None,
None)
def test_generate_hot_from_tosca_with_params_error(self):
tosca_file = './data/etsi_nfv/' \
'tosca_generate_hot_from_tosca_with_params_error.yaml'
param_file = './data/etsi_nfv/' \
'tosca_params_error.yaml'
vnfd_dict = self._load_yaml(tosca_file, update_import=True)
param_yaml = self._get_template(param_file)
dev_attrs = {
u'param_values': param_yaml
}
self.assertRaises(vnfm.ParamYAMLNotWellFormed,
self.tth._generate_hot_from_tosca,
vnfd_dict,
dev_attrs,
None,
None)
def test_generate_hot_from_tosca_parser_error(self):
tosca_file = './data/etsi_nfv/' \
'tosca_generate_hot_from_tosca_parser_error.yaml'
vnfd_dict = self._load_yaml(tosca_file, update_import=True)
# Input params
dev_attrs = {}
self.assertRaises(vnfm.ToscaParserFailed,
self.tth._generate_hot_from_tosca,
vnfd_dict,
dev_attrs,
None,
None)
def test_generate_hot_from_tosca_translator_error(self):
tosca_file = './data/etsi_nfv/' \
'tosca_generate_hot_from_tosca_translator_error.yaml'
vnfd_dict = self._load_yaml(tosca_file, update_import=True)
# Input params
dev_attrs = {}
self.assertRaises(vnfm.HeatTranslatorFailed,
self.tth._generate_hot_from_tosca,
vnfd_dict,
dev_attrs,
None,
None)
def test_generate_hot_from_tosca_with_scaling_invalid_inst_req(self):
tosca_file = './data/etsi_nfv/' \
'tosca_generate_hot_from_tosca_with_scaling_invalid_inst_req.yaml'
vnfd_dict = self._load_yaml(tosca_file, update_import=True)
# Input params
dev_attrs = {}
data = [{
"id": 'VL1',
"resource_id": 'neutron-network-uuid_VL1',
"ext_cps": [{
"cpd_id": "CP1",
"cp_config": [{
"cp_protocol_data": [{
"layer_protocol": "IP_OVER_ETHERNET",
"ip_over_ethernet": {
"ip_addresses": [{
'type': 'IPV4',
'fixed_addresses': ['1.1.1.1'],
'subnet_id': 'neutron-subnet-uuid_CP1'}]}
}]
}]
}]}
]
request = {'ext_virtual_links': data, 'flavour_id': 'simple'}
ctxt = context.get_admin_context()
inst_req_info = objects.InstantiateVnfRequest.obj_from_primitive(
request, ctxt)
self.assertRaises(vnfm.InvalidInstReqInfoForScaling,
self.tth._generate_hot_from_tosca,
vnfd_dict,
dev_attrs,
inst_req_info,
None)

View File

@ -14,8 +14,9 @@
# under the License.
import codecs
import mock
import os
import mock
import yaml
from oslo_serialization import jsonutils
@ -96,7 +97,7 @@ class FakeHeatClient(mock.Mock):
def _get_template(name):
filename = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "data/", name)
f = codecs.open(filename, encoding='utf-8', errors='strict')
with codecs.open(filename, encoding='utf-8', errors='strict') as f:
return f.read()
@ -119,6 +120,9 @@ class TestOpenStack(base.TestCase):
self._cos_db_plugin = \
common_services_db_plugin.CommonServicesPluginDb()
self.addCleanup(mock.patch.stopall)
yaml.SafeLoader.add_constructor(
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
lambda loader, node: dict(loader.construct_pairs(node)))
def _mock_heat_client(self):
self.heat_client = mock.Mock(wraps=FakeHeatClient())

View File

@ -15,7 +15,11 @@
import ddt
import mock
import os
import requests
import tempfile
from tacker.common import exceptions
from tacker import context
from tacker.extensions import vnfm
from tacker.tests.common import helpers
@ -31,18 +35,22 @@ from tacker.vnfm.infra_drivers.openstack import openstack
@ddt.ddt
class TestOpenStack(base.FixturedTestCase):
client_fixture_class = client.ClientFixture
sdk_connection_fixure_class = client.SdkConnectionFixture
def setUp(self):
super(TestOpenStack, self).setUp()
self.openstack = openstack.OpenStack()
self.context = context.get_admin_context()
self.url = client.HEAT_URL
self.heat_url = client.HEAT_URL
self.glance_url = client.GLANCE_URL
self.instance_uuid = uuidsentinel.instance_id
self.stack_id = uuidsentinel.stack_id
self.json_headers = {'content-type': 'application/json',
'location': 'http://heat-api/stacks/'
+ self.instance_uuid + '/myStack/60f83b5e'}
self._mock('tacker.common.clients.OpenstackClients.heat', self.cs)
mock.patch('tacker.common.clients.OpenstackSdkConnection.'
'openstack_connection', return_value=self.sdk_conn).start()
self.mock_log = mock.patch('tacker.vnfm.infra_drivers.openstack.'
'openstack.LOG').start()
mock.patch('time.sleep', return_value=None).start()
@ -51,7 +59,7 @@ class TestOpenStack(base.FixturedTestCase):
stack_outputs=True):
# response for heat_client's get()
for status in status_list:
url = self.url + '/stacks/' + self.instance_uuid
url = self.heat_url + '/stacks/' + self.instance_uuid
json = {'stack': fd_utils.get_dummy_stack(stack_outputs,
status=status)}
self.requests_mock.register_uri('GET', url, json=json,
@ -60,10 +68,10 @@ class TestOpenStack(base.FixturedTestCase):
def _response_in_resource_get(self, id, res_name=None):
# response for heat_client's resource_get()
if res_name:
url = self.url + '/stacks/' + id + ('/myStack/60f83b5e/'
url = self.heat_url + '/stacks/' + id + ('/myStack/60f83b5e/'
'resources/') + res_name
else:
url = self.url + '/stacks/' + id
url = self.heat_url + '/stacks/' + id
json = {'resource': fd_utils.get_dummy_resource()}
self.requests_mock.register_uri('GET', url, json=json,
@ -96,7 +104,7 @@ class TestOpenStack(base.FixturedTestCase):
"CREATE_COMPLETE"])
self._response_in_resource_get(self.instance_uuid,
res_name='SP1_group')
url = self.url + '/stacks/' + self.stack_id + '/resources'
url = self.heat_url + '/stacks/' + self.stack_id + '/resources'
json = {'resources': [fd_utils.get_dummy_resource()]}
self.requests_mock.register_uri('GET', url, json=json,
headers=self.json_headers)
@ -123,7 +131,7 @@ class TestOpenStack(base.FixturedTestCase):
None, None, vnf_dict, self.instance_uuid, {})
def _exception_response(self):
url = self.url + '/stacks/' + self.instance_uuid
url = self.heat_url + '/stacks/' + self.instance_uuid
body = {"error": Exception("any stuff")}
self.requests_mock.register_uri('GET', url, body=body,
status_code=404, headers=self.json_headers)
@ -202,13 +210,13 @@ class TestOpenStack(base.FixturedTestCase):
def _responses_in_resource_event_list(self, dummy_event):
# response for heat_client's resource_event_list()
url = self.url + '/stacks/' + self.instance_uuid
url = self.heat_url + '/stacks/' + self.instance_uuid
json = {'stack': [fd_utils.get_dummy_stack()]}
self.requests_mock.register_uri('GET', url, json=json,
headers=self.json_headers)
url = self.url + '/stacks/' + self.instance_uuid + ('/myStack/60f83b5e'
'/resources/SP1_scale_out/events?limit=1&sort_dir=desc&sort_keys='
'event_time')
url = self.heat_url + '/stacks/' + self.instance_uuid + (
'/myStack/60f83b5e/resources/SP1_scale_out/events?limit=1&sort_dir'
'=desc&sort_keys=event_time')
json = {'events': [dummy_event]}
self.requests_mock.register_uri('GET', url, json=json,
headers=self.json_headers)
@ -217,8 +225,8 @@ class TestOpenStack(base.FixturedTestCase):
dummy_event = fd_utils.get_dummy_event()
self._responses_in_resource_event_list(dummy_event)
# response for heat_client's resource_signal()
url = self.url + '/stacks/' + self.instance_uuid + ('/myStack/60f83b5e'
'/resources/SP1_scale_out/signal')
url = self.heat_url + '/stacks/' + self.instance_uuid + (
'/myStack/60f83b5e/resources/SP1_scale_out/signal')
self.requests_mock.register_uri('POST', url, json={},
headers=self.json_headers)
event_id = self.openstack.scale(plugin=self, context=self.context,
@ -229,7 +237,7 @@ class TestOpenStack(base.FixturedTestCase):
def _response_in_resource_get_list(self):
# response for heat_client's resource_get_list()
url = self.url + '/stacks/' + self.stack_id + '/resources'
url = self.heat_url + '/stacks/' + self.stack_id + '/resources'
json = {'resources': [fd_utils.get_dummy_resource()]}
self.requests_mock.register_uri('GET', url, json=json,
headers=self.json_headers)
@ -279,10 +287,10 @@ class TestOpenStack(base.FixturedTestCase):
def _response_in_resource_metadata(self, metadata=None):
# response for heat_client's resource_metadata()
url = self.url + '/stacks/' + self.instance_uuid + \
url = self.heat_url + '/stacks/' + self.instance_uuid + \
'/myStack/60f83b5e/resources/SP1_scale_out/metadata'
json = {'metadata': {'scaling_in_progress': metadata}}
self.requests_mock.register_uri('GET', url, json=json,
return self.requests_mock.register_uri('GET', url, json=json,
headers=self.json_headers)
def test_scale_wait_failed_with_stack_retries_0(self):
@ -317,3 +325,441 @@ class TestOpenStack(base.FixturedTestCase):
'so ignore it')
self.mock_log.warning.assert_called_once_with(error_reason)
self.assertEqual(b'{"vdu1": ["test1"]}', mgmt_ip)
def _responses_in_create_image(self, multiple_responses=False):
# response for glance_client's create()
json = fd_utils.get_fake_glance_image_dict()
url = os.path.join(self.glance_url, 'images')
if multiple_responses:
return self.requests_mock.register_uri(
'POST', url, [{'json': json, 'status_code': 201,
'headers': self.json_headers},
{'exc': requests.exceptions.ConnectTimeout}])
else:
return self.requests_mock.register_uri('POST', url, json=json,
headers=self.json_headers)
def _responses_in_import_image(self, raise_exception=False):
# response for glance_client's import()
json = fd_utils.get_fake_glance_image_dict()
url = os.path.join(
self.glance_url, 'images', uuidsentinel.image_id, 'import')
if raise_exception:
return self.requests_mock.register_uri('POST', url,
exc=requests.exceptions.ConnectTimeout)
else:
return self.requests_mock.register_uri('POST', url, json=json,
headers=self.json_headers)
def _responses_in_get_image(self, image_path=None, status='active',
hash_value='hash'):
# response for glance_client's import()
json = fd_utils.get_fake_glance_image_dict(image_path=image_path,
status=status,
hash_value=hash_value)
url = os.path.join(
self.glance_url, 'images', uuidsentinel.image_id)
return self.requests_mock.register_uri('GET', url, json=json,
headers=self.json_headers)
def _responses_in_upload_image(self, image_path=None, status='active',
hash_value='hash'):
# response for glance_client's upload()
json = fd_utils.get_fake_glance_image_dict(image_path=image_path,
status=status,
hash_value=hash_value)
url = os.path.join(
self.glance_url, 'images', uuidsentinel.image_id, 'file')
return self.requests_mock.register_uri('PUT', url, json=json,
headers=self.json_headers)
def test_pre_instantiation_vnf_image_with_file(self):
vnf_instance = fd_utils.get_vnf_instance_object()
# Create a temporary file as the openstacksdk will access it for
# calculating the hash value.
image_fd, image_path = tempfile.mkstemp()
vnf_software_image = fd_utils.get_vnf_software_image_object(
image_path=image_path)
vnf_software_images = {'node_name': vnf_software_image}
upload_image_url = self._responses_in_upload_image(image_path)
create_image_url = self._responses_in_create_image()
get_image_url = self._responses_in_get_image(image_path)
vnf_resources = self.openstack.pre_instantiation_vnf(
self.context, vnf_instance, None, vnf_software_images)
image_resource = vnf_resources['node_name'][0]
os.close(image_fd)
os.remove(image_path)
# Asserting the response as per the data given in the fake objects.
self.assertEqual(image_resource.resource_name,
'test-image')
self.assertEqual(image_resource.resource_status,
'CREATED')
self.assertEqual(image_resource.resource_type,
'image')
self.assertEqual(image_resource.vnf_instance_id,
vnf_instance.id)
self.assertEqual(upload_image_url.call_count, 1)
self.assertEqual(create_image_url.call_count, 1)
self.assertEqual(get_image_url.call_count, 2)
@mock.patch('tacker.common.utils.is_url', mock.MagicMock(
return_value=True))
def test_pre_instantiation_vnf_image_with_url(self):
image_path = "http://fake-url.net"
vnf_instance = fd_utils.get_vnf_instance_object()
vnf_software_image = fd_utils.get_vnf_software_image_object(
image_path=image_path)
vnf_software_images = {'node_name': vnf_software_image}
create_image_url = self._responses_in_create_image(image_path)
import_image_url = self._responses_in_import_image()
get_image_url = self._responses_in_get_image(image_path)
vnf_resources = self.openstack.pre_instantiation_vnf(
self.context, vnf_instance, None, vnf_software_images)
image_resource = vnf_resources['node_name'][0]
# Asserting the response as per the data given in the fake objects.
self.assertEqual(image_resource.resource_name,
'test-image')
self.assertEqual(image_resource.resource_status,
'CREATED')
self.assertEqual(image_resource.resource_type,
'image')
self.assertEqual(image_resource.vnf_instance_id,
vnf_instance.id)
self.assertEqual(create_image_url.call_count, 1)
self.assertEqual(import_image_url.call_count, 1)
self.assertEqual(get_image_url.call_count, 1)
@ddt.data(False, True)
def test_pre_instantiation_vnf_failed_in_image_creation(
self, exception_in_delete_image):
vnf_instance = fd_utils.get_vnf_instance_object()
vnf_software_image = fd_utils.get_vnf_software_image_object()
vnf_software_images = {'node_name1': vnf_software_image,
'node_name2': vnf_software_image}
# exception will occur in second iteration of image creation.
create_image_url = self._responses_in_create_image(
multiple_responses=True)
import_image_url = self._responses_in_import_image()
get_image_url = self._responses_in_get_image()
delete_image_url = self._response_in_delete_image(
uuidsentinel.image_id, exception=exception_in_delete_image)
self.assertRaises(exceptions.VnfPreInstantiationFailed,
self.openstack.pre_instantiation_vnf,
self.context, vnf_instance, None,
vnf_software_images)
self.assertEqual(create_image_url.call_count, 3)
self.assertEqual(import_image_url.call_count, 1)
self.assertEqual(get_image_url.call_count, 1)
delete_call_count = 2 if exception_in_delete_image else 1
self.assertEqual(delete_image_url.call_count, delete_call_count)
@ddt.data(False, True)
def test_pre_instantiation_vnf_failed_in_image_upload(
self, exception_in_delete_image):
vnf_instance = fd_utils.get_vnf_instance_object()
image_path = '/non/existent/file'
software_image_update = {'image_path': image_path}
vnf_software_image = fd_utils.get_vnf_software_image_object(
**software_image_update)
vnf_software_images = {'node_name1': vnf_software_image,
'node_name2': vnf_software_image}
# exception will occur in second iteration of image creation.
# No urls are accessed in this case because openstacksdk fails to
# access the file when it wants to calculate the hash.
self._responses_in_create_image(multiple_responses=True)
self._responses_in_upload_image(image_path)
self._responses_in_get_image()
self._response_in_delete_image(uuidsentinel.image_id,
exception=exception_in_delete_image)
self.assertRaises(exceptions.VnfPreInstantiationFailed,
self.openstack.pre_instantiation_vnf,
self.context, vnf_instance, None,
vnf_software_images)
def test_pre_instantiation_vnf_failed_with_mismatch_in_hash_value(self):
vnf_instance = fd_utils.get_vnf_instance_object()
vnf_software_image = fd_utils.get_vnf_software_image_object()
vnf_software_images = {'node_name1': vnf_software_image,
'node_name2': vnf_software_image}
# exception will occur in second iteration of image creation.
create_image_url = self._responses_in_create_image(
multiple_responses=True)
import_image_url = self._responses_in_import_image()
get_image_url = self._responses_in_get_image(
hash_value='diff-hash-value')
delete_image_url = self._response_in_delete_image(
uuidsentinel.image_id)
self.assertRaises(exceptions.VnfPreInstantiationFailed,
self.openstack.pre_instantiation_vnf,
self.context, vnf_instance, None,
vnf_software_images)
self.assertEqual(create_image_url.call_count, 1)
self.assertEqual(import_image_url.call_count, 1)
self.assertEqual(get_image_url.call_count, 1)
self.assertEqual(delete_image_url.call_count, 1)
def test_pre_instantiation_vnf_with_image_create_wait_failed(self):
vnf_instance = fd_utils.get_vnf_instance_object()
vnf_software_image = fd_utils.get_vnf_software_image_object()
vnf_software_images = {'node_name1': vnf_software_image,
'node_name2': vnf_software_image}
# exception will occurs in second iteration of image creation.
create_image_url = self._responses_in_create_image()
import_image_url = self._responses_in_import_image()
get_image_url = self._responses_in_get_image(status='pending_create')
self.assertRaises(exceptions.VnfPreInstantiationFailed,
self.openstack.pre_instantiation_vnf,
self.context, vnf_instance, None,
vnf_software_images)
self.assertEqual(create_image_url.call_count, 1)
self.assertEqual(import_image_url.call_count, 1)
self.assertEqual(get_image_url.call_count, 10)
def _exception_response_in_import_image(self):
url = os.path.join(self.glance_url, 'images', uuidsentinel.image_id,
'import')
return self.requests_mock.register_uri(
'POST', url, exc=requests.exceptions.ConnectTimeout)
def _response_in_delete_image(self, resource_id, exception=False):
# response for glance_client's delete()
url = os.path.join(
self.glance_url, 'images', resource_id)
if exception:
return self.requests_mock.register_uri(
'DELETE', url, exc=requests.exceptions.ConnectTimeout)
else:
return self.requests_mock.register_uri('DELETE', url, json={},
status_code=200,
headers=self.json_headers)
@ddt.data(True, False)
def test_pre_instantiation_vnf_failed_in_image_import(
self, exception_in_delete):
vnf_instance = fd_utils.get_vnf_instance_object()
vnf_software_image = fd_utils.get_vnf_software_image_object()
vnf_software_images = {'node_name': vnf_software_image}
create_image_url = self._responses_in_create_image()
import_image_exc_url = self._responses_in_import_image(
raise_exception=True)
delete_image_url = self._response_in_delete_image(
uuidsentinel.image_id, exception_in_delete)
self.assertRaises(exceptions.VnfPreInstantiationFailed,
self.openstack.pre_instantiation_vnf,
self.context, vnf_instance, None,
vnf_software_images)
self.assertEqual(create_image_url.call_count, 1)
self.assertEqual(import_image_exc_url.call_count, 2)
delete_call_count = 2 if exception_in_delete else 1
self.assertEqual(delete_image_url.call_count, delete_call_count)
@mock.patch('tacker.vnfm.infra_drivers.openstack.openstack.LOG')
def test_delete_vnf_instance_resource(self, mock_log):
vnf_instance = fd_utils.get_vnf_instance_object()
vnf_resource = fd_utils.get_vnf_resource_object()
delete_image_url = self._response_in_delete_image(
vnf_resource.resource_identifier)
self.openstack.delete_vnf_instance_resource(
self.context, vnf_instance, None, vnf_resource)
mock_log.info.assert_called()
self.assertEqual(delete_image_url.call_count, 1)
@mock.patch('tacker.vnfm.infra_drivers.openstack.openstack.LOG')
def test_delete_vnf_instance_resource_failed_with_exception(
self, mock_log):
vnf_instance = fd_utils.get_vnf_instance_object()
vnf_resource = fd_utils.get_vnf_resource_object()
delete_image_url = self._response_in_delete_image(
vnf_resource.resource_identifier, exception=True)
self.openstack.delete_vnf_instance_resource(
self.context, vnf_instance, None, vnf_resource)
mock_log.info.assert_called()
self.assertEqual(delete_image_url.call_count, 2)
@mock.patch('tacker.vnfm.infra_drivers.openstack.translate_template.'
'TOSCAToHOT._get_unsupported_resource_props')
def test_instantiate_vnf(self, mock_get_unsupported_resource_props):
vim_connection_info = fd_utils.get_vim_connection_info_object()
inst_req_info = fd_utils.get_instantiate_vnf_request()
vnfd_dict = fd_utils.get_vnfd_dict()
grant_response = fd_utils.get_grant_response_dict()
url = os.path.join(self.heat_url, 'stacks')
self.requests_mock.register_uri(
'POST', url, json={'stack': fd_utils.get_dummy_stack()},
headers=self.json_headers)
instance_id = self.openstack.instantiate_vnf(
self.context, None, vnfd_dict, vim_connection_info,
inst_req_info, grant_response)
self.assertEqual(uuidsentinel.instance_id, instance_id)
def _responses_in_stack_list(self, instance_id, resources=None):
resources = resources or []
url = os.path.join(self.heat_url, 'stacks', instance_id, 'resources')
self.requests_mock.register_uri('GET', url,
json={'resources': resources}, headers=self.json_headers)
response_list = [{'json': {'stacks': [fd_utils.get_dummy_stack(
attrs={'parent': uuidsentinel.instance_id})]}},
{'json': {'stacks': [fd_utils.get_dummy_stack()]}}]
url = os.path.join(self.heat_url, 'stacks?owner_id=' +
instance_id + '&show_nested=True')
self.requests_mock.register_uri('GET', url, response_list)
def test_post_vnf_instantiation(self):
v_s_resource_info = fd_utils.get_virtual_storage_resource_info(
desc_id="storage1", set_resource_id=False)
storage_resource_ids = [v_s_resource_info.id]
vnfc_resource_info = fd_utils.get_vnfc_resource_info(vdu_id="VDU_VNF",
storage_resource_ids=storage_resource_ids, set_resource_id=False)
v_l_resource_info = fd_utils.get_virtual_link_resource_info(
vnfc_resource_info.vnfc_cp_info[0].vnf_link_port_id,
vnfc_resource_info.vnfc_cp_info[0].id)
inst_vnf_info = fd_utils.get_vnf_instantiated_info(
virtual_storage_resource_info=[v_s_resource_info],
vnf_virtual_link_resource_info=[v_l_resource_info],
vnfc_resource_info=[vnfc_resource_info])
vnf_instance = fd_utils.get_vnf_instance_object(
instantiated_vnf_info=inst_vnf_info)
vim_connection_info = fd_utils.get_vim_connection_info_object()
resources = [{'resource_name': vnfc_resource_info.vdu_id,
'resource_type': vnfc_resource_info.compute_resource.
vim_level_resource_type,
'physical_resource_id': uuidsentinel.vdu_resource_id},
{'resource_name': v_s_resource_info.virtual_storage_desc_id,
'resource_type': v_s_resource_info.storage_resource.
vim_level_resource_type,
'physical_resource_id': uuidsentinel.storage_resource_id},
{'resource_name': vnfc_resource_info.vnfc_cp_info[0].cpd_id,
'resource_type': inst_vnf_info.vnf_virtual_link_resource_info[0].
vnf_link_ports[0].resource_handle.vim_level_resource_type,
'physical_resource_id': uuidsentinel.cp1_resource_id}]
self._responses_in_stack_list(inst_vnf_info.instance_id,
resources=resources)
self.openstack.post_vnf_instantiation(
self.context, vnf_instance, vim_connection_info)
self.assertEqual(vnf_instance.instantiated_vnf_info.
vnfc_resource_info[0].metadata['stack_id'],
inst_vnf_info.instance_id)
# Check if vnfc resource "VDU_VNF" is set with resource_id
self.assertEqual(uuidsentinel.vdu_resource_id,
vnf_instance.instantiated_vnf_info.vnfc_resource_info[0].
compute_resource.resource_id)
# Check if virtual storage resource "storage1" is set with resource_id
self.assertEqual(uuidsentinel.storage_resource_id,
vnf_instance.instantiated_vnf_info.
virtual_storage_resource_info[0].storage_resource.resource_id)
# Check if virtual link port "CP1" is set with resource_id
self.assertEqual(uuidsentinel.cp1_resource_id,
vnf_instance.instantiated_vnf_info.
vnf_virtual_link_resource_info[0].vnf_link_ports[0].
resource_handle.resource_id)
def test_post_vnf_instantiation_with_ext_managed_virtual_link(self):
v_s_resource_info = fd_utils.get_virtual_storage_resource_info(
desc_id="storage1", set_resource_id=False)
storage_resource_ids = [v_s_resource_info.id]
vnfc_resource_info = fd_utils.get_vnfc_resource_info(vdu_id="VDU_VNF",
storage_resource_ids=storage_resource_ids, set_resource_id=False)
v_l_resource_info = fd_utils.get_virtual_link_resource_info(
vnfc_resource_info.vnfc_cp_info[0].vnf_link_port_id,
vnfc_resource_info.vnfc_cp_info[0].id,
desc_id='ExternalVL1')
ext_managed_v_l_resource_info = \
fd_utils.get_ext_managed_virtual_link_resource_info(
uuidsentinel.virtual_link_port_id,
uuidsentinel.vnfc_cp_info_id,
desc_id='ExternalVL1')
inst_vnf_info = fd_utils.get_vnf_instantiated_info(
virtual_storage_resource_info=[v_s_resource_info],
vnf_virtual_link_resource_info=[v_l_resource_info],
vnfc_resource_info=[vnfc_resource_info],
ext_managed_virtual_link_info=[ext_managed_v_l_resource_info])
vnf_instance = fd_utils.get_vnf_instance_object(
instantiated_vnf_info=inst_vnf_info)
vim_connection_info = fd_utils.get_vim_connection_info_object()
resources = [{'resource_name': vnfc_resource_info.vdu_id,
'resource_type': vnfc_resource_info.compute_resource.
vim_level_resource_type,
'physical_resource_id': uuidsentinel.vdu_resource_id},
{'resource_name': v_s_resource_info.virtual_storage_desc_id,
'resource_type': v_s_resource_info.storage_resource.
vim_level_resource_type,
'physical_resource_id': uuidsentinel.storage_resource_id},
{'resource_name': vnfc_resource_info.vnfc_cp_info[0].cpd_id,
'resource_type': inst_vnf_info.vnf_virtual_link_resource_info[0].
vnf_link_ports[0].resource_handle.vim_level_resource_type,
'physical_resource_id': uuidsentinel.cp1_resource_id},
{'resource_name': v_l_resource_info.vnf_virtual_link_desc_id,
'resource_type': v_l_resource_info.network_resource.
vim_level_resource_type,
'physical_resource_id': uuidsentinel.v_l_resource_info_id}]
self._responses_in_stack_list(inst_vnf_info.instance_id,
resources=resources)
self.openstack.post_vnf_instantiation(
self.context, vnf_instance, vim_connection_info)
self.assertEqual(vnf_instance.instantiated_vnf_info.
vnfc_resource_info[0].metadata['stack_id'],
inst_vnf_info.instance_id)
# Check if vnfc resource "VDU_VNF" is set with resource_id
self.assertEqual(uuidsentinel.vdu_resource_id,
vnf_instance.instantiated_vnf_info.vnfc_resource_info[0].
compute_resource.resource_id)
# Check if virtual storage resource "storage1" is set with resource_id
self.assertEqual(uuidsentinel.storage_resource_id,
vnf_instance.instantiated_vnf_info.
virtual_storage_resource_info[0].storage_resource.resource_id)
# Check if virtual link port "CP1" is set with resource_id
self.assertEqual(uuidsentinel.cp1_resource_id,
vnf_instance.instantiated_vnf_info.
vnf_virtual_link_resource_info[0].vnf_link_ports[0].
resource_handle.resource_id)
# Check if ext managed virtual link port is set with resource_id
self.assertEqual(uuidsentinel.cp1_resource_id,
vnf_instance.instantiated_vnf_info.
ext_managed_virtual_link_info[0].vnf_link_ports[0].
resource_handle.resource_id)

View File

@ -44,6 +44,10 @@ BLOCKSTORAGE_ATTACHMENT = 'tosca.nodes.BlockStorageAttachment'
TOSCA_BINDS_TO = 'tosca.relationships.network.BindsTo'
VDU = 'tosca.nodes.nfv.VDU'
IMAGE = 'tosca.artifacts.Deployment.Image.VM'
ETSI_INST_LEVEL = 'tosca.policies.nfv.InstantiationLevels'
ETSI_SCALING_ASPECT = 'tosca.policies.nfv.ScalingAspects'
ETSI_SCALING_ASPECT_DELTA = 'tosca.policies.nfv.VduScalingAspectDeltas'
ETSI_INITIAL_DELTA = 'tosca.policies.nfv.VduInitialDelta'
HEAT_SOFTWARE_CONFIG = 'OS::Heat::SoftwareConfig'
OS_RESOURCES = {
'flavor': 'get_flavor_dict',
@ -106,7 +110,7 @@ def updateimports(template):
else:
template['imports'] = [defsfile]
if 'nfv' in template['tosca_definitions_version']:
if 'nfv' in template.get('tosca_definitions_version', {}):
nfvfile = path + 'tacker_nfv_defs.yaml'
template['imports'].append(nfvfile)
@ -482,7 +486,9 @@ def represent_odict(dump, tag, mapping, flow_style=None):
@log.log
def post_process_heat_template(heat_tpl, mgmt_ports, metadata,
alarm_resources, res_tpl, vol_res={},
unsupported_res_prop=None, unique_id=None):
unsupported_res_prop=None, unique_id=None,
inst_req_info=None, grant_info=None,
tosca=None):
#
# TODO(bobh) - remove when heat-translator can support literal strings.
#
@ -547,6 +553,15 @@ def post_process_heat_template(heat_tpl, mgmt_ports, metadata,
add_volume_resources(heat_dict, vol_res)
if unsupported_res_prop:
convert_unsupported_res_prop(heat_dict, unsupported_res_prop)
if grant_info:
convert_grant_info(heat_dict, grant_info)
if inst_req_info:
convert_inst_req_info(heat_dict, inst_req_info, tosca)
if heat_dict.get('parameters') and heat_dict.get(
'parameters', {}).get('vnfm_info'):
heat_dict.get('parameters').get('vnfm_info').update(
{'type': 'comma_delimited_list'})
yaml.SafeDumper.add_representer(OrderedDict,
lambda dumper, value: represent_odict(dumper,
@ -555,6 +570,344 @@ def post_process_heat_template(heat_tpl, mgmt_ports, metadata,
return yaml.safe_dump(heat_dict)
@log.log
def post_process_heat_template_for_scaling(
heat_tpl, mgmt_ports, metadata,
alarm_resources, res_tpl, vol_res={},
unsupported_res_prop=None, unique_id=None,
inst_req_info=None, grant_info=None,
tosca=None):
heat_dict = yamlparser.simple_ordered_parse(heat_tpl)
if inst_req_info:
check_inst_req_info_for_scaling(heat_dict, inst_req_info)
convert_inst_req_info(heat_dict, inst_req_info, tosca)
if grant_info:
convert_grant_info(heat_dict, grant_info)
yaml.SafeDumper.add_representer(OrderedDict,
lambda dumper, value: represent_odict(dumper,
u'tag:yaml.org,2002:map', value))
return yaml.safe_dump(heat_dict)
@log.log
def check_inst_req_info_for_scaling(heat_dict, inst_req_info):
# Check whether fixed ip_address or mac_address is set in CP,
# because CP with fixed IP address cannot be scaled.
if not inst_req_info.ext_virtual_links:
return
def _get_mac_ip(exp_cp):
mac = None
ip = None
for cp_conf in ext_cp.cp_config:
if cp_conf.cp_protocol_data is None:
continue
for cp_protocol in cp_conf.cp_protocol_data:
if cp_protocol.ip_over_ethernet is None:
continue
mac = cp_protocol.ip_over_ethernet.mac_address
for ip_address in \
cp_protocol.ip_over_ethernet.ip_addresses:
if ip_address.fixed_addresses:
ip = ip_address.fixed_addresses
return mac, ip
for ext_vl in inst_req_info.ext_virtual_links:
ext_cps = ext_vl.ext_cps
for ext_cp in ext_cps:
if not ext_cp.cp_config:
continue
mac, ip = _get_mac_ip(ext_cp)
cp_resource = heat_dict['resources'].get(ext_cp.cpd_id)
if cp_resource is not None:
if mac or ip:
raise vnfm.InvalidInstReqInfoForScaling()
@log.log
def convert_inst_req_info(heat_dict, inst_req_info, tosca):
# Case which extVls is defined.
ext_vl_infos = inst_req_info.ext_virtual_links
if ext_vl_infos is not None:
for ext_vl in ext_vl_infos:
_convert_ext_vls(heat_dict, ext_vl)
# Case which extMngVls is defined.
ext_mng_vl_infos = inst_req_info.ext_managed_virtual_links
if ext_mng_vl_infos is not None:
for ext_mng_vl in ext_mng_vl_infos:
_convert_ext_mng_vl(
heat_dict, ext_mng_vl.vnf_virtual_link_desc_id,
ext_mng_vl.resource_id)
# Check whether instLevelId is defined.
# Extract the initial number of scalable VDUs from the instantiation
# policy.
inst_level_id = inst_req_info.instantiation_level_id
# The format of dict required to calculate desired_capacity are
# shown below.
# { aspectId: { deltaId: deltaNum }}
aspect_delta_dict = {}
# { aspectId: [ vduId ]}
aspect_vdu_dict = {}
# { instLevelId: { aspectId: levelNum }}
inst_level_dict = {}
# { aspectId: deltaId }
aspect_id_dict = {}
# { vduId: initialDelta }
vdu_delta_dict = {}
tosca_policies = tosca.topology_template.policies
default_inst_level_id = _extract_policy_info(
tosca_policies, inst_level_dict,
aspect_delta_dict, aspect_id_dict,
aspect_vdu_dict, vdu_delta_dict)
if inst_level_id is not None:
# Case which instLevelId is defined.
_convert_desired_capacity(inst_level_id, inst_level_dict,
aspect_delta_dict, aspect_id_dict,
aspect_vdu_dict, vdu_delta_dict,
heat_dict)
elif inst_level_id is None and default_inst_level_id is not None:
# Case which instLevelId is not defined.
# In this case, use the default instLevelId.
_convert_desired_capacity(default_inst_level_id, inst_level_dict,
aspect_delta_dict, aspect_id_dict,
aspect_vdu_dict, vdu_delta_dict,
heat_dict)
else:
LOG.info('Because instLevelId is not defined and '
'there is no default level in TOSCA, '
'the conversion of desired_capacity is skipped.')
@log.log
def convert_grant_info(heat_dict, grant_info):
# Case which grant_info is defined.
if not grant_info:
return
for vdu_name, vnf_resources in grant_info.items():
_convert_grant_info_vdu(heat_dict, vdu_name, vnf_resources)
def _convert_ext_vls(heat_dict, ext_vl):
ext_cps = ext_vl.ext_cps
vl_id = ext_vl.resource_id
defined_ext_link_ports = [ext_link_port.resource_handle.resource_id
for ext_link_port in ext_vl.ext_link_ports]
def _replace_external_network_port(link_port_id, cpd_id):
for ext_link_port in ext_vl.ext_link_ports:
if ext_link_port.id == link_port_id:
if heat_dict['resources'].get(cpd_id) is not None:
_convert_ext_link_port(heat_dict, cpd_id,
ext_link_port.resource_handle.resource_id)
for ext_cp in ext_cps:
cp_resource = heat_dict['resources'].get(ext_cp.cpd_id)
if cp_resource is None:
return
# Update CP network properties to NEUTRON NETWORK-UUID
# defined in extVls.
cp_resource['properties']['network'] = vl_id
# Check whether extLinkPorts is defined.
for cp_config in ext_cp.cp_config:
for cp_protocol in cp_config.cp_protocol_data:
# Update the following CP properties to the values defined
# in extVls.
# - subnet
# - ip_address
# - mac_address
ip_over_ethernet = cp_protocol.ip_over_ethernet
if ip_over_ethernet:
if ip_over_ethernet.mac_address or\
ip_over_ethernet.ip_addresses:
if ip_over_ethernet.mac_address:
cp_resource['properties']['mac_address'] =\
ip_over_ethernet.mac_address
if ip_over_ethernet.ip_addresses:
_convert_fixed_ips_list(
'ip_address',
ip_over_ethernet.ip_addresses,
cp_resource)
elif defined_ext_link_ports:
_replace_external_network_port(cp_config.link_port_id,
ext_cp.cpd_id)
def _convert_fixed_ips_list(cp_key, cp_val, cp_resource):
for val in cp_val:
new_dict = {}
if val.fixed_addresses:
new_dict['ip_address'] = ''.join(val.fixed_addresses)
if val.subnet_id:
new_dict['subnet'] = val.subnet_id
fixed_ips_list = cp_resource['properties'].get('fixed_ips')
# Add if it doesn't exist yet.
if fixed_ips_list is None:
cp_resource['properties']['fixed_ips'] = [new_dict]
# Update if it already exists.
else:
for index, fixed_ips in enumerate(fixed_ips_list):
if fixed_ips.get(cp_key) is not None:
fixed_ips_list[index] = new_dict
else:
fixed_ips_list.append(new_dict)
sorted_list = sorted(fixed_ips_list)
cp_resource['properties']['fixed_ips'] = sorted_list
def _convert_ext_link_port(heat_dict, cp_name, ext_link_port):
# Delete CP resource and update VDU's properties
# related to CP defined in extLinkPorts.
del heat_dict['resources'][cp_name]
for rsrc_info in heat_dict['resources'].values():
if rsrc_info['type'] == 'OS::Nova::Server':
vdu_networks = rsrc_info['properties']['networks']
for index, vdu_network in enumerate(vdu_networks):
if isinstance(vdu_network['port'], dict) and\
vdu_network['port'].get('get_resource') == cp_name:
new_dict = {'port': ext_link_port}
rsrc_info['properties']['networks'][index] = new_dict
def _convert_ext_mng_vl(heat_dict, vl_name, vl_id):
# Delete resources related to VL defined in extMngVLs.
if heat_dict['resources'].get(vl_name) is not None:
del heat_dict['resources'][vl_name]
del heat_dict['resources'][vl_name + '_subnet']
del heat_dict['resources'][vl_name + '_qospolicy']
del heat_dict['resources'][vl_name + '_bandwidth']
for rsrc_info in heat_dict['resources'].values():
# Update CP's properties related to VL defined in extMngVls.
if rsrc_info['type'] == 'OS::Neutron::Port':
cp_network = rsrc_info['properties']['network']
if isinstance(cp_network, dict) and\
cp_network.get('get_resource') == vl_name:
rsrc_info['properties']['network'] = vl_id
# Update AutoScalingGroup's properties related to VL defined
# in extMngVls.
elif rsrc_info['type'] == 'OS::Heat::AutoScalingGroup':
asg_rsrc_props = \
rsrc_info['properties']['resource'].get('properties')
for vl_key, vl_val in asg_rsrc_props.items():
if vl_val.get('get_resource') == vl_name:
asg_rsrc_props[vl_key] = vl_id
def _extract_policy_info(tosca_policies, inst_level_dict,
aspect_delta_dict, aspect_id_dict,
aspect_vdu_dict, vdu_delta_dict):
default_inst_level_id = None
if tosca_policies is not []:
for p in tosca_policies:
if p.type == ETSI_SCALING_ASPECT_DELTA:
vdu_list = p.targets
aspect_id = p.properties['aspect']
deltas = p.properties['deltas']
delta_id_dict = {}
for delta_id, delta_val in deltas.items():
delta_num = delta_val['number_of_instances']
delta_id_dict[delta_id] = delta_num
aspect_delta_dict[aspect_id] = delta_id_dict
aspect_vdu_dict[aspect_id] = vdu_list
elif p.type == ETSI_INST_LEVEL:
inst_levels = p.properties['levels']
for level_id, inst_val in inst_levels.items():
scale_info = inst_val['scale_info']
aspect_level_dict = {}
for aspect_id, scale_level in scale_info.items():
aspect_level_dict[aspect_id] = \
scale_level['scale_level']
inst_level_dict[level_id] = aspect_level_dict
default_inst_level_id = p.properties.get('default_level')
# On TOSCA definitions, step_deltas is list and
# multiple description is possible,
# but only single description is supported.
# (first win)
# Like heat-translator.
elif p.type == ETSI_SCALING_ASPECT:
aspects = p.properties['aspects']
for aspect_id, aspect_val in aspects.items():
delta_names = aspect_val['step_deltas']
delta_name = delta_names[0]
aspect_id_dict[aspect_id] = delta_name
elif p.type == ETSI_INITIAL_DELTA:
vdus = p.targets
initial_delta = \
p.properties['initial_delta']['number_of_instances']
for vdu in vdus:
vdu_delta_dict[vdu] = initial_delta
return default_inst_level_id
def _convert_desired_capacity(inst_level_id, inst_level_dict,
aspect_delta_dict, aspect_id_dict,
aspect_vdu_dict, vdu_delta_dict,
heat_dict):
al_dict = inst_level_dict.get(inst_level_id)
if al_dict is not None:
# Get level_num.
for aspect_id, level_num in al_dict.items():
delta_id = aspect_id_dict.get(aspect_id)
# Get delta_num.
if delta_id is not None:
delta_num = \
aspect_delta_dict.get(aspect_id).get(delta_id)
# Get initial_delta.
vdus = aspect_vdu_dict.get(aspect_id)
initial_delta = None
for vdu in vdus:
initial_delta = vdu_delta_dict.get(vdu)
if initial_delta is not None:
# Calculate desired_capacity.
desired_capacity = initial_delta + delta_num * level_num
# Convert desired_capacity on HOT.
for rsrc_key, rsrc_info in heat_dict['resources'].items():
if rsrc_info['type'] == 'OS::Heat::AutoScalingGroup' and \
rsrc_key == aspect_id:
rsrc_info['properties']['desired_capacity'] = \
desired_capacity
else:
LOG.warning('Because target instLevelId is not defined in TOSCA, '
'the conversion of desired_capacity is skipped.')
pass
def _convert_grant_info_vdu(heat_dict, vdu_name, vnf_resources):
for vnf_resource in vnf_resources:
if vnf_resource.resource_type == "image":
# Update VDU's properties related to
# image defined in grant_info.
vdu_info = heat_dict.get('resources').get(vdu_name)
if vdu_info is not None:
vdu_props = vdu_info.get('properties')
if vdu_props.get('image') is None:
vdu_props.update({'image':
vnf_resource.resource_identifier})
@log.log
def add_volume_resources(heat_dict, vol_res):
# Add cinder volumes
@ -821,34 +1174,41 @@ def get_scaling_group_dict(ht_template, scaling_policy_names):
return scaling_group_dict
def get_nested_resources_name(template):
for policy in template.policies:
if (policy.type_definition.is_derived_from(SCALING)):
nested_resource_name = policy.name + '_res.yaml'
return nested_resource_name
def get_nested_resources_name(hot):
nested_resource_names = []
hot_yaml = yaml.safe_load(hot)
for r_key, r_val in hot_yaml.get('resources').items():
if r_val.get('type') == 'OS::Heat::AutoScalingGroup':
nested_resource_name = r_val.get('properties', {}).get(
'resource', {}).get('type', None)
nested_resource_names.append(nested_resource_name)
return nested_resource_names
def get_sub_heat_tmpl_name(template):
for policy in template.policies:
if (policy.type_definition.is_derived_from(SCALING)):
sub_heat_tmpl_name = policy.name + '_' + \
uuidutils.generate_uuid() + '_res.yaml'
return sub_heat_tmpl_name
def get_sub_heat_tmpl_name(tmpl_name):
return uuidutils.generate_uuid() + tmpl_name
def update_nested_scaling_resources(nested_resources, mgmt_ports, metadata,
res_tpl, unsupported_res_prop=None):
res_tpl, unsupported_res_prop=None,
grant_info=None, inst_req_info=None):
nested_tpl = dict()
if nested_resources:
nested_resource_name, nested_resources_yaml =\
list(nested_resources.items())[0]
for nested_resource_name, nested_resources_yaml in \
nested_resources.items():
nested_resources_dict =\
yamlparser.simple_ordered_parse(nested_resources_yaml)
if metadata.get('vdus'):
for vdu_name, metadata_dict in metadata['vdus'].items():
if nested_resources_dict['resources'].get(vdu_name):
nested_resources_dict['resources'][vdu_name]['properties']['metadata'] = \
metadata_dict
vdu_dict = nested_resources_dict['resources'][vdu_name]
vdu_dict['properties']['metadata'] = metadata_dict
convert_grant_info(nested_resources_dict, grant_info)
# Replace external virtual links if specified in the inst_req_info
if inst_req_info is not None:
for ext_vl in inst_req_info.ext_virtual_links:
_convert_ext_vls(nested_resources_dict, ext_vl)
add_resources_tpl(nested_resources_dict, res_tpl)
for res in nested_resources_dict["resources"].values():
if not res['type'] == HEAT_SOFTWARE_CONFIG:
@ -861,6 +1221,7 @@ def update_nested_scaling_resources(nested_resources, mgmt_ports, metadata,
convert_unsupported_res_prop(nested_resources_dict,
unsupported_res_prop)
if mgmt_ports:
for outputname, portname in mgmt_ports.items():
ipval = {'get_attr': [portname, 'fixed_ips', 0, 'ip_address']}
output = {outputname: {'value': ipval}}
@ -869,9 +1230,11 @@ def update_nested_scaling_resources(nested_resources, mgmt_ports, metadata,
else:
nested_resources_dict['outputs'] = output
LOG.debug(_('Added output for %s'), outputname)
yaml.SafeDumper.add_representer(
OrderedDict, lambda dumper, value: represent_odict(
dumper, u'tag:yaml.org,2002:map', value))
nested_tpl[nested_resource_name] =\
yaml.safe_dump(nested_resources_dict)
return nested_tpl

View File

View File

@ -0,0 +1,33 @@
# 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.
import abc
import six
@six.add_metaclass(abc.ABCMeta)
class VnfInstanceAbstractDriver(object):
@abc.abstractmethod
def instantiate_vnf(self, context, vnf_instance_id, instantiate_vnf_req):
"""instantiate vnf request.
:param context: context
:param vnf_instance_id: uuid of vnf_instance
:param instantiate_vnf_req: object of InstantiateVnfRequest
:return: None
"""
pass

731
tacker/vnflcm/utils.py Normal file
View File

@ -0,0 +1,731 @@
# 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.
import io
import os
import six
import yaml
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import uuidutils
from toscaparser import tosca_template
from tacker.common import exceptions
from tacker.common import utils
from tacker.extensions import nfvo
from tacker import objects
from tacker.objects import fields
from tacker.tosca import utils as toscautils
from tacker.vnfm import vim_client
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
def _get_vim(context, vim_connection_info):
vim_client_obj = vim_client.VimClient()
if vim_connection_info:
vim_id = vim_connection_info[0].vim_id
access_info = vim_connection_info[0].access_info
if access_info:
region_name = access_info.get('region')
else:
region_name = None
else:
vim_id = None
region_name = None
try:
vim_res = vim_client_obj.get_vim(
context, vim_id, region_name=region_name)
except nfvo.VimNotFoundException:
raise exceptions.VimConnectionNotFound(vim_id=vim_id)
vim_res['vim_auth'].update({'region': region_name})
vim_info = {'id': vim_res['vim_id'], 'vim_id': vim_res['vim_id'],
'vim_type': vim_res['vim_type'],
'access_info': vim_res['vim_auth']}
return vim_info
def _get_vnfd_dict(context, vnfd_id, flavour_id):
vnf_package_id = _get_vnf_package_id(context, vnfd_id)
vnf_package_base_path = cfg.CONF.vnf_package.vnf_package_csar_path
vnf_package_csar_path = vnf_package_base_path + '/' + vnf_package_id
vnfd_dict = _get_flavour_based_vnfd(vnf_package_csar_path, flavour_id)
# Remove requirements from substitution mapping
vnfd_dict.get('topology_template').get(
'substitution_mappings').pop('requirements')
return vnfd_dict
def _get_vnf_package_id(context, vnfd_id):
vnf_package = objects.VnfPackageVnfd.get_by_id(context, vnfd_id)
return vnf_package.package_uuid
def _create_grant_request(vnfd_dict, package_uuid):
node_templates = vnfd_dict.get('topology_template',
{}).get('node_templates', {})
vnf_software_images = {}
if not node_templates:
return vnf_software_images
def _build_vnf_software_image(sw_image_data, artifact_image_path):
vnf_sw_image = objects.VnfSoftwareImage()
vnf_sw_image.image_path = artifact_image_path
vnf_sw_image.name = sw_image_data.get('name')
vnf_sw_image.version = sw_image_data.get('version')
if sw_image_data.get('checksum'):
checksum = sw_image_data.get('checksum')
if checksum.get('algorithm'):
vnf_sw_image.algorithm = checksum.get('algorithm')
if checksum.get('hash'):
vnf_sw_image.hash = checksum.get('hash')
vnf_sw_image.container_format = sw_image_data.get(
'container_format')
vnf_sw_image.disk_format = sw_image_data.get('disk_format')
if sw_image_data.get('min_disk'):
min_disk = utils.MemoryUnit.convert_unit_size_to_num(
sw_image_data.get('min_disk'), 'GB')
vnf_sw_image.min_disk = min_disk
else:
vnf_sw_image.min_disk = 0
if sw_image_data.get('min_ram'):
min_ram = utils.MemoryUnit.convert_unit_size_to_num(
sw_image_data.get('min_ram'), 'MB')
vnf_sw_image.min_ram = min_ram
else:
vnf_sw_image.min_ram = 0
return vnf_sw_image
def _get_image_path(artifact_image_path, package_uuid):
vnf_package_path = cfg.CONF.vnf_package.vnf_package_csar_path
artifact_image_path = os.path.join(
vnf_package_path, package_uuid,
artifact_image_path.split('../')[-1])
return artifact_image_path
for node, value in node_templates.items():
if not value.get(
'type') in ['tosca.nodes.nfv.Vdu.Compute',
'tosca.nodes.nfv.Vdu.VirtualBlockStorage']:
continue
sw_image_data = value.get('properties', {}).get('sw_image_data')
artifacts = value.get('artifacts', {})
for artifact, sw_image in artifacts.items():
artifact_image_path = None
if isinstance(sw_image, six.string_types):
artifact_image_path = sw_image
elif sw_image.get('type') == 'tosca.artifacts.nfv.SwImage':
artifact_image_path = sw_image.get('file', {})
if sw_image_data and artifact_image_path:
is_url = utils.is_url(artifact_image_path)
if not is_url:
artifact_image_path = _get_image_path(artifact_image_path,
package_uuid)
vnf_software_image = _build_vnf_software_image(
sw_image_data, artifact_image_path)
vnf_software_images[node] = vnf_software_image
break
return vnf_software_images
def _make_final_vnf_dict(vnfd_dict, id, name, param_values):
return {'vnfd': {
'attributes': {
'vnfd': str(vnfd_dict)}},
'id': id,
'name': name,
'attributes': {
'param_values': str(param_values),
'stack_name': name or ("vnflcm_" + id)}}
def _get_flavour_based_vnfd(csar_path, flavour_id):
ext = [".yaml", ".yml"]
file_path_and_data = {}
imp_list = []
for item in os.listdir(csar_path):
src_path = os.path.join(csar_path, item)
if os.path.isdir(src_path):
for file in os.listdir(src_path):
if file.endswith(tuple(ext)):
source_file_path = os.path.join(src_path, file)
with open(source_file_path) as file_obj:
data = yaml.safe_load(file_obj)
substitution_map = data.get(
'topology_template',
{}).get('substitution_mappings', {})
if substitution_map.get(
'properties', {}).get('flavour_id') == flavour_id:
if data.get('imports'):
for imp in data.get('imports'):
imp_path = os.path.join(src_path, imp)
imp_list.append(imp_path)
data.update({'imports': imp_list})
return data
elif src_path.endswith(tuple(ext)):
file_data = yaml.safe_load(io.open(src_path))
substitution_map = file_data.get(
'topology_template', {}).get('substitution_mappings', {})
if substitution_map.get(
'properties', {}).get('flavour_id') == flavour_id:
if file_data.get('imports'):
for imp in file_data.get('imports'):
imp_list.append(os.path.join(src_path, imp))
file_data.update({'imports': imp_list})
return file_data
return file_path_and_data
def _get_param_data(vnfd_dict, instantiate_vnf_req):
param_value = {}
additional_param = instantiate_vnf_req.additional_params
if additional_param is None:
additional_param = {}
substitution_map = vnfd_dict.get('topology_template',
{}).get('substitution_mappings', {})
input_attributes = vnfd_dict.get('topology_template', {}).get('inputs')
if substitution_map is not None:
subs_map_node_type = substitution_map.get('node_type')
import_paths = vnfd_dict.get('imports')
for imp_path in import_paths:
with open(imp_path) as file_obj:
import_data = yaml.safe_load(file_obj)
imp_node_type = import_data.get('node_types')
if imp_node_type:
for key, value in imp_node_type.items():
if key == subs_map_node_type:
properties = value.get('properties')
for key, prop in properties.items():
if additional_param.get(key):
param_value.update({
key: additional_param.get(key)})
else:
param_value.update({key: prop.get('default')})
for input_attr, value in input_attributes.items():
if additional_param.get(input_attr):
param_value.update({input_attr: additional_param.get(
input_attr)})
return param_value
def _get_vim_connection_info_from_vnf_req(vnf_instance, instantiate_vnf_req):
vim_connection_obj_list = []
if not instantiate_vnf_req.vim_connection_info:
return vim_connection_obj_list
for vim_connection in instantiate_vnf_req.vim_connection_info:
vim_conn = objects.VimConnectionInfo(id=vim_connection.id,
vim_id=vim_connection.vim_id, vim_type=vim_connection.vim_type,
access_info=vim_connection.access_info)
vim_connection_obj_list.append(vim_conn)
return vim_connection_obj_list
def _build_instantiated_vnf_info(vnfd_dict, instantiate_vnf_req,
vnf_instance, vim_id):
inst_vnf_info = vnf_instance.instantiated_vnf_info
inst_vnf_info.vnf_state = fields.VnfOperationalStateType.STARTED
inst_vnf_info.ext_cp_info = _set_ext_cp_info(instantiate_vnf_req)
inst_vnf_info.ext_virtual_link_info = _set_ext_virtual_link_info(
instantiate_vnf_req, inst_vnf_info.ext_cp_info)
node_templates = vnfd_dict.get(
'topology_template', {}).get('node_templates')
vnfc_resource_info, virtual_storage_resource_info = \
_get_vnfc_resource_info(vnfd_dict, instantiate_vnf_req, vim_id)
inst_vnf_info.vnfc_resource_info = vnfc_resource_info
inst_vnf_info.virtual_storage_resource_info = \
virtual_storage_resource_info
inst_vnf_info.vnf_virtual_link_resource_info = \
_build_vnf_virtual_link_resource_info(
node_templates, instantiate_vnf_req,
inst_vnf_info.vnfc_resource_info, vim_id)
inst_vnf_info.ext_managed_virtual_link_info = \
_build_ext_managed_virtual_link_info(instantiate_vnf_req,
inst_vnf_info)
vnf_instance.instantiated_vnf_info = inst_vnf_info
def _get_compute_nodes(vnfd_dict, instantiate_vnf_req):
"""Read the node templates and prepare VDU data in below format
{
'VDU1': {
'CP': [CP1, CP2],
'VIRTUAL_STORAGE': [virtual_storage1]
},
}
"""
node_templates = vnfd_dict.get(
'topology_template', {}).get('node_templates')
vdu_resources = {}
for key, value in node_templates.items():
if value.get('type') != 'tosca.nodes.nfv.Vdu.Compute':
continue
desired_capacity = _convert_desired_capacity(
instantiate_vnf_req.instantiation_level_id, vnfd_dict, key)
cp_list = _get_cp_for_vdu(key, node_templates)
virtual_storages = []
requirements = value.get('requirements', [])
for requirement in requirements:
if requirement.get('virtual_storage'):
virtual_storages.append(
requirement.get('virtual_storage'))
vdu_resources[key] = {"CP": cp_list,
"VIRTUAL_STORAGE": virtual_storages,
"COUNT": desired_capacity}
return vdu_resources
def _get_virtual_link_nodes(node_templates):
virtual_link_nodes = {}
for key, value in node_templates.items():
if value.get('type') == 'tosca.nodes.nfv.VnfVirtualLink':
cp_list = _get_cp_for_vl(key, node_templates)
virtual_link_nodes[key] = cp_list
return virtual_link_nodes
def _get_cp_for_vdu(vdu, node_templates):
cp_list = []
for key, value in node_templates.items():
if value.get('type') != 'tosca.nodes.nfv.VduCp':
continue
requirements = value.get('requirements', [])
for requirement in requirements:
if requirement.get('virtual_binding') and vdu == \
requirement.get('virtual_binding'):
cp_list.append(key)
return cp_list
def _get_cp_for_vl(vl, node_templates):
cp_list = []
for key, value in node_templates.items():
if value.get('type') != 'tosca.nodes.nfv.VduCp':
continue
requirements = value.get('requirements', [])
for requirement in requirements:
if requirement.get('virtual_link') and vl == \
requirement.get('virtual_link'):
cp_list.append(key)
return cp_list
def _build_vnf_virtual_link_resource_info(node_templates, instantiate_vnf_req,
vnfc_resource_info, vim_id):
virtual_link_nodes_with_cp = _get_virtual_link_nodes(node_templates)
# Read the external networks and extcps from InstantiateVnfRequest
for ext_virt_link in instantiate_vnf_req.ext_virtual_links:
virtual_link_nodes_with_cp[ext_virt_link.id] = [extcp.cpd_id for extcp
in ext_virt_link.ext_cps]
virtual_link_resource_info_list = []
def _get_network_resource(vl_node):
resource_handle = objects.ResourceHandle()
found = False
for ext_mg_vl in instantiate_vnf_req.ext_managed_virtual_links:
if ext_mg_vl.vnf_virtual_link_desc_id == vl_node:
resource_handle.resource_id = ext_mg_vl.resource_id
# TODO(tpatil): This cannot be set here.
resource_handle.vim_level_resource_type = \
'OS::Neutron::Net'
found = True
break
if not found:
# check if it exists in the ext_virtual_links
for ext_virt_link in instantiate_vnf_req.ext_virtual_links:
if ext_virt_link.id == vl_node:
resource_handle.resource_id = ext_virt_link.resource_id
# TODO(tpatil): This cannot be set here.
resource_handle.vim_level_resource_type = \
'OS::Neutron::Net'
found = True
break
return resource_handle
def _get_vnf_link_port_info(cp):
vnf_link_port_info = objects.VnfLinkPortInfo()
vnf_link_port_info.id = uuidutils.generate_uuid()
resource_handle = objects.ResourceHandle()
for ext_virt_link in instantiate_vnf_req.ext_virtual_links:
for extcp in ext_virt_link.ext_cps:
if extcp.cpd_id == cp:
for cpconfig in extcp.cp_config:
if cpconfig.link_port_id:
resource_handle.resource_id = \
cpconfig.link_port_id
# TODO(tpatil): This shouldn't be set here.
resource_handle.vim_level_resource_type = \
'OS::Neutron::Port'
break
vnf_link_port_info.resource_handle = resource_handle
return vnf_link_port_info
for node, cp_list in virtual_link_nodes_with_cp.items():
vnf_vl_resource_info = objects.VnfVirtualLinkResourceInfo()
vnf_vl_resource_info.id = uuidutils.generate_uuid()
vnf_vl_resource_info.vnf_virtual_link_desc_id = node
vnf_vl_resource_info.network_resource = _get_network_resource(node)
vnf_link_port_info_list = []
for cp in cp_list:
for vnfc_resource in vnfc_resource_info:
for vnfc_cp in vnfc_resource.vnfc_cp_info:
if vnfc_cp.cpd_id == cp:
vnf_link_port_info = _get_vnf_link_port_info(cp)
vnf_link_port_info.cp_instance_id = vnfc_cp.id
# Identifier of the "vnfLinkPorts" structure in the
# "vnfVirtualLinkResourceInfo" structure.
vnfc_cp.vnf_link_port_id = vnf_link_port_info.id
vnf_link_port_info_list.append(vnf_link_port_info)
vnf_vl_resource_info.vnf_link_ports = vnf_link_port_info_list
virtual_link_resource_info_list.append(vnf_vl_resource_info)
return virtual_link_resource_info_list
def _build_vnf_cp_info(instantiate_vnf_req, cp_list):
vnfc_cp_info_list = []
if not cp_list:
return vnfc_cp_info_list
def _set_vnf_exp_cp_id_protocol_data(vnfc_cp_info):
for ext_virt_link in instantiate_vnf_req.ext_virtual_links:
for extcp in ext_virt_link.ext_cps:
if extcp.cpd_id == cp:
vnfc_cp_info.cp_protocol_info = \
_set_cp_protocol_info(extcp)
for cpconfig in extcp.cp_config:
vnfc_cp_info.vnf_ext_cp_id = cpconfig.link_port_id
break
for cp in cp_list:
vnfc_cp_info = objects.VnfcCpInfo()
vnfc_cp_info.id = uuidutils.generate_uuid()
vnfc_cp_info.cpd_id = cp
_set_vnf_exp_cp_id_protocol_data(vnfc_cp_info)
vnfc_cp_info_list.append(vnfc_cp_info)
return vnfc_cp_info_list
def _build_virtual_storage_info(virtual_storages):
for storage_node in virtual_storages:
virtual_storage = objects.VirtualStorageResourceInfo()
virtual_storage.id = uuidutils.generate_uuid()
virtual_storage.virtual_storage_desc_id = storage_node
virtual_storage.storage_resource = objects.ResourceHandle()
yield virtual_storage
def _get_vnfc_resource_info(vnfd_dict, instantiate_vnf_req, vim_id):
vdu_resources = _get_compute_nodes(vnfd_dict, instantiate_vnf_req)
vnfc_resource_info_list = []
virtual_storage_resource_info_list = []
def _build_vnfc_resource_info(vdu, vdu_resource):
vnfc_resource_info = objects.VnfcResourceInfo()
vnfc_resource_info.id = uuidutils.generate_uuid()
vnfc_resource_info.vdu_id = vdu
vnfc_resource_info.compute_resource = objects.ResourceHandle()
vnfc_cp_info_list = _build_vnf_cp_info(instantiate_vnf_req,
vdu_resource.get("CP"))
vnfc_resource_info.vnfc_cp_info = vnfc_cp_info_list
virtual_storages = vdu_resource.get("VIRTUAL_STORAGE")
vdu_storages = []
for storage in _build_virtual_storage_info(virtual_storages):
vdu_storages.append(storage)
virtual_storage_resource_info_list.append(storage)
storage_resource_ids = [info.id for info in vdu_storages]
vnfc_resource_info.storage_resource_ids = storage_resource_ids
return vnfc_resource_info
for vdu, vdu_resource in vdu_resources.items():
count = vdu_resource.get('COUNT', 1)
for num_instance in range(count):
vnfc_resource_info = _build_vnfc_resource_info(vdu, vdu_resource)
vnfc_resource_info_list.append(vnfc_resource_info)
return vnfc_resource_info_list, virtual_storage_resource_info_list
def _set_ext_cp_info(instantiate_vnf_req):
ext_cp_info_list = []
if not instantiate_vnf_req.ext_virtual_links:
return ext_cp_info_list
for ext_virt_link in instantiate_vnf_req.ext_virtual_links:
if not ext_virt_link.ext_cps:
continue
for ext_cp in ext_virt_link.ext_cps:
ext_cp_info = objects.VnfExtCpInfo(
id=uuidutils.generate_uuid(),
cpd_id=ext_cp.cpd_id,
cp_protocol_info=_set_cp_protocol_info(ext_cp),
ext_link_port_id=_get_ext_link_port_id(ext_virt_link,
ext_cp.cpd_id))
ext_cp_info_list.append(ext_cp_info)
return ext_cp_info_list
def _get_ext_link_port_id(ext_virtual_link, cpd_id):
if not ext_virtual_link.ext_link_ports:
return
for ext_link in ext_virtual_link.ext_link_ports:
if ext_link.id == cpd_id:
return ext_link.id
def _build_ip_over_ethernet_address_info(cp_protocol_data):
"""Convert IpOverEthernetAddressData to IpOverEthernetAddressInfo"""
if not cp_protocol_data.ip_over_ethernet:
return
ip_over_ethernet_add_info = objects.IpOverEthernetAddressInfo()
ip_over_ethernet_add_info.mac_address = \
cp_protocol_data.ip_over_ethernet.mac_address
if not cp_protocol_data.ip_over_ethernet.ip_addresses:
return ip_over_ethernet_add_info
ip_address_list = []
for ip_address in cp_protocol_data.ip_over_ethernet.ip_addresses:
ip_address_info = objects.vnf_instantiated_info.IpAddress(
type=ip_address.type,
addresses=ip_address.fixed_addresses,
is_dynamic=(False if ip_address.fixed_addresses else True),
subnet_id=ip_address.subnet_id)
ip_address_list.append(ip_address_info)
ip_over_ethernet_add_info.ip_addresses = ip_address_list
return ip_over_ethernet_add_info
def _build_cp_protocol_info(cp_protocol_data):
ip_over_ethernet_add_info = _build_ip_over_ethernet_address_info(
cp_protocol_data)
cp_protocol_info = objects.CpProtocolInfo(
layer_protocol=cp_protocol_data.layer_protocol,
ip_over_ethernet=ip_over_ethernet_add_info)
return cp_protocol_info
def _set_cp_protocol_info(ext_cp):
"""Convert CpProtocolData to CpProtocolInfo"""
cp_protocol_info_list = []
if not ext_cp.cp_config:
return cp_protocol_info_list
for cp_config in ext_cp.cp_config:
for cp_protocol_data in cp_config.cp_protocol_data:
cp_protocol_info = _build_cp_protocol_info(cp_protocol_data)
cp_protocol_info_list.append(cp_protocol_info)
return cp_protocol_info_list
def _set_ext_virtual_link_info(instantiate_vnf_req, ext_cp_info):
ext_virtual_link_list = []
if not instantiate_vnf_req.ext_virtual_links:
return ext_virtual_link_list
for ext_virtual_link in instantiate_vnf_req.ext_virtual_links:
res_handle = objects.ResourceHandle()
res_handle.resource_id = ext_virtual_link.resource_id
ext_virtual_link_info = objects.ExtVirtualLinkInfo(
id=ext_virtual_link.id,
resource_handle=res_handle,
ext_link_ports=_set_ext_link_port(ext_virtual_link,
ext_cp_info))
ext_virtual_link_list.append(ext_virtual_link_info)
return ext_virtual_link_list
def _set_ext_link_port(ext_virtual_links, ext_cp_info):
ext_link_port_list = []
if not ext_virtual_links.ext_link_ports:
return ext_link_port_list
for ext_link_port in ext_virtual_links.ext_link_ports:
resource_handle = ext_link_port.resource_handle.obj_clone()
cp_instance_id = None
if ext_virtual_links.ext_cps:
for ext_cp in ext_cp_info:
cp_instance_id = ext_cp.id
ext_link_port_info = objects.ExtLinkPortInfo(id=ext_link_port.id,
resource_handle=resource_handle, cp_instance_id=cp_instance_id)
ext_link_port_list.append(ext_link_port_info)
return ext_link_port_list
def _build_ext_managed_virtual_link_info(instantiate_vnf_req, inst_vnf_info):
def _network_resource(ext_managed_vl):
resource_handle = objects.ResourceHandle(
resource_id=ext_managed_vl.resource_id)
# TODO(tpatil): Remove hard coding of resource type as
# OS::Neutron::Net resource type is specific to OpenStack infra
# driver. It could be different for other infra drivers like
# Kubernetes.
resource_handle.vim_level_resource_type = 'OS::Neutron::Net'
return resource_handle
ext_managed_virtual_link_info = []
ext_managed_virt_link_from_req = \
instantiate_vnf_req.ext_managed_virtual_links
for ext_managed_vl in ext_managed_virt_link_from_req:
ext_managed_virt_info = objects.ExtManagedVirtualLinkInfo()
ext_managed_virt_info.id = ext_managed_vl.id
ext_managed_virt_info.vnf_virtual_link_desc_id =\
ext_managed_vl.vnf_virtual_link_desc_id
ext_managed_virt_info.network_resource =\
_network_resource(ext_managed_vl)
# Populate the vnf_link_ports from vnf_virtual_link_resource_info
# of instantiated_vnf_info.
for vnf_vl_res_info in inst_vnf_info.vnf_virtual_link_resource_info:
if ext_managed_vl.vnf_virtual_link_desc_id ==\
vnf_vl_res_info.vnf_virtual_link_desc_id:
vnf_link_ports = []
for vnf_lp in vnf_vl_res_info.vnf_link_ports:
vnf_link_ports.append(vnf_lp.obj_clone())
ext_managed_virt_info.vnf_link_ports = vnf_link_ports
ext_managed_virtual_link_info.append(ext_managed_virt_info)
return ext_managed_virtual_link_info
def _convert_desired_capacity(inst_level_id, vnfd_dict, vdu):
aspect_delta_dict = {}
aspect_vdu_dict = {}
inst_level_dict = {}
aspect_id_dict = {}
vdu_delta_dict = {}
desired_capacity = 1
tosca = tosca_template.ToscaTemplate(parsed_params={}, a_file=False,
yaml_dict_tpl=vnfd_dict)
tosca_policies = tosca.topology_template.policies
default_inst_level_id = toscautils._extract_policy_info(
tosca_policies, inst_level_dict,
aspect_delta_dict, aspect_id_dict,
aspect_vdu_dict, vdu_delta_dict)
if vdu_delta_dict.get(vdu) is None:
return desired_capacity
if inst_level_id:
instantiation_level = inst_level_id
elif default_inst_level_id:
instantiation_level = default_inst_level_id
else:
return desired_capacity
al_dict = inst_level_dict.get(instantiation_level)
if not al_dict:
return desired_capacity
for aspect_id, level_num in al_dict.items():
delta_id = aspect_id_dict.get(aspect_id)
if delta_id is not None:
delta_num = \
aspect_delta_dict.get(aspect_id).get(delta_id)
vdus = aspect_vdu_dict.get(aspect_id)
initial_delta = None
for vdu in vdus:
initial_delta = vdu_delta_dict.get(vdu)
if initial_delta is not None:
desired_capacity = initial_delta + delta_num * level_num
return desired_capacity

View File

@ -0,0 +1,153 @@
# 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.
import copy
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import encodeutils
from oslo_utils import excutils
from tacker.common import log
from tacker.common import driver_manager
from tacker.common import exceptions
from tacker import objects
from tacker.objects import fields
from tacker.vnflcm import abstract_driver
from tacker.vnflcm import utils as vnflcm_utils
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver):
def __init__(self):
super(VnfLcmDriver, self).__init__()
self._vnf_manager = driver_manager.DriverManager(
'tacker.tacker.vnfm.drivers',
cfg.CONF.tacker.infra_driver)
def _vnf_instance_update(self, context, vnf_instance, **kwargs):
"""Update vnf instance in the database using kwargs as value."""
for k, v in kwargs.items():
setattr(vnf_instance, k, v)
vnf_instance.save()
def _instantiate_vnf(self, context, vnf_instance, vim_connection_info,
instantiate_vnf_req):
vnfd_dict = vnflcm_utils._get_vnfd_dict(context, vnf_instance.vnfd_id,
instantiate_vnf_req.flavour_id)
param_for_subs_map = vnflcm_utils._get_param_data(vnfd_dict,
instantiate_vnf_req)
package_uuid = vnflcm_utils._get_vnf_package_id(context,
vnf_instance.vnfd_id)
vnf_software_images = vnflcm_utils._create_grant_request(vnfd_dict,
package_uuid)
vnf_resources = self._vnf_manager.invoke(
vim_connection_info.vim_type, 'pre_instantiation_vnf',
context=context, vnf_instance=vnf_instance,
vim_connection_info=vim_connection_info,
vnf_software_images=vnf_software_images)
# save the vnf resources in the db
for _, resources in vnf_resources.items():
for vnf_resource in resources:
vnf_resource.create()
vnfd_dict_to_create_final_dict = copy.deepcopy(vnfd_dict)
final_vnf_dict = vnflcm_utils._make_final_vnf_dict(
vnfd_dict_to_create_final_dict, vnf_instance.id,
vnf_instance.vnf_instance_name, param_for_subs_map)
try:
instance_id = self._vnf_manager.invoke(
vim_connection_info.vim_type, 'instantiate_vnf',
context=context, vnf_instance=vnf_instance,
vnfd_dict=final_vnf_dict, grant_response=vnf_resources,
vim_connection_info=vim_connection_info,
instantiate_vnf_req=instantiate_vnf_req)
except Exception as exp:
with excutils.save_and_reraise_exception():
exp.reraise = False
LOG.error("Unable to instantiate vnf instance "
"%(id)s due to error : %(error)s",
{"id": vnf_instance.id, "error":
encodeutils.exception_to_unicode(exp)})
raise exceptions.VnfInstantiationFailed(
id=vnf_instance.id,
error=encodeutils.exception_to_unicode(exp))
vnf_instance.instantiated_vnf_info = objects.InstantiatedVnfInfo(
flavour_id=instantiate_vnf_req.flavour_id,
instantiation_level_id=instantiate_vnf_req.instantiation_level_id,
vnf_instance_id=vnf_instance.id,
instance_id=instance_id,
ext_cp_info=[])
try:
self._vnf_manager.invoke(
vim_connection_info.vim_type, 'create_wait',
plugin=self, context=context,
vnf_dict=final_vnf_dict,
vnf_id=final_vnf_dict['instance_id'],
auth_attr=vim_connection_info.access_info)
except Exception as exp:
with excutils.save_and_reraise_exception():
exp.reraise = False
LOG.error("Vnf creation wait failed for vnf instance "
"%(id)s due to error : %(error)s",
{"id": vnf_instance.id, "error":
encodeutils.exception_to_unicode(exp)})
raise exceptions.VnfInstantiationWaitFailed(
id=vnf_instance.id,
error=encodeutils.exception_to_unicode(exp))
vnflcm_utils._build_instantiated_vnf_info(vnfd_dict,
instantiate_vnf_req, vnf_instance, vim_connection_info.vim_id)
self._vnf_manager.invoke(vim_connection_info.vim_type,
'post_vnf_instantiation', context=context,
vnf_instance=vnf_instance,
vim_connection_info=vim_connection_info)
@log.log
def instantiate_vnf(self, context, vnf_instance, instantiate_vnf_req):
vim_connection_info_list = vnflcm_utils.\
_get_vim_connection_info_from_vnf_req(vnf_instance,
instantiate_vnf_req)
self._vnf_instance_update(context, vnf_instance,
vim_connection_info=vim_connection_info_list)
vim_info = vnflcm_utils._get_vim(context,
instantiate_vnf_req.vim_connection_info)
vim_connection_info = objects.VimConnectionInfo.obj_from_primitive(
vim_info, context)
self._instantiate_vnf(context, vnf_instance, vim_connection_info,
instantiate_vnf_req)
self._vnf_instance_update(context, vnf_instance,
instantiation_state=fields.VnfInstanceState.INSTANTIATED,
task_state=None)

View File

@ -73,3 +73,32 @@ class VnfAbstractDriver(extensions.PluginInterface):
@abc.abstractmethod
def heal_vdu(self, plugin, context, vnf_dict, heal_request_data):
pass
@abc.abstractmethod
def pre_instantiation_vnf(self, context, vnf_instance,
vim_connection_info, vnf_software_images):
"""Create resources required for instantiating Vnf.
:param context: A RequestContext
:param vnf_instance: Object tacker.objects.VnfInstance
:vim_info: Credentials to initialize Vim connection
:vnf_software_images: Dict of key:value pair,
<VDU/Storage node name>:tacker.objects.VnfSoftwareImage.
"""
pass
@abc.abstractmethod
def delete_vnf_instance_resource(self, context, vnf_instance,
vim_connection_info, vnf_resource):
pass
@abc.abstractmethod
def instantiate_vnf(self, context, vnf_instance, vnfd_dict,
vim_connection_info, instantiate_vnf_req,
grant_response):
pass
@abc.abstractmethod
def post_vnf_instantiation(self, context, vnf_instance,
vim_connection_info):
pass

View File

@ -550,3 +550,20 @@ class Kubernetes(abstract_driver.VnfAbstractDriver,
def heal_vdu(self, plugin, context, vnf_dict, heal_request_data):
pass
def pre_instantiation_vnf(self, context, vnf_instance,
vim_connection_info, image_data):
raise NotImplementedError()
def delete_vnf_instance_resource(self, context, vnf_instance,
vim_connection_info, vnf_resource):
raise NotImplementedError()
def instantiate_vnf(self, context, vnf_instance, vnfd_dict,
vim_connection_info, instantiate_vnf_req,
grant_response):
raise NotImplementedError()
def post_vnf_instantiation(self, context, vnf_instance,
vim_connection_info):
raise NotImplementedError()

View File

@ -76,3 +76,20 @@ class VnfNoop(abstract_driver.VnfAbstractDriver):
def heal_vdu(self, plugin, context, vnf_dict, heal_request_data):
pass
def pre_instantiation_vnf(self, context, vnf_instance,
vim_connection_info, image_data):
pass
def delete_vnf_instance_resource(self, context, vnf_instance,
vim_connection_info, vnf_resource):
pass
def instantiate_vnf(self, context, vnf_instance, vnfd_dict,
vim_connection_info, instantiate_vnf_req,
grant_response):
pass
def post_vnf_instantiation(self, context, vnf_instance,
vim_connection_info):
pass

View File

@ -0,0 +1,63 @@
# 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.
import sys
from oslo_log import log as logging
from glanceclient import exc
from tacker.common import clients
from tacker.extensions import vnflcm
LOG = logging.getLogger(__name__)
class GlanceClient(object):
def __init__(self, vim_connection_info, version=None):
super(GlanceClient, self).__init__()
self.connection = clients.OpenstackSdkConnection(
vim_connection_info, version).connection
def create(self, name, **fields):
try:
return self.connection.image.create_image(
name, allow_duplicates=True, **fields)
except exc.HTTPException:
type_, value, tb = sys.exc_info()
raise vnflcm.GlanceClientException(msg=value)
def delete(self, image_id):
try:
self.connection.image.delete_image(image_id)
except exc.HTTPNotFound:
LOG.warning("Image %(image)s created not found "
"at cleanup", {'image': image_id})
def import_image(self, image, web_path):
try:
self.connection.image.import_image(
image, method='web-download', uri=web_path)
except exc.HTTPException:
type_, value, tb = sys.exc_info()
raise vnflcm.GlanceClientException(msg=value)
def get(self, image_id):
try:
return self.connection.image.get_image(image_id)
except exc.HTTPNotFound:
LOG.warning("Image %(image)s created not found ",
{'image': image_id})

View File

@ -29,6 +29,16 @@ class HeatClient(object):
self.resource_types = self.heat.resource_types
self.resources = self.heat.resources
def _stack_ids(self, stack_id):
filters = {"owner_id": stack_id,
"show_nested": True}
for stack in self.stacks.list(**{"filters": filters}):
yield stack.id
if stack.parent and stack.parent == stack_id:
for x in self._stack_ids(stack.id):
yield x
def create(self, fields):
fields = fields.copy()
fields.update({
@ -53,6 +63,13 @@ class HeatClient(object):
def get(self, stack_id):
return self.stacks.get(stack_id)
def get_stack_nested_depth(self, stack_id):
stack_ids = self._stack_ids(stack_id)
if stack_ids:
return len(list(stack_ids))
return 0
def update(self, stack_id, **kwargs):
try:
return self.stacks.update(stack_id, **kwargs)

View File

@ -14,19 +14,26 @@
# License for the specific language governing permissions and limitations
# under the License.
import time
from oslo_config import cfg
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_utils import encodeutils
from oslo_utils import excutils
import yaml
from tacker._i18n import _
from tacker.common import exceptions
from tacker.common import log
from tacker.common import utils
from tacker.extensions import vnflcm
from tacker.extensions import vnfm
from tacker import objects
from tacker.vnfm.infra_drivers import abstract_driver
from tacker.vnfm.infra_drivers.openstack import constants as infra_cnst
from tacker.vnfm.infra_drivers.openstack import glance_client as gc
from tacker.vnfm.infra_drivers.openstack import heat_client as hc
from tacker.vnfm.infra_drivers.openstack import translate_template
from tacker.vnfm.infra_drivers.openstack import vdu
@ -85,6 +92,8 @@ class OpenStack(abstract_driver.VnfAbstractDriver,
super(OpenStack, self).__init__()
self.STACK_RETRIES = cfg.CONF.openstack_vim.stack_retries
self.STACK_RETRY_WAIT = cfg.CONF.openstack_vim.stack_retry_wait
self.IMAGE_RETRIES = 10
self.IMAGE_RETRY_WAIT = 10
def get_type(self):
return 'openstack'
@ -96,13 +105,14 @@ class OpenStack(abstract_driver.VnfAbstractDriver,
return 'Openstack infra driver'
@log.log
def create(self, plugin, context, vnf, auth_attr):
def create(self, plugin, context, vnf, auth_attr,
inst_req_info=None, grant_info=None):
LOG.debug('vnf %s', vnf)
region_name = vnf.get('placement_attr', {}).get('region_name', None)
heatclient = hc.HeatClient(auth_attr, region_name)
tth = translate_template.TOSCAToHOT(vnf, heatclient)
tth = translate_template.TOSCAToHOT(vnf, heatclient,
inst_req_info, grant_info)
tth.generate_hot()
stack = self._create_stack(heatclient, tth.vnf, tth.fields)
return stack['stack']['id']
@ -442,3 +452,347 @@ class OpenStack(abstract_driver.VnfAbstractDriver,
except Exception:
LOG.error("VNF '%s' failed to heal", vnf_dict['id'])
raise vnfm.VNFHealFailed(vnf_id=vnf_dict['id'])
@log.log
def pre_instantiation_vnf(self, context, vnf_instance,
vim_connection_info, vnf_software_images):
glance_client = gc.GlanceClient(vim_connection_info)
vnf_resources = {}
def _roll_back_images():
# Delete all previously created images for vnf
for key, resources in vnf_resources.items():
for vnf_resource in resources:
try:
glance_client.delete(vnf_resource.resource_identifier)
except Exception:
LOG.error("Failed to delete image %(uuid)s "
"for vnf %(id)s",
{"uuid": vnf_resource.resource_identifier,
"id": vnf_instance.id})
for node_name, vnf_sw_image in vnf_software_images.items():
name = vnf_sw_image.name
image_path = vnf_sw_image.image_path
is_url = utils.is_url(image_path)
if not is_url:
filename = image_path
else:
filename = None
try:
LOG.info("Creating image %(name)s for vnf %(id)s",
{"name": name, "id": vnf_instance.id})
image_data = {"min_disk": vnf_sw_image.min_disk,
"min_ram": vnf_sw_image.min_ram,
"disk_format": vnf_sw_image.disk_format,
"container_format": vnf_sw_image.container_format,
"visibility": "private"}
if filename:
image_data.update({"filename": filename})
image = glance_client.create(name, **image_data)
LOG.info("Image %(name)s created successfully for vnf %(id)s",
{"name": name, "id": vnf_instance.id})
except Exception as exp:
with excutils.save_and_reraise_exception():
exp.reraise = False
LOG.error("Failed to create image %(name)s for vnf %(id)s"
"due to error: %(error)s",
{"name": name, "id": vnf_instance.id,
"error": encodeutils.exception_to_unicode(exp)})
# Delete previously created images
_roll_back_images()
raise exceptions.VnfPreInstantiationFailed(
id=vnf_instance.id,
error=encodeutils.exception_to_unicode(exp))
try:
if is_url:
glance_client.import_image(image, image_path)
self._image_create_wait(image.id, vnf_sw_image.hash,
glance_client, 'active', vnflcm.ImageCreateWaitFailed)
vnf_resource = objects.VnfResource(context=context,
vnf_instance_id=vnf_instance.id,
resource_name=name, resource_type="image",
resource_status="CREATED", resource_identifier=image.id)
vnf_resources[node_name] = [vnf_resource]
except Exception as exp:
with excutils.save_and_reraise_exception():
exp.reraise = False
LOG.error("Image %(name)s not active for vnf %(id)s"
"error: %(error)s",
{"name": name, "id": vnf_instance.id,
"error": encodeutils.exception_to_unicode(exp)})
err_msg = "Failed to delete image %(uuid)s for vnf %(id)s"
# Delete the image
try:
glance_client.delete(image.id)
except Exception:
LOG.error(err_msg, {"uuid": image.id,
"id": vnf_instance.id})
# Delete all previously created images for vnf
_roll_back_images()
raise exceptions.VnfPreInstantiationFailed(
id=vnf_instance.id,
error=encodeutils.exception_to_unicode(exp))
return vnf_resources
def _image_create_wait(self, image_uuid, hash_value, glance_client,
expected_status, exception_class):
retries = self.IMAGE_RETRIES
while retries > 0:
retries = retries - 1
image = glance_client.get(image_uuid)
status = image.status
if status == expected_status:
# NOTE(tpatil): If image is uploaded using import_image
# ,sdk doesn't validate checksum. So, verify checksum/hash
# for both scenarios upload from file and URL here.
if hash_value != image.hash_value:
msg = 'Image %s checksum verification failed'
raise Exception(msg % image_uuid)
LOG.debug('Image status: %(image_uuid)s %(status)s',
{'image_uuid': image_uuid, 'status': status})
return True
time.sleep(self.IMAGE_RETRY_WAIT)
LOG.debug('Image %(image_uuid)s status: %(status)',
{"image_uuid": image_uuid, "status": status})
if retries == 0 and image.status != expected_status:
error_reason = ("Image {image_uuid} could not get active "
"within {wait} seconds").format(
wait=(self.IMAGE_RETRIES *
self.IMAGE_RETRY_WAIT),
image_uuid=image_uuid)
raise exception_class(reason=error_reason)
@log.log
def delete_vnf_instance_resource(self, context, vnf_instance,
vim_connection_info, vnf_resource):
LOG.info("Deleting resource '%(name)s' of type ' %(type)s' for vnf"
"%(id)s", {"type": vnf_resource.resource_type,
"name": vnf_resource.resource_name,
"id": vnf_instance.id})
glance_client = gc.GlanceClient(vim_connection_info)
try:
glance_client.delete(vnf_resource.resource_identifier)
LOG.info("Deleted resource '%(name)s' of type ' %(type)s' for vnf"
"%(id)s", {"type": vnf_resource.resource_type,
"name": vnf_resource.resource_name,
"id": vnf_instance.id})
except Exception:
LOG.info("Failed to delete resource '%(name)s' of type"
" %(type)s' for vnf %(id)s",
{"type": vnf_resource.resource_type,
"name": vnf_resource.resource_name,
"id": vnf_instance.id})
def instantiate_vnf(self, context, vnf_instance, vnfd_dict,
vim_connection_info, instantiate_vnf_req,
grant_response):
access_info = vim_connection_info.access_info
region_name = access_info.get('region')
placement_attr = vnfd_dict.get('placement_attr', {})
placement_attr.update({'region_name': region_name})
vnfd_dict['placement_attr'] = placement_attr
instance_id = self.create(None, context, vnfd_dict,
access_info, inst_req_info=instantiate_vnf_req,
grant_info=grant_response)
vnfd_dict['instance_id'] = instance_id
return instance_id
@log.log
def post_vnf_instantiation(self, context, vnf_instance,
vim_connection_info):
inst_vnf_info = vnf_instance.instantiated_vnf_info
access_info = vim_connection_info.access_info
heatclient = hc.HeatClient(access_info,
region_name=access_info.get('region'))
stack_resources = self._get_stack_resources(
inst_vnf_info.instance_id, heatclient)
self._update_vnfc_resources(vnf_instance, stack_resources)
def _update_resource_handle(self, vnf_instance, resource_handle,
stack_resources, resource_name):
if not stack_resources:
LOG.warning("Failed to set resource handle for resource "
"%(resource)s for vnf %(id)s", {"resource": resource_name,
"id": vnf_instance.id})
return
resource_data = stack_resources.pop(resource_name, None)
if not resource_data:
LOG.warning("Failed to set resource handle for resource "
"%(resource)s for vnf %(id)s",
{"resource": resource_name, "id": vnf_instance.id})
return
resource_handle.resource_id = resource_data.get(
'physical_resource_id')
resource_handle.vim_level_resource_type = resource_data.get(
'resource_type')
def _update_vnfc_resource_info(self, vnf_instance, vnfc_res_info,
stack_resources, update_network_resource=True):
inst_vnf_info = vnf_instance.instantiated_vnf_info
def _pop_stack_resources(resource_name):
for stack_id, resources in stack_resources.items():
if resource_name in resources.keys():
return stack_id, resources
return None, {}
def _populate_virtual_link_resource_info(vnf_virtual_link_desc_id,
pop_resources):
vnf_virtual_link_resource_info = \
inst_vnf_info.vnf_virtual_link_resource_info
for vnf_vl_resource_info in vnf_virtual_link_resource_info:
if (vnf_vl_resource_info.vnf_virtual_link_desc_id !=
vnf_virtual_link_desc_id):
continue
vl_resource_data = pop_resources.pop(
vnf_virtual_link_desc_id, None)
if not vl_resource_data:
_, resources = _pop_stack_resources(
vnf_virtual_link_desc_id)
if not resources:
# NOTE(tpatil): network_resource is already set
# from the instantiatevnfrequest during instantiation.
continue
vl_resource_data = resources.get(
vnf_virtual_link_desc_id)
resource_handle = vnf_vl_resource_info.network_resource
resource_handle.resource_id = \
vl_resource_data.get('physical_resource_id')
resource_handle.vim_level_resource_type = \
vl_resource_data.get('resource_type')
def _populate_virtual_link_port(vnfc_cp_info, pop_resources):
vnf_virtual_link_resource_info = \
inst_vnf_info.vnf_virtual_link_resource_info
for vnf_vl_resource_info in vnf_virtual_link_resource_info:
vl_link_port_found = False
for vl_link_port in vnf_vl_resource_info.vnf_link_ports:
if vl_link_port.cp_instance_id == vnfc_cp_info.id:
vl_link_port_found = True
self._update_resource_handle(vnf_instance,
vl_link_port.resource_handle, pop_resources,
vnfc_cp_info.cpd_id)
if vl_link_port_found:
yield vnf_vl_resource_info.vnf_virtual_link_desc_id
def _populate_virtual_storage(vnfc_resource_info, pop_resources):
virtual_storage_resource_info = inst_vnf_info. \
virtual_storage_resource_info
for storage_id in vnfc_resource_info.storage_resource_ids:
for vir_storage_res_info in virtual_storage_resource_info:
if vir_storage_res_info.id == storage_id:
self._update_resource_handle(vnf_instance,
vir_storage_res_info.storage_resource,
pop_resources,
vir_storage_res_info.virtual_storage_desc_id)
break
stack_id, pop_resources = _pop_stack_resources(
vnfc_res_info.vdu_id)
self._update_resource_handle(vnf_instance,
vnfc_res_info.compute_resource, pop_resources,
vnfc_res_info.vdu_id)
vnfc_res_info.metadata.update({"stack_id": stack_id})
_populate_virtual_storage(vnfc_res_info, pop_resources)
# Find out associated VLs, and CP used by vdu_id
virtual_links = set()
for vnfc_cp_info in vnfc_res_info.vnfc_cp_info:
for vl_desc_id in _populate_virtual_link_port(vnfc_cp_info,
pop_resources):
virtual_links.add(vl_desc_id)
if update_network_resource:
for vl_desc_id in virtual_links:
_populate_virtual_link_resource_info(vl_desc_id,
pop_resources)
def _update_ext_managed_virtual_link_ports(self, inst_vnf_info,
ext_managed_vl_info):
vnf_virtual_link_resource_info = \
inst_vnf_info.vnf_virtual_link_resource_info
def _update_link_port(vl_port):
for ext_vl_port in ext_managed_vl_info.vnf_link_ports:
if vl_port.id == ext_vl_port.id:
# Update the resource_id
ext_vl_port.resource_handle.resource_id =\
vl_port.resource_handle.resource_id
ext_vl_port.resource_handle.vim_level_resource_type =\
vl_port.resource_handle.vim_level_resource_type
break
for vnf_vl_resource_info in vnf_virtual_link_resource_info:
if (vnf_vl_resource_info.vnf_virtual_link_desc_id !=
ext_managed_vl_info.vnf_virtual_link_desc_id):
continue
for vl_port in vnf_vl_resource_info.vnf_link_ports:
_update_link_port(vl_port)
def _update_vnfc_resources(self, vnf_instance, stack_resources):
inst_vnf_info = vnf_instance.instantiated_vnf_info
for vnfc_res_info in inst_vnf_info.vnfc_resource_info:
self._update_vnfc_resource_info(vnf_instance, vnfc_res_info,
stack_resources)
# update vnf_link_ports of ext_managed_virtual_link_info using already
# populated vnf_link_ports from vnf_virtual_link_resource_info.
for ext_mng_vl_info in inst_vnf_info.ext_managed_virtual_link_info:
self._update_ext_managed_virtual_link_ports(inst_vnf_info,
ext_mng_vl_info)
def _get_stack_resources(self, stack_id, heatclient):
def _stack_ids(stack_id):
filters = {
"owner_id": stack_id,
"show_nested": True
}
yield stack_id
for stack in heatclient.stacks.list(**{"filters": filters}):
if stack.parent and stack.parent == stack_id:
for x in _stack_ids(stack.id):
yield x
resource_details = {}
for id in _stack_ids(stack_id):
resources = {}
child_stack = False if id == stack_id else True
for stack_resource in heatclient.resources.list(id):
resource_data = {"resource_type":
stack_resource.resource_type,
"physical_resource_id":
stack_resource.physical_resource_id}
resources[stack_resource.resource_name] = resource_data
resource_details[id] = resources
resource_details[id].update({'child_stack': child_stack})
return resource_details

View File

@ -53,7 +53,7 @@ SCALING_POLICY = 'tosca.policies.tacker.Scaling'
class TOSCAToHOT(object):
"""Convert TOSCA template to HOT template."""
def __init__(self, vnf, heatclient):
def __init__(self, vnf, heatclient, inst_req_info=None, grant_info=None):
self.vnf = vnf
self.heatclient = heatclient
self.attributes = {}
@ -65,18 +65,20 @@ class TOSCAToHOT(object):
self.fields = None
self.STACK_FLAVOR_EXTRA = cfg.CONF.openstack_vim.flavor_extra_specs
self.appmonitoring_dict = None
self.grant_info = grant_info
self.inst_req_info = inst_req_info
@log.log
def generate_hot(self):
self._get_vnfd()
dev_attrs = self._update_fields()
vnfd_dict = yamlparser.simple_ordered_parse(self.vnfd_yaml)
LOG.debug('vnfd_dict %s', vnfd_dict)
self._get_unsupported_resource_props(self.heatclient)
self._generate_hot_from_tosca(vnfd_dict, dev_attrs)
self._generate_hot_from_tosca(vnfd_dict, dev_attrs,
self.inst_req_info, self.grant_info)
self.fields['template'] = self.heat_template_yaml
if not self.vnf['attributes'].get('heat_template'):
self.vnf['attributes']['heat_template'] = self.fields['template']
@ -249,7 +251,9 @@ class TOSCAToHOT(object):
self.unsupported_props = unsupported_resource_props
@log.log
def _generate_hot_from_tosca(self, vnfd_dict, dev_attrs):
def _generate_hot_from_tosca(self, vnfd_dict, dev_attrs,
inst_req_info=None,
grant_info=None):
parsed_params = {}
if 'param_values' in dev_attrs and dev_attrs['param_values'] != "":
try:
@ -291,18 +295,23 @@ class TOSCAToHOT(object):
self.vnf, tosca, metadata, unique_id=unique_id)
monitoring_dict = toscautils.get_vdu_monitoring(tosca)
mgmt_ports = toscautils.get_mgmt_ports(tosca)
nested_resource_name = toscautils.get_nested_resources_name(tosca)
sub_heat_tmpl_name = toscautils.get_sub_heat_tmpl_name(tosca)
res_tpl = toscautils.get_resources_dict(tosca,
self.STACK_FLAVOR_EXTRA)
toscautils.post_process_template(tosca)
scaling_policy_names = toscautils.get_scaling_policy(tosca)
try:
translator = tosca_translator.TOSCATranslator(tosca, parsed_params)
heat_template_yaml = translator.translate()
if nested_resource_name:
nested_resource_names = toscautils.get_nested_resources_name(
heat_template_yaml)
if nested_resource_names:
for nested_resource_name in nested_resource_names:
sub_heat_tmpl_name = \
toscautils.get_sub_heat_tmpl_name(nested_resource_name)
sub_heat_template_yaml =\
translator.translate_to_yaml_files_dict(sub_heat_tmpl_name)
translator.translate_to_yaml_files_dict(
sub_heat_tmpl_name)
nested_resource_yaml = \
sub_heat_template_yaml[nested_resource_name]
LOG.debug("nested_resource_yaml: %s", nested_resource_yaml)
@ -315,9 +324,11 @@ class TOSCAToHOT(object):
if self.nested_resources:
nested_tpl = toscautils.update_nested_scaling_resources(
self.nested_resources, mgmt_ports, metadata,
res_tpl, self.unsupported_props)
self.nested_resources,
mgmt_ports, metadata, res_tpl, self.unsupported_props,
grant_info=grant_info, inst_req_info=inst_req_info)
self.fields['files'] = nested_tpl
for nested_resource_name in nested_tpl.keys():
self.vnf['attributes'][nested_resource_name] =\
nested_tpl[nested_resource_name]
mgmt_ports.clear()
@ -327,11 +338,25 @@ class TOSCAToHOT(object):
heat_template_yaml, scaling_policy_names)
self.vnf['attributes']['scaling_group_names'] =\
jsonutils.dump_as_bytes(scaling_group_dict)
heat_template_yaml = toscautils.post_process_heat_template(
heat_template_yaml, mgmt_ports, metadata, alarm_resources,
res_tpl, block_storage_details, self.unsupported_props,
unique_id=unique_id)
unique_id=unique_id, inst_req_info=inst_req_info,
grant_info=grant_info, tosca=tosca)
try:
for nested_resource_name in self.nested_resources.keys():
self.nested_resources[nested_resource_name] = \
toscautils.post_process_heat_template_for_scaling(
self.nested_resources[nested_resource_name],
mgmt_ports, metadata, alarm_resources,
res_tpl, block_storage_details, self.unsupported_props,
unique_id=unique_id, inst_req_info=inst_req_info,
grant_info=grant_info, tosca=tosca)
except Exception as e:
LOG.debug("post_process_heat_template_for_scaling "
"error: %s", str(e))
raise
self.heat_template_yaml = heat_template_yaml
self.monitoring_dict = monitoring_dict

View File

@ -130,6 +130,12 @@ def expected_errors(errors):
# Handle an authorized exception, will be
# automatically converted to a HTTP 401.
raise
elif isinstance(exc, exception.Conflict):
# Note(tpatil): Handle a conflict error, which
# happens due to resources in wrong state.
# ResourceExceptionHandler silently converts Conflict
# to HTTPConflict
raise
LOG.exception("Unexpected exception in API method")
msg = _('Unexpected API Error. Please report this at '
@ -861,6 +867,9 @@ class ResourceExceptionHandler(object):
raise Fault(exception.ConvertedException(
code=ex_value.code,
explanation=ex_value.format_message()))
elif isinstance(ex_value, exception.Conflict):
raise Fault(webob.exc.HTTPConflict(
explanation=ex_value.format_message()))
elif isinstance(ex_value, TypeError):
exc_info = (ex_type, ex_value, ex_traceback)
LOG.error('Exception handling resource: %s', ex_value,