diff --git a/cisco_extensions/__init__.py b/cisco_extensions/__init__.py new file mode 100644 index 00000000000..5fc5d889193 --- /dev/null +++ b/cisco_extensions/__init__.py @@ -0,0 +1,71 @@ +# 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 +import routes +import webob.dec +import webob.exc + +from quantum import manager +from quantum.api import faults +from quantum.api import networks +from quantum.api import ports +from quantum.common import flags +from quantum.common import wsgi +from cisco_extensions import portprofiles +from cisco_extensions import extensions + + +LOG = logging.getLogger('quantum_extension.api') +FLAGS = flags.FLAGS + + +class ExtRouterV01(wsgi.Router): + """ + Routes requests on the Quantum API to the appropriate controller + """ + + def __init__(self, ext_mgr=None): + uri_prefix = '/tenants/{tenant_id}/' + + mapper = routes.Mapper() + plugin = manager.QuantumManager().get_plugin() + controller = portprofiles.Controller(plugin) + ext_controller = extensions.Controller(plugin) + mapper.connect("home", "/", controller=ext_controller, + action="list_extension", + conditions=dict(method=['GET'])) + #mapper.redirect("/", "www.google.com") + mapper.resource("portprofiles", "portprofiles", + controller=controller, + path_prefix=uri_prefix) + mapper.connect("associate_portprofile", + uri_prefix + + 'portprofiles/{portprofile_id}/assignment{.format}', + controller=controller, + action="associate_portprofile", + conditions=dict(method=['PUT'])) + mapper.connect("disassociate_portprofile", + uri_prefix + + 'portprofiles/{portprofile_id}/assignment{.format}', + controller=controller, + action="disassociate_portprofile", + conditions=dict(method=['DELETE'])) + + super(ExtRouterV01, self).__init__(mapper) diff --git a/cisco_extensions/exceptions.py b/cisco_extensions/exceptions.py new file mode 100644 index 00000000000..415731e3851 --- /dev/null +++ b/cisco_extensions/exceptions.py @@ -0,0 +1,148 @@ +# 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 + + +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): + return self._error_string + + +class ProcessExecutionError(IOError): + 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): + def __init__(self, message=None): + super(Error, self).__init__(message) + + +class ApiError(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): + pass + + +class ClassNotFound(NotFound): + message = _("Class %(class_name)s could not be found") + + +class PortprofileNotFound(NotFound): + message = _("Portprofile %(_id)s could not be found") + + +class PortNotFound(NotFound): + message = _("Port %(port_id)s could not be found " \ + "on Network %(net_id)s") + + +""" + + +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): + pass + + +class NotAuthorized(Error): + pass + + +class NotEmpty(Error): + pass + + +class 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): + pass + + +def wrap_exception(f): + def _wrap(*args, **kw): + try: + return f(*args, **kw) + except Exception, e: + if not isinstance(e, Error): + #exc_type, exc_value, exc_traceback = sys.exc_info() + logging.exception('Uncaught exception') + #logging.error(traceback.extract_stack(exc_traceback)) + raise Error(str(e)) + raise + _wrap.func_name = f.func_name + return _wrap diff --git a/cisco_extensions/extensions.py b/cisco_extensions/extensions.py new file mode 100644 index 00000000000..34bc37e814a --- /dev/null +++ b/cisco_extensions/extensions.py @@ -0,0 +1,42 @@ +# 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 +import webob.dec + +from quantum.common import wsgi +from quantum.api import api_common as common + + +LOG = logging.getLogger('quantum.api.cisco_extension.extensions') + + +class Controller(common.QuantumController): + + def __init__(self, plugin): + #self._plugin = plugin + #super(QuantumController, self).__init__() + self._resource_name = 'extensions' + super(Controller, self).__init__(plugin) + + def list_extension(self, req): + """Respond to a request for listing all extension api.""" + response = "extensions api list" + return response + + \ No newline at end of file diff --git a/cisco_extensions/faults.py b/cisco_extensions/faults.py new file mode 100644 index 00000000000..c965f731dc6 --- /dev/null +++ b/cisco_extensions/faults.py @@ -0,0 +1,111 @@ +# 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 webob.dec +import webob.exc + +from quantum.api import api_common as common +from quantum.common import wsgi + + +class Fault(webob.exc.HTTPException): + """Error codes for API faults""" + + _fault_names = { + 400: "malformedRequest", + 401: "unauthorized", + 420: "networkNotFound", + 421: "PortprofileInUse", + 430: "portNotFound", + 431: "requestedStateInvalid", + 432: "portInUse", + 440: "alreadyAttached", + 450: "PortprofileNotFound", + 470: "serviceUnavailable", + 471: "pluginFault"} + + def __init__(self, exception): + """Create a Fault for the given webob.exc.exception.""" + self.wrapped_exc = exception + + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + """Generate a WSGI response based on the exception passed to ctor.""" + #print ("*********TEST2") + # 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}} + # '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.content_type = content_type + return self.wrapped_exc + + +class PortprofileNotFound(webob.exc.HTTPClientError): + """ + subclass of :class:`~HTTPClientError` + + This indicates that the server did not find the Portprofile specified + in the HTTP request + + code: 450, title: Portprofile not Found + """ + #print ("*********TEST1") + code = 450 + title = 'Portprofile Not Found' + explanation = ('Unable to find a Portprofile with' + + ' the specified identifier.') + + +class PortNotFound(webob.exc.HTTPClientError): + """ + subclass of :class:`~HTTPClientError` + + This indicates that the server did not find the port specified + in the HTTP request for a given network + + code: 430, title: Port not Found + """ + code = 430 + title = 'Port not Found' + explanation = ('Unable to find a port with the specified identifier.') + + +class RequestedStateInvalid(webob.exc.HTTPClientError): + """ + subclass of :class:`~HTTPClientError` + + This indicates that the server could not update the port state to + to the request value + + code: 431, title: Requested State Invalid + """ + code = 431 + title = 'Requested State Invalid' + explanation = ('Unable to update port state with specified value.') + + diff --git a/cisco_extensions/portprofiles.py b/cisco_extensions/portprofiles.py new file mode 100644 index 00000000000..5d195528f87 --- /dev/null +++ b/cisco_extensions/portprofiles.py @@ -0,0 +1,180 @@ +# 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 +import webob.dec +from quantum.common import wsgi +from webob import exc + +from quantum.api import api_common as common + +from cisco_extensions import pprofiles as pprofiles_view +from cisco_extensions import exceptions as exception +from cisco_extensions import faults as faults + +LOG = logging.getLogger('quantum.api.portprofiles') + + +class Controller(common.QuantumController): + """ portprofile API controller + based on QuantumController """ + + _portprofile_ops_param_list = [{ + 'param-name': 'portprofile-name', + 'required': True}, { + 'param-name': 'vlan-id', + 'required': True}, { + 'param-name': 'assignment', + 'required': False}] + + _assignprofile_ops_param_list = [{ + 'param-name': 'network-id', + 'required': True}, { + 'param-name': 'port-id', + 'required': True}] + + _serialization_metadata = { + "application/xml": { + "attributes": { + "portprofile": ["id", "name"], + }, + }, + } + + def __init__(self, plugin): + self._resource_name = 'portprofile' + super(Controller, self).__init__(plugin) + + def index(self, request, tenant_id): + """ Returns a list of portprofile ids """ + #TODO: this should be for a given tenant!!! + return self._items(request, tenant_id, is_detail=False) + + def _items(self, request, tenant_id, is_detail): + """ Returns a list of portprofiles. """ + portprofiles = self._plugin.get_all_portprofiles(tenant_id) + builder = pprofiles_view.get_view_builder(request) + result = [builder.build(portprofile, is_detail)['portprofile'] + for portprofile in portprofiles] + return dict(portprofiles=result) + + def show(self, request, tenant_id, id): + """ Returns portprofile details for the given portprofile id """ + try: + portprofile = self._plugin.get_portprofile_details( + tenant_id, id) + builder = pprofiles_view.get_view_builder(request) + #build response with details + result = builder.build(portprofile, True) + return dict(portprofiles=result) + except exception.PortprofileNotFound as e: + return faults.Fault(faults.PortprofileNotFound(e)) + #return faults.Fault(e) + + def create(self, request, tenant_id): + """ Creates a new portprofile for a given tenant """ + #look for portprofile name in request + try: + req_params = \ + self._parse_request_params(request, + self._portprofile_ops_param_list) + except exc.HTTPError as e: + return faults.Fault(e) + portprofile = self._plugin.\ + create_portprofile(tenant_id, + req_params['portprofile-name'], + req_params['vlan-id']) + builder = pprofiles_view.get_view_builder(request) + result = builder.build(portprofile) + return dict(portprofiles=result) + + def update(self, request, tenant_id, id): + """ Updates the name for the portprofile with the given id """ + try: + req_params = \ + self._parse_request_params(request, + self._portprofile_ops_param_list) + except exc.HTTPError as e: + return faults.Fault(e) + try: + portprofile = self._plugin.\ + rename_portprofile(tenant_id, + id, req_params['portprofile-name']) + + builder = pprofiles_view.get_view_builder(request) + result = builder.build(portprofile, True) + return dict(portprofiles=result) + except exception.PortprofileNotFound as e: + return faults.Fault(faults.PortprofileNotFound(e)) + + def delete(self, request, tenant_id, id): + """ Destroys the portprofile with the given id """ + try: + self._plugin.delete_portprofile(tenant_id, id) + return exc.HTTPAccepted() + except exception.PortprofileNotFound as e: + return faults.Fault(faults.PortprofileNotFound(e)) + + #added for cisco's extension + def associate_portprofile(self, request, tenant_id, portprofile_id): + content_type = request.best_match_content_type() + print "Content type:%s" % content_type + + try: + req_params = \ + self._parse_request_params(request, + self._assignprofile_ops_param_list) + except exc.HTTPError as e: + return faults.Fault(e) + 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, + net_id, port_id, + portprofile_id) + return exc.HTTPAccepted() + except exception.PortprofileNotFound as e: + return faults.Fault(faults.PortprofileNotFound(e)) + except exception.PortNotFound as e: + return faults.Fault(faults.PortNotFound(e)) + + #added for Cisco extension + def disassociate_portprofile(self, request, tenant_id, portprofile_id): + content_type = request.best_match_content_type() + print "Content type:%s" % content_type + + try: + req_params = \ + self._parse_request_params(request, + self._assignprofile_ops_param_list) + except exc.HTTPError as e: + return faults.Fault(e) + net_id = req_params['network-id'].strip() + #print "*****net id "+net_id + port_id = req_params['port-id'].strip() + try: + self._plugin. \ + disassociate_portprofile(tenant_id, + net_id, port_id, portprofile_id) + return exc.HTTPAccepted() + except exception.PortprofileNotFound as e: + return faults.Fault(faults.PortprofileNotFound(e)) + except exception.PortNotFound as e: + return faults.Fault(faults.PortNotFound(e)) diff --git a/cisco_extensions/pprofiles.py b/cisco_extensions/pprofiles.py new file mode 100644 index 00000000000..ba7f8a328bb --- /dev/null +++ b/cisco_extensions/pprofiles.py @@ -0,0 +1,45 @@ + + +import os + + +def get_view_builder(req): + base_url = req.application_url + return ViewBuilder(base_url) + + +class ViewBuilder(object): + """ + ViewBuilder for Portprofile, + derived from quantum.views.networks + """ + def __init__(self, base_url): + """ + :param base_url: url of the root wsgi application + """ + self.base_url = base_url + + def build(self, portprofile_data, is_detail=False): + """Generic method used to generate a portprofile entity.""" + print "portprofile-DATA:%s" %portprofile_data + if is_detail: + portprofile = self._build_detail(portprofile_data) + else: + portprofile = self._build_simple(portprofile_data) + return portprofile + + def _build_simple(self, portprofile_data): + """Return a simple model of a server.""" + return dict(portprofile=dict(id=portprofile_data['profile-id'])) + + def _build_detail(self, portprofile_data): + """Return a simple model of a server.""" + if (portprofile_data['assignment']==None): + return dict(portprofile=dict(id=portprofile_data['profile-id'], + name=portprofile_data['profile-name'], + vlan_id=portprofile_data['vlan-id'])) + else: + return dict(portprofile=dict(id=portprofile_data['profile-id'], + name=portprofile_data['profile-name'], + vlan_id=portprofile_data['vlan-id'], + assignment=portprofile_data['assignment']))