diff --git a/extensions/_credential_view.py b/extensions/_credential_view.py index 453cd8b24d..3c8ce49a49 100644 --- a/extensions/_credential_view.py +++ b/extensions/_credential_view.py @@ -47,12 +47,12 @@ class ViewBuilder(object): return credential def _build_simple(self, credential_data): - """Return a simple model of a server.""" + """Return a simple description of credential.""" return dict(credential=dict(id=credential_data['credential_id'])) def _build_detail(self, credential_data): - """Return a simple model of a server.""" + """Return a detailed description of credential.""" return dict(credential=dict(id=credential_data['credential_id'], name=credential_data['user_name'], - password=credential_data['password'])) \ No newline at end of file + password=credential_data['password'])) diff --git a/extensions/_exceptions.py b/extensions/_exceptions.py deleted file mode 100644 index aeae4b647e..0000000000 --- a/extensions/_exceptions.py +++ /dev/null @@ -1,181 +0,0 @@ -""" -# vim: tabstop=4 shiftwidth=4 softtabstop=4 -# -# Copyright 2011 Cisco Systems, 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. -# -# @author: Ying Liu, Cisco Systems, Inc. -# -""" -import logging - - -# pylint: disable-msg=E0602 -class ExtensionException(Exception): - """Quantum Cisco api Exception - - Taken from nova.exception.NovaException - To correctly use this class, inherit from it and define - a 'message' property. That message will get printf'd - with the keyword arguments provided to the constructor. - - """ - message = _("An unknown exception occurred.") - - def __init__(self, **kwargs): - try: - self._error_string = self.message % kwargs - - except Exception: - # at least get the core message out if something happened - self._error_string = self.message - - def __str__(self): - """Error Msg""" - return self._error_string - - -class ProcessExecutionError(IOError): - """Process Exe Error""" - def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None, - description=None): - if description is None: - description = "Unexpected error while running command." - if exit_code is None: - exit_code = '-' - message = "%s\nCommand: %s\nExit code: %s\nStdout: %r\nStderr: %r" % ( - description, cmd, exit_code, stdout, stderr) - IOError.__init__(self, message) - - -class Error(Exception): - """Error Class""" - def __init__(self, message=None): - super(Error, self).__init__(message) - - -class ApiError(Error): - """Api Error""" - def __init__(self, message='Unknown', code='Unknown'): - self.message = message - self.code = code - super(ApiError, self).__init__('%s: %s' % (code, message)) - - -class NotFound(ExtensionException): - """Error Msg""" - pass - - -class ClassNotFound(NotFound): - """Extension Error Msg""" - message = _("Class %(class_name)s could not be found") - - -class PortprofileNotFound(NotFound): - """Extension Error Msg""" - message = _("Portprofile %(_id)s could not be found") - - -class NovatenantNotFound(NotFound): - """Extension Error Msg""" - message = _("Novatenant %(_id)s could not be found") - - -class PortNotFound(NotFound): - """Extension Error""" - message = _("Port %(port_id)s could not be found " \ - "on Network %(net_id)s") - - -class CredentialNotFound(NotFound): - """Extension Error""" - message = _("Credential %(_id)s could not be found") - - -class QosNotFound(NotFound): - """Extension Error""" - message = _("QoS %(_id)s could not be found") - - -""" - - -class PortprofileInUse(ExtensionException): - message = _("Unable to complete operation on Portprofile %(net_id)s. " \ - "There is one or more attachments plugged into its ports.") - - -class PortInUse(ExtensionException): - message = _("Unable to complete operation on port %(port_id)s " \ - "for Portprofile %(net_id)s. The attachment '%(att_id)s" \ - "is plugged into the logical port.") - -class AlreadyAttached(ExtensionException): - message = _("Unable to plug the attachment %(att_id)s into port " \ - "%(port_id)s for Portprofile %(net_id)s. The attachment is " \ - "already plugged into port %(att_port_id)s") - -""" - - -class Duplicate(Error): - """Duplication Error""" - pass - - -class NotAuthorized(Error): - "Not Auth Error" - pass - - -class NotEmpty(Error): - """Not Empty Error""" - pass - - -class Invalid(Error): - """Invalid Error""" - pass - - -class InvalidContentType(Invalid): - message = _("Invalid content type %(content_type)s.") - - -class BadInputError(Exception): - """Error resulting from a client sending bad input to a server""" - pass - - -class MissingArgumentError(Error): - """Miss arg error""" - pass - - -def wrap_exception(afunc): - """Wrap Exception""" - def _wrap(*args, **kw): - """Internal Wrap Exception func""" - try: - return afunc(*args, **kw) - except Exception, exp: - if not isinstance(exp, Error): - #exc_type, exc_value, exc_traceback = sys.exc_info() - logging.exception('Uncaught exception') - #logging.error(traceback.extract_stack(exc_traceback)) - raise Error(str(exp)) - raise - _wrap.func_name = afunc.func_name - return _wrap diff --git a/extensions/_novatenant_view.py b/extensions/_novatenant_view.py index 6843361fb2..a25654916e 100644 --- a/extensions/_novatenant_view.py +++ b/extensions/_novatenant_view.py @@ -18,6 +18,7 @@ # @author: Ying Liu, Cisco Systems, Inc. # """ +from quantum.plugins.cisco.common import cisco_constants as const def get_view_builder(req): @@ -39,8 +40,8 @@ class ViewBuilder(object): def build_host(self, host_data): """Return host description.""" - return dict(host_list=host_data['host_list']) + return dict(host_list=host_data[const.HOST_LIST]) def build_vif(self, vif_data): """Return VIF description.""" - return dict(vif_desc=vif_data['vif_desc']) + return dict(vif_desc=vif_data[const.VIF_DESC]) diff --git a/extensions/_pprofiles.py b/extensions/_pprofiles.py index ba94c472bc..cf851bae08 100644 --- a/extensions/_pprofiles.py +++ b/extensions/_pprofiles.py @@ -47,7 +47,7 @@ class ViewBuilder(object): return portprofile def _build_simple(self, portprofile_data): - """Return a simple model of a portprofile""" + """Return a simple description of a portprofile""" return dict(portprofile=dict(id=portprofile_data['profile_id'])) def _build_detail(self, portprofile_data): diff --git a/extensions/_qos_view.py b/extensions/_qos_view.py index ca3f76caf1..3ad0d30c3c 100644 --- a/extensions/_qos_view.py +++ b/extensions/_qos_view.py @@ -46,11 +46,11 @@ class ViewBuilder(object): return qos def _build_simple(self, qos_data): - """Return a simple model of a server.""" + """Return a simple description of qos.""" return dict(qos=dict(id=qos_data['qos_id'])) def _build_detail(self, qos_data): - """Return a simple model of a server.""" + """Return a detailed description of qos.""" return dict(qos=dict(id=qos_data['qos_id'], name=qos_data['qos_name'], description=qos_data['qos_desc'])) diff --git a/extensions/credential.py b/extensions/credential.py index 85043940b4..31c49bdd09 100644 --- a/extensions/credential.py +++ b/extensions/credential.py @@ -21,14 +21,13 @@ import logging from webob import exc -from extensions import _credential_view as credential_view -from quantum.plugins.cisco.common import cisco_exceptions as exception -#from extensions import _exceptions as exception -from extensions import _faults as faults +from extensions import _credential_view as credential_view from quantum.api import api_common as common from quantum.common import extensions from quantum.manager import QuantumManager +from quantum.plugins.cisco.common import cisco_exceptions as exception +from quantum.plugins.cisco.common import cisco_faults as faults LOG = logging.getLogger('quantum.api.credentials') @@ -37,7 +36,7 @@ class Credential(object): """extension class Credential""" def __init__(self): pass - + @classmethod def get_name(cls): """ Returns Ext Resource Name """ @@ -60,7 +59,7 @@ class Credential(object): @classmethod def get_updated(cls): - """ Returns Ext Resource Name """ + """ Returns Ext Resource Update Time """ return "2011-07-25T13:25:27-06:00" @classmethod @@ -68,7 +67,6 @@ class Credential(object): """ Returns Ext Resources """ parent_resource = dict(member_name="tenant", collection_name="extensions/csco/tenants") - controller = CredentialController(QuantumManager.get_plugin()) return [extensions.ResourceExtension('credentials', controller, parent=parent_resource)] @@ -85,7 +83,7 @@ class CredentialController(common.QuantumController): 'required': True}, { 'param-name': 'password', 'required': True}] - + _serialization_metadata = { "application/xml": { "attributes": { @@ -97,8 +95,7 @@ class CredentialController(common.QuantumController): def __init__(self, plugin): self._resource_name = 'credential' self._plugin = plugin - #super(CredentialController, self).__init__(plugin) - + def index(self, request, tenant_id): """ Returns a list of credential ids """ return self._items(request, tenant_id, is_detail=False) @@ -126,7 +123,6 @@ class CredentialController(common.QuantumController): def create(self, request, tenant_id): """ Creates a new credential for a given tenant """ - #look for credential name in request try: req_params = \ self._parse_request_params(request, @@ -168,4 +164,3 @@ class CredentialController(common.QuantumController): return exc.HTTPAccepted() except exception.CredentialNotFound as exp: return faults.Fault(faults.CredentialNotFound(exp)) - \ No newline at end of file diff --git a/extensions/novatenant.py b/extensions/novatenant.py index 4f21516bc5..828efec36a 100644 --- a/extensions/novatenant.py +++ b/extensions/novatenant.py @@ -21,13 +21,11 @@ from webob import exc from extensions import _novatenant_view as novatenant_view -from quantum.common import exceptions as qexception -#from extensions import _exceptions as exception -from extensions import _faults as faults - from quantum.api import api_common as common +from quantum.common import exceptions as qexception from quantum.common import extensions from quantum.manager import QuantumManager +from quantum.plugins.cisco.common import cisco_faults as faults class Novatenant(object): @@ -42,27 +40,27 @@ class Novatenant(object): @classmethod def get_alias(cls): - """ Returns Ext Resource Name """ + """ Returns Ext Resource alias""" return "Cisco Nova Tenant" @classmethod def get_description(cls): - """ Returns Ext Resource Name """ + """ Returns Ext Resource Description """ return "novatenant resource is used by nova side to invoke quantum api" @classmethod def get_namespace(cls): - """ Returns Ext Resource Name """ + """ Returns Ext Resource Namespace """ return "http://docs.ciscocloud.com/api/ext/novatenant/v1.0" @classmethod def get_updated(cls): - """ Returns Ext Resource Name """ + """ Returns Ext Resource Updated Time """ return "2011-08-09T13:25:27-06:00" @classmethod def get_resources(cls): - """ Returns Ext Resource Name """ + """ Returns Ext Resource """ parent_resource = dict(member_name="tenant", collection_name="extensions/csco/tenants") member_actions = {'schedule_host': "PUT", @@ -98,16 +96,8 @@ class NovatenantsController(common.QuantumController): def __init__(self, plugin): self._resource_name = 'novatenant' self._plugin = plugin - #super(NovatenantsController, self).__init__(plugin) - - def index(self, request, tenant_id): - """ Returns a list of novatenant ids """ - return "novatenant is a dummy resource" - - def _items(self, request, tenant_id, is_detail): - """ Returns a list of novatenants. """ - return "novatenant is a dummy resource" - + + #added for cisco's extension # pylint: disable-msg=E1101,W0613 def show(self, request, tenant_id, id): """ Returns novatenant details for the given novatenant id """ @@ -144,7 +134,6 @@ class NovatenantsController(common.QuantumController): builder = novatenant_view.get_view_builder(request) result = builder.build_host(host) return result - #return exc.HTTPAccepted() except qexception.PortNotFound as exp: return faults.Fault(faults.PortNotFound(exp)) diff --git a/extensions/portprofile.py b/extensions/portprofile.py index b0fbb65dd6..4dd9c7b41c 100644 --- a/extensions/portprofile.py +++ b/extensions/portprofile.py @@ -22,14 +22,12 @@ from webob import exc from extensions import _pprofiles as pprofiles_view -from quantum.plugins.cisco.common import cisco_exceptions as exception -from quantum.common import exceptions as qexception -#from extensions import _exceptions as exception -from extensions import _faults as faults - from quantum.api import api_common as common +from quantum.common import exceptions as qexception from quantum.common import extensions from quantum.manager import QuantumManager +from quantum.plugins.cisco.common import cisco_exceptions as exception +from quantum.plugins.cisco.common import cisco_faults as faults class Portprofile(object): @@ -59,7 +57,7 @@ class Portprofile(object): @classmethod def get_updated(cls): - """ Returns Ext Resource Updateed time """ + """ Returns Ext Resource Updated time """ return "2011-07-23T13:25:27-06:00" @classmethod @@ -68,7 +66,7 @@ class Portprofile(object): parent_resource = dict(member_name="tenant", collection_name="extensions/csco/tenants") member_actions = {'associate_portprofile': "PUT", - 'disassociate_portprofile': "POST"} + 'disassociate_portprofile': "PUT"} controller = PortprofilesController(QuantumManager.get_plugin()) return [extensions.ResourceExtension('portprofiles', controller, parent=parent_resource, @@ -82,7 +80,6 @@ class PortprofilesController(common.QuantumController): def __init__(self, plugin): self._resource_name = 'portprofile' self._plugin = plugin - #super(PortprofilesController, self).__init__(plugin) self._portprofile_ops_param_list = [{ 'param-name': 'portprofile_name', @@ -130,7 +127,6 @@ class PortprofilesController(common.QuantumController): return dict(portprofiles=result) except exception.PortProfileNotFound as exp: return faults.Fault(faults.PortprofileNotFound(exp)) - #return faults.Fault(exp) def create(self, request, tenant_id): """ Creates a new portprofile for a given tenant """ @@ -188,7 +184,6 @@ class PortprofilesController(common.QuantumController): except exc.HTTPError as exp: return faults.Fault(exp) net_id = req_params['network-id'].strip() - #print "*****net id "+net_id port_id = req_params['port-id'].strip() try: self._plugin.associate_portprofile(tenant_id, @@ -212,7 +207,6 @@ class PortprofilesController(common.QuantumController): except exc.HTTPError as exp: return faults.Fault(exp) net_id = req_params['network-id'].strip() - #print "*****net id "+net_id port_id = req_params['port-id'].strip() try: self._plugin. \ diff --git a/extensions/qos.py b/extensions/qos.py index 3c0f3da782..6e8cb72334 100644 --- a/extensions/qos.py +++ b/extensions/qos.py @@ -22,13 +22,12 @@ import logging from webob import exc from extensions import _qos_view as qos_view -from quantum.plugins.cisco.common import cisco_exceptions as exception -from extensions import _exceptions as exte -from extensions import _faults as faults - from quantum.api import api_common as common from quantum.common import extensions from quantum.manager import QuantumManager +from quantum.plugins.cisco.common import cisco_exceptions as exception +from quantum.plugins.cisco.common import cisco_faults as faults + LOG = logging.getLogger('quantum.api.qoss') @@ -94,7 +93,6 @@ class QosController(common.QuantumController): def __init__(self, plugin): self._resource_name = 'qos' self._plugin = plugin - #super(QosController, self).__init__(plugin) def index(self, request, tenant_id): """ Returns a list of qos ids """ diff --git a/quantum/cli.py b/quantum/cli.py index 6b19f98fcf..bb92437f6e 100644 --- a/quantum/cli.py +++ b/quantum/cli.py @@ -240,7 +240,7 @@ def api_plug_iface(client, *args): res = client.attach_resource(nid, pid, data) except Exception, e: LOG.error("Failed to plug iface \"%s\" to port \"%s\": %s" % (vid, - pid, output)) + pid, e)) return LOG.debug(res) print "Plugged interface \"%s\" to port:%s on network:%s" % (vid, pid, nid) @@ -386,6 +386,6 @@ if __name__ == "__main__": commands[cmd]["api_func"](client, *args) else: quantum = QuantumManager() - manager = quantum.get_manager() + manager = quantum.get_plugin() commands[cmd]["func"](manager, *args) sys.exit(0) diff --git a/quantum/client.py b/quantum/client.py index 517ba6d667..74ac1958ba 100644 --- a/quantum/client.py +++ b/quantum/client.py @@ -20,16 +20,32 @@ import httplib import socket import urllib from quantum.common.wsgi import Serializer +from quantum.common import exceptions + +EXCEPTIONS = { + 400: exceptions.BadInputError, + 401: exceptions.NotAuthorized, + 420: exceptions.NetworkNotFound, + 421: exceptions.NetworkInUse, + 422: exceptions.NetworkNameExists, + 430: exceptions.PortNotFound, + 431: exceptions.StateInvalid, + 432: exceptions.PortInUse, + 440: exceptions.AlreadyAttached, + 441: exceptions.AttachmentNotReady, +} -class api_call(object): +class ApiCall(object): """A Decorator to add support for format and tenant overriding""" - def __init__(self, f): - self.f = f + def __init__(self, function): + self.function = function def __get__(self, instance, owner): def with_params(*args, **kwargs): - # Temporarily set format and tenant for this request + """ + Temporarily sets the format and tenant for this request + """ (format, tenant) = (instance.format, instance.tenant) if 'format' in kwargs: @@ -37,7 +53,7 @@ class api_call(object): if 'tenant' in kwargs: instance.tenant = kwargs['tenant'] - ret = self.f(instance, *args) + ret = self.function(instance, *args) (instance.format, instance.tenant) = (format, tenant) return ret return with_params @@ -47,9 +63,7 @@ class Client(object): """A base client class - derived from Glance.BaseClient""" - action_prefix = '/v0.1/tenants/{tenant_id}' - - """Action query strings""" + # Action query strings networks_path = "/networks" network_path = "/networks/%s" ports_path = "/networks/%s/ports" @@ -58,7 +72,7 @@ class Client(object): def __init__(self, host="127.0.0.1", port=9696, use_ssl=False, tenant=None, format="xml", testingStub=None, key_file=None, cert_file=None, - logger=None): + logger=None, action_prefix="/v0.1/tenants/{tenant_id}"): """ Creates a new client to some service. @@ -81,6 +95,7 @@ class Client(object): self.key_file = key_file self.cert_file = cert_file self.logger = logger + self.action_prefix = action_prefix def get_connection_type(self): """ @@ -114,7 +129,7 @@ class Client(object): # Add format and tenant_id action += ".%s" % self.format - action = Client.action_prefix + action + action = self.action_prefix + action action = action.replace('{tenant_id}', self.tenant) if type(params) is dict: @@ -133,9 +148,9 @@ class Client(object): certs = dict((x, certs[x]) for x in certs if certs[x] != None) if self.use_ssl and len(certs): - c = connection_type(self.host, self.port, **certs) + conn = connection_type(self.host, self.port, **certs) else: - c = connection_type(self.host, self.port) + conn = connection_type(self.host, self.port) if self.logger: self.logger.debug("Quantum Client Request:\n" \ @@ -143,8 +158,8 @@ class Client(object): if body: self.logger.debug(body) - c.request(method, action, body, headers) - res = c.getresponse() + conn.request(method, action, body, headers) + res = conn.getresponse() status_code = self.get_status_code(res) data = res.read() @@ -158,6 +173,8 @@ class Client(object): httplib.NO_CONTENT): return self.deserialize(data, status_code) else: + if res.status in EXCEPTIONS: + raise EXCEPTIONS[res.status]() raise Exception("Server returned error: %s" % res.read()) except (socket.error, IOError), e: @@ -175,6 +192,10 @@ class Client(object): return response.status def serialize(self, data): + """ + Serializes a dictionary with a single key (which can contain any + structure) into either xml or json + """ if data is None: return None elif type(data) is dict: @@ -184,65 +205,72 @@ class Client(object): % type(data)) def deserialize(self, data, status_code): + """ + Deserializes a an xml or json string into a dictionary + """ if status_code == 202: return data return Serializer().deserialize(data, self.content_type()) def content_type(self, format=None): + """ + Returns the mime-type for either 'xml' or 'json'. Defaults to the + currently set format + """ if not format: format = self.format return "application/%s" % (format) - @api_call + @ApiCall def list_networks(self): """ Fetches a list of all networks for a tenant """ return self.do_request("GET", self.networks_path) - @api_call + @ApiCall def show_network_details(self, network): """ Fetches the details of a certain network """ return self.do_request("GET", self.network_path % (network)) - @api_call + @ApiCall def create_network(self, body=None): """ Creates a new network """ return self.do_request("POST", self.networks_path, body=body) - @api_call + @ApiCall def update_network(self, network, body=None): """ Updates a network """ return self.do_request("PUT", self.network_path % (network), body=body) - @api_call + @ApiCall def delete_network(self, network): """ Deletes the specified network """ return self.do_request("DELETE", self.network_path % (network)) - @api_call + @ApiCall def list_ports(self, network): """ Fetches a list of ports on a given network """ return self.do_request("GET", self.ports_path % (network)) - @api_call + @ApiCall def show_port_details(self, network, port): """ Fetches the details of a certain port """ return self.do_request("GET", self.port_path % (network, port)) - @api_call + @ApiCall def create_port(self, network, body=None): """ Creates a new port on a given network @@ -250,14 +278,14 @@ class Client(object): body = self.serialize(body) return self.do_request("POST", self.ports_path % (network), body=body) - @api_call + @ApiCall def delete_port(self, network, port): """ Deletes the specified port from a network """ return self.do_request("DELETE", self.port_path % (network, port)) - @api_call + @ApiCall def set_port_state(self, network, port, body=None): """ Sets the state of the specified port @@ -265,14 +293,14 @@ class Client(object): return self.do_request("PUT", self.port_path % (network, port), body=body) - @api_call + @ApiCall def show_port_attachment(self, network, port): """ Fetches the attachment-id associated with the specified port """ return self.do_request("GET", self.attachment_path % (network, port)) - @api_call + @ApiCall def attach_resource(self, network, port, body=None): """ Sets the attachment-id of the specified port @@ -280,7 +308,7 @@ class Client(object): return self.do_request("PUT", self.attachment_path % (network, port), body=body) - @api_call + @ApiCall def detach_resource(self, network, port): """ Removes the attachment-id of the specified port diff --git a/quantum/common/exceptions.py b/quantum/common/exceptions.py index 478ddd551b..83fd9fabe9 100644 --- a/quantum/common/exceptions.py +++ b/quantum/common/exceptions.py @@ -111,6 +111,10 @@ class AlreadyAttached(QuantumException): "already plugged into port %(att_port_id)s") +class AttachmentNotReady(QuantumException): + message = _("The attachment %(att_id)s is not ready") + + class NetworkNameExists(QuantumException): message = _("Unable to set network name to %(net_name). " \ "Network with id %(net_id) already has this name for " \ diff --git a/quantum/common/extensions.py b/quantum/common/extensions.py index f13a0c3370..79680b2254 100644 --- a/quantum/common/extensions.py +++ b/quantum/common/extensions.py @@ -226,9 +226,22 @@ class ExtensionMiddleware(wsgi.Middleware): for resource in self.ext_mgr.get_resources(): LOG.debug(_('Extended resource: %s'), resource.collection) + for action, method in resource.collection_actions.iteritems(): + path_prefix = "" + parent = resource.parent + conditions = dict(method=[method]) + path = "/%s/%s" % (resource.collection, action) + if parent: + path_prefix = "/%s/{%s_id}" % (parent["collection_name"], + parent["member_name"]) + with mapper.submapper(controller=resource.controller, + action=action, + path_prefix=path_prefix, + conditions=conditions) as submap: + submap.connect(path) + submap.connect("%s.:(format)" % path) mapper.resource(resource.collection, resource.collection, controller=resource.controller, - collection=resource.collection_actions, member=resource.member_actions, parent_resource=resource.parent) diff --git a/quantum/plugins/cisco/README b/quantum/plugins/cisco/README index 5b3d59a429..503894a662 100755 --- a/quantum/plugins/cisco/README +++ b/quantum/plugins/cisco/README @@ -134,7 +134,8 @@ nexus_ip_address=10.0.0.1 # Use shortened interface syntax, e.g. "1/10" not "Ethernet1/10". nexus_first_port=1/10 nexus_second_port=1/11 -#Port number where the SSH will be running at Nexus Switch, e.g.: 22 (Default) +#Port number where SSH will be running on the Nexus switch. Typically this is 22 +#unless you've configured your switch otherwise. nexus_ssh_port=22 [DRIVER] @@ -153,8 +154,15 @@ name=quantum.plugins.cisco.nexus.cisco_nexus_network_driver.CiscoNEXUSDriver mysql -u -p -e "create database quantum_l2network" - 5b. Enter the quantum_l2netowrk database configuration info in the + 5b. Enter the quantum_l2network database configuration info in the quantum/plugins/cisco/conf/db_conn.ini file. + + 5c. If there is a change in the plugin configuration, service would need + to be restarted after dropping and re-creating the database using + the following commands - + +mysql -u -p -e "drop database quantum_l2network" +mysql -u -p -e "create database quantum_l2network" 6. Verify that you have the correct credentials for each IP address listed in quantum/plugins/cisco/conf/credentials.ini. Example: @@ -229,7 +237,8 @@ result the quantum/plugins/cisco/run_tests.py script. Set the environment variable PLUGIN_DIR to the location of the plugin directory. This is manadatory if the run_tests.sh script is used. - export PLUGIN_DIR=quantum/plugins/cisco + In bash : export PLUGIN_DIR=quantum/plugins/cisco + tcsh/csh : setenv PLUGIN_DIR quantum/plugins/cisco ./run_tests.sh quantum.plugins.cisco.tests.unit.test_l2networkApi or @@ -240,7 +249,8 @@ result the quantum/plugins/cisco/run_tests.py script. 2. Specific Plugin unit test (needs environment setup as indicated in the pre-requisites): - export PLUGIN_DIR=quantum/plugins/cisco + In bash : export PLUGIN_DIR=quantum/plugins/cisco + tcsh/csh : setenv PLUGIN_DIR quantum/plugins/cisco ./run_tests.sh quantum.plugins.cisco.tests.unit. or @@ -254,7 +264,9 @@ result the quantum/plugins/cisco/run_tests.py script. 3. All unit tests (needs environment setup as indicated in the pre-requisites): - export PLUGIN_DIR=quantum/plugins/cisco + In bash : export PLUGIN_DIR=quantum/plugins/cisco + tcsh/csh : setenv PLUGIN_DIR quantum/plugins/cisco + ./run_tests.sh quantum.plugins.cisco.tests.unit or @@ -266,6 +278,13 @@ result the quantum/plugins/cisco/run_tests.py script. change later. Location quantum/plugins/cisco/tests/unit/test_cisco_extension.py + The script can be executed by : + ./run_tests.sh quantum.plugins.cisco.tests.unit.test_cisco_extension + + or + + python run_tests.py quantum.plugins.cisco.tests.unit.test_cisco_extension + Bingo bango bongo! That's it! Thanks for taking the leap into Quantum. ...Oh, boy! diff --git a/quantum/plugins/cisco/common/cisco_credentials.py b/quantum/plugins/cisco/common/cisco_credentials.py index d19e439283..02eab54690 100644 --- a/quantum/plugins/cisco/common/cisco_credentials.py +++ b/quantum/plugins/cisco/common/cisco_credentials.py @@ -22,8 +22,8 @@ import logging as LOG import os -from quantum.plugins.cisco.common import cisco_constants as const from quantum.plugins.cisco.common import cisco_configparser as confp +from quantum.plugins.cisco.common import cisco_constants as const from quantum.plugins.cisco.common import cisco_exceptions as cexc from quantum.plugins.cisco.db import l2network_db as cdb diff --git a/quantum/plugins/cisco/common/cisco_exceptions.py b/quantum/plugins/cisco/common/cisco_exceptions.py index 5e9eb77da6..f8440a61b3 100644 --- a/quantum/plugins/cisco/common/cisco_exceptions.py +++ b/quantum/plugins/cisco/common/cisco_exceptions.py @@ -1,4 +1,3 @@ -""" # vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2011 Cisco Systems, Inc. All rights reserved. @@ -17,7 +16,7 @@ # # @author: Sumit Naiksatam, Cisco Systems, Inc. # @author: Rohit Agarwalla, Cisco Systems, Inc. -""" + """ Exceptions used by the Cisco plugin """ @@ -86,18 +85,18 @@ class VlanIDNotFound(exceptions.QuantumException): class VlanIDNotAvailable(exceptions.QuantumException): - """VLAN ID is reserved""" - message = _("No available Vlan ID found") + """No VLAN ID available""" + message = _("No Vlan ID available") class QosNotFound(exceptions.QuantumException): - """QoS ID could not be found""" + """QoS level with this ID cannot be found""" message = _("QoS level %(qos_id)s could not be found " \ "for tenant %(tenant_id)s") class QoSLevelInvalidDelete(exceptions.QuantumException): - """QoS ID could not be deleted""" + """QoS is associated with a port profile, hence cannot be deleted""" message = _("QoS level %(qos_id)s could not be deleted " \ "for tenant %(tenant_id)s since association exists") @@ -109,7 +108,7 @@ class QosNameAlreadyExists(exceptions.QuantumException): class CredentialNotFound(exceptions.QuantumException): - """Credential ID could not be found""" + """Credential with this ID cannot be found""" message = _("Credential %(credential_id)s could not be found " \ "for tenant %(tenant_id)s") diff --git a/extensions/_faults.py b/quantum/plugins/cisco/common/cisco_faults.py similarity index 81% rename from extensions/_faults.py rename to quantum/plugins/cisco/common/cisco_faults.py index 6bdd3a48b9..ed50ed65ac 100644 --- a/extensions/_faults.py +++ b/quantum/plugins/cisco/common/cisco_faults.py @@ -20,7 +20,6 @@ """ import webob.dec -from quantum.api import api_common as common from quantum.common import wsgi @@ -30,12 +29,7 @@ class Fault(webob.exc.HTTPException): _fault_names = { 400: "malformedRequest", 401: "unauthorized", - 420: "networkNotFound", 421: "PortprofileInUse", - 430: "portNotFound", - 431: "requestedStateInvalid", - 432: "portInUse", - 440: "alreadyAttached", 450: "PortprofileNotFound", 451: "CredentialNotFound", 452: "QoSNotFound", @@ -49,21 +43,19 @@ class Fault(webob.exc.HTTPException): @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): - """Generate a WSGI response based on the exception passed to ctor.""" + """Generate a WSGI response based on the + exception passed to constructor.""" # Replace the body with fault details. code = self.wrapped_exc.status_int fault_name = self._fault_names.get(code, "quantumServiceFault") fault_data = { fault_name: { 'code': code, - 'message': self.wrapped_exc.explanation, - 'detail': self.wrapped_exc.detail}} + 'message': self.wrapped_exc.explanation}} # 'code' is an attribute on the fault tag itself - metadata = {'application/xml': {'attributes': {fault_name: 'code'}}} - default_xmlns = common.XML_NS_V10 - serializer = wsgi.Serializer(metadata, default_xmlns) content_type = req.best_match_content_type() - #self.wrapped_exc.body = serializer.serialize(fault_data, content_type) + self.wrapped_exc.body = wsgi.Serializer().\ + serialize(fault_data, content_type) self.wrapped_exc.content_type = content_type return self.wrapped_exc @@ -79,7 +71,7 @@ class PortprofileNotFound(webob.exc.HTTPClientError): """ code = 450 title = 'Portprofile Not Found' - explanation = ('Unable to find a Portprofile with' + explanation = ('Unable to find a Portprofile with' + ' the specified identifier.') @@ -95,8 +87,8 @@ class PortNotFound(webob.exc.HTTPClientError): code = 430 title = 'Port not Found' explanation = ('Unable to find a port with the specified identifier.') - - + + class CredentialNotFound(webob.exc.HTTPClientError): """ subclass of :class:`~HTTPClientError` @@ -108,10 +100,10 @@ class CredentialNotFound(webob.exc.HTTPClientError): """ code = 451 title = 'Credential Not Found' - explanation = ('Unable to find a Credential with' + explanation = ('Unable to find a Credential with' + ' the specified identifier.') - - + + class QosNotFound(webob.exc.HTTPClientError): """ subclass of :class:`~HTTPClientError` @@ -123,10 +115,10 @@ class QosNotFound(webob.exc.HTTPClientError): """ code = 452 title = 'QoS Not Found' - explanation = ('Unable to find a QoS with' + explanation = ('Unable to find a QoS with' + ' the specified identifier.') - - + + class NovatenantNotFound(webob.exc.HTTPClientError): """ subclass of :class:`~HTTPClientError` @@ -138,7 +130,7 @@ class NovatenantNotFound(webob.exc.HTTPClientError): """ code = 453 title = 'Nova tenant Not Found' - explanation = ('Unable to find a Novatenant with' + explanation = ('Unable to find a Novatenant with' + ' the specified identifier.') diff --git a/quantum/plugins/cisco/conf/quantum.conf.ciscoext b/quantum/plugins/cisco/conf/quantum.conf.ciscoext index 925a5ae903..116a6d9a73 100644 --- a/quantum/plugins/cisco/conf/quantum.conf.ciscoext +++ b/quantum/plugins/cisco/conf/quantum.conf.ciscoext @@ -12,7 +12,7 @@ bind_host = 0.0.0.0 bind_port = 9696 # Path to the extensions -api_extensions_path = ../extensions +api_extensions_path = ../../../../extensions [pipeline:extensions_app_with_filter] pipeline = extensions extensions_test_app @@ -21,4 +21,4 @@ pipeline = extensions extensions_test_app paste.filter_factory = quantum.common.extensions:plugin_aware_extension_middleware_factory [app:extensions_test_app] -paste.app_factory = tests.unit.test_cisco_extension:app_factory +paste.app_factory = quantum.plugins.cisco.tests.unit.test_cisco_extension:app_factory diff --git a/quantum/plugins/cisco/db/l2network_db.py b/quantum/plugins/cisco/db/l2network_db.py index 81557264ab..30a1f5d36e 100644 --- a/quantum/plugins/cisco/db/l2network_db.py +++ b/quantum/plugins/cisco/db/l2network_db.py @@ -20,8 +20,8 @@ from sqlalchemy.orm import exc from quantum.common import exceptions as q_exc from quantum.plugins.cisco import l2network_plugin_configuration as conf from quantum.plugins.cisco.common import cisco_exceptions as c_exc +from quantum.plugins.cisco.db import l2network_models -import l2network_models import logging as LOG import quantum.plugins.cisco.db.api as db import quantum.plugins.cisco.db.nexus_db as ndb @@ -117,17 +117,16 @@ def reserve_vlanid(): LOG.debug("reserve_vlanid() called") session = db.get_session() try: - vlanids = session.query(l2network_models.VlanID).\ + rvlan = session.query(l2network_models.VlanID).\ filter_by(vlan_used=False).\ - all() - rvlan = vlanids[0] + first() rvlanid = session.query(l2network_models.VlanID).\ filter_by(vlan_id=rvlan["vlan_id"]).\ one() rvlanid["vlan_used"] = True session.merge(rvlanid) session.flush() - return vlanids[0]["vlan_id"] + return rvlan["vlan_id"] except exc.NoResultFound: raise c_exc.VlanIDNotAvailable() diff --git a/quantum/plugins/cisco/db/l2network_models.py b/quantum/plugins/cisco/db/l2network_models.py index 8051de5849..63caf92fde 100644 --- a/quantum/plugins/cisco/db/l2network_models.py +++ b/quantum/plugins/cisco/db/l2network_models.py @@ -185,6 +185,6 @@ class Credential(BASE, L2NetworkBase): self.password = password def __repr__(self): - return "" % \ + return "" % \ (self.credential_id, self.tenant_id, self.credential_name, self.user_name, self.password) diff --git a/quantum/plugins/cisco/db/nexus_db.py b/quantum/plugins/cisco/db/nexus_db.py index 054ad6b2ed..f2410990cc 100644 --- a/quantum/plugins/cisco/db/nexus_db.py +++ b/quantum/plugins/cisco/db/nexus_db.py @@ -20,9 +20,9 @@ import logging as LOG from sqlalchemy.orm import exc import quantum.plugins.cisco.db.api as db -import nexus_models from quantum.plugins.cisco.common import cisco_exceptions as c_exc +from quantum.plugins.cisco.db import nexus_models def get_all_nexusport_bindings(): diff --git a/quantum/plugins/cisco/db/nexus_models.py b/quantum/plugins/cisco/db/nexus_models.py index 51a210990d..bd6c765ec9 100644 --- a/quantum/plugins/cisco/db/nexus_models.py +++ b/quantum/plugins/cisco/db/nexus_models.py @@ -27,8 +27,6 @@ class NexusPortBinding(BASE, L2NetworkBase): id = Column(Integer, primary_key=True, autoincrement=True) port_id = Column(String(255)) - #vlan_id = Column(Integer, ForeignKey("vlan_bindings.vlan_id"), \ - # nullable=False) vlan_id = Column(Integer, nullable=False) def __init__(self, port_id, vlan_id): diff --git a/quantum/plugins/cisco/l2network_plugin.py b/quantum/plugins/cisco/l2network_plugin.py index 84f5602b90..8eb908657d 100644 --- a/quantum/plugins/cisco/l2network_plugin.py +++ b/quantum/plugins/cisco/l2network_plugin.py @@ -26,10 +26,11 @@ import platform from quantum.common import exceptions as exc from quantum.common import utils from quantum.quantum_plugin_base import QuantumPluginBase + from quantum.plugins.cisco import l2network_plugin_configuration as conf from quantum.plugins.cisco.common import cisco_constants as const -from quantum.plugins.cisco.common import cisco_exceptions as cexc from quantum.plugins.cisco.common import cisco_credentials as cred +from quantum.plugins.cisco.common import cisco_exceptions as cexc from quantum.plugins.cisco.common import cisco_utils as cutil from quantum.plugins.cisco.db import api as db from quantum.plugins.cisco.db import l2network_db as cdb @@ -441,7 +442,7 @@ class L2Network(QuantumPluginBase): return credential def rename_credential(self, tenant_id, credential_id, new_name): - """Do nothing for this resource""" + """Rename the particular credential resource""" LOG.debug("rename_credential() called\n") try: credential = cdb.get_credential(tenant_id, credential_id) diff --git a/quantum/plugins/cisco/l2network_plugin_configuration.py b/quantum/plugins/cisco/l2network_plugin_configuration.py index 45eadd172e..ec20fcceb0 100644 --- a/quantum/plugins/cisco/l2network_plugin_configuration.py +++ b/quantum/plugins/cisco/l2network_plugin_configuration.py @@ -29,6 +29,9 @@ CONF_PARSER_OBJ = confp.\ CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) + \ "/" + CONF_FILE) +""" +Reading the conf for the l2network_plugin +""" SECTION_CONF = CONF_PARSER_OBJ['VLANS'] VLAN_NAME_PREFIX = SECTION_CONF['vlan_name_prefix'] VLAN_START = SECTION_CONF['vlan_start'] @@ -55,6 +58,9 @@ CONF_PARSER_OBJ = confp.\ CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) + \ "/" + CONF_FILE) +""" +Reading the config for the device plugins +""" PLUGINS = CONF_PARSER_OBJ.walk(CONF_PARSER_OBJ.dummy) CONF_FILE = "conf/db_conn.ini" @@ -63,6 +69,9 @@ CONF_PARSER_OBJ = confp.\ CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) + \ "/" + CONF_FILE) +""" +Reading DB config for the Quantum DB +""" SECTION_CONF = CONF_PARSER_OBJ['DATABASE'] DB_NAME = SECTION_CONF['name'] DB_USER = SECTION_CONF['user'] diff --git a/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py b/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py index 06b6ad30ee..a760fefad3 100644 --- a/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py +++ b/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py @@ -49,12 +49,19 @@ class CiscoNEXUSDriver(): username=nexus_user, password=nexus_password) return man + def create_xml_snippet(self, cutomized_config): + """ + Creates the Proper XML structure for the Nexus Switch Configuration + """ + conf_xml_snippet = snipp.EXEC_CONF_SNIPPET % (cutomized_config) + return conf_xml_snippet + def enable_vlan(self, mgr, vlanid, vlanname): """ Creates a VLAN on Nexus Switch given the VLAN ID and Name """ confstr = snipp.CMD_VLAN_CONF_SNIPPET % (vlanid, vlanname) - confstr = snipp.EXEC_CONF_PREFIX + confstr + snipp.EXEC_CONF_POSTFIX + confstr = self.create_xml_snippet(confstr) mgr.edit_config(target='running', config=confstr) def disable_vlan(self, mgr, vlanid): @@ -62,7 +69,7 @@ class CiscoNEXUSDriver(): Delete a VLAN on Nexus Switch given the VLAN ID """ confstr = snipp.CMD_NO_VLAN_CONF_SNIPPET % vlanid - confstr = snipp.EXEC_CONF_PREFIX + confstr + snipp.EXEC_CONF_POSTFIX + confstr = self.create_xml_snippet(confstr) mgr.edit_config(target='running', config=confstr) def enable_port_trunk(self, mgr, interface): @@ -70,7 +77,7 @@ class CiscoNEXUSDriver(): Enables trunk mode an interface on Nexus Switch """ confstr = snipp.CMD_PORT_TRUNK % (interface) - confstr = snipp.EXEC_CONF_PREFIX + confstr + snipp.EXEC_CONF_POSTFIX + confstr = self.create_xml_snippet(confstr) LOG.debug("NexusDriver: %s" % confstr) mgr.edit_config(target='running', config=confstr) @@ -79,7 +86,7 @@ class CiscoNEXUSDriver(): Disables trunk mode an interface on Nexus Switch """ confstr = snipp.CMD_NO_SWITCHPORT % (interface) - confstr = snipp.EXEC_CONF_PREFIX + confstr + snipp.EXEC_CONF_POSTFIX + confstr = self.create_xml_snippet(confstr) LOG.debug("NexusDriver: %s" % confstr) mgr.edit_config(target='running', config=confstr) @@ -89,7 +96,7 @@ class CiscoNEXUSDriver(): VLANID """ confstr = snipp.CMD_VLAN_INT_SNIPPET % (interface, vlanid) - confstr = snipp.EXEC_CONF_PREFIX + confstr + snipp.EXEC_CONF_POSTFIX + confstr = self.create_xml_snippet(confstr) LOG.debug("NexusDriver: %s" % confstr) mgr.edit_config(target='running', config=confstr) @@ -99,7 +106,7 @@ class CiscoNEXUSDriver(): VLANID """ confstr = snipp.CMD_NO_VLAN_INT_SNIPPET % (interface, vlanid) - confstr = snipp.EXEC_CONF_PREFIX + confstr + snipp.EXEC_CONF_POSTFIX + confstr = self.create_xml_snippet(confstr) LOG.debug("NexusDriver: %s" % confstr) mgr.edit_config(target='running', config=confstr) diff --git a/quantum/plugins/cisco/nexus/cisco_nexus_snippets.py b/quantum/plugins/cisco/nexus/cisco_nexus_snippets.py index 9c7dab058e..df713fb5bc 100644 --- a/quantum/plugins/cisco/nexus/cisco_nexus_snippets.py +++ b/quantum/plugins/cisco/nexus/cisco_nexus_snippets.py @@ -29,14 +29,10 @@ LOG.getLogger(const.LOGGER_COMPONENT_NAME) # The following are standard strings, messages used to communicate with Nexus, -EXEC_CONF_PREFIX = """ +EXEC_CONF_SNIPPET = """ - <__XML__MODE__exec_configure> -""" - - -EXEC_CONF_POSTFIX = """ + <__XML__MODE__exec_configure>%s @@ -156,4 +152,5 @@ FILTER_SHOW_VLAN_BRIEF_SNIPPET = """ - """ + +""" diff --git a/quantum/plugins/cisco/nova/__init__.py b/quantum/plugins/cisco/nova/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/quantum/plugins/cisco/nova/quantum_aware_scheduler.py b/quantum/plugins/cisco/nova/quantum_aware_scheduler.py new file mode 100644 index 0000000000..0f73618ee0 --- /dev/null +++ b/quantum/plugins/cisco/nova/quantum_aware_scheduler.py @@ -0,0 +1,96 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, 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. +# +# @author: Sumit Naiksatam, Cisco Systems, Inc. +# + +from nova import exception as excp +from nova import flags +from nova import log as logging +from nova.scheduler import driver +from quantum.client import Client +from quantum.common.wsgi import Serializer + +LOG = logging.getLogger('quantum.plugins.cisco.nova.quantum_aware_scheduler') + +FLAGS = flags.FLAGS +flags.DEFINE_string('quantum_host', "127.0.0.1", + 'IP address of the quantum network service.') +flags.DEFINE_integer('quantum_port', 9696, + 'Listening port for Quantum network service') + +HOST = FLAGS.quantum_host +PORT = FLAGS.quantum_port +USE_SSL = False +ACTION_PREFIX_EXT = '/v0.1' +ACTION_PREFIX_CSCO = ACTION_PREFIX_EXT + \ + '/extensions/csco/tenants/{tenant_id}' +TENANT_ID = 'nova' +CSCO_EXT_NAME = 'Cisco Nova Tenant' + + +class QuantumScheduler(driver.Scheduler): + """ + Quantum network service dependent scheduler + Obtains the hostname from Quantum using an extension API + """ + def __init__(self): + # We have to send a dummy tenant name here since the client + # needs some tenant name, but the tenant name will not be used + # since the extensions URL does not require it + client = Client(HOST, PORT, USE_SSL, format='json', + action_prefix=ACTION_PREFIX_EXT, tenant="dummy") + request_url = "/extensions" + data = client.do_request('GET', request_url) + LOG.debug("Obtained supported extensions from Quantum: %s" % data) + for ext in data['extensions']: + name = ext['name'] + if name == CSCO_EXT_NAME: + LOG.debug("Quantum plugin supports required \"%s\" extension" + "for the scheduler." % name) + return + LOG.error("Quantum plugin does not support required \"%s\" extension" + " for the scheduler. Scheduler will quit." % CSCO_EXT_NAME) + raise excp.ServiceUnavailable() + + def schedule(self, context, topic, *args, **kwargs): + """Gets the host name from the Quantum service""" + instance_id = kwargs['instance_id'] + user_id = \ + kwargs['request_spec']['instance_properties']['user_id'] + project_id = \ + kwargs['request_spec']['instance_properties']['project_id'] + + instance_data_dict = \ + {'novatenant': \ + {'instance_id': instance_id, + 'instance_desc': \ + {'user_id': user_id, + 'project_id': project_id}}} + + client = Client(HOST, PORT, USE_SSL, format='json', tenant=TENANT_ID, + action_prefix=ACTION_PREFIX_CSCO) + request_url = "/novatenants/" + project_id + "/get_host" + data = client.do_request('PUT', request_url, body=instance_data_dict) + + hostname = data["host_list"]["host_1"] + if not hostname: + raise driver.NoValidHost(_("Scheduler was unable to locate a host" + " for this request. Is the appropriate" + " service running?")) + + LOG.debug(_("Quantum service returned host: %s") % hostname) + return hostname diff --git a/quantum/plugins/cisco/nova/vifdirect.py b/quantum/plugins/cisco/nova/vifdirect.py new file mode 100644 index 0000000000..c1fd09af46 --- /dev/null +++ b/quantum/plugins/cisco/nova/vifdirect.py @@ -0,0 +1,112 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright 2011 Cisco Systems, 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. +# +# @author: Sumit Naiksatam, Cisco Systems, Inc. +# + +"""VIF drivers for interface type direct.""" + +from nova import exception as excp +from nova import flags +from nova import log as logging +from nova.network import linux_net +from nova.virt.libvirt import netutils +from nova import utils +from nova.virt.vif import VIFDriver +from quantum.client import Client +from quantum.common.wsgi import Serializer + +LOG = logging.getLogger('quantum.plugins.cisco.nova.vifdirect') + +FLAGS = flags.FLAGS +flags.DEFINE_string('quantum_host', "127.0.0.1", + 'IP address of the quantum network service.') +flags.DEFINE_integer('quantum_port', 9696, + 'Listening port for Quantum network service') + +HOST = FLAGS.quantum_host +PORT = FLAGS.quantum_port +USE_SSL = False +TENANT_ID = 'nova' +ACTION_PREFIX_EXT = '/v0.1' +ACTION_PREFIX_CSCO = ACTION_PREFIX_EXT + \ + '/extensions/csco/tenants/{tenant_id}' +TENANT_ID = 'nova' +CSCO_EXT_NAME = 'Cisco Nova Tenant' + + +class Libvirt802dot1QbhDriver(VIFDriver): + """VIF driver for Linux bridge.""" + def __init__(self): + # We have to send a dummy tenant name here since the client + # needs some tenant name, but the tenant name will not be used + # since the extensions URL does not require it + client = Client(HOST, PORT, USE_SSL, format='json', + action_prefix=ACTION_PREFIX_EXT, tenant="dummy") + request_url = "/extensions" + data = client.do_request('GET', request_url) + LOG.debug("Obtained supported extensions from Quantum: %s" % data) + for ext in data['extensions']: + name = ext['name'] + if name == CSCO_EXT_NAME: + LOG.debug("Quantum plugin supports required \"%s\" extension" + "for the VIF driver." % name) + return + LOG.error("Quantum plugin does not support required \"%s\" extension" + " for the VIF driver. nova-compute will quit." \ + % CSCO_EXT_NAME) + raise excp.ServiceUnavailable() + + def _get_configurations(self, instance, network, mapping): + """Gets the device name and the profile name from Quantum""" + + instance_id = instance['id'] + user_id = instance['user_id'] + project_id = instance['project_id'] + vif_id = mapping['vif_uuid'] + + instance_data_dict = \ + {'novatenant': \ + {'instance_id': instance_id, + 'instance_desc': \ + {'user_id': user_id, + 'project_id': project_id, + 'vif_id': vif_id}}} + + client = Client(HOST, PORT, USE_SSL, format='json', tenant=TENANT_ID, + action_prefix=ACTION_PREFIX_CSCO) + request_url = "/novatenants/" + project_id + "/get_instance_port" + data = client.do_request('PUT', request_url, body=instance_data_dict) + + device = data['vif_desc']['device'] + portprofile = data['vif_desc']['portprofile'] + LOG.debug(_("Quantum provided the device: %s") % device) + LOG.debug(_("Quantum provided the portprofile: %s") % portprofile) + mac_id = mapping['mac'].replace(':', '') + + result = { + 'id': mac_id, + 'mac_address': mapping['mac'], + 'device_name': device, + 'profile_name': portprofile, + } + + return result + + def plug(self, instance, network, mapping): + return self._get_configurations(instance, network, mapping) + + def unplug(self, instance, network, mapping): + pass diff --git a/quantum/plugins/cisco/tests/unit/test_cisco_extension.py b/quantum/plugins/cisco/tests/unit/test_cisco_extension.py index 172a2e7122..0408ef3f6f 100644 --- a/quantum/plugins/cisco/tests/unit/test_cisco_extension.py +++ b/quantum/plugins/cisco/tests/unit/test_cisco_extension.py @@ -17,12 +17,12 @@ # @authors: Shweta Padubidri, Cisco Systems, Inc. # Peter Strunk , Cisco Systems, Inc. # Shubhangi Satras , Cisco Systems, Inc. -import json -import os.path -import routes import unittest import logging import webob +import json +import os.path +import routes from webtest import TestApp from extensions import credential from extensions import portprofile @@ -41,9 +41,9 @@ from quantum.manager import QuantumManager from quantum.plugins.cisco import l2network_plugin TEST_CONF_FILE = os.path.join(os.path.dirname(__file__), os.pardir, - os.pardir, 'etc', 'quantum.conf.ciscoext') -EXTENSIONS_PATH = os.path.join(os.path.dirname(__file__), os.pardir, - os.pardir, "extensions") + os.pardir, 'conf', 'quantum.conf.ciscoext') +EXTENSIONS_PATH = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, + os.pardir, os.pardir, os.pardir, "extensions") LOG = logging.getLogger('quantum.plugins.cisco.tests.test_cisco_extensions') @@ -62,10 +62,13 @@ class ExtensionsTestApp(wsgi.Router): class PortprofileExtensionTest(unittest.TestCase): def setUp(self): + + """ Set up function """ + parent_resource = dict(member_name="tenant", collection_name="extensions/csco/tenants") member_actions = {'associate_portprofile': "PUT", - 'disassociate_portprofile': "POST"} + 'disassociate_portprofile': "PUT"} controller = portprofile.PortprofilesController( QuantumManager.get_plugin()) res_ext = extensions.ResourceExtension('portprofiles', controller, @@ -89,6 +92,8 @@ class PortprofileExtensionTest(unittest.TestCase): def test_list_portprofile(self): + """ Test List Portprofile""" + LOG.debug("test_list_portprofile - START") req_body1 = json.dumps(self.test_port_profile) create_response1 = self.test_app.post( @@ -121,6 +126,8 @@ class PortprofileExtensionTest(unittest.TestCase): def test_create_portprofile(self): + """ Test create Portprofile""" + LOG.debug("test_create_portprofile - START") req_body = json.dumps(self.test_port_profile) index_response = self.test_app.post(self.profile_path, req_body, @@ -138,6 +145,8 @@ class PortprofileExtensionTest(unittest.TestCase): def test_create_portprofileBADRequest(self): + """ Test create Portprofile Bad Request""" + LOG.debug("test_create_portprofileBADRequest - START") index_response = self.test_app.post(self.profile_path, 'BAD_REQUEST', content_type=self.contenttype, @@ -147,6 +156,8 @@ class PortprofileExtensionTest(unittest.TestCase): def test_show_portprofile(self): + """ Test show Portprofile """ + LOG.debug("test_show_portprofile - START") req_body = json.dumps(self.test_port_profile) index_response = self.test_app.post(self.profile_path, req_body, @@ -165,6 +176,8 @@ class PortprofileExtensionTest(unittest.TestCase): def test_show_portprofileDNE(self, portprofile_id='100'): + """ Test show Portprofile does not exist""" + LOG.debug("test_show_portprofileDNE - START") show_path_temp = self.portprofile_path + portprofile_id show_port_path = str(show_path_temp) @@ -174,6 +187,8 @@ class PortprofileExtensionTest(unittest.TestCase): def test_update_portprofile(self): + """ Test update Portprofile""" + LOG.debug("test_update_portprofile - START") req_body = json.dumps(self.test_port_profile) index_response = self.test_app.post( @@ -191,12 +206,14 @@ class PortprofileExtensionTest(unittest.TestCase): rename_response = self.test_app.put(rename_path, rename_req_body) self.assertEqual(200, rename_response.status_int) - # Clean Up - Delete the Port Profile + # Clean Up - Delete the Port Profile self.tear_down_profile(rename_path) LOG.debug("test_update_portprofile - END") def test_update_portprofileBADRequest(self): + """ Test update Portprofile Bad Request""" + LOG.debug("test_update_portprofileBADRequest - START") req_body = json.dumps(self.test_port_profile) index_response = self.test_app.post( @@ -217,6 +234,8 @@ class PortprofileExtensionTest(unittest.TestCase): def test_update_portprofileDNE(self, portprofile_id='100'): + """ Test update Portprofile does not exist""" + LOG.debug("test_update_portprofileiDNE - START") rename_port_profile = {'portprofile': {'portprofile_name': 'cisco_rename_portprofile', @@ -231,6 +250,8 @@ class PortprofileExtensionTest(unittest.TestCase): def test_delete_portprofile(self): + """ Test delete Portprofile""" + LOG.debug("test_delete_portprofile - START") req_body = json.dumps(self.test_port_profile) index_response = self.test_app.post( @@ -248,6 +269,8 @@ class PortprofileExtensionTest(unittest.TestCase): def test_delete_portprofileDNE(self, portprofile_id='100'): + """ Test delete Portprofile does not exist""" + LOG.debug("test_delete_portprofileDNE - START") delete_path_temp = self.portprofile_path + portprofile_id delete_path = str(delete_path_temp) @@ -257,6 +280,8 @@ class PortprofileExtensionTest(unittest.TestCase): def create_request(self, path, body, content_type, method='GET'): + """ Test create request""" + LOG.debug("test_create_request - START") req = webob.Request.blank(path) req.method = method @@ -268,6 +293,8 @@ class PortprofileExtensionTest(unittest.TestCase): def _create_network(self, name=None): + """ Test create network""" + LOG.debug("Creating network - START") if name: net_name = name @@ -286,6 +313,8 @@ class PortprofileExtensionTest(unittest.TestCase): def _create_port(self, network_id, port_state): + """ Test create port""" + LOG.debug("Creating port for network %s - START", network_id) port_path = "/tenants/tt/networks/%s/ports" % network_id port_req_data = {'port': {'port-state': '%s' % port_state}} @@ -301,6 +330,8 @@ class PortprofileExtensionTest(unittest.TestCase): def test_associate_portprofile(self): + """ Test associate portprofile""" + LOG.debug("test_associate_portprofile - START") net_id = self._create_network() port_id = self._create_port(net_id, "ACTIVE") @@ -336,6 +367,8 @@ class PortprofileExtensionTest(unittest.TestCase): def test_associate_portprofileDNE(self, portprofile_id='100'): + """ Test associate portprofile does not exist""" + LOG.debug("test_associate_portprofileDNE - START") test_port_assign_data = {'portprofile': {'network-id': '001', 'port-id': '1'}} @@ -350,6 +383,8 @@ class PortprofileExtensionTest(unittest.TestCase): def test_disassociate_portprofile(self): + """ Test disassociate portprofile""" + LOG.debug("test_disassociate_portprofile - START") net_id = self._create_network() port_id = self._create_port(net_id, "ACTIVE") @@ -375,7 +410,7 @@ class PortprofileExtensionTest(unittest.TestCase): "/disassociate_portprofile" disassociate_path = str(disassociate_path_temp) - disassociate_response = self.test_app.post( + disassociate_response = self.test_app.put( disassociate_path, req_assign_body, content_type=self.contenttype) self.assertEqual(202, disassociate_response.status_int) @@ -388,21 +423,33 @@ class PortprofileExtensionTest(unittest.TestCase): LOG.debug("test_disassociate_portprofile - END") def tear_down_profile(self, delete_profile_path): + + """ Tear down profile""" + self.test_app.delete(delete_profile_path) def tear_down_associate_profile(self, delete_profile_path, dissociate_profile_path, req_body): - self.test_app.post(dissociate_profile_path, req_body, + + """ Tear down associate profile""" + + self.test_app.put(dissociate_profile_path, req_body, content_type=self.contenttype) self.tear_down_profile(delete_profile_path) def tearDown(self): + + """ Tear down """ + db.clear_db() class NovatenantExtensionTest(unittest.TestCase): def setUp(self): + + """ Set up function""" + parent_resource = dict(member_name="tenant", collection_name="extensions/csco/tenants") member_actions = {'schedule_host': "PUT", @@ -416,44 +463,49 @@ class NovatenantExtensionTest(unittest.TestCase): SimpleExtensionManager(res_ext)) self.contenttype = 'application/json' self.novatenants_path = '/extensions/csco/tenants/tt/novatenants/' - self.test_instance_data = {'novatenant': {'instance_id': 1, + self.test_associate_data = {'novatenant': {'instance_id': 1, 'instance_desc': {'key1': '1', 'key2': '2'}}} def test_schedule_host(self): + """ Test get host""" LOG.debug("test_schedule_host - START") - req_body = json.dumps(self.test_instance_data) + req_body = json.dumps(self.test_associate_data) host_path = self.novatenants_path + "001/schedule_host" host_response = self.test_app.put( - host_path, req_body, - content_type=self.contenttype) + host_path, req_body, + content_type=self.contenttype) self.assertEqual(200, host_response.status_int) LOG.debug("test_schedule_host - END") def test_schedule_hostBADRequest(self): + """ Test get host bad request""" LOG.debug("test_schedule_hostBADRequest - START") host_path = self.novatenants_path + "001/schedule_host" host_response = self.test_app.put( - host_path, 'BAD_REQUEST', - content_type=self.contenttype, status='*') + host_path, 'BAD_REQUEST', + content_type=self.contenttype, status='*') self.assertEqual(400, host_response.status_int) LOG.debug("test_schedule_hostBADRequest - END") - def test_instance_port(self): - LOG.debug("test_instance_port - START") - req_body = json.dumps(self.test_instance_data) - instance_port_path = self.novatenants_path + "001/associate_port" - instance_port_response = self.test_app.put( - instance_port_path, req_body, + def test_associate_port(self): + """ Test get associate port """ + LOG.debug("test_associate_port - START") + req_body = json.dumps(self.test_associate_data) + associate_port_path = self.novatenants_path + "001/associate_port" + associate_port_response = self.test_app.put( + associate_port_path, req_body, content_type=self.contenttype) - self.assertEqual(200, instance_port_response.status_int) - LOG.debug("test_instance_port - END") + self.assertEqual(200, associate_port_response.status_int) + LOG.debug("test_associate_port - END") class QosExtensionTest(unittest.TestCase): def setUp(self): + """ Set up function """ + parent_resource = dict(member_name="tenant", collection_name="extensions/csco/tenants") controller = qos.QosController(QuantumManager.get_plugin()) @@ -470,6 +522,8 @@ class QosExtensionTest(unittest.TestCase): def test_create_qos(self): + """ Test create qos """ + LOG.debug("test_create_qos - START") req_body = json.dumps(self.test_qos_data) index_response = self.test_app.post(self.qos_path, @@ -488,6 +542,8 @@ class QosExtensionTest(unittest.TestCase): def test_create_qosBADRequest(self): + """ Test create qos bad request """ + LOG.debug("test_create_qosBADRequest - START") index_response = self.test_app.post(self.qos_path, 'BAD_REQUEST', @@ -497,6 +553,9 @@ class QosExtensionTest(unittest.TestCase): LOG.debug("test_create_qosBADRequest - END") def test_list_qoss(self): + + """ Test list qoss """ + LOG.debug("test_list_qoss - START") req_body1 = json.dumps(self.test_qos_data) create_resp1 = self.test_app.post(self.qos_path, req_body1, @@ -524,6 +583,9 @@ class QosExtensionTest(unittest.TestCase): LOG.debug("test_list_qoss - END") def test_show_qos(self): + + """ Test show qos """ + LOG.debug("test_show_qos - START") req_body = json.dumps(self.test_qos_data) index_response = self.test_app.post(self.qos_path, req_body, @@ -541,6 +603,9 @@ class QosExtensionTest(unittest.TestCase): LOG.debug("test_show_qos - END") def test_show_qosDNE(self, qos_id='100'): + + """ Test show qos does not exist""" + LOG.debug("test_show_qosDNE - START") show_path_temp = self.qos_second_path + qos_id show_qos_path = str(show_path_temp) @@ -550,6 +615,8 @@ class QosExtensionTest(unittest.TestCase): def test_update_qos(self): + """ Test update qos """ + LOG.debug("test_update_qos - START") req_body = json.dumps(self.test_qos_data) index_response = self.test_app.post(self.qos_path, req_body, @@ -567,6 +634,9 @@ class QosExtensionTest(unittest.TestCase): LOG.debug("test_update_qos - END") def test_update_qosDNE(self, qos_id='100'): + + """ Test update qos does not exist """ + LOG.debug("test_update_qosDNE - START") rename_req_body = json.dumps({'qos': {'qos_name': 'cisco_rename_qos', 'qos_desc': {'PPS': 50, 'TTL': 5}}}) @@ -578,6 +648,9 @@ class QosExtensionTest(unittest.TestCase): LOG.debug("test_update_qosDNE - END") def test_update_qosBADRequest(self): + + """ Test update qos bad request """ + LOG.debug("test_update_qosBADRequest - START") req_body = json.dumps(self.test_qos_data) index_response = self.test_app.post(self.qos_path, req_body, @@ -597,6 +670,8 @@ class QosExtensionTest(unittest.TestCase): def test_delete_qos(self): + """ Test delte qos """ + LOG.debug("test_delete_qos - START") req_body = json.dumps({'qos': {'qos_name': 'cisco_test_qos', 'qos_desc': {'PPS': 50, 'TTL': 5}}}) @@ -612,6 +687,9 @@ class QosExtensionTest(unittest.TestCase): LOG.debug("test_delete_qos - END") def test_delete_qosDNE(self, qos_id='100'): + + """ Test delte qos does not exist""" + LOG.debug("test_delete_qosDNE - START") delete_path_temp = self.qos_second_path + qos_id delete_path = str(delete_path_temp) @@ -620,12 +698,18 @@ class QosExtensionTest(unittest.TestCase): LOG.debug("test_delete_qosDNE - END") def tearDownQos(self, delete_profile_path): + + """ Tear Down Qos """ + self.test_app.delete(delete_profile_path) class CredentialExtensionTest(unittest.TestCase): def setUp(self): + + """ Set up function """ + parent_resource = dict(member_name="tenant", collection_name="extensions/csco/tenants") controller = credential.CredentialController( @@ -643,6 +727,9 @@ class CredentialExtensionTest(unittest.TestCase): 'password': 'newPasswd1'}} def test_list_credentials(self): + + """ Test list credentials """ + #Create Credential before listing LOG.debug("test_list_credentials - START") req_body1 = json.dumps(self.test_credential_data) @@ -675,6 +762,9 @@ class CredentialExtensionTest(unittest.TestCase): LOG.debug("test_list_credentials - END") def test_create_credential(self): + + """ Test create credential """ + LOG.debug("test_create_credential - START") req_body = json.dumps(self.test_credential_data) index_response = self.test_app.post( @@ -691,6 +781,9 @@ class CredentialExtensionTest(unittest.TestCase): LOG.debug("test_create_credential - END") def test_create_credentialBADRequest(self): + + """ Test create credential bad request """ + LOG.debug("test_create_credentialBADRequest - START") index_response = self.test_app.post( self.credential_path, 'BAD_REQUEST', @@ -699,6 +792,9 @@ class CredentialExtensionTest(unittest.TestCase): LOG.debug("test_create_credentialBADRequest - END") def test_show_credential(self): + + """ Test show credential """ + LOG.debug("test_show_credential - START") req_body = json.dumps(self.test_credential_data) index_response = self.test_app.post( @@ -714,6 +810,9 @@ class CredentialExtensionTest(unittest.TestCase): LOG.debug("test_show_credential - END") def test_show_credentialDNE(self, credential_id='100'): + + """ Test show credential does not exist """ + LOG.debug("test_show_credentialDNE - START") show_path_temp = self.cred_second_path + credential_id show_cred_path = str(show_path_temp) @@ -722,6 +821,9 @@ class CredentialExtensionTest(unittest.TestCase): LOG.debug("test_show_credentialDNE - END") def test_update_credential(self): + + """ Test update credential """ + LOG.debug("test_update_credential - START") req_body = json.dumps(self.test_credential_data) @@ -744,6 +846,9 @@ class CredentialExtensionTest(unittest.TestCase): LOG.debug("test_update_credential - END") def test_update_credBADReq(self): + + """ Test update credential bad request """ + LOG.debug("test_update_credBADReq - START") req_body = json.dumps(self.test_credential_data) index_response = self.test_app.post( @@ -760,6 +865,9 @@ class CredentialExtensionTest(unittest.TestCase): LOG.debug("test_update_credBADReq - END") def test_update_credentialDNE(self, credential_id='100'): + + """ Test update credential does not exist""" + LOG.debug("test_update_credentialDNE - START") rename_req_body = json.dumps({'credential': {'credential_name': 'cred3', @@ -773,6 +881,9 @@ class CredentialExtensionTest(unittest.TestCase): LOG.debug("test_update_credentialDNE - END") def test_delete_credential(self): + + """ Test delete credential """ + LOG.debug("test_delete_credential - START") req_body = json.dumps(self.test_credential_data) index_response = self.test_app.post( @@ -788,6 +899,9 @@ class CredentialExtensionTest(unittest.TestCase): LOG.debug("test_delete_credential - END") def test_delete_credentialDNE(self, credential_id='100'): + + """ Test delete credential does not exist """ + LOG.debug("test_delete_credentialDNE - START") delete_path_temp = self.cred_second_path + credential_id delete_path = str(delete_path_temp) diff --git a/quantum/plugins/cisco/tests/unit/test_l2networkApi.py b/quantum/plugins/cisco/tests/unit/test_l2networkApi.py index aff7b6c13e..901da1b8ae 100644 --- a/quantum/plugins/cisco/tests/unit/test_l2networkApi.py +++ b/quantum/plugins/cisco/tests/unit/test_l2networkApi.py @@ -19,13 +19,11 @@ import logging import unittest -#import time from quantum.common import exceptions as exc from quantum.plugins.cisco.common import cisco_constants as const from quantum.plugins.cisco.common import cisco_exceptions as cexc from quantum.plugins.cisco import l2network_plugin from quantum.plugins.cisco import l2network_plugin_configuration as conf -#from quantum.plugins.cisco.common import cisco_utils as utils from quantum.plugins.cisco.db import api as db from quantum.plugins.cisco.db import l2network_db as cdb @@ -72,7 +70,6 @@ class CoreAPITestFunc(unittest.TestCase): tenant_id, new_net_dict[const.NET_ID]) self.assertRaises(exc.NetworkNotFound, db.network_get, new_net_dict[const.NET_ID]) - #self.assertEqual(net, None) self.assertEqual( new_net_dict[const.NET_ID], delete_net_dict[const.NET_ID]) LOG.debug("test_delete_network - END") @@ -303,11 +300,6 @@ class CoreAPITestFunc(unittest.TestCase): port_dict[const.PORT_ID]) self.assertRaises(exc.PortNotFound, db.port_get, new_net_dict[const.NET_ID], port_dict[const.PORT_ID]) - #self.assertEqual(port, None) - # self.assertEqual(delete_port_dict[const.PORT_STATE], port_state) -# self.assertEqual(delete_port_dict[const.NET_ID], new_net_dict[NET_ID]) -# self.assertEqual(delete_port_dict[const.PORT_ID], -# new_net_dict[PORT_ID]) self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID]) self.assertEqual(delete_port_dict[const.PORT_ID], port_dict[const.PORT_ID]) @@ -470,10 +462,6 @@ class CoreAPITestFunc(unittest.TestCase): port_dict[const.PORT_ID], remote_interface) port = db.port_get(new_net_dict[const.NET_ID], port_dict[const.PORT_ID]) - # self.assertEqual( - # self._l2network_plugin._networks[new_net_dict[const.NET_ID]] - # [const.NET_PORTS][port_dict[const.PORT_ID]] - # [const.ATTACHMENT], remote_interface) self.assertEqual(port[const.INTERFACEID], remote_interface) self.tearDownNetworkPortInterface( tenant_id, new_net_dict[const.NET_ID], @@ -553,9 +541,6 @@ class CoreAPITestFunc(unittest.TestCase): port_dict[const.PORT_ID]) port = db.port_get(new_net_dict[const.NET_ID], port_dict[const.PORT_ID]) - # self.assertEqual(self._l2network_plugin._networks - # [new_net_dict[const.NET_ID]][const.NET_PORTS] - # [port_dict[const.PORT_ID]][const.ATTACHMENT], None) self.assertEqual(port[const.INTERFACEID], None) self.tearDownNetworkPort(tenant_id, new_net_dict[const.NET_ID], port_dict[const.PORT_ID]) @@ -616,12 +601,6 @@ class CoreAPITestFunc(unittest.TestCase): port_profile = cdb.get_portprofile(tenant_id, port_profile_id) self.assertEqual(port_profile[const.PPNAME], profile_name) self.assertEqual(port_profile[const.PPQOS], qos) - # self.assertEqual( - # self._l2network_plugin._portprofiles[port_profile_id]['vlan-id'], - # vlan_id) - #self.assertEqual( - # self._l2network_plugin._portprofiles[port_profile_id] - # ['profile-name'], profile_name) self.tearDownPortProfile(tenant_id, port_profile_id) LOG.debug("test_create_portprofile - tenant id: %s - END", net_tenant_id) @@ -641,10 +620,7 @@ class CoreAPITestFunc(unittest.TestCase): tenant_id, self.profile_name, self.qos) port_profile_id = port_profile_dict['profile_id'] self._l2network_plugin.delete_portprofile(tenant_id, port_profile_id) -# port_profile = cdb.get_portprofile(tenant_id, port_profile_id) self.assertRaises(Exception, cdb.get_portprofile, port_profile_id) -# self.assertEqual(port_profile, {}) -# self.assertEqual(self._l2network_plugin._portprofiles, {}) LOG.debug("test_delete_portprofile - tenant id: %s - END", net_tenant_id) @@ -714,14 +690,6 @@ class CoreAPITestFunc(unittest.TestCase): new_pplist.append(new_pp) self.assertTrue(new_pplist[0] in port_profile_list) self.assertTrue(new_pplist[1] in port_profile_list) -# self.assertEqual(self._l2network_plugin._portprofiles - # [port_profile_id1]['vlan-id'], self.vlan_id) - # self.assertEqual(self._l2network_plugin._portprofiles - # [port_profile_id1]['profile-name'], self.profile_name) - # self.assertEqual(self._l2network_plugin._portprofiles - # [port_profile_id2]['vlan-id'], vlan_id2) - # self.assertEqual(self._l2network_plugin._portprofiles - # [port_profile_id2]['profile-name'], profile_name2) self.tearDownPortProfile(tenant_id, port_profile_id1) self.tearDownPortProfile(tenant_id, port_profile_id2) @@ -816,9 +784,6 @@ class CoreAPITestFunc(unittest.TestCase): port_profile_associate = cdb.get_pp_binding(tenant_id, port_profile_id) self.assertEqual(port_profile_associate[const.PORTID], port_dict[const.PORT_ID]) - #self.assertEqual( - # self._l2network_plugin._portprofiles[port_profile_id] - # [const.PROFILE_ASSOCIATIONS][0], port_id) self.tearDownAssociatePortProfile( tenant_id, new_net_dict[const.NET_ID], port_dict[const.PORT_ID], port_profile_id) @@ -863,8 +828,6 @@ class CoreAPITestFunc(unittest.TestCase): port_dict[const.PORT_ID], port_profile_id) port_profile_associate = cdb.get_pp_binding(tenant_id, port_profile_id) self.assertEqual(port_profile_associate, []) -# self.assertEqual(self._l2network_plugin._portprofiles - # [port_profile_id][const.PROFILE_ASSOCIATIONS], []) self.tearDownPortProfile(tenant_id, port_profile_id) self.tearDownNetworkPort( tenant_id, new_net_dict[const.NET_ID], @@ -883,7 +846,6 @@ class CoreAPITestFunc(unittest.TestCase): tenant_id, net_id, port_id, profile_id) LOG.debug("test_disassociate_portprofileDNE - END") -# def test_disassociate_portprofile_Unassociated def test_get_vlan_name(self, net_tenant_id=None, vlan_id="NewVlan", vlan_prefix=conf.VLAN_NAME_PREFIX): """ @@ -923,22 +885,17 @@ class CoreAPITestFunc(unittest.TestCase): LOG.debug("test_validate_port_state - END") def setUp(self): + """ + Set up function + """ self.tenant_id = "test_tenant" self.network_name = "test_network" self.profile_name = "test_tenant_port_profile" - # self.vlan_id = "test_tenant_vlanid300" self.qos = "test_qos" self.port_state = const.PORT_UP self.net_id = '00005' self.port_id = 'p0005' self.remote_interface = 'new_interface' - #sql_query = "drop database quantum_l2network" - #sql_query_2 = "create database quantum_l2network" - #self._utils = utils.DBUtils() - #self._utils.execute_db_query(sql_query) - #time.sleep(10) - #self._utils.execute_db_query(sql_query_2) - #time.sleep(10) self._l2network_plugin = l2network_plugin.L2Network() """ @@ -950,29 +907,50 @@ class CoreAPITestFunc(unittest.TestCase): db.clear_db() def tearDownNetwork(self, tenant_id, network_dict_id): + """ + Tear down the Network + """ self._l2network_plugin.delete_network(tenant_id, network_dict_id) def tearDownPortOnly(self, tenant_id, network_dict_id, port_id): + """ + Tear doen the port + """ self._l2network_plugin.delete_port(tenant_id, network_dict_id, port_id) def tearDownNetworkPort(self, tenant_id, network_dict_id, port_id): + """ + Tear down Network Port + """ self._l2network_plugin.delete_port(tenant_id, network_dict_id, port_id) self.tearDownNetwork(tenant_id, network_dict_id) def tearDownNetworkPortInterface(self, tenant_id, network_dict_id, port_id): + """ + Tear down Network Port Interface + """ self._l2network_plugin.unplug_interface(tenant_id, network_dict_id, port_id) self.tearDownNetworkPort(tenant_id, network_dict_id, port_id) def tearDownPortProfile(self, tenant_id, port_profile_id): + """ + Tear down Port Profile + """ self._l2network_plugin.delete_portprofile(tenant_id, port_profile_id) def tearDownPortProfileBinding(self, tenant_id, port_profile_id): + """ + Tear down port profile binding + """ self._l2network_plugin.delete_portprofile(tenant_id, port_profile_id) def tearDownAssociatePortProfile(self, tenant_id, net_id, port_id, port_profile_id): + """ + Tear down associate port profile + """ self._l2network_plugin.disassociate_portprofile( tenant_id, net_id, port_id, port_profile_id) self.tearDownPortProfile(tenant_id, port_profile_id) diff --git a/quantum/plugins/cisco/tests/unit/test_nexus_plugin.py b/quantum/plugins/cisco/tests/unit/test_nexus_plugin.py index f95352eef0..65711e7c0e 100644 --- a/quantum/plugins/cisco/tests/unit/test_nexus_plugin.py +++ b/quantum/plugins/cisco/tests/unit/test_nexus_plugin.py @@ -28,6 +28,9 @@ LOG = logging.getLogger('quantum.tests.test_nexus') class TestNexusPlugin(unittest.TestCase): def setUp(self): + """ + Set up function + """ self.tenant_id = "test_tenant_cisco1" self.net_name = "test_network_cisco1" @@ -281,18 +284,3 @@ class TestNexusPlugin(unittest.TestCase): Clean up functions after the tests """ self._cisco_nexus_plugin.delete_network(tenant_id, network_dict_id) - -# def test_create_network(self): -# _test_create_network(self._cisco_nexus_plugin) - -# def test_delete_network(self): -# _test_delete_network(self._cisco_nexus_plugin) - -# def test_rename_network(self): -# _test_rename_network(self._cisco_nexus_plugin) - -# def test_show_network(self): -# _test_get_network_details(self._cisco_nexus_plugin) - -# def test_list_networks(self): -# _test_list_all_networks(self._cisco_nexus_plugin) diff --git a/quantum/plugins/cisco/tests/unit/test_ucs_driver.py b/quantum/plugins/cisco/tests/unit/test_ucs_driver.py index eaf646ff52..9da92e5b06 100644 --- a/quantum/plugins/cisco/tests/unit/test_ucs_driver.py +++ b/quantum/plugins/cisco/tests/unit/test_ucs_driver.py @@ -73,6 +73,7 @@ ASSOCIATE_PROFILE_OUTPUT = " 1 + + cwd = os.getcwd() + + working_dir = os.path.abspath("tests") + c = config.Config(stream=sys.stdout, + env=os.environ, + verbosity=3, + workingDir=working_dir) + exit_status = run_tests(c) + + if invoke_once: + sys.exit(0) + + os.chdir(cwd) + + working_dir = os.path.abspath("quantum/plugins/openvswitch/tests") + c = config.Config(stream=sys.stdout, + env=os.environ, + verbosity=3, + workingDir=working_dir) + exit_status = exit_status or run_tests(c) + + sys.exit(exit_status) diff --git a/quantum/plugins/openvswitch/tests/__init__.py b/quantum/plugins/openvswitch/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/quantum/plugins/openvswitch/tests/test_vlan_map.py b/quantum/plugins/openvswitch/tests/test_vlan_map.py new file mode 100644 index 0000000000..e67f5987a2 --- /dev/null +++ b/quantum/plugins/openvswitch/tests/test_vlan_map.py @@ -0,0 +1,36 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright 2011 Nicira Networks, 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 unittest +from quantum.plugins.openvswitch.ovs_quantum_plugin import VlanMap + + +class VlanMapTest(unittest.TestCase): + + def setUp(self): + self.vmap = VlanMap() + + def tearDown(self): + pass + + def testAddVlan(self): + vlan_id = self.vmap.acquire("foobar") + self.assertTrue(vlan_id == 2) + + def testReleaseVlan(self): + vlan_id = self.vmap.acquire("foobar") + self.vmap.release("foobar") + self.assertTrue(self.vmap.get(vlan_id) == None) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000000..dad3ddcd63 --- /dev/null +++ b/setup.py @@ -0,0 +1,34 @@ +import os +import sys +from setuptools import setup, find_packages + +def read(fname): + return open(os.path.join(os.path.dirname(__file__), fname)).read() + +requirements = ['httplib2','eventlet','routes','webob'] + +setup( + name = "Quantum", + version = "0.1", + description = "Layer 2 network as a service for Openstack", + long_description = read('README'), + url = 'http://launchpad.net/quantum', + license = 'Apache', + author = 'Netstack', + author_email = 'netstack@launchpad.net', + packages = find_packages(exclude=['tests']), + classifiers = [ + 'Development Status :: 4 - Beta', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + 'License :: OSI Approved :: BSD License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + ], + namespace_packages = ["quantum"], + install_requires = requirements, + + tests_require = ["nose"], + test_suite = "nose.collector", +) diff --git a/tests/unit/test_extensions.py b/tests/unit/test_extensions.py index 14435e9141..1860c5666a 100644 --- a/tests/unit/test_extensions.py +++ b/tests/unit/test_extensions.py @@ -62,7 +62,7 @@ class ResourceExtensionTest(unittest.TestCase): def custom_member_action(self, request, id): return {'member_action': 'value'} - def custom_collection_action(self, request): + def custom_collection_action(self, request, **kwargs): return {'collection': 'value'} def test_resource_can_be_added_as_extension(self): @@ -87,7 +87,7 @@ class ResourceExtensionTest(unittest.TestCase): self.assertEqual(200, response.status_int) self.assertEqual(json.loads(response.body)['member_action'], "value") - def test_resource_extension_with_custom_collection_action(self): + def test_resource_extension_for_get_custom_collection_action(self): controller = self.ResourceExtensionController() collections = {'custom_collection_action': "GET"} res_ext = extensions.ResourceExtension('tweedles', controller, @@ -98,6 +98,69 @@ class ResourceExtensionTest(unittest.TestCase): self.assertEqual(200, response.status_int) self.assertEqual(json.loads(response.body)['collection'], "value") + def test_resource_extension_for_put_custom_collection_action(self): + controller = self.ResourceExtensionController() + collections = {'custom_collection_action': "PUT"} + res_ext = extensions.ResourceExtension('tweedles', controller, + collection_actions=collections) + test_app = setup_extensions_test_app(SimpleExtensionManager(res_ext)) + + response = test_app.put("/tweedles/custom_collection_action") + + self.assertEqual(200, response.status_int) + self.assertEqual(json.loads(response.body)['collection'], 'value') + + def test_resource_extension_for_post_custom_collection_action(self): + controller = self.ResourceExtensionController() + collections = {'custom_collection_action': "POST"} + res_ext = extensions.ResourceExtension('tweedles', controller, + collection_actions=collections) + test_app = setup_extensions_test_app(SimpleExtensionManager(res_ext)) + + response = test_app.post("/tweedles/custom_collection_action") + + self.assertEqual(200, response.status_int) + self.assertEqual(json.loads(response.body)['collection'], 'value') + + def test_resource_extension_for_delete_custom_collection_action(self): + controller = self.ResourceExtensionController() + collections = {'custom_collection_action': "DELETE"} + res_ext = extensions.ResourceExtension('tweedles', controller, + collection_actions=collections) + test_app = setup_extensions_test_app(SimpleExtensionManager(res_ext)) + + response = test_app.delete("/tweedles/custom_collection_action") + + self.assertEqual(200, response.status_int) + self.assertEqual(json.loads(response.body)['collection'], 'value') + + def test_resource_ext_for_formatted_req_on_custom_collection_action(self): + controller = self.ResourceExtensionController() + collections = {'custom_collection_action': "GET"} + res_ext = extensions.ResourceExtension('tweedles', controller, + collection_actions=collections) + test_app = setup_extensions_test_app(SimpleExtensionManager(res_ext)) + + response = test_app.get("/tweedles/custom_collection_action.json") + + self.assertEqual(200, response.status_int) + self.assertEqual(json.loads(response.body)['collection'], "value") + + def test_resource_ext_for_nested_resource_custom_collection_action(self): + controller = self.ResourceExtensionController() + collections = {'custom_collection_action': "GET"} + parent = dict(collection_name='beetles', member_name='beetle') + res_ext = extensions.ResourceExtension('tweedles', controller, + collection_actions=collections, + parent=parent) + test_app = setup_extensions_test_app(SimpleExtensionManager(res_ext)) + + response = test_app.get("/beetles/beetle_id" + "/tweedles/custom_collection_action") + + self.assertEqual(200, response.status_int) + self.assertEqual(json.loads(response.body)['collection'], "value") + def test_returns_404_for_non_existant_extension(self): test_app = setup_extensions_test_app(SimpleExtensionManager(None)) diff --git a/tools/batch_config.py b/tools/batch_config.py index 8415513d01..f9684c82af 100644 --- a/tools/batch_config.py +++ b/tools/batch_config.py @@ -15,106 +15,45 @@ # under the License. # @author: Dan Wendlandt, Nicira Networks, Inc. -import httplib import logging as LOG -import json -import socket -import sys -import urllib - -from quantum.manager import QuantumManager from optparse import OptionParser -from quantum.common.wsgi import Serializer -from quantum.cli import MiniClient +import sys + +from quantum.client import Client +from quantum.manager import QuantumManager FORMAT = "json" CONTENT_TYPE = "application/" + FORMAT -def delete_all_nets(client, tenant_id): - res = client.do_request(tenant_id, 'GET', "/networks." + FORMAT) - resdict = json.loads(res.read()) - LOG.debug(resdict) - for n in resdict["networks"]: +def delete_all_nets(client): + res = client.list_networks() + for n in res["networks"]: nid = n["id"] - - res = client.do_request(tenant_id, 'GET', - "/networks/%s/ports.%s" % (nid, FORMAT)) - output = res.read() - if res.status != 200: - LOG.error("Failed to list ports: %s" % output) - continue - rd = json.loads(output) - LOG.debug(rd) - for port in rd["ports"]: - pid = port["id"] - - data = {'port': {'attachment-id': ''}} - body = Serializer().serialize(data, CONTENT_TYPE) - res = client.do_request(tenant_id, 'DELETE', - "/networks/%s/ports/%s/attachment.%s" % \ - (nid, pid, FORMAT), body=body) - output = res.read() - LOG.debug(output) - if res.status != 202: - LOG.error("Failed to unplug iface from port \"%s\": %s" % (vid, - pid, output)) - continue - LOG.info("Unplugged interface from port:%s on network:%s" % (pid, - nid)) - - res = client.do_request(tenant_id, 'DELETE', - "/networks/%s/ports/%s.%s" % (nid, pid, FORMAT)) - output = res.read() - if res.status != 202: - LOG.error("Failed to delete port: %s" % output) - continue + pres = client.list_ports(nid) + for port in pres["ports"]: + pid = port['id'] + client.detach_resource(nid, pid) + client.delete_port(nid, pid) print "Deleted Virtual Port:%s " \ "on Virtual Network:%s" % (pid, nid) - - res = client.do_request(tenant_id, 'DELETE', - "/networks/" + nid + "." + FORMAT) - status = res.status - if status != 202: - Log.error("Failed to delete network: %s" % nid) - output = res.read() - print output - else: - print "Deleted Virtual Network with ID:%s" % nid + client.delete_network(nid) + print "Deleted Virtual Network with ID:%s" % nid -def create_net_with_attachments(net_name, iface_ids): +def create_net_with_attachments(client, net_name, iface_ids): data = {'network': {'net-name': '%s' % net_name}} - body = Serializer().serialize(data, CONTENT_TYPE) - res = client.do_request(tenant_id, 'POST', - "/networks." + FORMAT, body=body) - rd = json.loads(res.read()) - LOG.debug(rd) - nid = rd["networks"]["network"]["id"] + res = client.create_network(data) + nid = res["networks"]["network"]["id"] print "Created a new Virtual Network %s with ID:%s" % (net_name, nid) for iface_id in iface_ids: - res = client.do_request(tenant_id, 'POST', - "/networks/%s/ports.%s" % (nid, FORMAT)) - output = res.read() - if res.status != 200: - LOG.error("Failed to create port: %s" % output) - continue - rd = json.loads(output) - new_port_id = rd["ports"]["port"]["id"] + res = client.create_port(nid) + new_port_id = res["ports"]["port"]["id"] print "Created Virtual Port:%s " \ "on Virtual Network:%s" % (new_port_id, nid) data = {'port': {'attachment-id': '%s' % iface_id}} - body = Serializer().serialize(data, CONTENT_TYPE) - res = client.do_request(tenant_id, 'PUT', - "/networks/%s/ports/%s/attachment.%s" %\ - (nid, new_port_id, FORMAT), body=body) - output = res.read() - LOG.debug(output) - if res.status != 202: - LOG.error("Failed to plug iface \"%s\" to port \"%s\": %s" % \ - (iface_id, new_port_id, output)) - continue + client.attach_resource(nid, new_port_id, data) print "Plugged interface \"%s\" to port:%s on network:%s" % \ (iface_id, new_port_id, nid) @@ -149,7 +88,6 @@ if __name__ == "__main__": if len(args) < 1: parser.print_help() - help() sys.exit(1) nets = {} @@ -163,12 +101,13 @@ if __name__ == "__main__": print "nets: %s" % str(nets) - client = MiniClient(options.host, options.port, options.ssl) + client = Client(options.host, options.port, options.ssl, + format='json', tenant=tenant_id) if options.delete: - delete_all_nets(client, tenant_id) + delete_all_nets(client) for net_name, iface_ids in nets.items(): - create_net_with_attachments(net_name, iface_ids) + create_net_with_attachments(client, net_name, iface_ids) sys.exit(0)