Browse Source

Implementation of Tacker NFV MANO API

Introduce new Tacker REST API endpoints 'vnfd' and 'vnf' based on NFV
MANO in 'vnfm' extension. Existing 'device' and'device_template'
endpoints will be retained for backward compatibility.

Change-Id: I9dffd4bee25b1f49aea7478f2bd779cf0f6dfb49
Implements: blueprint tacker-api-mano
changes/67/222967/9
Sripriya 7 years ago
parent
commit
f8bc9cc05c
  1. 2
      .testr.conf
  2. 279
      doc/source/devref/mano_api.rst
  3. 2
      etc/tacker/tacker.conf
  4. 1
      requirements.txt
  5. 2
      setup.cfg
  6. 2
      tacker/common/config.py
  7. 48
      tacker/db/vm/vm_db.py
  8. 294
      tacker/extensions/vnfm.py
  9. 3
      tacker/manager.py
  10. 10
      tacker/plugins/common/constants.py
  11. 2
      tacker/services/service_base.py
  12. 2
      tacker/tests/functional/vnfd/test_vnfd.py
  13. 49
      tacker/tests/unit/db/base.py
  14. 60
      tacker/tests/unit/db/utils.py
  15. 0
      tacker/tests/unit/vm/__init__.py
  16. 162
      tacker/tests/unit/vm/test_plugin.py
  17. 14
      tacker/vm/drivers/heat/heat.py
  18. 66
      tacker/vm/plugin.py
  19. 4
      tox.ini

2
.testr.conf

@ -1,4 +1,4 @@
[DEFAULT]
test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_LOG_CAPTURE=1 ${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./tacker/tests/unit} $LISTOPT $IDOPTION
test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_LOG_CAPTURE=1 ${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./tacker/tests/unit/vm} $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

279
doc/source/devref/mano_api.rst

