diff --git a/etc/quantum.conf b/etc/quantum.conf index e4d910b400..9fd2036554 100644 --- a/etc/quantum.conf +++ b/etc/quantum.conf @@ -17,7 +17,7 @@ api_extensions_path = extensions [composite:quantum] use = egg:Paste#urlmap /: quantumversions -/v0.1: quantumapi +/v1.0: quantumapi [pipeline:quantumapi] pipeline = extensions quantumapiapp diff --git a/quantum/api/__init__.py b/quantum/api/__init__.py index 39944491af..0ff7ea2349 100644 --- a/quantum/api/__init__.py +++ b/quantum/api/__init__.py @@ -26,6 +26,7 @@ import webob.exc from quantum import manager from quantum.api import faults +from quantum.api import attachments from quantum.api import networks from quantum.api import ports from quantum.common import flags @@ -58,24 +59,29 @@ class APIRouterV01(wsgi.Router): path_prefix=uri_prefix) mapper.resource('port', 'ports', controller=ports.Controller(plugin), + collection={'detail': 'GET'}, + member={'detail': 'GET'}, parent_resource=dict(member_name='network', collection_name=uri_prefix +\ 'networks')) + + attachments_ctrl = attachments.Controller(plugin) + mapper.connect("get_resource", uri_prefix + 'networks/{network_id}/' \ 'ports/{id}/attachment{.format}', - controller=ports.Controller(plugin), + controller=attachments_ctrl, action="get_resource", conditions=dict(method=['GET'])) mapper.connect("attach_resource", uri_prefix + 'networks/{network_id}/' \ 'ports/{id}/attachment{.format}', - controller=ports.Controller(plugin), + controller=attachments_ctrl, action="attach_resource", conditions=dict(method=['PUT'])) mapper.connect("detach_resource", uri_prefix + 'networks/{network_id}/' \ 'ports/{id}/attachment{.format}', - controller=ports.Controller(plugin), + controller=attachments_ctrl, action="detach_resource", conditions=dict(method=['DELETE'])) diff --git a/quantum/api/api_common.py b/quantum/api/api_common.py index 140a7c5b9b..b3847bce20 100644 --- a/quantum/api/api_common.py +++ b/quantum/api/api_common.py @@ -38,7 +38,7 @@ class QuantumController(wsgi.Controller): for param in params: param_name = param['param-name'] param_value = None - # 1- parse request body + # Parameters are expected to be in request body only if req.body: des_body = self._deserialize(req.body, req.best_match_content_type()) @@ -50,22 +50,13 @@ class QuantumController(wsgi.Controller): LOG.error(line) raise exc.HTTPBadRequest(msg) param_value = data.get(param_name, None) - if not param_value: - # 2- parse request headers - # prepend param name with a 'x-' prefix - param_value = req.headers.get("x-" + param_name, None) - # 3- parse request query parameters - if not param_value: - try: - param_value = req.str_GET[param_name] - except KeyError: - #param not found - pass - if not param_value and param['required']: - msg = ("Failed to parse request. " + - "Parameter: " + param_name + " not specified") - for line in msg.split('\n'): - LOG.error(line) - raise exc.HTTPBadRequest(msg) + + # If the parameter wasn't found and it was required, return 400 + if not param_value and param['required']: + msg = ("Failed to parse request. " + + "Parameter: " + param_name + " not specified") + for line in msg.split('\n'): + LOG.error(line) + raise exc.HTTPBadRequest(msg) results[param_name] = param_value or param.get('default-value') return results diff --git a/quantum/api/attachments.py b/quantum/api/attachments.py new file mode 100644 index 0000000000..14f4d94058 --- /dev/null +++ b/quantum/api/attachments.py @@ -0,0 +1,86 @@ +# Copyright 2011 Citrix Systems. +# 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 logging + +from webob import exc + +from quantum.api import api_common as common +from quantum.api import faults +from quantum.api.views import attachments as attachments_view +from quantum.common import exceptions as exception + +LOG = logging.getLogger('quantum.api.ports') + + +class Controller(common.QuantumController): + """ Port API controller for Quantum API """ + + _attachment_ops_param_list = [{ + 'param-name': 'id', + 'required': True}, ] + + _serialization_metadata = { + "application/xml": { + "attributes": { + "attachment": ["id"], } + }, + } + + def __init__(self, plugin): + self._resource_name = 'attachment' + super(Controller, self).__init__(plugin) + + def get_resource(self, request, tenant_id, network_id, id): + try: + att_data = self._plugin.get_port_details( + tenant_id, network_id, id) + builder = attachments_view.get_view_builder(request) + result = builder.build(att_data)['attachment'] + return dict(attachment=result) + except exception.NetworkNotFound as e: + return faults.Fault(faults.NetworkNotFound(e)) + except exception.PortNotFound as e: + return faults.Fault(faults.PortNotFound(e)) + + def attach_resource(self, request, tenant_id, network_id, id): + try: + request_params = \ + self._parse_request_params(request, + self._attachment_ops_param_list) + except exc.HTTPError as e: + return faults.Fault(e) + try: + self._plugin.plug_interface(tenant_id, network_id, id, + request_params['id']) + return exc.HTTPNoContent() + except exception.NetworkNotFound as e: + return faults.Fault(faults.NetworkNotFound(e)) + except exception.PortNotFound as e: + return faults.Fault(faults.PortNotFound(e)) + except exception.PortInUse as e: + return faults.Fault(faults.PortInUse(e)) + except exception.AlreadyAttached as e: + return faults.Fault(faults.AlreadyAttached(e)) + + def detach_resource(self, request, tenant_id, network_id, id): + try: + self._plugin.unplug_interface(tenant_id, + network_id, id) + return exc.HTTPNoContent() + except exception.NetworkNotFound as e: + return faults.Fault(faults.NetworkNotFound(e)) + except exception.PortNotFound as e: + return faults.Fault(faults.PortNotFound(e)) diff --git a/quantum/api/faults.py b/quantum/api/faults.py index 670ef9f899..ef4d50bc6b 100644 --- a/quantum/api/faults.py +++ b/quantum/api/faults.py @@ -31,7 +31,6 @@ class Fault(webob.exc.HTTPException): 401: "unauthorized", 420: "networkNotFound", 421: "networkInUse", - 422: "networkNameExists", 430: "portNotFound", 431: "requestedStateInvalid", 432: "portInUse", @@ -92,22 +91,6 @@ class NetworkInUse(webob.exc.HTTPClientError): explanation = ('Unable to remove the network: attachments still plugged.') -class NetworkNameExists(webob.exc.HTTPClientError): - """ - subclass of :class:`~HTTPClientError` - - This indicates that the server could not set the network name to the - specified value because another network for the same tenant already has - that name. - - code: 422, title: Network Name Exists - """ - code = 422 - title = 'Network Name Exists' - explanation = ('Unable to set network name: tenant already has network' \ - ' with same name.') - - class PortNotFound(webob.exc.HTTPClientError): """ subclass of :class:`~HTTPClientError` diff --git a/quantum/api/networks.py b/quantum/api/networks.py index 27709eb594..db4d864d52 100644 --- a/quantum/api/networks.py +++ b/quantum/api/networks.py @@ -29,7 +29,7 @@ class Controller(common.QuantumController): """ Network API controller for Quantum API """ _network_ops_param_list = [{ - 'param-name': 'net-name', + 'param-name': 'name', 'required': True}, ] _serialization_metadata = { @@ -37,38 +37,43 @@ class Controller(common.QuantumController): "attributes": { "network": ["id", "name"], "port": ["id", "state"], - }, - "plurals": {"networks": "network"} - }, + "attachment": ["id"]}, + "plurals": {"networks": "network", + "ports": "port"}}, } def __init__(self, plugin): self._resource_name = 'network' super(Controller, self).__init__(plugin) - def index(self, request, tenant_id): - """ Returns a list of network ids """ - #TODO: this should be for a given tenant!!! - return self._items(request, tenant_id) - def _item(self, req, tenant_id, network_id, net_details=True, port_details=False): # We expect get_network_details to return information # concerning logical ports as well. network = self._plugin.get_network_details( tenant_id, network_id) + port_list = self._plugin.get_all_ports( + tenant_id, network_id) + ports_data = [self._plugin.get_port_details( + tenant_id, network_id, port['port-id']) + for port in port_list] builder = networks_view.get_view_builder(req) - result = builder.build(network, net_details, port_details)['network'] + result = builder.build(network, net_details, + ports_data, port_details)['network'] return dict(network=result) - def _items(self, req, tenant_id, net_details=False, port_details=False): + def _items(self, req, tenant_id, net_details=False): """ Returns a list of networks. """ networks = self._plugin.get_all_networks(tenant_id) builder = networks_view.get_view_builder(req) - result = [builder.build(network, net_details, port_details)['network'] + result = [builder.build(network, net_details)['network'] for network in networks] return dict(networks=result) + def index(self, request, tenant_id): + """ Returns a list of network ids """ + return self._items(request, tenant_id) + def show(self, request, tenant_id, id): """ Returns network details for the given network id """ try: @@ -80,23 +85,13 @@ class Controller(common.QuantumController): def detail(self, request, **kwargs): tenant_id = kwargs.get('tenant_id') network_id = kwargs.get('id') - try: - if network_id: - # show details for a given network - return self._item(request, tenant_id, network_id, - net_details=True, port_details=True) - else: - # show details for all networks - return self._items(request, tenant_id, - net_details=True, port_details=False) - network = self._plugin.get_network_details( - tenant_id, id) - builder = networks_view.get_view_builder(request) - #build response with details - result = builder.build(network, True) - return dict(networks=result) - except exception.NetworkNotFound as e: - return faults.Fault(faults.NetworkNotFound(e)) + if network_id: + # show details for a given network + return self._item(request, tenant_id, network_id, + net_details=True, port_details=True) + else: + # show details for all networks + return self._items(request, tenant_id, net_details=True) def create(self, request, tenant_id): """ Creates a new network for a given tenant """ @@ -107,15 +102,13 @@ class Controller(common.QuantumController): self._network_ops_param_list) except exc.HTTPError as e: return faults.Fault(e) - try: - network = self._plugin.\ - create_network(tenant_id, - request_params['net-name']) - builder = networks_view.get_view_builder(request) - result = builder.build(network) - return dict(networks=result) - except exception.NetworkNameExists as e: - return faults.Fault(faults.NetworkNameExists(e)) + network = self._plugin.\ + create_network(tenant_id, + request_params['name']) + builder = networks_view.get_view_builder(request) + result = builder.build(network)['network'] + #MUST RETURN 202??? + return dict(network=result) def update(self, request, tenant_id, id): """ Updates the name for the network with the given id """ @@ -127,18 +120,16 @@ class Controller(common.QuantumController): return faults.Fault(e) try: self._plugin.rename_network(tenant_id, id, - request_params['net-name']) - return exc.HTTPAccepted() + request_params['name']) + return exc.HTTPNoContent() except exception.NetworkNotFound as e: return faults.Fault(faults.NetworkNotFound(e)) - except exception.NetworkNameExists as e: - return faults.Fault(faults.NetworkNameExists(e)) def delete(self, request, tenant_id, id): """ Destroys the network with the given id """ try: self._plugin.delete_network(tenant_id, id) - return exc.HTTPAccepted() + return exc.HTTPNoContent() except exception.NetworkNotFound as e: return faults.Fault(faults.NetworkNotFound(e)) except exception.NetworkInUse as e: diff --git a/quantum/api/ports.py b/quantum/api/ports.py index 042ea673c3..77e2b54556 100644 --- a/quantum/api/ports.py +++ b/quantum/api/ports.py @@ -29,55 +29,79 @@ class Controller(common.QuantumController): """ Port API controller for Quantum API """ _port_ops_param_list = [{ - 'param-name': 'port-state', + 'param-name': 'state', 'default-value': 'DOWN', 'required': False}, ] - _attachment_ops_param_list = [{ - 'param-name': 'attachment-id', - 'required': True}, ] - _serialization_metadata = { "application/xml": { "attributes": { - "port": ["id", "state"], }, - "plurals": {"ports": "port"} - }, + "port": ["id", "state"], + "attachment": ["id"]}, + "plurals": {"ports": "port"}}, } def __init__(self, plugin): self._resource_name = 'port' super(Controller, self).__init__(plugin) - def index(self, request, tenant_id, network_id): - """ Returns a list of port ids for a given network """ - return self._items(request, tenant_id, network_id, is_detail=False) - - def _items(self, request, tenant_id, network_id, is_detail): - """ Returns a list of networks. """ + def _items(self, request, tenant_id, network_id, + port_details=False): + """ Returns a list of ports. """ try: - ports = self._plugin.get_all_ports(tenant_id, network_id) + port_list = self._plugin.get_all_ports(tenant_id, network_id) builder = ports_view.get_view_builder(request) - result = [builder.build(port, is_detail)['port'] - for port in ports] + + # Load extra data for ports if required. + if port_details: + port_list_detail = \ + [self._plugin.get_port_details( + tenant_id, network_id, port['port-id']) + for port in port_list] + port_list = port_list_detail + + result = [builder.build(port, port_details)['port'] + for port in port_list] return dict(ports=result) except exception.NetworkNotFound as e: return faults.Fault(faults.NetworkNotFound(e)) + def _item(self, request, tenant_id, network_id, port_id, + att_details=False): + """ Returns a specific port. """ + port = self._plugin.get_port_details( + tenant_id, network_id, port_id) + builder = ports_view.get_view_builder(request) + result = builder.build(port, port_details=True, + att_details=att_details)['port'] + return dict(port=result) + + def index(self, request, tenant_id, network_id): + """ Returns a list of port ids for a given network """ + return self._items(request, tenant_id, network_id, port_details=False) + def show(self, request, tenant_id, network_id, id): """ Returns port details for given port and network """ try: - port = self._plugin.get_port_details( - tenant_id, network_id, id) - builder = ports_view.get_view_builder(request) - #build response with details - result = builder.build(port, True)['port'] - return dict(port=result) + return self._item(request, tenant_id, network_id, id) except exception.NetworkNotFound as e: return faults.Fault(faults.NetworkNotFound(e)) except exception.PortNotFound as e: return faults.Fault(faults.PortNotFound(e)) + def detail(self, request, **kwargs): + tenant_id = kwargs.get('tenant_id') + network_id = kwargs.get('network_id') + port_id = kwargs.get('id') + if port_id: + # show details for a given network + return self._item(request, tenant_id, + network_id, port_id, att_details=True) + else: + # show details for all port + return self._items(request, tenant_id, + network_id, port_details=True) + def create(self, request, tenant_id, network_id): """ Creates a new port for a given network """ #look for port state in request @@ -89,10 +113,10 @@ class Controller(common.QuantumController): try: port = self._plugin.create_port(tenant_id, network_id, - request_params['port-state']) + request_params['state']) builder = ports_view.get_view_builder(request) - result = builder.build(port) - return dict(ports=result) + result = builder.build(port)['port'] + return dict(port=result) except exception.NetworkNotFound as e: return faults.Fault(faults.NetworkNotFound(e)) except exception.StateInvalid as e: @@ -107,11 +131,9 @@ class Controller(common.QuantumController): except exc.HTTPError as e: return faults.Fault(e) try: - port = self._plugin.update_port(tenant_id, network_id, id, - request_params['port-state']) - builder = ports_view.get_view_builder(request) - result = builder.build(port, True) - return dict(ports=result) + self._plugin.update_port(tenant_id, network_id, id, + request_params['state']) + return exc.HTTPNoContent() except exception.NetworkNotFound as e: return faults.Fault(faults.NetworkNotFound(e)) except exception.PortNotFound as e: @@ -124,53 +146,10 @@ class Controller(common.QuantumController): #look for port state in request try: self._plugin.delete_port(tenant_id, network_id, id) - return exc.HTTPAccepted() - # TODO(salvatore-orlando): Handle portInUse error + return exc.HTTPNoContent() except exception.NetworkNotFound as e: return faults.Fault(faults.NetworkNotFound(e)) except exception.PortNotFound as e: return faults.Fault(faults.PortNotFound(e)) except exception.PortInUse as e: return faults.Fault(faults.PortInUse(e)) - - def get_resource(self, request, tenant_id, network_id, id): - try: - result = self._plugin.get_port_details( - tenant_id, network_id, id).get('attachment-id', - None) - return dict(attachment=result) - except exception.NetworkNotFound as e: - return faults.Fault(faults.NetworkNotFound(e)) - except exception.PortNotFound as e: - return faults.Fault(faults.PortNotFound(e)) - - def attach_resource(self, request, tenant_id, network_id, id): - try: - request_params = \ - self._parse_request_params(request, - self._attachment_ops_param_list) - except exc.HTTPError as e: - return faults.Fault(e) - try: - self._plugin.plug_interface(tenant_id, - network_id, id, - request_params['attachment-id']) - return exc.HTTPAccepted() - except exception.NetworkNotFound as e: - return faults.Fault(faults.NetworkNotFound(e)) - except exception.PortNotFound as e: - return faults.Fault(faults.PortNotFound(e)) - except exception.PortInUse as e: - return faults.Fault(faults.PortInUse(e)) - except exception.AlreadyAttached as e: - return faults.Fault(faults.AlreadyAttached(e)) - - def detach_resource(self, request, tenant_id, network_id, id): - try: - self._plugin.unplug_interface(tenant_id, - network_id, id) - return exc.HTTPAccepted() - except exception.NetworkNotFound as e: - return faults.Fault(faults.NetworkNotFound(e)) - except exception.PortNotFound as e: - return faults.Fault(faults.PortNotFound(e)) diff --git a/quantum/api/versions.py b/quantum/api/versions.py index 18635040a0..2fd6812e29 100644 --- a/quantum/api/versions.py +++ b/quantum/api/versions.py @@ -31,11 +31,11 @@ class Versions(wsgi.Application): """Respond to a request for all Quantum API versions.""" version_objs = [ { - "id": "v0.1", + "id": "v1.0", "status": "CURRENT", }, { - "id": "v1.0", + "id": "v1.1", "status": "FUTURE", }, ] diff --git a/quantum/api/views/attachments.py b/quantum/api/views/attachments.py new file mode 100644 index 0000000000..31351a1e0f --- /dev/null +++ b/quantum/api/views/attachments.py @@ -0,0 +1,37 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Citrix Systems +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +def get_view_builder(req): + base_url = req.application_url + return ViewBuilder(base_url) + + +class ViewBuilder(object): + + def __init__(self, base_url): + """ + :param base_url: url of the root wsgi application + """ + self.base_url = base_url + + def build(self, attachment_data): + """Generic method used to generate an attachment entity.""" + if attachment_data['attachment']: + return dict(attachment=dict(id=attachment_data['attachment'])) + else: + return dict(attachment={}) diff --git a/quantum/api/views/networks.py b/quantum/api/views/networks.py index eaa9901ff9..4b2e77df28 100644 --- a/quantum/api/views/networks.py +++ b/quantum/api/views/networks.py @@ -15,8 +15,6 @@ # License for the specific language governing permissions and limitations # under the License. -from quantum.api.views import ports as ports_view - def get_view_builder(req): base_url = req.application_url @@ -31,17 +29,16 @@ class ViewBuilder(object): """ self.base_url = base_url - def build(self, network_data, net_detail=False, port_detail=False): + def build(self, network_data, net_detail=False, + ports_data=None, port_detail=False): """Generic method used to generate a network entity.""" if net_detail: network = self._build_detail(network_data) else: network = self._build_simple(network_data) if port_detail: - builder = ports_view.ViewBuilder(self.base_url) - ports = [builder.build(port_data, port_detail)['port'] - for port_data in network_data['net-ports'].values()] - network['ports'] = ports + ports = [self._build_port(port_data) for port_data in ports_data] + network['network']['ports'] = ports return network def _build_simple(self, network_data): @@ -55,6 +52,8 @@ class ViewBuilder(object): def _build_port(self, port_data): """Return details about a specific logical port.""" - return dict(port=dict(id=port_data['port-id'], - state=port_data['port-state'], - attachment=port_data['attachment'])) + port_dict = dict(id=port_data['port-id'], + state=port_data['port-state']) + if port_data['attachment']: + port_dict['attachment'] = dict(id=port_data['attachment']) + return port_dict diff --git a/quantum/api/views/ports.py b/quantum/api/views/ports.py index b743888ce3..164726eb3c 100644 --- a/quantum/api/views/ports.py +++ b/quantum/api/views/ports.py @@ -29,19 +29,11 @@ class ViewBuilder(object): """ self.base_url = base_url - def build(self, port_data, is_detail=False): + def build(self, port_data, port_details=False, att_details=False): """Generic method used to generate a port entity.""" - if is_detail: - port = self._build_detail(port_data) - else: - port = self._build_simple(port_data) + port = dict(port=dict(id=port_data['port-id'])) + if port_details: + port['port']['state'] = port_data['port-state'] + if att_details and port_data['attachment']: + port['port']['attachment'] = dict(id=port_data['attachment']) return port - - def _build_simple(self, port_data): - """Return a simple model of a port.""" - return dict(port=dict(id=port_data['port-id'])) - - def _build_detail(self, port_data): - """Return a simple model of a port (with its state).""" - return dict(port=dict(id=port_data['port-id'], - state=port_data['port-state'])) diff --git a/quantum/cli.py b/quantum/cli.py index bb92437f6e..4c40ab2a89 100644 --- a/quantum/cli.py +++ b/quantum/cli.py @@ -69,7 +69,7 @@ def api_create_net(client, *args): LOG.debug(res) nid = None try: - nid = res["networks"]["network"]["id"] + nid = res["network"]["id"] except Exception, e: print "Failed to create network" # TODO(bgh): grab error details from ws request result @@ -104,7 +104,7 @@ def detail_net(manager, *args): def api_detail_net(client, *args): tid, nid = args try: - res = client.show_network_details(nid)["networks"]["network"] + res = client.show_network_details(nid)["network"] except Exception, e: LOG.error("Failed to get network details: %s" % e) return @@ -121,7 +121,7 @@ def api_detail_net(client, *args): pid = port["id"] res = client.show_port_attachment(nid, pid) LOG.debug(res) - remote_iface = res["attachment"] + remote_iface = res["attachment"]["id"] print "\tRemote interface:%s" % remote_iface @@ -133,7 +133,7 @@ def rename_net(manager, *args): def api_rename_net(client, *args): tid, nid, name = args - data = {'network': {'net-name': '%s' % name}} + data = {'network': {'name': '%s' % name}} try: res = client.update_network(nid, data) except Exception, e: @@ -179,7 +179,7 @@ def api_create_port(client, *args): except Exception, e: LOG.error("Failed to create port: %s" % e) return - new_port = res["ports"]["port"]["id"] + new_port = res["port"]["id"] print "Created Virtual Port:%s " \ "on Virtual Network:%s" % (new_port, nid) @@ -214,16 +214,17 @@ def detail_port(manager, *args): def api_detail_port(client, *args): tid, nid, pid = args try: - port = client.show_port_details(nid, pid)["ports"]["port"] + port = client.show_port_details(nid, pid)["port"] + att = client.show_port_attachment(nid, pid) except Exception, e: LOG.error("Failed to get port details: %s" % e) return - id = port["id"] - attachment = port["attachment"] + id = port['id'] + interface_id = att['id'] LOG.debug(port) print "Virtual Port:%s on Virtual Network:%s " \ - "contains remote interface:%s" % (pid, nid, attachment) + "contains remote interface:%s" % (pid, nid, interface_id) def plug_iface(manager, *args): @@ -236,7 +237,7 @@ def plug_iface(manager, *args): def api_plug_iface(client, *args): tid, nid, pid, vid = args try: - data = {'port': {'attachment-id': '%s' % vid}} + data = {'attachment': {'id': '%s' % vid}} res = client.attach_resource(nid, pid, data) except Exception, e: LOG.error("Failed to plug iface \"%s\" to port \"%s\": %s" % (vid, diff --git a/quantum/client.py b/quantum/client.py index 74ac1958ba..c75b5a243e 100644 --- a/quantum/client.py +++ b/quantum/client.py @@ -27,13 +27,10 @@ EXCEPTIONS = { 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, -} + 440: exceptions.AlreadyAttached} class ApiCall(object): @@ -72,7 +69,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, action_prefix="/v0.1/tenants/{tenant_id}"): + logger=None, action_prefix="/v1.0/tenants/{tenant_id}"): """ Creates a new client to some service. diff --git a/quantum/common/exceptions.py b/quantum/common/exceptions.py index 83fd9fabe9..816851f7a5 100644 --- a/quantum/common/exceptions.py +++ b/quantum/common/exceptions.py @@ -111,16 +111,6 @@ 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 " \ - "tenant %(tenant_id)") - - class Duplicate(Error): pass diff --git a/quantum/common/wsgi.py b/quantum/common/wsgi.py index 94df7913c6..11fdd68c1e 100644 --- a/quantum/common/wsgi.py +++ b/quantum/common/wsgi.py @@ -122,6 +122,7 @@ class Request(webob.Request): Based on the query extension then the Accept header. """ + # First lookup http request parts = self.path.rsplit('.', 1) LOG.debug("Request parts:%s", parts) if len(parts) > 1: @@ -129,21 +130,26 @@ class Request(webob.Request): if format in ['json', 'xml']: return 'application/{0}'.format(parts[1]) + #Then look up content header + type_from_header = self.get_content_type() + if type_from_header: + return type_from_header ctypes = ['application/json', 'application/xml'] + + #Finally search in Accept-* headers bm = self.accept.best_match(ctypes) return bm or 'application/json' def get_content_type(self): allowed_types = ("application/xml", "application/json") if not "Content-Type" in self.headers: - msg = _("Missing Content-Type") - LOG.debug(msg) - raise webob.exc.HTTPBadRequest(msg) + LOG.debug(_("Missing Content-Type")) + return None type = self.content_type if type in allowed_types: return type LOG.debug(_("Wrong Content-Type: %s") % type) - raise webob.exc.HTTPBadRequest("Invalid content type") + return None class Application(object): diff --git a/quantum/db/api.py b/quantum/db/api.py index 8153bd9dae..ae93204d4d 100644 --- a/quantum/db/api.py +++ b/quantum/db/api.py @@ -17,6 +17,8 @@ # @author: Brad Hall, Nicira Networks, Inc. # @author: Dan Wendlandt, Nicira Networks, Inc. +import logging + from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker, exc @@ -27,6 +29,7 @@ from quantum.db import models _ENGINE = None _MAKER = None BASE = models.BASE +LOG = logging.getLogger('quantum.db.api') def configure_db(options): @@ -78,6 +81,11 @@ def unregister_models(): def _check_duplicate_net_name(tenant_id, net_name): + """Checks whether a network with the same name + already exists for the tenant. + """ + + #TODO(salvatore-orlando): Not used anymore - candidate for removal session = get_session() try: net = session.query(models.Network).\ @@ -94,7 +102,6 @@ def _check_duplicate_net_name(tenant_id, net_name): def network_create(tenant_id, name): session = get_session() - _check_duplicate_net_name(tenant_id, name) with session.begin(): net = models.Network(tenant_id, name) session.add(net) diff --git a/quantum/db/models.py b/quantum/db/models.py index 240b28349e..409e91dea1 100644 --- a/quantum/db/models.py +++ b/quantum/db/models.py @@ -70,13 +70,14 @@ class Port(BASE, QuantumBase): uuid = Column(String(255), primary_key=True) network_id = Column(String(255), ForeignKey("networks.uuid"), nullable=False) - interface_id = Column(String(255)) + interface_id = Column(String(255), nullable=True) # Port state - Hardcoding string value at the moment state = Column(String(8)) def __init__(self, network_id): self.uuid = str(uuid.uuid4()) self.network_id = network_id + self.interface_id = None self.state = "DOWN" def __repr__(self): diff --git a/quantum/plugins/SamplePlugin.py b/quantum/plugins/SamplePlugin.py index 8fa701dd68..27dba1f3a2 100644 --- a/quantum/plugins/SamplePlugin.py +++ b/quantum/plugins/SamplePlugin.py @@ -352,7 +352,7 @@ class FakePlugin(object): LOG.debug("FakePlugin.get_port_details() called") port = self._get_port(tenant_id, net_id, port_id) return {'port-id': str(port.uuid), - 'attachment-id': port.interface_id, + 'attachment': port.interface_id, 'port-state': port.state} def create_port(self, tenant_id, net_id, port_state=None): @@ -407,10 +407,10 @@ class FakePlugin(object): specified Virtual Network. """ LOG.debug("FakePlugin.plug_interface() called") + port = self._get_port(tenant_id, net_id, port_id) # Validate attachment self._validate_attachment(tenant_id, net_id, port_id, remote_interface_id) - port = self._get_port(tenant_id, net_id, port_id) if port['interface_id']: raise exc.PortInUse(net_id=net_id, port_id=port_id, att_id=port['interface_id']) diff --git a/tests/unit/test_api.py b/tests/unit/test_api.py index e56a2cdd77..a374dffceb 100644 --- a/tests/unit/test_api.py +++ b/tests/unit/test_api.py @@ -48,7 +48,7 @@ class APITest(unittest.TestCase): if expected_res_status == 200: network_data = Serializer().deserialize(network_res.body, content_type) - return network_data['networks']['network']['id'] + return network_data['network']['id'] def _create_port(self, network_id, port_state, format, custom_req_body=None, expected_res_status=200): @@ -61,7 +61,7 @@ class APITest(unittest.TestCase): self.assertEqual(port_res.status_int, expected_res_status) if expected_res_status == 200: port_data = Serializer().deserialize(port_res.body, content_type) - return port_data['ports']['port']['id'] + return port_data['port']['id'] def _test_create_network(self, format): LOG.debug("_test_create_network - format:%s - START", format) @@ -74,8 +74,7 @@ class APITest(unittest.TestCase): self.assertEqual(show_network_res.status_int, 200) network_data = Serializer().deserialize(show_network_res.body, content_type) - self.assertEqual(network_id, - network_data['network']['id']) + self.assertEqual(network_id, network_data['network']['id']) LOG.debug("_test_create_network - format:%s - END", format) def _test_create_network_badrequest(self, format): @@ -102,6 +101,25 @@ class APITest(unittest.TestCase): self.assertEqual(len(network_data['networks']), 2) LOG.debug("_test_list_networks - format:%s - END", format) + def _test_list_networks_detail(self, format): + LOG.debug("_test_list_networks_detail - format:%s - START", format) + content_type = "application/%s" % format + self._create_network(format, "net_1") + self._create_network(format, "net_2") + list_network_req = testlib.network_list_detail_request(self.tenant_id, + format) + list_network_res = list_network_req.get_response(self.api) + self.assertEqual(list_network_res.status_int, 200) + network_data = self._net_serializer.deserialize( + list_network_res.body, content_type) + # Check network count: should return 2 + self.assertEqual(len(network_data['networks']), 2) + # Check contents - id & name for each network + for network in network_data['networks']: + self.assertTrue('id' in network and 'name' in network) + self.assertTrue(network['id'] and network['name']) + LOG.debug("_test_list_networks_detail - format:%s - END", format) + def _test_show_network(self, format): LOG.debug("_test_show_network - format:%s - START", format) content_type = "application/%s" % format @@ -118,6 +136,25 @@ class APITest(unittest.TestCase): network_data['network']) LOG.debug("_test_show_network - format:%s - END", format) + def _test_show_network_detail(self, format): + LOG.debug("_test_show_network_detail - format:%s - START", format) + content_type = "application/%s" % format + # Create a network and a port + network_id = self._create_network(format) + port_id = self._create_port(network_id, "ACTIVE", format) + show_network_req = testlib.show_network_detail_request( + self.tenant_id, network_id, format) + show_network_res = show_network_req.get_response(self.api) + self.assertEqual(show_network_res.status_int, 200) + network_data = self._net_serializer.deserialize( + show_network_res.body, content_type) + self.assertEqual({'id': network_id, + 'name': self.network_name, + 'ports': [{'id': port_id, + 'state': 'ACTIVE'}]}, + network_data['network']) + LOG.debug("_test_show_network_detail - format:%s - END", format) + def _test_show_network_not_found(self, format): LOG.debug("_test_show_network_not_found - format:%s - START", format) show_network_req = testlib.show_network_request(self.tenant_id, @@ -137,7 +174,7 @@ class APITest(unittest.TestCase): new_name, format) update_network_res = update_network_req.get_response(self.api) - self.assertEqual(update_network_res.status_int, 202) + self.assertEqual(update_network_res.status_int, 204) show_network_req = testlib.show_network_request(self.tenant_id, network_id, format) @@ -187,7 +224,7 @@ class APITest(unittest.TestCase): network_id, format) delete_network_res = delete_network_req.get_response(self.api) - self.assertEqual(delete_network_res.status_int, 202) + self.assertEqual(delete_network_res.status_int, 204) list_network_req = testlib.network_list_request(self.tenant_id, format) list_network_res = list_network_req.get_response(self.api) @@ -213,7 +250,7 @@ class APITest(unittest.TestCase): port_id, attachment_id) attachment_res = attachment_req.get_response(self.api) - self.assertEquals(attachment_res.status_int, 202) + self.assertEquals(attachment_res.status_int, 204) LOG.debug("Deleting network %(network_id)s"\ " of tenant %(tenant_id)s", locals()) @@ -241,6 +278,27 @@ class APITest(unittest.TestCase): self.assertEqual(len(port_data['ports']), 2) LOG.debug("_test_list_ports - format:%s - END", format) + def _test_list_ports_detail(self, format): + LOG.debug("_test_list_ports_detail - format:%s - START", format) + content_type = "application/%s" % format + port_state = "ACTIVE" + network_id = self._create_network(format) + self._create_port(network_id, port_state, format) + self._create_port(network_id, port_state, format) + list_port_req = testlib.port_list_detail_request(self.tenant_id, + network_id, format) + list_port_res = list_port_req.get_response(self.api) + self.assertEqual(list_port_res.status_int, 200) + port_data = self._port_serializer.deserialize( + list_port_res.body, content_type) + # Check port count: should return 2 + self.assertEqual(len(port_data['ports']), 2) + # Check contents - id & name for each network + for port in port_data['ports']: + self.assertTrue('id' in port and 'state' in port) + self.assertTrue(port['id'] and port['state']) + LOG.debug("_test_list_ports_detail - format:%s - END", format) + def _test_show_port(self, format): LOG.debug("_test_show_port - format:%s - START", format) content_type = "application/%s" % format @@ -258,6 +316,44 @@ class APITest(unittest.TestCase): port_data['port']) LOG.debug("_test_show_port - format:%s - END", format) + def _test_show_port_detail(self, format): + LOG.debug("_test_show_port - format:%s - START", format) + content_type = "application/%s" % format + port_state = "ACTIVE" + network_id = self._create_network(format) + port_id = self._create_port(network_id, port_state, format) + + # Part 1 - no attachment + show_port_req = testlib.show_port_detail_request(self.tenant_id, + network_id, port_id, format) + show_port_res = show_port_req.get_response(self.api) + self.assertEqual(show_port_res.status_int, 200) + port_data = self._port_serializer.deserialize( + show_port_res.body, content_type) + self.assertEqual({'id': port_id, 'state': port_state}, + port_data['port']) + + # Part 2 - plug attachment into port + interface_id = "test_interface" + put_attachment_req = testlib.put_attachment_request(self.tenant_id, + network_id, + port_id, + interface_id, + format) + put_attachment_res = put_attachment_req.get_response(self.api) + self.assertEqual(put_attachment_res.status_int, 204) + show_port_req = testlib.show_port_detail_request(self.tenant_id, + network_id, port_id, format) + show_port_res = show_port_req.get_response(self.api) + self.assertEqual(show_port_res.status_int, 200) + port_data = self._port_serializer.deserialize( + show_port_res.body, content_type) + self.assertEqual({'id': port_id, 'state': port_state, + 'attachment': {'id': interface_id}}, + port_data['port']) + + LOG.debug("_test_show_port_detail - format:%s - END", format) + def _test_show_port_networknotfound(self, format): LOG.debug("_test_show_port_networknotfound - format:%s - START", format) @@ -343,7 +439,7 @@ class APITest(unittest.TestCase): network_id, port_id, format) delete_port_res = delete_port_req.get_response(self.api) - self.assertEqual(delete_port_res.status_int, 202) + self.assertEqual(delete_port_res.status_int, 204) list_port_req = testlib.port_list_request(self.tenant_id, network_id, format) list_port_res = list_port_req.get_response(self.api) @@ -367,7 +463,7 @@ class APITest(unittest.TestCase): port_id, attachment_id) attachment_res = attachment_req.get_response(self.api) - self.assertEquals(attachment_res.status_int, 202) + self.assertEquals(attachment_res.status_int, 204) LOG.debug("Deleting port %(port_id)s for network %(network_id)s"\ " of tenant %(tenant_id)s", locals()) delete_port_req = testlib.port_delete_request(self.tenant_id, @@ -417,7 +513,7 @@ class APITest(unittest.TestCase): new_port_state, format) update_port_res = update_port_req.get_response(self.api) - self.assertEqual(update_port_res.status_int, 200) + self.assertEqual(update_port_res.status_int, 204) show_port_req = testlib.show_port_request(self.tenant_id, network_id, port_id, format) @@ -427,6 +523,22 @@ class APITest(unittest.TestCase): show_port_res.body, content_type) self.assertEqual({'id': port_id, 'state': new_port_state}, port_data['port']) + # now set it back to the original value + update_port_req = testlib.update_port_request(self.tenant_id, + network_id, port_id, + port_state, + format) + update_port_res = update_port_req.get_response(self.api) + self.assertEqual(update_port_res.status_int, 204) + show_port_req = testlib.show_port_request(self.tenant_id, + network_id, port_id, + format) + show_port_res = show_port_req.get_response(self.api) + self.assertEqual(show_port_res.status_int, 200) + port_data = self._port_serializer.deserialize( + show_port_res.body, content_type) + self.assertEqual({'id': port_id, 'state': port_state}, + port_data['port']) LOG.debug("_test_set_port_state - format:%s - END", format) def _test_set_port_state_networknotfound(self, format): @@ -491,7 +603,7 @@ class APITest(unittest.TestCase): interface_id, format) put_attachment_res = put_attachment_req.get_response(self.api) - self.assertEqual(put_attachment_res.status_int, 202) + self.assertEqual(put_attachment_res.status_int, 204) get_attachment_req = testlib.get_attachment_request(self.tenant_id, network_id, port_id, @@ -499,7 +611,7 @@ class APITest(unittest.TestCase): get_attachment_res = get_attachment_req.get_response(self.api) attachment_data = Serializer().deserialize(get_attachment_res.body, content_type) - self.assertEqual(attachment_data['attachment'], interface_id) + self.assertEqual(attachment_data['attachment']['id'], interface_id) LOG.debug("_test_show_attachment - format:%s - END", format) def _test_show_attachment_networknotfound(self, format): @@ -544,7 +656,7 @@ class APITest(unittest.TestCase): interface_id, format) put_attachment_res = put_attachment_req.get_response(self.api) - self.assertEqual(put_attachment_res.status_int, 202) + self.assertEqual(put_attachment_res.status_int, 204) LOG.debug("_test_put_attachment - format:%s - END", format) def _test_put_attachment_networknotfound(self, format): @@ -593,13 +705,13 @@ class APITest(unittest.TestCase): interface_id, format) put_attachment_res = put_attachment_req.get_response(self.api) - self.assertEqual(put_attachment_res.status_int, 202) + self.assertEqual(put_attachment_res.status_int, 204) del_attachment_req = testlib.delete_attachment_request(self.tenant_id, network_id, port_id, format) del_attachment_res = del_attachment_req.get_response(self.api) - self.assertEqual(del_attachment_res.status_int, 202) + self.assertEqual(del_attachment_res.status_int, 204) LOG.debug("_test_delete_attachment - format:%s - END", format) def _test_delete_attachment_networknotfound(self, format): @@ -670,6 +782,12 @@ class APITest(unittest.TestCase): def test_list_networks_xml(self): self._test_list_networks('xml') + def test_list_networks_detail_json(self): + self._test_list_networks_detail('json') + + def test_list_networks_detail_xml(self): + self._test_list_networks_detail('xml') + def test_create_network_json(self): self._test_create_network('json') @@ -694,6 +812,12 @@ class APITest(unittest.TestCase): def test_show_network_xml(self): self._test_show_network('xml') + def test_show_network_detail_json(self): + self._test_show_network_detail('json') + + def test_show_network_detail_xml(self): + self._test_show_network_detail('xml') + def test_delete_network_json(self): self._test_delete_network('json') @@ -730,12 +854,24 @@ class APITest(unittest.TestCase): def test_list_ports_xml(self): self._test_list_ports('xml') + def test_list_ports_detail_json(self): + self._test_list_ports_detail('json') + + def test_list_ports_detail_xml(self): + self._test_list_ports_detail('xml') + def test_show_port_json(self): self._test_show_port('json') def test_show_port_xml(self): self._test_show_port('xml') + def test_show_port_detail_json(self): + self._test_show_port_detail('json') + + def test_show_port_detail_xml(self): + self._test_show_port_detail('xml') + def test_show_port_networknotfound_json(self): self._test_show_port_networknotfound('json') diff --git a/tests/unit/test_clientlib.py b/tests/unit/test_clientlib.py index 128d069234..ce3e7186b0 100644 --- a/tests/unit/test_clientlib.py +++ b/tests/unit/test_clientlib.py @@ -43,7 +43,7 @@ class ServerStub(): return self.content def status(self): - return status + return self.status # To test error codes, set the host to 10.0.0.1, and the port to the code def __init__(self, host, port=9696, key_file="", cert_file=""): diff --git a/tests/unit/testlib_api.py b/tests/unit/testlib_api.py index 0a50d616e0..cd3cce0063 100644 --- a/tests/unit/testlib_api.py +++ b/tests/unit/testlib_api.py @@ -12,26 +12,45 @@ def create_request(path, body, content_type, method='GET'): return req -def network_list_request(tenant_id, format='xml'): +def _network_list_request(tenant_id, format='xml', detail=False): method = 'GET' - path = "/tenants/%(tenant_id)s/networks.%(format)s" % locals() + detail_str = detail and '/detail' or '' + path = "/tenants/%(tenant_id)s/networks" \ + "%(detail_str)s.%(format)s" % locals() + content_type = "application/%s" % format + return create_request(path, None, content_type, method) + + +def network_list_request(tenant_id, format='xml'): + return _network_list_request(tenant_id, format) + + +def network_list_detail_request(tenant_id, format='xml'): + return _network_list_request(tenant_id, format, detail=True) + + +def _show_network_request(tenant_id, network_id, format='xml', detail=False): + method = 'GET' + detail_str = detail and '/detail' or '' + path = "/tenants/%(tenant_id)s/networks" \ + "/%(network_id)s%(detail_str)s.%(format)s" % locals() content_type = "application/%s" % format return create_request(path, None, content_type, method) def show_network_request(tenant_id, network_id, format='xml'): - method = 'GET' - path = "/tenants/%(tenant_id)s/networks" \ - "/%(network_id)s.%(format)s" % locals() - content_type = "application/%s" % format - return create_request(path, None, content_type, method) + return _show_network_request(tenant_id, network_id, format) + + +def show_network_detail_request(tenant_id, network_id, format='xml'): + return _show_network_request(tenant_id, network_id, format, detail=True) def new_network_request(tenant_id, network_name='new_name', format='xml', custom_req_body=None): method = 'POST' path = "/tenants/%(tenant_id)s/networks.%(format)s" % locals() - data = custom_req_body or {'network': {'net-name': '%s' % network_name}} + data = custom_req_body or {'network': {'name': '%s' % network_name}} content_type = "application/%s" % format body = Serializer().serialize(data, content_type) return create_request(path, body, content_type, method) @@ -42,7 +61,7 @@ def update_network_request(tenant_id, network_id, network_name, format='xml', method = 'PUT' path = "/tenants/%(tenant_id)s/networks" \ "/%(network_id)s.%(format)s" % locals() - data = custom_req_body or {'network': {'net-name': '%s' % network_name}} + data = custom_req_body or {'network': {'name': '%s' % network_name}} content_type = "application/%s" % format body = Serializer().serialize(data, content_type) return create_request(path, body, content_type, method) @@ -56,20 +75,41 @@ def network_delete_request(tenant_id, network_id, format='xml'): return create_request(path, None, content_type, method) -def port_list_request(tenant_id, network_id, format='xml'): +def _port_list_request(tenant_id, network_id, format='xml', detail=False): method = 'GET' + detail_str = detail and '/detail' or '' path = "/tenants/%(tenant_id)s/networks/" \ - "%(network_id)s/ports.%(format)s" % locals() + "%(network_id)s/ports%(detail_str)s.%(format)s" % locals() + content_type = "application/%s" % format + return create_request(path, None, content_type, method) + + +def port_list_request(tenant_id, network_id, format='xml'): + return _port_list_request(tenant_id, network_id, format) + + +def port_list_detail_request(tenant_id, network_id, format='xml'): + return _port_list_request(tenant_id, network_id, + format, detail=True) + + +def _show_port_request(tenant_id, network_id, port_id, + format='xml', detail=False): + method = 'GET' + detail_str = detail and '/detail' or '' + path = "/tenants/%(tenant_id)s/networks/%(network_id)s" \ + "/ports/%(port_id)s%(detail_str)s.%(format)s" % locals() content_type = "application/%s" % format return create_request(path, None, content_type, method) def show_port_request(tenant_id, network_id, port_id, format='xml'): - method = 'GET' - path = "/tenants/%(tenant_id)s/networks/%(network_id)s" \ - "/ports/%(port_id)s.%(format)s" % locals() - content_type = "application/%s" % format - return create_request(path, None, content_type, method) + return _show_port_request(tenant_id, network_id, port_id, format) + + +def show_port_detail_request(tenant_id, network_id, port_id, format='xml'): + return _show_port_request(tenant_id, network_id, port_id, + format, detail=True) def new_port_request(tenant_id, network_id, port_state, @@ -78,7 +118,7 @@ def new_port_request(tenant_id, network_id, port_state, path = "/tenants/%(tenant_id)s/networks/" \ "%(network_id)s/ports.%(format)s" % locals() data = custom_req_body or port_state and \ - {'port': {'port-state': '%s' % port_state}} + {'port': {'state': '%s' % port_state}} content_type = "application/%s" % format body = data and Serializer().serialize(data, content_type) return create_request(path, body, content_type, method) @@ -97,7 +137,7 @@ def update_port_request(tenant_id, network_id, port_id, port_state, method = 'PUT' path = "/tenants/%(tenant_id)s/networks" \ "/%(network_id)s/ports/%(port_id)s.%(format)s" % locals() - data = custom_req_body or {'port': {'port-state': '%s' % port_state}} + data = custom_req_body or {'port': {'state': '%s' % port_state}} content_type = "application/%s" % format body = Serializer().serialize(data, content_type) return create_request(path, body, content_type, method) @@ -116,7 +156,7 @@ def put_attachment_request(tenant_id, network_id, port_id, method = 'PUT' path = "/tenants/%(tenant_id)s/networks/" \ "%(network_id)s/ports/%(port_id)s/attachment.%(format)s" % locals() - data = {'port': {'attachment-id': attachment_id}} + data = {'attachment': {'id': attachment_id}} content_type = "application/%s" % format body = Serializer().serialize(data, content_type) return create_request(path, body, content_type, method)