diff --git a/quantum/api/__init__.py b/quantum/api/__init__.py index e2b0013868b..39944491aff 100644 --- a/quantum/api/__init__.py +++ b/quantum/api/__init__.py @@ -53,6 +53,8 @@ class APIRouterV01(wsgi.Router): uri_prefix = '/tenants/{tenant_id}/' mapper.resource('network', 'networks', controller=networks.Controller(plugin), + collection={'detail': 'GET'}, + member={'detail': 'GET'}, path_prefix=uri_prefix) mapper.resource('port', 'ports', controller=ports.Controller(plugin), diff --git a/quantum/api/api_common.py b/quantum/api/api_common.py index 19e189e90e2..c588a83567d 100644 --- a/quantum/api/api_common.py +++ b/quantum/api/api_common.py @@ -57,8 +57,7 @@ class QuantumController(wsgi.Controller): pass if not param_value and param['required']: msg = ("Failed to parse request. " + - "Parameter: %(param_name)s " + - "not specified" % locals()) + "Parameter: " + param_name + " not specified") for line in msg.split('\n'): LOG.error(line) raise exc.HTTPBadRequest(msg) diff --git a/quantum/api/faults.py b/quantum/api/faults.py index 35f6c1073da..ef4d50bc6b2 100644 --- a/quantum/api/faults.py +++ b/quantum/api/faults.py @@ -52,7 +52,7 @@ class Fault(webob.exc.HTTPException): fault_name: { 'code': code, 'message': self.wrapped_exc.explanation, - 'detail': self.wrapped_exc.detail}} + 'detail': str(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 diff --git a/quantum/api/networks.py b/quantum/api/networks.py index 9bad90ab29e..9afc09c24e3 100644 --- a/quantum/api/networks.py +++ b/quantum/api/networks.py @@ -36,7 +36,9 @@ class Controller(common.QuantumController): "application/xml": { "attributes": { "network": ["id", "name"], + "port": ["id", "state"], }, + "plurals": {"networks": "network"} }, } @@ -47,19 +49,46 @@ class Controller(common.QuantumController): 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, is_detail=False) + return self._items(request, tenant_id) - def _items(self, request, tenant_id, is_detail): + 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) + builder = networks_view.get_view_builder(req) + result = builder.build(network, net_details, port_details)['network'] + return dict(network=result) + + def _items(self, req, tenant_id, net_details=False, port_details=False): """ Returns a list of networks. """ networks = self._plugin.get_all_networks(tenant_id) - builder = networks_view.get_view_builder(request) - result = [builder.build(network, is_detail)['network'] + builder = networks_view.get_view_builder(req) + result = [builder.build(network, net_details, port_details)['network'] for network in networks] return dict(networks=result) def show(self, request, tenant_id, id): """ Returns network details for the given network id """ try: + return self._item(request, tenant_id, id, + net_details=True, port_details=False) + except exception.NetworkNotFound as e: + return faults.Fault(faults.NetworkNotFound(e)) + + 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) diff --git a/quantum/api/ports.py b/quantum/api/ports.py index 8a7ee81c106..042ea673c3a 100644 --- a/quantum/api/ports.py +++ b/quantum/api/ports.py @@ -40,7 +40,10 @@ class Controller(common.QuantumController): _serialization_metadata = { "application/xml": { "attributes": { - "port": ["id", "state"], }, }, } + "port": ["id", "state"], }, + "plurals": {"ports": "port"} + }, + } def __init__(self, plugin): self._resource_name = 'port' @@ -68,8 +71,8 @@ class Controller(common.QuantumController): tenant_id, network_id, id) builder = ports_view.get_view_builder(request) #build response with details - result = builder.build(port, True) - return dict(ports=result) + result = builder.build(port, True)['port'] + return dict(port=result) except exception.NetworkNotFound as e: return faults.Fault(faults.NetworkNotFound(e)) except exception.PortNotFound as e: @@ -105,7 +108,7 @@ class Controller(common.QuantumController): return faults.Fault(e) try: port = self._plugin.update_port(tenant_id, network_id, id, - request_params['port-state']) + request_params['port-state']) builder = ports_view.get_view_builder(request) result = builder.build(port, True) return dict(ports=result) @@ -122,7 +125,7 @@ class Controller(common.QuantumController): try: self._plugin.delete_port(tenant_id, network_id, id) return exc.HTTPAccepted() - #TODO(salvatore-orlando): Handle portInUse error + # TODO(salvatore-orlando): Handle portInUse error except exception.NetworkNotFound as e: return faults.Fault(faults.NetworkNotFound(e)) except exception.PortNotFound as e: diff --git a/quantum/api/views/__init__.py b/quantum/api/views/__init__.py index cd6a1d3e299..3e54802128e 100644 --- a/quantum/api/views/__init__.py +++ b/quantum/api/views/__init__.py @@ -13,4 +13,3 @@ # 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: Somik Behera, Nicira Networks, Inc. diff --git a/quantum/api/views/networks.py b/quantum/api/views/networks.py index 98c69730e58..2242e00f794 100644 --- a/quantum/api/views/networks.py +++ b/quantum/api/views/networks.py @@ -15,7 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. -import os +from quantum.api.views import ports as ports_view def get_view_builder(req): @@ -31,19 +31,34 @@ class ViewBuilder(object): """ self.base_url = base_url - def build(self, network_data, is_detail=False): + def build(self, network_data, net_detail=False, port_detail=False): """Generic method used to generate a network entity.""" - if is_detail: + 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 return network def _build_simple(self, network_data): - """Return a simple model of a server.""" + """Return a simple model of a network.""" return dict(network=dict(id=network_data['net-id'])) def _build_detail(self, network_data): - """Return a simple model of a server.""" + """Return a detailed model of a network.""" + # net-ports might not be present in response from plugin + ports = network_data.get('net-ports', None) + portcount = ports and len(ports) or 0 return dict(network=dict(id=network_data['net-id'], - name=network_data['net-name'])) + name=network_data['net-name'], + PortCount=portcount)) + + 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'])) diff --git a/quantum/api/views/ports.py b/quantum/api/views/ports.py index 71dac252686..b743888ce35 100644 --- a/quantum/api/views/ports.py +++ b/quantum/api/views/ports.py @@ -38,10 +38,10 @@ class ViewBuilder(object): return port def _build_simple(self, port_data): - """Return a simple model of a server.""" + """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 server.""" + """Return a simple model of a port (with its state).""" return dict(port=dict(id=port_data['port-id'], - state=port_data['port-state'])) + state=port_data['port-state'])) diff --git a/quantum/cli.py b/quantum/cli.py index 02bafbd09dc..0f6a0c27e0b 100644 --- a/quantum/cli.py +++ b/quantum/cli.py @@ -114,7 +114,7 @@ def create_net(manager, *args): def api_create_net(client, *args): tid, name = args - data = {'network': {'network-name': '%s' % name}} + data = {'network': {'net-name': '%s' % name}} body = Serializer().serialize(data, CONTENT_TYPE) res = client.do_request(tid, 'POST', "/networks." + FORMAT, body=body) rd = json.loads(res.read()) @@ -185,7 +185,7 @@ def rename_net(manager, *args): def api_rename_net(client, *args): tid, nid, name = args - data = {'network': {'network-name': '%s' % name}} + data = {'network': {'net-name': '%s' % name}} body = Serializer().serialize(data, CONTENT_TYPE) res = client.do_request(tid, 'PUT', "/networks/%s.%s" % (nid, FORMAT), body=body) diff --git a/quantum/common/wsgi.py b/quantum/common/wsgi.py index d883746acb5..ca9734e878e 100644 --- a/quantum/common/wsgi.py +++ b/quantum/common/wsgi.py @@ -20,17 +20,13 @@ Utility methods for working with WSGI servers """ -import json import logging import sys -import datetime from xml.dom import minidom -import eventlet import eventlet.wsgi eventlet.patcher.monkey_patch(all=False, socket=True) -import routes import routes.middleware import webob.dec import webob.exc @@ -135,7 +131,6 @@ class Request(webob.Request): ctypes = ['application/json', 'application/xml'] bm = self.accept.best_match(ctypes) - LOG.debug("BM:%s", bm) return bm or 'application/json' def get_content_type(self): @@ -332,7 +327,6 @@ class Controller(object): """ Call the method specified in req.environ by RoutesMiddleware. """ - LOG.debug("HERE - wsgi.Controller.__call__") arg_dict = req.environ['wsgiorg.routing_args'][1] action = arg_dict['action'] method = getattr(self, action) @@ -345,8 +339,6 @@ class Controller(object): if type(result) is dict: content_type = req.best_match_content_type() - LOG.debug("Content type:%s", content_type) - LOG.debug("Result:%s", result) default_xmlns = self.get_default_xmlns(req) body = self._serialize(result, content_type, default_xmlns) @@ -425,7 +417,10 @@ class Serializer(object): The string must be in the format of a supported MIME type. """ - return self.get_deserialize_handler(content_type)(datastring) + try: + return self.get_deserialize_handler(content_type)(datastring) + except Exception: + raise webob.exc.HTTPBadRequest("Could not deserialize data") def get_deserialize_handler(self, content_type): handlers = { @@ -457,7 +452,8 @@ class Serializer(object): if len(node.childNodes) == 1 and node.childNodes[0].nodeType == 3: return node.childNodes[0].nodeValue elif node.nodeName in listnames: - return [self._from_xml_node(n, listnames) for n in node.childNodes] + return [self._from_xml_node(n, listnames) + for n in node.childNodes if n.nodeType != node.TEXT_NODE] else: result = dict() for attr in node.attributes.keys(): @@ -482,7 +478,7 @@ class Serializer(object): if not xmlns and self.default_xmlns: node.setAttribute('xmlns', self.default_xmlns) - return node.toprettyxml(indent=' ') + return node.toprettyxml(indent='', newl='') def _to_xml_node(self, doc, metadata, nodename, data): """Recursive method to convert data members to XML nodes.""" @@ -493,9 +489,7 @@ class Serializer(object): xmlns = metadata.get('xmlns', None) if xmlns: result.setAttribute('xmlns', xmlns) - LOG.debug("DATA:%s", data) if type(data) is list: - LOG.debug("TYPE IS LIST") collections = metadata.get('list_collections', {}) if nodename in collections: metadata = collections[nodename] @@ -514,7 +508,6 @@ class Serializer(object): node = self._to_xml_node(doc, metadata, singular, item) result.appendChild(node) elif type(data) is dict: - LOG.debug("TYPE IS DICT") collections = metadata.get('dict_collections', {}) if nodename in collections: metadata = collections[nodename] @@ -533,8 +526,7 @@ class Serializer(object): node = self._to_xml_node(doc, metadata, k, v) result.appendChild(node) else: - # Type is atom - LOG.debug("TYPE IS ATOM:%s", data) + # Type is atom. node = doc.createTextNode(str(data)) result.appendChild(node) return result diff --git a/quantum/manager.py b/quantum/manager.py index 388324a8db9..727a2c81040 100644 --- a/quantum/manager.py +++ b/quantum/manager.py @@ -21,7 +21,6 @@ Quantum's Manager class is responsible for parsing a config file and instantiating the correct plugin that concretely implement quantum_plugin_base class. - The caller should make sure that QuantumManager is a singleton. """ import gettext diff --git a/quantum/plugins.ini b/quantum/plugins.ini index 448cab59ea7..307d2b48d2c 100644 --- a/quantum/plugins.ini +++ b/quantum/plugins.ini @@ -1,4 +1,3 @@ [PLUGIN] # Quantum plugin provider module -#provider = quantum.plugins.SamplePlugin.FakePlugin -provider = quantum.plugins.openvswitch.ovs_quantum_plugin.OVSQuantumPlugin +provider = quantum.plugins.SamplePlugin.FakePlugin diff --git a/quantum/plugins/SamplePlugin.py b/quantum/plugins/SamplePlugin.py index 68cd8e54e7e..313ac8e1dba 100644 --- a/quantum/plugins/SamplePlugin.py +++ b/quantum/plugins/SamplePlugin.py @@ -164,7 +164,6 @@ class DummyDataPlugin(object): retrieved a list of all the remote vifs that are attached to the network """ - print("get_network_details() called\n") vifs_on_net = ["/tenant1/networks/net_id/portid/vif2.0"] return vifs_on_net @@ -192,12 +191,6 @@ class DummyDataPlugin(object): #return the port id return 201 - def update_port(self, tenant_id, net_id, port_id, port_state): - """ - Updates the state of a port on the specified Virtual Network. - """ - print("update_port() called\n") - def delete_port(self, tenant_id, net_id, port_id): """ Deletes a port on a specified Virtual Network, @@ -295,8 +288,11 @@ class FakePlugin(object): """ LOG.debug("FakePlugin.get_network_details() called") net = self._get_network(tenant_id, net_id) + # Retrieves ports for network + ports = self.get_all_ports(tenant_id, net_id) return {'net-id': str(net.uuid), - 'net-name': net.name} + 'net-name': net.name, + 'net-ports': ports} def create_network(self, tenant_id, net_name): """ diff --git a/quantum/plugins/openvswitch/README b/quantum/plugins/openvswitch/README index cd21240b3de..f38a18c09d9 100644 --- a/quantum/plugins/openvswitch/README +++ b/quantum/plugins/openvswitch/README @@ -84,10 +84,10 @@ $ python ovs_quantum_agent.py ovs_quantum_plugin.ini # -- Getting quantum up and running - Start quantum [on the quantum service host]: -~/src/quantum-framework$ PYTHONPATH=.:$PYTHONPATH python bin/quantum etc/quantum.conf +~/src/quantum $ PYTHONPATH=.:$PYTHONPATH python bin/quantum etc/quantum.conf - Run ovs_quantum_plugin.py via the quantum plugin framework cli [on the quantum service host] -~/src/quantum-framework$ PYTHONPATH=.:$PYTHONPATH python quantum/cli.py +~/src/quantum$ PYTHONPATH=.:$PYTHONPATH python quantum/cli.py This will show help all of the available commands. diff --git a/tests/unit/test_api.py b/tests/unit/test_api.py index c09af76990e..d17e6ed87fa 100644 --- a/tests/unit/test_api.py +++ b/tests/unit/test_api.py @@ -75,7 +75,7 @@ class APITest(unittest.TestCase): network_data = Serializer().deserialize(show_network_res.body, content_type) self.assertEqual(network_id, - network_data['networks']['network']['id']) + network_data['network']['id']) LOG.debug("_test_create_network - format:%s - END", format) def _test_create_network_badrequest(self, format): @@ -96,8 +96,8 @@ class APITest(unittest.TestCase): format) list_network_res = list_network_req.get_response(self.api) self.assertEqual(list_network_res.status_int, 200) - network_data = Serializer().deserialize(list_network_res.body, - content_type) + 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) LOG.debug("_test_list_networks - format:%s - END", format) @@ -111,10 +111,12 @@ class APITest(unittest.TestCase): format) show_network_res = show_network_req.get_response(self.api) self.assertEqual(show_network_res.status_int, 200) - network_data = Serializer().deserialize(show_network_res.body, - content_type) - self.assertEqual({'id': network_id, 'name': self.network_name}, - network_data['networks']['network']) + network_data = self._net_serializer.deserialize( + show_network_res.body, content_type) + self.assertEqual({'id': network_id, + 'name': self.network_name, + 'PortCount': 0}, + network_data['network']) LOG.debug("_test_show_network - format:%s - END", format) def _test_show_network_not_found(self, format): @@ -142,10 +144,12 @@ class APITest(unittest.TestCase): format) show_network_res = show_network_req.get_response(self.api) self.assertEqual(show_network_res.status_int, 200) - network_data = Serializer().deserialize(show_network_res.body, - content_type) - self.assertEqual({'id': network_id, 'name': new_name}, - network_data['networks']['network']) + network_data = self._net_serializer.deserialize( + show_network_res.body, content_type) + self.assertEqual({'id': network_id, + 'name': new_name, + 'PortCount': 0}, + network_data['network']) LOG.debug("_test_rename_network - format:%s - END", format) def _test_rename_network_badrequest(self, format): @@ -189,8 +193,8 @@ class APITest(unittest.TestCase): list_network_req = testlib.network_list_request(self.tenant_id, format) list_network_res = list_network_req.get_response(self.api) - network_list_data = Serializer().deserialize(list_network_res.body, - content_type) + network_list_data = self._net_serializer.deserialize( + list_network_res.body, content_type) network_count = len(network_list_data['networks']) self.assertEqual(network_count, 0) LOG.debug("_test_delete_network - format:%s - END", format) @@ -233,8 +237,8 @@ class APITest(unittest.TestCase): network_id, format) list_port_res = list_port_req.get_response(self.api) self.assertEqual(list_port_res.status_int, 200) - port_data = Serializer().deserialize(list_port_res.body, - content_type) + 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) LOG.debug("_test_list_ports - format:%s - END", format) @@ -250,10 +254,10 @@ class APITest(unittest.TestCase): format) show_port_res = show_port_req.get_response(self.api) self.assertEqual(show_port_res.status_int, 200) - port_data = Serializer().deserialize(show_port_res.body, - content_type) + port_data = self._port_serializer.deserialize( + show_port_res.body, content_type) self.assertEqual({'id': port_id, 'state': port_state}, - port_data['ports']['port']) + port_data['port']) LOG.debug("_test_show_port - format:%s - END", format) def _test_show_port_networknotfound(self, format): @@ -291,8 +295,9 @@ class APITest(unittest.TestCase): 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 = Serializer().deserialize(show_port_res.body, content_type) - self.assertEqual(port_id, port_data['ports']['port']['id']) + port_data = self._port_serializer.deserialize( + show_port_res.body, content_type) + self.assertEqual(port_id, port_data['port']['id']) LOG.debug("_test_create_port - format:%s - END", format) def _test_create_port_networknotfound(self, format): @@ -329,8 +334,8 @@ class APITest(unittest.TestCase): list_port_req = testlib.port_list_request(self.tenant_id, network_id, format) list_port_res = list_port_req.get_response(self.api) - port_list_data = Serializer().deserialize(list_port_res.body, - content_type) + port_list_data = self._port_serializer.deserialize( + list_port_res.body, content_type) port_count = len(port_list_data['ports']) self.assertEqual(port_count, 0) LOG.debug("_test_delete_port - format:%s - END", format) @@ -405,10 +410,10 @@ class APITest(unittest.TestCase): format) show_port_res = show_port_req.get_response(self.api) self.assertEqual(show_port_res.status_int, 200) - network_data = Serializer().deserialize(show_port_res.body, - content_type) + port_data = self._port_serializer.deserialize( + show_port_res.body, content_type) self.assertEqual({'id': port_id, 'state': new_port_state}, - network_data['ports']['port']) + port_data['port']) LOG.debug("_test_set_port_state - format:%s - END", format) def _test_set_port_state_networknotfound(self, format): @@ -614,12 +619,32 @@ class APITest(unittest.TestCase): LOG.debug("_test_delete_attachment_portnotfound - " \ "format:%s - END", format) + def _test_unparsable_data(self, format): + LOG.debug("_test_unparsable_data - " \ + " format:%s - START", format) + + data = "this is not json or xml" + method = 'POST' + content_type = "application/%s" % format + tenant_id = self.tenant_id + path = "/tenants/%(tenant_id)s/networks.%(format)s" % locals() + network_req = testlib.create_request(path, data, content_type, method) + network_res = network_req.get_response(self.api) + self.assertEqual(network_res.status_int, 400) + + LOG.debug("_test_unparsable_data - " \ + "format:%s - END", format) + def setUp(self): options = {} options['plugin_provider'] = 'quantum.plugins.SamplePlugin.FakePlugin' self.api = server.APIRouterV01(options) self.tenant_id = "test_tenant" self.network_name = "test_network" + self._net_serializer = \ + Serializer(server.networks.Controller._serialization_metadata) + self._port_serializer = \ + Serializer(server.ports.Controller._serialization_metadata) def tearDown(self): """Clear the test environment""" @@ -829,3 +854,9 @@ class APITest(unittest.TestCase): def test_delete_attachment_portnotfound_json(self): self._test_delete_attachment_portnotfound('json') + + def test_unparsable_data_xml(self): + self._test_unparsable_data('xml') + + def test_unparsable_data_json(self): + self._test_unparsable_data('json') diff --git a/tools/batch_config.py b/tools/batch_config.py index 63f4a5223d2..8415513d01d 100644 --- a/tools/batch_config.py +++ b/tools/batch_config.py @@ -84,7 +84,7 @@ def delete_all_nets(client, tenant_id): def create_net_with_attachments(net_name, iface_ids): - data = {'network': {'network-name': '%s' % net_name}} + data = {'network': {'net-name': '%s' % net_name}} body = Serializer().serialize(data, CONTENT_TYPE) res = client.do_request(tenant_id, 'POST', "/networks." + FORMAT, body=body)