--- /dev/null
+# 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)
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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.')
+
+
--- /dev/null
+# 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))
--- /dev/null
+
+
+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']))