diff --git a/.testr.conf b/.testr.conf index 46000f87a..493eb58be 100644 --- a/.testr.conf +++ b/.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 diff --git a/doc/source/devref/mano_api.rst b/doc/source/devref/mano_api.rst new file mode 100644 index 000000000..0a65be3b0 --- /dev/null +++ b/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 " + }, + "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 " + }, + "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 " + }, + "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 + 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: + \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: { + 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. diff --git a/etc/tacker/tacker.conf b/etc/tacker/tacker.conf index 78ac9d5da..2f5fd4ced 100644 --- a/etc/tacker/tacker.conf +++ b/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 diff --git a/requirements.txt b/requirements.txt index d6ce0d177..3f7d1ac95 100644 --- a/requirements.txt +++ b/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 diff --git a/setup.cfg b/setup.cfg index 1514057be..5048b70dd 100644 --- a/setup.cfg +++ b/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 = diff --git a/tacker/common/config.py b/tacker/common/config.py index 31773fff9..74551a1af 100644 --- a/tacker/common/config.py +++ b/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', diff --git a/tacker/db/vm/vm_db.py b/tacker/db/vm/vm_db.py index a78cbec3d..a3d7fdfb8 100644 --- a/tacker/db/vm/vm_db.py +++ b/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) \ No newline at end of file diff --git a/tacker/extensions/servicevm.py b/tacker/extensions/vnfm.py similarity index 69% rename from tacker/extensions/servicevm.py rename to tacker/extensions/vnfm.py index 54d58c207..204771d45 100644 --- a/tacker/extensions/servicevm.py +++ b/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 -# +# 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,9 +156,137 @@ 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 = { + 'vnfds': { + '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, + }, + }, + + 'vnfs': { + '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 + }, + 'vnfd_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, + 'allow_put': False, + 'is_visible': True, + }, + }, + 'device_templates': { 'id': { 'allow_post': False, @@ -288,109 +409,31 @@ RESOURCE_ATTRIBUTE_MAP = { 'is_visible': True, 'default': [], }, - 'services': { - 'allow_post': False, - 'allow_put': False, - 'validate': {'type:uuid': None}, - 'is_visible': True, - }, 'status': { 'allow_post': False, 'allow_put': False, '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,16 +465,52 @@ 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_vnfd(self, context, vnfd): + pass + + @abc.abstractmethod + def delete_vnfd(self, context, vnfd_id): + pass + + @abc.abstractmethod + def get_vnfd(self, context, vnfd_id, fields=None): + pass + + @abc.abstractmethod + def get_vnfds(self, context, filters=None, fields=None): + pass + + @abc.abstractmethod + def get_vnfs(self, context, filters=None, fields=None): + pass + + @abc.abstractmethod + def get_vnf(self, context, vnf_id, fields=None): + pass + + @abc.abstractmethod + def create_vnf(self, context, vnf): + pass + + @abc.abstractmethod + def update_vnf( + self, context, vnf_id, vnf): + pass + + @abc.abstractmethod + def delete_vnf(self, context, vnf_id): + pass @abc.abstractmethod def create_device_template(self, context, device_template): @@ -473,24 +548,3 @@ class ServiceVMPluginBase(ServicePluginBase): @abc.abstractmethod def delete_device(self, context, device_id): pass - - @abc.abstractmethod - def attach_interface(self, context, id, port_id): - pass - - @abc.abstractmethod - def detach_interface(self, contexct, id, port_id): - pass - - @abc.abstractmethod - def get_service_instances(self, context, filters=None, fields=None): - pass - - @abc.abstractmethod - def get_service_instance(self, context, service_instance_id, fields=None): - pass - - @abc.abstractmethod - def update_service_instance(self, context, service_instance_id, - service_instance): - pass diff --git a/tacker/manager.py b/tacker/manager.py index 74a7827b6..12451dd42 100644 --- a/tacker/manager.py +++ b/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 == '': diff --git a/tacker/plugins/common/constants.py b/tacker/plugins/common/constants.py index 81eaf1038..e30cbf84c 100644 --- a/tacker/plugins/common/constants.py +++ b/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 diff --git a/tacker/services/service_base.py b/tacker/services/service_base.py index 34afdd184..dcbb7c3ed 100644 --- a/tacker/services/service_base.py +++ b/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 = [] diff --git a/tacker/tests/functional/vnfd/test_vnfd.py b/tacker/tests/functional/vnfd/test_vnfd.py index 0738d323b..d9aba3aec 100644 --- a/tacker/tests/functional/vnfd/test_vnfd.py +++ b/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) diff --git a/tacker/tests/unit/db/base.py b/tacker/tests/unit/db/base.py new file mode 100644 index 000000000..b27659dcc --- /dev/null +++ b/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()) diff --git a/tacker/tests/unit/db/utils.py b/tacker/tests/unit/db/utils.py new file mode 100644 index 000000000..e0645949f --- /dev/null +++ b/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'}}}}}}} diff --git a/tacker/tests/unit/vm/__init__.py b/tacker/tests/unit/vm/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tacker/tests/unit/vm/test_plugin.py b/tacker/tests/unit/vm/test_plugin.py new file mode 100644 index 000000000..2ba2c9101 --- /dev/null +++ b/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) \ No newline at end of file diff --git a/tacker/vm/drivers/heat/heat.py b/tacker/vm/drivers/heat/heat.py index 2d9d38e0d..aa70abf57 100644 --- a/tacker/vm/drivers/heat/heat.py +++ b/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: diff --git a/tacker/vm/plugin.py b/tacker/vm/plugin.py index c8b014775..d7a0c28fd 100644 --- a/tacker/vm/plugin.py +++ b/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'] def __init__(self): - super(ServiceVMPlugin, self).__init__() + super(VNFMPlugin, self).__init__() self._pool = eventlet.GreenPool() self._device_manager = driver_manager.DriverManager( 'tacker.servicevm.device.drivers', @@ -184,18 +184,18 @@ class ServiceVMPlugin(vm_db.ServiceResourcePluginDb, ServiceVMMgmtMixin): infra_driver = template.get('infra_driver') if not attributes.is_attr_set(infra_driver): LOG.debug(_('hosting device driver must be specified')) - raise servicevm.InfraDriverNotSpecified() + raise vnfm.InfraDriverNotSpecified() if infra_driver not in self._device_manager: LOG.debug(_('unknown hosting device driver ' '%(infra_driver)s in %(drivers)s'), {'infra_driver': infra_driver, 'drivers': cfg.CONF.servicevm.infra_driver}) - raise servicevm.InvalidInfraDriver(infra_driver=infra_driver) + raise vnfm.InvalidInfraDriver(infra_driver=infra_driver) service_types = template.get('service_types') if not attributes.is_attr_set(service_types): LOG.debug(_('service type must be specified')) - raise servicevm.ServiceTypesNotSpecified() + raise vnfm.ServiceTypesNotSpecified() for service_type in service_types: # TODO(yamahata): # framework doesn't know what services are valid for now. @@ -206,7 +206,7 @@ class ServiceVMPlugin(vm_db.ServiceResourcePluginDb, ServiceVMMgmtMixin): infra_driver, 'create_device_template_pre', plugin=self, context=context, device_template=device_template) - return super(ServiceVMPlugin, self).create_device_template( + return super(VNFMPlugin, self).create_device_template( context, device_template) ########################################################################### @@ -258,7 +258,7 @@ class ServiceVMPlugin(vm_db.ServiceResourcePluginDb, ServiceVMMgmtMixin): self._device_manager.invoke( driver_name, 'create_wait', plugin=self, context=context, device_dict=device_dict, device_id=instance_id) - except servicevm.DeviceCreateWaitFailed: + except vnfm.DeviceCreateWaitFailed: instance_id = None del device_dict['instance_id'] @@ -415,27 +415,6 @@ class ServiceVMPlugin(vm_db.ServiceResourcePluginDb, ServiceVMMgmtMixin): self._delete_device_post(context, device_id, None) self.spawn_n(self._delete_device_wait, context, device_dict) - def _do_interface(self, context, device_id, port_id, action): - device_dict = self._update_device_pre(context, device_id) - driver_name = self._infra_driver_name(device_dict) - instance_id = self._instance_id(device_dict) - - try: - self._device_manager.invoke(driver_name, action, plugin=self, - context=context, device_id=instance_id) - except Exception: - with excutils.save_and_reraise_exception(): - device_dict['status'] = constants.ERROR - self._update_device_post(context, device_id, constants.ERROR) - - self._update_device_post(context, device_dict['id'], constants.ACTIVE) - - def attach_interface(self, context, id, port_id): - return self._do_interface(context, id, port_id, 'attach_interface') - - def detach_interface(self, context, id, port_id): - return self._do_interface(context, id, port_id, 'dettach_interface') - ########################################################################### # logical service instance # @@ -473,7 +452,7 @@ class ServiceVMPlugin(vm_db.ServiceResourcePluginDb, ServiceVMMgmtMixin): def _create_service_instance_db(self, context, device_id, service_instance_param, managed_by_user): - return super(ServiceVMPlugin, self)._create_service_instance( + return super(VNFMPlugin, self)._create_service_instance( context, device_id, service_instance_param, managed_by_user) def _create_service_instance_by_type( @@ -683,3 +662,24 @@ class ServiceVMPlugin(vm_db.ServiceResourcePluginDb, ServiceVMMgmtMixin): self.spawn_n( self._delete_service_instance_wait, context, device, service_instance, {}, None, None) + + def create_vnf(self, context, vnf): + vnf['device'] = vnf.pop('vnf') + vnf_attributes = vnf['device'] + vnf_attributes['template_id'] = vnf_attributes.pop('vnfd_id') + vnf_dict = self.create_device(context, vnf) + vnf_dict['vnfd_id'] = vnf_dict.pop('template_id') + return vnf_dict + + def update_vnf( + self, context, vnf_id, vnf): + vnf['device'] = vnf.pop('vnf') + return self.update_device(context, vnf_id, vnf) + + def delete_vnf(self, context, vnf_id): + self.delete_device(context, vnf_id) + + def create_vnfd(self, context, vnfd): + vnfd['device_template'] = vnfd.pop('vnfd') + new_dict = self.create_device_template(context, vnfd) + return new_dict \ No newline at end of file diff --git a/tox.ini b/tox.ini index f6e5f8064..7f3a1cbda 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,7 @@ # TODO(yamahata): enable tests of py26, py27, py33, py34. # Those unit tests are temporalily disabled until its stabilization #envlist = py26,py27,py33,py34,pep8 -envlist = pep8 +envlist = pep8,py27 minversion = 1.6 skipsdist = True @@ -10,8 +10,6 @@ skipsdist = True # disable unit tests for now until stabilization [testenv:py26] commands = bash -c "echo do nothing for py26 for now. enable after stablized" -[testenv:py27] -commands = bash -c "echo do nothing for py27 for now. enable after stablized" [testenv:py33] commands = bash -c "echo do nothing for py33 for now. enable after stablized" [testenv:py34]