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:
parent
7fe80b0e8b
commit
a14590620b
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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'
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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
|
||||
|
@ -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',
|
||||
|
40
tacker/conductor/conductorrpc/vnf_lcm_rpc.py
Normal file
40
tacker/conductor/conductorrpc/vnf_lcm_rpc.py
Normal 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)
|
@ -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)
|
||||
|
31
tacker/extensions/vnflcm.py
Normal file
31
tacker/extensions/vnflcm.py
Normal 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')
|
@ -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
|
||||
|
@ -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')
|
||||
|
368
tacker/objects/instantiate_vnf_req.py
Normal file
368
tacker/objects/instantiate_vnf_req.py
Normal 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
|
@ -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):
|
||||
|
@ -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'
|
||||
}
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
|
Binary file not shown.
BIN
tacker/tests/etc/samples/sample_vnf_package_csar_with_policy.zip
Normal file
BIN
tacker/tests/etc/samples/sample_vnf_package_csar_with_policy.zip
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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
|
||||
|
@ -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')
|
||||
|
@ -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'}
|
||||
|
@ -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],
|
||||
|
@ -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."""
|
||||
|
||||
|
@ -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)
|
||||
|
49
tacker/tests/unit/vnflcm/test_utils.py
Normal file
49
tacker/tests/unit/vnflcm/test_utils.py
Normal 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)
|
336
tacker/tests/unit/vnflcm/test_vnflcm_driver.py
Normal file
336
tacker/tests/unit/vnflcm/test_vnflcm_driver.py
Normal 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)
|
@ -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
|
File diff suppressed because it is too large
Load Diff
@ -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: {}
|
@ -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: {}
|
@ -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
|
@ -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
|
@ -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
|
@ -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 ]
|
@ -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
|
@ -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 ]
|
@ -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 ]
|
@ -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
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
cpus: [,
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
@ -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())
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
0
tacker/vnflcm/__init__.py
Normal file
0
tacker/vnflcm/__init__.py
Normal file
33
tacker/vnflcm/abstract_driver.py
Normal file
33
tacker/vnflcm/abstract_driver.py
Normal 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
731
tacker/vnflcm/utils.py
Normal 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
|
153
tacker/vnflcm/vnflcm_driver.py
Normal file
153
tacker/vnflcm/vnflcm_driver.py
Normal 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)
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
63
tacker/vnfm/infra_drivers/openstack/glance_client.py
Normal file
63
tacker/vnfm/infra_drivers/openstack/glance_client.py
Normal 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})
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user