@ -0,0 +1,279 @@
************************
Tacker MANO API Overview
************************
Tacker MANO API introduces new REST API end-points based on ETSI NFV MANO
standards [1]. The two new resources introduced are 'vnfd' and 'vnf' for
describing the 'vnfm' extension. The resources request and response formats are
described in below sections.
API versions
============
Lists information for Tacker API version.
**GET /**
List API versions - Lists information about all Networking API versions.
::
Response:
{
"versions": [
{
"status": "CURRENT",
"id": "v1.0",
"links": [
{
"href": "http://10.18.160.13:8888/v1.0",
"rel": "self"
}
]
}
]
}
Vnfds
=====
**GET /v1.0/vnfds**
List vnfds - List vnfds stored in the VNF catalog.
::
Response:
{
"vnfds": [
{
"service_types": [
{
"service_type": "vnfd",
"id": "378b774d-89f5-4634-9c65-9c49ed6f00ce"
}
],
"description": "OpenWRT with services",
"tenant_id": "4dd6c1d7b6c94af980ca886495bcfed0",
"mgmt_driver": "openwrt",
"infra_driver": "heat",
"attributes": {
"vnfd": "template_name: OpenWRT\r\ndescription:
template_description <sample_vnfd_template>"
},
"id": "247b045e-d64f-4ae0-a3b4-8441b9e5892c",
"name": "openwrt_services"
}
]
}
**GET /v1.0/vnfds/{vnfd_id}**
Show vnfd - Show information for a specified vnfd id.
::
Response:
{
"vnfd": {
"service_types": [
{
"service_type": "vnfd",
"id": "378b774d-89f5-4634-9c65-9c49ed6f00ce"
}
],
"description": "OpenWRT with services",
"tenant_id": "4dd6c1d7b6c94af980ca886495bcfed0",
"mgmt_driver": "openwrt",
"infra_driver": "heat",
"attributes": {
"vnfd": "template_name: OpenWRT\r\ndescription:
template_description <sample_vnfd_template>"
},
"id": "247b045e-d64f-4ae0-a3b4-8441b9e5892c",
"name": "openwrt_services"
}
}
**POST /v1.0/vnfds**
Create vnfd - Create a vnfd entry based on the vnfd template.
::
Request:
{"auth": {"tenantName": "admin", "passwordCredentials": {"username": "admin",
"password": "devstack"}}, "vnfd": {"attributes": {"vnfd": "template_name:
OpenWRT \r\ndescription: OpenWRT router\r\n\r\nservice_properties:\r\n Id:
sample-vnfd\r\n vendor: tacker\r\n version: 1\r\n\r\nvdus:\r\n vdu1:\r\n
id: vdu1\r\n vm_image: cirros-0.3.2-x86_64-uec\r\n instance_type:
m1.tiny\r\n\r\n network_interfaces:\r\n management:\r\n network:
net_mgmt\r\n management: true\r\n pkt_in:\r\n network:
net0\r\n pkt_out:\r\n network: net1\r\n\r\n placement_policy:
\r\n availability_zone: nova\r\n\r\n auto-scaling: noop\r\n
monitoring_policy: noop\r\n failure_policy: noop\r\n\r\n config:\r\n
param0: key0\r\n param1: key1"}, "service_types": [{"service_type":
"vnfd"}], "mgmt_driver": "noop", "infra_driver": "heat"}}
::
Response:
{
"vnfd": {
"service_types": [
{
"service_type": "vnfd",
"id": "336fe422-9fba-47c7-87fb-d48475c3e0ce"
}
],
"description": "OpenWRT router",
"tenant_id": "4dd6c1d7b6c94af980ca886495bcfed0",
"mgmt_driver": "noop",
"infra_driver": "heat",
"attributes": {
"vnfd": "template_name: OpenWRT \r\ndescription:
template_description <sample_vnfd_template>"
},
"id": "ab10a543-22ee-43af-a441-05a9d32a57da",
"name": "OpenWRT"
}
}
**DELETE /v1.0/vnfds/{vnfd_id}**
Delete vnfd - Deletes a specified vnfd_id from the VNF catalog.
This operation does not accept a request body and does not return a response
body.
Vnfs
====
**GET /v1.0/vnfs**
List vnfs - Lists instantiated vnfs in VNF Manager
::
Response:
{
"vnfs": [
{
"status": "ACTIVE",
"name": "open_wrt",
"tenant_id": "4dd6c1d7b6c94af980ca886495bcfed0",
"instance_id": "f7c93726-fb8d-4036-8349-2e82f196e8f6",
"mgmt_url": "{\"vdu1\": \"192.168.120.3\"}",
"attributes": {
"service_type": "firewall",
"param_values": "",
"heat_template": "description: sample_template_description
type: OS::Nova::Server\n",
"monitoring_policy": "noop",
"failure_policy": "noop"
},
"id": "c9b4f5a5-d304-473a-a57e-b665b1f9eb8f",
"description": "OpenWRT with services"
}
]
}
**GET /v1.0/vnfs/{vnf_id}**
Show vnf - Show information for a specified vnf_id.
::
Response:
{
"vnf": [
{
"status": "ACTIVE",
"name": "open_wrt",
"tenant_id": "4dd6c1d7b6c94af980ca886495bcfed0",
"instance_id": "f7c93726-fb8d-4036-8349-2e82f196e8f6",
"mgmt_url": "{\"vdu1\": \"192.168.120.3\"}",
"attributes": {
"service_type": "firewall",
"param_values": "",
"heat_template": "description: OpenWRT with services\n
sample_template_description type: OS::Nova::Server\n",
"monitoring_policy": "noop", "failure_policy": "noop"
},
"id": "c9b4f5a5-d304-473a-a57e-b665b1f9eb8f",
"description": "OpenWRT with services"
}
]
}
**POST /v1.0/vnfs**
Create vnf - Create a vnf based on the vnfd template id.
::
Request:
{"auth": {"tenantName": "admin", "passwordCredentials": {"username": "admin",
"password": "devstack"}}, "vnf":
{"vnfd_id": "d770ddd7-6014-4191-92d8-a2cd7a6cecd8"}}
::
Response:
{
"vnf": {
"status": "PENDING_CREATE",
"name": "",
"tenant_id": "4dd6c1d7b6c94af980ca886495bcfed0",
"description": "OpenWRT with services",
"instance_id": "4f0d6222-afa0-4f02-8e19-69e7e4fd7edc",
"mgmt_url": null,
"attributes": {
"service_type": "firewall",
"heat_template": "description: OpenWRT with services\n
<sample_heat_template> type: OS::Nova::Server\n",
"monitoring_policy": "noop",
"failure_policy": "noop"
},
"id": "e3158513-92f4-4587-b949-70ad0bcbb2dd",
"vnfd_id": "247b045e-d64f-4ae0-a3b4-8441b9e5892c"
}
}
**PUT /v1.0/vnfs/{vnf_id}**
Update vnf - Update a vnf based on user config file or data.
::
Request:
{"auth": {"tenantName": "admin", "passwordCredentials": {"username": "admin",
"password": "devstack"}}, "vnf": {"attributes": {"config": "vdus:\n vdu1:
<sample_vdu_config> \n\n"}}}
::
Response:
{
"vnf": {
"status": "PENDING_UPDATE",
"name": "",
"tenant_id": "4dd6c1d7b6c94af980ca886495bcfed0",
"instance_id": "4f0d6222-afa0-4f02-8e19-69e7e4fd7edc",
"mgmt_url": "{\"vdu1\": \"192.168.120.4\"}",
"attributes": {
"service_type": "firewall",
"monitoring_policy": "noop",
"config": "vdus:\n vdu1:\n config: {<sample_vdu_config>
type: OS::Nova::Server\n",
"failure_policy": "noop"
},
"id": "e3158513-92f4-4587-b949-70ad0bcbb2dd",
"description": "OpenWRT with services"
}
}
**DELETE /v1.0/vnfs/{vnf_id}**
Delete vnf - Deletes a specified vnf_id from the VNF list.

2
etc/tacker/tacker.conf

@ -61,7 +61,7 @@ lock_path = $state_path/lock
#
# service_plugins =
# Example: service_plugins = router,firewall,lbaas,vpnaas,metering
service_plugins = tacker.vm.plugin.ServiceVMPlugin
service_plugins = tacker.vm.plugin.VNFMPlugin
# Paste configuration file
# api_paste_config = api-paste.ini

1
requirements.txt

@ -18,6 +18,7 @@ netaddr>=0.7.12
#python-tackerclient>=2.3.4,<3
SQLAlchemy<1.1.0,>=0.9.7
WebOb>=1.2.3
python-heatclient>=0.3.0
python-keystoneclient>=1.1.0
alembic>=0.7.2
six>=1.9.0

2
setup.cfg

@ -43,7 +43,7 @@ console_scripts =
tacker-rootwrap = oslo.rootwrap.cmd:main
tacker.service_plugins =
dummy = tacker.tests.unit.dummy_plugin:DummyServicePlugin
servicevm = tacker.vm.plugin.ServiceVMPlugin
vnfm = tacker.vm.plugin:VNFMPlugin
tacker.openstack.common.cache.backends =
memory = tacker.openstack.common.cache._backends.memory:MemoryBackend
tacker.servicevm.device.drivers =

2
tacker/common/config.py

@ -40,6 +40,8 @@ core_opts = [
help=_("The API paste config file to use")),
cfg.StrOpt('api_extensions_path', default="",
help=_("The path for API extensions")),
cfg.ListOpt('service_plugins', default=[],
help=_("The service plugins Tacker will use")),
cfg.StrOpt('policy_file', default="policy.json",
help=_("The policy file to use")),
cfg.StrOpt('auth_strategy', default='keystone',

48
tacker/db/vm/vm_db.py

@ -32,7 +32,7 @@ from tacker.db import api as qdbapi
from tacker.db import db_base
from tacker.db import model_base
from tacker.db import models_v1
from tacker.extensions import servicevm
from tacker.extensions import vnfm
from tacker import manager
from tacker.openstack.common import log as logging
from tacker.openstack.common import uuidutils
@ -265,8 +265,7 @@ class ServiceContextEntry(dict):
})
class ServiceResourcePluginDb(servicevm.ServiceVMPluginBase,
db_base.CommonDbMixin):
class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
@property
def _core_plugin(self):
@ -278,20 +277,20 @@ class ServiceResourcePluginDb(servicevm.ServiceVMPluginBase,
def __init__(self):
qdbapi.register_models()
super(ServiceResourcePluginDb, self).__init__()
super(VNFMPluginDb, self).__init__()
def _get_resource(self, context, model, id):
try:
return self._get_by_id(context, model, id)
except orm_exc.NoResultFound:
if issubclass(model, DeviceTemplate):
raise servicevm.DeviceTemplateNotFound(device_tempalte_id=id)
raise vnfm.DeviceTemplateNotFound(device_tempalte_id=id)
elif issubclass(model, ServiceType):
raise servicevm.ServiceTypeNotFound(service_type_id=id)
raise vnfm.ServiceTypeNotFound(service_type_id=id)
elif issubclass(model, ServiceInstance):
raise servicevm.ServiceInstanceNotFound(service_instance_id=id)
raise vnfm.ServiceInstanceNotFound(service_instance_id=id)
if issubclass(model, Device):
raise servicevm.DeviceNotFound(device_id=id)
raise vnfm.DeviceNotFound(device_id=id)
else:
raise
@ -390,13 +389,13 @@ class ServiceResourcePluginDb(servicevm.ServiceVMPluginBase,
if (not attributes.is_attr_set(infra_driver)):
LOG.debug(_('hosting device driver unspecified'))
raise servicevm.InfraDriverNotSpecified()
raise vnfm.InfraDriverNotSpecified()
if (not attributes.is_attr_set(mgmt_driver)):
LOG.debug(_('mgmt driver unspecified'))
raise servicevm.MGMTDriverNotSpecified()
raise vnfm.MGMTDriverNotSpecified()
if (not attributes.is_attr_set(service_types)):
LOG.debug(_('service types unspecified'))
raise servicevm.SeviceTypesNotSpecified()
raise vnfm.SeviceTypesNotSpecified()
with context.session.begin(subtransactions=True):
template_id = str(uuid.uuid4())
@ -444,7 +443,7 @@ class ServiceResourcePluginDb(servicevm.ServiceVMPluginBase,
devices_db = context.session.query(Device).filter_by(
template_id=device_template_id).first()
if devices_db is not None:
raise servicevm.DeviceTemplateInUse(
raise vnfm.DeviceTemplateInUse(
device_template_id=device_template_id)
context.session.query(ServiceType).filter_by(
@ -597,9 +596,9 @@ class ServiceResourcePluginDb(servicevm.ServiceVMPluginBase,
filter(Device.status.in_(current_statuses)).
with_lockmode('update').one())
except orm_exc.NoResultFound:
raise servicevm.DeviceNotFound(device_id=device_id)
raise vnfm.DeviceNotFound(device_id=device_id)
if device_db.status == constants.PENDING_UPDATE:
raise servicevm.DeviceInUse(device_id=device_id)
raise vnfm.DeviceInUse(device_id=device_id)
device_db.update({'status': new_status})
return device_db
@ -633,7 +632,7 @@ class ServiceResourcePluginDb(servicevm.ServiceVMPluginBase,
binding_db = (context.session.query(ServiceDeviceBinding).
filter_by(device_id=device_id).first())
if binding_db is not None:
raise servicevm.DeviceInUse(device_id=device_id)
raise vnfm.DeviceInUse(device_id=device_id)
device_db = self._get_device_db(
context, device_id, _ACTIVE_UPDATE_ERROR_DEAD,
constants.PENDING_DELETE)
@ -851,7 +850,7 @@ class ServiceResourcePluginDb(servicevm.ServiceVMPluginBase,
with_lockmode('update').one())
if service_instance.managed_by_user != managed_by_user:
raise servicevm.ServiceInstanceNotManagedByUser(
raise vnfm.ServiceInstanceNotManagedByUser(
service_instance_id=service_instance_id)
service_instance.status = constants.PENDING_DELETE
@ -864,7 +863,7 @@ class ServiceResourcePluginDb(servicevm.ServiceVMPluginBase,
assert binding_db
# check only. _post method will delete it.
if len(binding_db) > 1:
raise servicevm.ServiceInstanceInUse(
raise vnfm.ServiceInstanceInUse(
service_instance_id=service_instance_id)
def _delete_service_instance_post(self, context, service_instance_id):
@ -930,3 +929,18 @@ class ServiceResourcePluginDb(servicevm.ServiceVMPluginBase,
return self._get_collection(
context, ServiceInstance, self._make_service_instance_dict,
filters=filters, fields=fields)
def get_vnfs(self, context, filters=None, fields=None):
return self.get_devices(context, filters, fields)
def get_vnf(self, context, vnf_id, fields=None):
return self.get_device(context, vnf_id, fields)
def delete_vnfd(self, context, vnfd_id):
self.delete_device_template(context, vnfd_id)
def get_vnfd(self, context, vnfd_id, fields=None):
return self.get_device_template(context, vnfd_id, fields)
def get_vnfds(self, context, filters=None, fields=None):
return self.get_device_templates(context, filters, fields)

294
tacker/extensions/servicevm.py → tacker/extensions/vnfm.py

@ -1,11 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2013, 2014 Intel Corporation.
# Copyright 2013, 2014 Isaku Yamahata <isaku.yamahata at intel com>
# <isaku.yamahata at gmail com>
# Copyright 2015 Intel Corporation..
# 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
@ -17,8 +12,6 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# @author: Isaku Yamahata, Intel Corporation.
import abc
@ -30,7 +23,7 @@ from tacker.api.v1 import resource_helper
from tacker.common import exceptions
from tacker.openstack.common import log as logging
from tacker.plugins.common import constants
from tacker.services.service_base import ServicePluginBase
from tacker.services.service_base import NFVPluginBase
LOG = logging.getLogger(__name__)
@ -163,10 +156,9 @@ def _validate_service_context_list(data, valid_values=None):
attr.validators['type:service_type_list'] = _validate_service_type_list
attr.validators['type:service_context_list'] = _validate_service_context_list
RESOURCE_ATTRIBUTE_MAP = {
'device_templates': {
'vnfds': {
'id': {
'allow_post': False,
'allow_put': False,
@ -227,7 +219,7 @@ RESOURCE_ATTRIBUTE_MAP = {
},
},
'devices': {
'vnfs': {
'id': {
'allow_post': False,
'allow_put': False,
@ -242,7 +234,7 @@ RESOURCE_ATTRIBUTE_MAP = {
'required_by_policy': True,
'is_visible': True
},
'template_id': {
'vnfd_id': {
'allow_post': True,
'allow_put': False,
'validate': {'type:uuid': None},
@ -288,11 +280,134 @@ RESOURCE_ATTRIBUTE_MAP = {
'is_visible': True,
'default': [],
},
'services': {
'status': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
},
'device_templates': {
'id': {
'allow_post': False,
'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
'primary_key': True,
},
'tenant_id': {
'allow_post': True,
'allow_put': False,
'validate': {'type:string': None},
'required_by_policy': True,
'is_visible': True,
},
'name': {
'allow_post': True,
'allow_put': True,
'validate': {'type:string': None},
'is_visible': True,
'default': '',
},
'description': {
'allow_post': True,
'allow_put': True,
'validate': {'type:string': None},
'is_visible': True,
'default': '',
},
'service_types': {
'allow_post': True,
'allow_put': False,
'convert_to': attr.convert_to_list,
'validate': {'type:service_type_list': None},
'is_visible': True,
'default': attr.ATTR_NOT_SPECIFIED,
},
'infra_driver': {
'allow_post': True,
'allow_put': False,
'validate': {'type:string': None},
'is_visible': True,
'default': attr.ATTR_NOT_SPECIFIED,
},
'mgmt_driver': {
'allow_post': True,
'allow_put': False,
'validate': {'type:string': None},
'is_visible': True,
'default': attr.ATTR_NOT_SPECIFIED,
},
'attributes': {
'allow_post': True,
'allow_put': False,
'convert_to': attr.convert_none_to_empty_dict,
'validate': {'type:dict_or_nodata': None},
'is_visible': True,
'default': None,
},
},
'devices': {
'id': {
'allow_post': False,
'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
'primary_key': True
},
'tenant_id': {
'allow_post': True,
'allow_put': False,
'validate': {'type:string': None},
'required_by_policy': True,
'is_visible': True
},
'template_id': {
'allow_post': True,
'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
},
'name': {
'allow_post': True,
'allow_put': True,
'validate': {'type:string': None},
'is_visible': True,
'default': '',
},
'description': {
'allow_post': True,
'allow_put': True,
'validate': {'type:string': None},
'is_visible': True,
'default': '',
},
'instance_id': {
'allow_post': False,
'allow_put': False,
'validate': {'type:string': None},
'is_visible': True,
},
'mgmt_url': {
'allow_post': False,
'allow_put': False,
'validate': {'type:string': None},
'is_visible': True,
},
'attributes': {
'allow_post': True,
'allow_put': True,
'validate': {'type:dict_or_none': None},
'is_visible': True,
'default': {},
},
'service_contexts': {
'allow_post': True,
'allow_put': False,
'validate': {'type:service_context_list': None},
'is_visible': True,
'default': [],
},
'status': {
'allow_post': False,
@ -300,97 +415,25 @@ RESOURCE_ATTRIBUTE_MAP = {
'is_visible': True,
},
},
# 'service_instances': {
# 'id': {
# 'allow_post': False,
# 'allow_put': False,
# 'validate': {'type:uuid': None},
# 'is_visible': True,
# 'primary_key': True
# },
# 'tenant_id': {
# 'allow_post': True,
# 'allow_put': False,
# 'validate': {'type:string': None},
# 'required_by_policy': True,
# 'is_visible': True
# },
# 'name': {
# 'allow_post': True,
# 'allow_put': True,
# 'validate': {'type:string': None},
# 'is_visible': True,
# },
# 'service_type_id': {
# 'allow_post': True,
# 'allow_put': False,
# 'validate': {'type:uuid': None},
# 'is_visible': True,
# },
# 'service_table_id': {
# 'allow_post': True,
# 'allow_put': False,
# 'validate': {'type:string': None},
# 'is_visible': True,
# },
# 'mgmt_driver': {
# 'allow_post': True,
# 'allow_put': False,
# 'validate': {'type:string': None},
# 'is_visible': True,
# },
# 'mgmt_url': {
# 'allow_post': True,
# 'allow_put': False,
# 'validate': {'type:string': None},
# 'is_visible': True,
# },
# 'service_contexts': {
# 'allow_post': True,
# 'allow_put': False,
# 'validate': {'type:service_context_list': None},
# 'is_visible': True,
# },
# 'devices': {
# 'allow_post': True,
# 'allow_put': False,
# 'validate': {'type:uuid_list': None},
# 'convert_to': attr.convert_to_list,
# 'is_visible': True,
# },
# 'status': {
# 'allow_post': False,
# 'allow_put': False,
# 'is_visible': True,
# },
# 'kwargs': {
# 'allow_post': True,
# 'allow_put': True,
# 'validate': {'type:dict_or_none': None},
# 'is_visible': True,
# 'default': {},
# },
# },
}
class Servicevm(extensions.ExtensionDescriptor):
class Vnfm(extensions.ExtensionDescriptor):
@classmethod
def get_name(cls):
return 'Service VM'
return 'VNFM'
@classmethod
def get_alias(cls):
return 'servicevm'
return 'VNF Manager'
@classmethod
def get_description(cls):
return "Extension for ServiceVM service"
return "Extension for VNF Manager"
@classmethod
def get_namespace(cls):
return 'http://wiki.openstack.org/Tacker/ServiceVM'
return 'http://wiki.openstack.org/Tacker'
@classmethod
def get_updated(cls):
@ -401,23 +444,19 @@ class Servicevm(extensions.ExtensionDescriptor):
special_mappings = {}
plural_mappings = resource_helper.build_plural_mappings(
special_mappings, RESOURCE_ATTRIBUTE_MAP)
plural_mappings['devices'] = 'device'
plural_mappings['service_types'] = 'service_type'
plural_mappings['service_contexts'] = 'service_context'
plural_mappings['services'] = 'service'
attr.PLURALS.update(plural_mappings)
action_map = {'device': {'attach_interface': 'PUT',
'detach_interface': 'PUT'}}
return resource_helper.build_resource_info(
plural_mappings, RESOURCE_ATTRIBUTE_MAP, constants.SERVICEVM,
translate_name=True, action_map=action_map)
plural_mappings, RESOURCE_ATTRIBUTE_MAP, constants.VNFM,
translate_name=True)
@classmethod
def get_plugin_interface(cls):
return ServiceVMPluginBase
return VNFMPluginBase
def update_attributes_map(self, attributes):
super(Servicevm, self).update_attributes_map(
super(Vnfm, self).update_attributes_map(
attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP)
def get_extended_resources(self, version):
@ -426,71 +465,86 @@ class Servicevm(extensions.ExtensionDescriptor):
@six.add_metaclass(abc.ABCMeta)
class ServiceVMPluginBase(ServicePluginBase):
class VNFMPluginBase(NFVPluginBase):
def get_plugin_name(self):
return constants.SERVICEVM
return constants.VNFM
def get_plugin_type(self):
return constants.SERVICEVM
return constants.VNFM
def get_plugin_description(self):
return 'Service VM plugin'
return 'Tacker VNF Manager plugin'
@abc.abstractmethod
def create_device_template(self, context, device_template):
def create_vnfd(self, context, vnfd):
pass
@abc.abstractmethod
def delete_device_template(self, context, device_template_id):
def delete_vnfd(self, context, vnfd_id):
pass
@abc.abstractmethod
def get_device_template(self, context, device_template_id, fields=None):
def get_vnfd(self, context, vnfd_id, fields=None):
pass
@abc.abstractmethod
def get_device_templates(self, context, filters=None, fields=None):
def get_vnfds(self, context, filters=None, fields=None):
pass
@abc.abstractmethod
def get_devices(self, context, filters=None, fields=None):
def get_vnfs(self, context, filters=None, fields=None):
pass
@abc.abstractmethod
def get_device(self, context, device_id, fields=None):
def get_vnf(self, context, vnf_id, fields=None):
pass
@abc.abstractmethod
def create_device(self, context, device):
def create_vnf(self, context, vnf):
pass
@abc.abstractmethod
def update_device(
self, context, device_id, device):
def update_vnf(
self, context, vnf_id, vnf):
pass
@abc.abstractmethod
def delete_device(self, context, device_id):
def delete_vnf(self, context, vnf_id):
pass
@abc.abstractmethod
def attach_interface(self, context, id, port_id):
def create_device_template(self, context, device_template):
pass
@abc.abstractmethod
def detach_interface(self, contexct, id, port_id):
def delete_device_template(self, context, device_template_id):
pass
@abc.abstractmethod
def get_service_instances(self, context, filters=None, fields=None):
def get_device_template(self, context, device_template_id, fields=None):
pass
@abc.abstractmethod
def get_device_templates(self, context, filters=None, fields=None):
pass
@abc.abstractmethod
def get_devices(self, context, filters=None, fields=None):
pass
@abc.abstractmethod
def get_device(self, context, device_id, fields=None):
pass
@abc.abstractmethod
def create_device(self, context, device):
pass
@abc.abstractmethod
def get_service_instance(self, context, service_instance_id, fields=None):
def update_device(
self, context, device_id, device):
pass
@abc.abstractmethod
def update_service_instance(self, context, service_instance_id,
service_instance):
def delete_device(self, context, device_id):
pass

3
tacker/manager.py

@ -107,8 +107,7 @@ class TackerManager(object):
Starts from the core plugin and checks if it supports
advanced services then loads classes provided in configuration.
"""
# plugin_providers = cfg.CONF.service_plugins
plugin_providers = ['tacker.vm.plugin.ServiceVMPlugin']
plugin_providers = cfg.CONF.service_plugins
LOG.debug(_("Loading service plugins: %s"), plugin_providers)
for provider in plugin_providers:
if provider == '':

10
tacker/plugins/common/constants.py

@ -18,18 +18,12 @@
# service type constants:
CORE = "CORE"
DUMMY = "DUMMY"
SERVICEVM = "SERVICEVM"
#maps extension alias to service type
EXT_TO_SERVICE_MAPPING = {
'dummy': DUMMY,
'servicevm': SERVICEVM,
}
VNFM = "VNFM"
COMMON_PREFIXES = {
CORE: "",
DUMMY: "/dummy_svc",
SERVICEVM: "",
VNFM: "",
}
# Service operation status constants

2
tacker/services/service_base.py

@ -27,7 +27,7 @@ LOG = logging.getLogger(__name__)
@six.add_metaclass(abc.ABCMeta)
class ServicePluginBase(extensions.PluginInterface):
class NFVPluginBase(extensions.PluginInterface):
"""Define base interface for any Advanced Service plugin."""
supported_extension_aliases = []

2
tacker/tests/functional/vnfd/test_vnfd.py

@ -30,7 +30,7 @@ class VnfTestJSON(base.BaseTackerTest):
toscal_str = open(yaml_file).read()
data['tosca'] = toscal_str
toscal = data['tosca']
tosca_arg = {'vnfd': {'vnfd': toscal}}
tosca_arg = {'vnfd': {'attributes': {'vnfd': toscal}}}
vnfd_instance = self.client.create_vnfd(body=tosca_arg)
self.assertIsNotNone(vnfd_instance)

49
tacker/tests/unit/db/base.py

@ -0,0 +1,49 @@
# 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 fixtures
import testtools
from tacker.db import api as db_api
from tacker.db import model_base
class SqlFixture(fixtures.Fixture):
# flag to indicate that the models have been loaded
_TABLES_ESTABLISHED = False
def setUp(self):
super(SqlFixture, self).setUp()
# Register all data models
engine = db_api.get_engine()
if not SqlFixture._TABLES_ESTABLISHED:
model_base.BASE.metadata.create_all(engine)
SqlFixture._TABLES_ESTABLISHED = True
def clear_tables():
with engine.begin() as conn:
for table in reversed(
model_base.BASE.metadata.sorted_tables):
conn.execute(table.delete())
self.addCleanup(clear_tables)
class SqlTestCase(testtools.TestCase):
def setUp(self):
super(SqlTestCase, self).setUp()
self.useFixture(SqlFixture())

60
tacker/tests/unit/db/utils.py

@ -0,0 +1,60 @@
# 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.
def get_dummy_vnfd_obj():
return {u'vnfd': {u'service_types': [{u'service_type': u'vnfd'}],
'name': 'dummy_vnfd', 'tenant_id':
u'ad7ebc56538745a08ef7c5e97f8bd437', u'mgmt_driver': u'noop',
u'infra_driver': u'fake_driver', u'attributes': {u'vnfd':
u'template_name: OpenWRT'
u' \r\ndescription: OpenWRT router'
u'\r\n\r\nservice_properties:\r\n'
u' Id: sample-vnfd\r\n vendor:'
u' tacker\r\n version: '
u'1\r\n\r\nvdus:'
u'\r\n vdu1:\r\n id: vdu1\r\n'
u' vm_image:'
u' cirros-0.3.2-x86_64-uec\r\n'
u' instance_type: m1.tiny\r\n\r\n'
u' network_interfaces:\r\n'
u' management:\r\n'
u' network: net_mgmt\r\n'
u' management: true\r\n'
u' pkt_in:\r\n network:'
u' net0\r\n pkt_out:\r\n'
u' network: net1\r\n\r\n'
u' placement_policy:\r\n'
u' availability_zone: nova\r\n'
u'\r'
u'\n auto-scaling: noop\r\n'
u' monitoring_policy: noop\r\n'
u' failure_policy: noop\r\n\r\n'
u' config:\r\n param0: key0'
u'\r\n param1: key1'},
'description': 'dummy_vnfd_description'},
u'auth': {u'tenantName': u'admin', u'passwordCredentials': {
u'username': u'admin', u'password': u'devstack'}}}
def get_dummy_vnf_obj():
return {'vnf': {'description': 'dummy_vnf_description', 'tenant_id':
u'ad7ebc56538745a08ef7c5e97f8bd437', 'name': 'dummy_vnf',
'service_contexts': [], 'attributes': {}}}
def get_dummy_vnf_config_obj():
return {'vnf': {u'attributes': {u'config': {'vdus': {'vdu1': {
'config': {'firewall': 'dummy_firewall_values'}}}}}}}

0
tacker/tests/unit/vm/__init__.py

162
tacker/tests/unit/vm/test_plugin.py

@ -0,0 +1,162 @@
# 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 mock
import uuid
from tacker import context
from tacker.db.vm import vm_db
from tacker.tests.unit.db import base as db_base
from tacker.tests.unit.db import utils
from tacker.vm import plugin
class FakeDriverManager(mock.Mock):
def invoke(self, *args, **kwargs):
if 'create' in args:
return str(uuid.uuid4())
class FakeDeviceStatus(mock.Mock):
pass
class FakeGreenPool(mock.Mock):
pass
class TestVNFMPlugin(db_base.SqlTestCase):
def setUp(self):
super(TestVNFMPlugin, self).setUp()
self.addCleanup(mock.patch.stopall)
self.context = context.get_admin_context()
self._mock_device_manager()
self._mock_device_status()
self._mock_green_pool()
self.vnfm_plugin = plugin.VNFMPlugin()
def _mock_device_manager(self):
self._device_manager = mock.Mock(wraps=FakeDriverManager())
self._device_manager.__contains__ = mock.Mock(
return_value=True)
fake_device_manager = mock.Mock()
fake_device_manager.return_value = self._device_manager
self._mock(
'tacker.common.driver_manager.DriverManager', fake_device_manager)
def _mock_device_status(self):
self._device_status = mock.Mock(wraps=FakeDeviceStatus())
fake_device_status = mock.Mock()
fake_device_status.return_value = self._device_status
self._mock(
'tacker.vm.monitor.DeviceStatus', fake_device_status)
def _mock_green_pool(self):
self._pool = mock.Mock(wraps=FakeGreenPool())
fake_green_pool = mock.Mock()
fake_green_pool.return_value = self._pool
self._mock(
'eventlet.GreenPool', fake_green_pool)
def _mock(self, target, new=mock.DEFAULT):
patcher = mock.patch(target, new)
return patcher.start()
def _insert_dummy_device_template(self):
session = self.context.session
device_template = vm_db.DeviceTemplate(
id='eb094833-995e-49f0-a047-dfb56aaf7c4e',
tenant_id='ad7ebc56538745a08ef7c5e97f8bd437',
name='fake_template',
description='fake_template_description',
infra_driver='fake_driver',
mgmt_driver='fake_mgmt_driver')
session.add(device_template)
session.flush()
return device_template
def _insert_dummy_device(self):
session = self.context.session
device_db = vm_db.Device(id='6261579e-d6f3-49ad-8bc3-a9cb974778ff',
tenant_id='ad7ebc56538745a08ef7c5e97f8bd437',
name='fake_device',
description='fake_device_description',
instance_id=
'da85ea1a-4ec4-4201-bbb2-8d9249eca7ec',
template_id=
'eb094833-995e-49f0-a047-dfb56aaf7c4e',
status='ACTIVE')
session.add(device_db)
session.flush()
return device_db
def test_create_vnfd(self):
vnfd_obj = utils.get_dummy_vnfd_obj()
result = self.vnfm_plugin.create_vnfd(self.context, vnfd_obj)
self.assertIsNotNone(result)
self.assertIn('id', result)
self.assertIn('service_types', result)
self.assertIn('attributes', result)
self._device_manager.invoke.assert_called_once_with(mock.ANY,
mock.ANY,
plugin=mock.ANY,
context=mock.ANY,
device_template=
mock.ANY)
def test_create_vnf(self):
device_template_obj = self._insert_dummy_device_template()
vnf_obj = utils.get_dummy_vnf_obj()
vnf_obj['vnf']['vnfd_id'] = device_template_obj['id']
result = self.vnfm_plugin.create_vnf(self.context, vnf_obj)
self.assertIsNotNone(result)
self.assertIn('id', result)
self.assertIn('instance_id', result)
self.assertIn('status', result)
self.assertIn('attributes', result)
self.assertIn('mgmt_url', result)
self._device_manager.invoke.assert_called_with(mock.ANY, mock.ANY,
plugin=mock.ANY,
context=mock.ANY,
device=mock.ANY)
self._pool.spawn_n.assert_called_once_with(mock.ANY)
def test_delete_vnf(self):
self._insert_dummy_device_template()
dummy_device_obj = self._insert_dummy_device()
self.vnfm_plugin.delete_vnf(self.context, dummy_device_obj[
'id'])
self._device_manager.invoke.assert_called_with(mock.ANY, mock.ANY,
plugin=mock.ANY,
context=mock.ANY,
device_id=mock.ANY)
self._device_status.delete_hosting_device.assert_called_with(mock.ANY)
self._pool.spawn_n.assert_called_once_with(mock.ANY, mock.ANY,
mock.ANY)
def test_update_vnf(self):
self._insert_dummy_device_template()
dummy_device_obj = self._insert_dummy_device()
vnf_config_obj = utils.get_dummy_vnf_config_obj()
result = self.vnfm_plugin.update_vnf(self.context, dummy_device_obj[
'id'], vnf_config_obj)
self.assertIsNotNone(result)
self.assertEqual(dummy_device_obj['id'], result['id'])
self.assertIn('instance_id', result)
self.assertIn('status', result)
self.assertIn('attributes', result)
self.assertIn('mgmt_url', result)
self._pool.spawn_n.assert_called_once_with(mock.ANY, mock.ANY,
mock.ANY)

14
tacker/vm/drivers/heat/heat.py

@ -31,7 +31,7 @@ from keystoneclient.v2_0 import client as ks_client
from oslo_config import cfg
from tacker.common import log
from tacker.extensions import servicevm
from tacker.extensions import vnfm
from tacker.openstack.common import jsonutils
from tacker.openstack.common import log as logging
from tacker.vm.drivers import abstract_driver
@ -119,13 +119,13 @@ class DeviceHeat(abstract_driver.DeviceAbstractDriver):
self._update_params(value, paramvalues[key], False)
else:
LOG.debug('Key missing Value: %s', key)
raise servicevm.InputValuesMissing()
raise vnfm.InputValuesMissing()
elif 'get_input' in value:
if value['get_input'] in paramvalues:
original[key] = paramvalues[value['get_input']]
else:
LOG.debug('Key missing Value: %s', key)
raise servicevm.InputValuesMissing()
raise vnfm.InputValuesMissing()
else:
self._update_params(value, paramvalues, True)
@ -172,12 +172,12 @@ class DeviceHeat(abstract_driver.DeviceAbstractDriver):
LOG.debug('param_vattrs_yaml', param_vattrs_dict)
except Exception as e:
LOG.debug("Not Well Formed: %s", str(e))
raise servicevm.ParamYAMLNotWellFormed(
raise vnfm.ParamYAMLNotWellFormed(
error_msg_details=str(e))
else:
self._update_params(vnfd_dict, param_vattrs_dict)
else:
raise servicevm.ParamYAMLInputMissing()
raise vnfm.ParamYAMLInputMissing()
KEY_LIST = (('description', 'description'),
)
@ -231,7 +231,7 @@ class DeviceHeat(abstract_driver.DeviceAbstractDriver):
'user_data_format']
properties['user_data'] = vdu_dict['user_data']
elif 'user_data' in vdu_dict or 'user_data_format' in vdu_dict:
raise servicevm.UserDataFormatNotFound()
raise vnfm.UserDataFormatNotFound()
if ('placement_policy' in vdu_dict and
'availability_zone' in vdu_dict['placement_policy']):
properties['availability_zone'] = vdu_dict[
@ -449,7 +449,7 @@ class HeatClient:
return self.stacks.create(**fields)
except heatException.HTTPException:
type_, value, tb = sys.exc_info()
raise servicevm.HeatClientException(msg=value)
raise vnfm.HeatClientException(msg=value)
def delete(self, stack_id):
try:

66
tacker/vm/plugin.py

@ -31,7 +31,7 @@ from tacker.common import driver_manager
from tacker import context as t_context
from tacker.db.vm import proxy_db # noqa
from tacker.db.vm import vm_db
from tacker.extensions import servicevm
from tacker.extensions import vnfm
from tacker.openstack.common import excutils
from tacker.openstack.common import log as logging
from tacker.plugins.common import constants
@ -41,7 +41,7 @@ from tacker.vm import monitor
LOG = logging.getLogger(__name__)
class ServiceVMMgmtMixin(object):
class VNFMMgmtMixin(object):
OPTS = [
cfg.MultiStrOpt(
'mgmt_driver', default=[],
@ -52,7 +52,7 @@ class ServiceVMMgmtMixin(object):
cfg.CONF.register_opts(OPTS, 'servicevm')
def __init__(self):
super(ServiceVMMgmtMixin, self).__init__()
super(VNFMMgmtMixin, self).__init__()
self._mgmt_manager = driver_manager.DriverManager(
'tacker.servicevm.mgmt.drivers', cfg.CONF.servicevm.mgmt_driver)
@ -152,7 +152,7 @@ class ServiceVMMgmtMixin(object):
service_instance=service_instance_dict, kwargs=kwargs)
class ServiceVMPlugin(vm_db.ServiceResourcePluginDb, ServiceVMMgmtMixin):
class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
"""ServiceVMPlugin which supports ServiceVM framework
"""
OPTS = [
@ -161,10 +161,10 @@ class ServiceVMPlugin(vm_db.ServiceResourcePluginDb, ServiceVMMgmtMixin):
help=_('Hosting device drivers servicevm plugin will use')),
]
cfg.CONF.register_opts(OPTS, 'servicevm')
supported_extension_aliases = ['servicevm']
supported_extension_aliases = ['vnfm']