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
changes/02/707802/34
Niraj 3 years ago committed by tpatil
parent 7fe80b0e8b
commit a14590620b
  1. 10
      lower-constraints.txt
  2. 3
      requirements.txt
  3. 168
      tacker/api/schemas/vnf_lcm.py
  4. 33
      tacker/api/validation/parameter_types.py
  5. 25
      tacker/api/validation/validators.py
  6. 160
      tacker/api/vnflcm/v1/controller.py
  7. 39
      tacker/common/clients.py
  8. 1
      tacker/common/driver_manager.py
  9. 31
      tacker/common/exceptions.py
  10. 78
      tacker/common/utils.py
  11. 69
      tacker/conductor/conductor_server.py
  12. 40
      tacker/conductor/conductorrpc/vnf_lcm_rpc.py
  13. 2
      tacker/conf/__init__.py
  14. 31
      tacker/extensions/vnflcm.py
  15. 5
      tacker/extensions/vnfm.py
  16. 1
      tacker/objects/__init__.py
  17. 368
      tacker/objects/instantiate_vnf_req.py
  18. 21
      tacker/objects/vnf_package.py
  19. 11
      tacker/policies/vnf_lcm.py
  20. BIN
      tacker/tests/etc/samples/sample_vnf_package_csar.zip
  21. BIN
      tacker/tests/etc/samples/sample_vnf_package_csar_with_policy.zip
  22. BIN
      tacker/tests/etc/samples/sample_vnf_package_csar_with_short_notation.zip
  23. BIN
      tacker/tests/etc/samples/sample_vnf_package_csar_without_policy.zip
  24. 12
      tacker/tests/unit/base.py
  25. 98
      tacker/tests/unit/conductor/test_conductor_server.py
  26. 25
      tacker/tests/unit/db/utils.py
  27. 13
      tacker/tests/unit/objects/fakes.py
  28. 571
      tacker/tests/unit/vnflcm/fakes.py
  29. 450
      tacker/tests/unit/vnflcm/test_controller.py
  30. 49
      tacker/tests/unit/vnflcm/test_utils.py
  31. 336
      tacker/tests/unit/vnflcm/test_vnflcm_driver.py
  32. 202
      tacker/tests/unit/vnfm/infra_drivers/openstack/data/etsi_nfv/etsi_nfv_sol001_common_types.yaml
  33. 1352
      tacker/tests/unit/vnfm/infra_drivers/openstack/data/etsi_nfv/etsi_nfv_sol001_vnfd_types.yaml
  34. 65
      tacker/tests/unit/vnfm/infra_drivers/openstack/data/etsi_nfv/hot/hot_generate_hot_from_tosca.yaml
  35. 61
      tacker/tests/unit/vnfm/infra_drivers/openstack/data/etsi_nfv/hot/scaling/hot_generate_hot_from_tosca_with_scaling.yaml
  36. 41
      tacker/tests/unit/vnfm/infra_drivers/openstack/data/etsi_nfv/hot/scaling/worker_instance.hot.yaml
  37. 115
      tacker/tests/unit/vnfm/infra_drivers/openstack/data/etsi_nfv/tosca_generate_hot_from_tosca.yaml
  38. 16
      tacker/tests/unit/vnfm/infra_drivers/openstack/data/etsi_nfv/tosca_generate_hot_from_tosca_parser_error.yaml
  39. 48
      tacker/tests/unit/vnfm/infra_drivers/openstack/data/etsi_nfv/tosca_generate_hot_from_tosca_translator_error.yaml
  40. 197
      tacker/tests/unit/vnfm/infra_drivers/openstack/data/etsi_nfv/tosca_generate_hot_from_tosca_with_params_error.yaml
  41. 183
      tacker/tests/unit/vnfm/infra_drivers/openstack/data/etsi_nfv/tosca_generate_hot_from_tosca_with_scaling.yaml
  42. 105
      tacker/tests/unit/vnfm/infra_drivers/openstack/data/etsi_nfv/tosca_generate_hot_from_tosca_with_scaling_invalid_inst_req.yaml
  43. 197
      tacker/tests/unit/vnfm/infra_drivers/openstack/data/etsi_nfv/tosca_generate_hot_from_tosca_with_substitution_mappings_error.yaml
  44. 3
      tacker/tests/unit/vnfm/infra_drivers/openstack/data/etsi_nfv/tosca_params_error.yaml
  45. 33
      tacker/tests/unit/vnfm/infra_drivers/openstack/fixture_data/client.py
  46. 272
      tacker/tests/unit/vnfm/infra_drivers/openstack/fixture_data/fixture_data_utils.py
  47. 297
      tacker/tests/unit/vnfm/infra_drivers/openstack/test_etsi_translate_template.py
  48. 10
      tacker/tests/unit/vnfm/infra_drivers/openstack/test_openstack.py
  49. 476
      tacker/tests/unit/vnfm/infra_drivers/openstack/test_openstack_driver.py
  50. 427
      tacker/tosca/utils.py
  51. 0
      tacker/vnflcm/__init__.py
  52. 33
      tacker/vnflcm/abstract_driver.py
  53. 731
      tacker/vnflcm/utils.py
  54. 153
      tacker/vnflcm/vnflcm_driver.py
  55. 29
      tacker/vnfm/infra_drivers/abstract_driver.py
  56. 17
      tacker/vnfm/infra_drivers/kubernetes/kubernetes_driver.py
  57. 17
      tacker/vnfm/infra_drivers/noop.py
  58. 63
      tacker/vnfm/infra_drivers/openstack/glance_client.py
  59. 17
      tacker/vnfm/infra_drivers/openstack/heat_client.py
  60. 360
      tacker/vnfm/infra_drivers/openstack/openstack.py
  61. 65
      tacker/vnfm/infra_drivers/openstack/translate_template.py
  62. 9
      tacker/wsgi.py

@ -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',

@ -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)

@ -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')

@ -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