diff --git a/quantum/api/__init__.py b/quantum/api/__init__.py index 40dd058bab..619de63a97 100644 --- a/quantum/api/__init__.py +++ b/quantum/api/__init__.py @@ -52,6 +52,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 19e189e90e..c588a83567 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 35f6c1073d..ef4d50bc6b 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 9bad90ab29..9afc09c24e 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 8a7ee81c10..042ea673c3 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 cd6a1d3e29..3e54802128 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 98c69730e5..2242e00f79 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 71dac25268..b743888ce3 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/common/wsgi.py b/quantum/common/wsgi.py index ea26052342..ca9734e878 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) @@ -460,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(): @@ -496,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] @@ -517,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] @@ -536,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 2a2383b3e2..3d244b1b27 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/SamplePlugin.py b/quantum/plugins/SamplePlugin.py index b1486bf7c3..34573d2fe5 100644 --- a/quantum/plugins/SamplePlugin.py +++ b/quantum/plugins/SamplePlugin.py @@ -159,7 +159,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 @@ -187,12 +186,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, @@ -290,8 +283,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 cd21240b3d..f38a18c09d 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 edc6063628..d17e6ed87f 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): @@ -636,6 +641,10 @@ class APITest(unittest.TestCase): 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""" diff --git a/tools/batch_config.py b/tools/batch_config.py index 63f4a5223d..8415513d01 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)