From b12f8559e3011faeab09cd09b5e213214194de97 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Wed, 11 Jan 2012 17:26:47 +0000 Subject: [PATCH] bp/api-filters This changeset implements filters for core Quantum API and provides unit tests Change-Id: I8247b3587c2cc8e53785781a45d1e457980261d2 --- .bzrignore | 2 +- quantum/api/networks.py | 30 ++- quantum/api/ports.py | 29 +- quantum/api/views/filters.py | 160 +++++++++++ quantum/plugins/cisco/l2network_plugin.py | 4 +- .../plugins/openvswitch/ovs_quantum_plugin.py | 5 +- quantum/plugins/sample/SamplePlugin.py | 12 +- quantum/quantum_plugin_base.py | 17 +- quantum/tests/unit/_test_api.py | 89 +++--- quantum/tests/unit/test_api.py | 254 +++++++++++++++++- quantum/tests/unit/testlib_api.py | 29 +- 11 files changed, 564 insertions(+), 67 deletions(-) create mode 100644 quantum/api/views/filters.py diff --git a/.bzrignore b/.bzrignore index fa741971c2..d4e18c6093 100644 --- a/.bzrignore +++ b/.bzrignore @@ -9,4 +9,4 @@ ChangeLog *.pid *.log quantum/vcsversion.py -.ropeproject \ No newline at end of file +.ropeproject diff --git a/quantum/api/networks.py b/quantum/api/networks.py index 4229b6020c..bf90c31654 100644 --- a/quantum/api/networks.py +++ b/quantum/api/networks.py @@ -20,6 +20,7 @@ from webob import exc from quantum.api import api_common as common from quantum.api import faults from quantum.api.views import networks as networks_view +from quantum.api.views import filters from quantum.common import exceptions as exception LOG = logging.getLogger('quantum.api.networks') @@ -53,8 +54,10 @@ class Controller(common.QuantumController): # 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) + # Doing this in the API is inefficient + # TODO(salvatore-orlando): This should be fixed with Bug #834012 + # Don't pass filter options + 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] @@ -64,8 +67,27 @@ class Controller(common.QuantumController): return dict(network=result) def _items(self, request, tenant_id, net_details=False): - """ Returns a list of networks. """ - networks = self._plugin.get_all_networks(tenant_id) + """ Returns a list of networks. + Ideally, the plugin would perform filtering, + returning only the items matching filters specified + on the request query string. + However, plugins are not required to support filtering. + In this case, this function will filter the complete list + of networks returned by the plugin + + """ + filter_opts = {} + filter_opts.update(request.str_GET) + networks = self._plugin.get_all_networks(tenant_id, + filter_opts=filter_opts) + # Inefficient, API-layer filtering + # will be performed only for the filters not implemented by the plugin + # NOTE(salvatore-orlando): the plugin is supposed to leave only filters + # it does not implement in filter_opts + networks = filters.filter_networks(networks, + self._plugin, + tenant_id, + filter_opts) builder = networks_view.get_view_builder(request, self.version) result = [builder.build(network, net_details)['network'] for network in networks] diff --git a/quantum/api/ports.py b/quantum/api/ports.py index fb5a578646..d9b15c0e80 100644 --- a/quantum/api/ports.py +++ b/quantum/api/ports.py @@ -16,6 +16,7 @@ import logging from quantum.api import api_common as common +from quantum.api.views import filters from quantum.api.views import ports as ports_view from quantum.common import exceptions as exception @@ -48,11 +49,25 @@ class Controller(common.QuantumController): def _items(self, request, tenant_id, network_id, port_details=False): - """ Returns a list of ports. """ - port_list = self._plugin.get_all_ports(tenant_id, network_id) + """ Returns a list of ports. + Ideally, the plugin would perform filtering, + returning only the items matching filters specified + on the request query string. + However, plugins are not required to support filtering. + In this case, this function will filter the complete list + of ports returned by the plugin + """ + filter_opts = {} + filter_opts.update(request.str_GET) + port_list = self._plugin.get_all_ports(tenant_id, + network_id, + filter_opts=filter_opts) + builder = ports_view.get_view_builder(request, self.version) # Load extra data for ports if required. + # This can be inefficient. + # TODO(salvatore-orlando): the fix for bug #834012 should deal with it if port_details: port_list_detail = \ [self._plugin.get_port_details( @@ -60,6 +75,16 @@ class Controller(common.QuantumController): for port in port_list] port_list = port_list_detail + # Perform manual filtering if not supported by plugin + # Inefficient, API-layer filtering + # will be performed only if the plugin does + # not support filtering + # NOTE(salvatore-orlando): the plugin is supposed to leave only filters + # it does not implement in filter_opts + port_list = filters.filter_ports(port_list, self._plugin, + tenant_id, network_id, + filter_opts) + result = [builder.build(port, port_details)['port'] for port in port_list] return dict(ports=result) diff --git a/quantum/api/views/filters.py b/quantum/api/views/filters.py new file mode 100644 index 0000000000..e4964f56bf --- /dev/null +++ b/quantum/api/views/filters.py @@ -0,0 +1,160 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 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 + + +LOG = logging.getLogger('quantum.api.views.filters') + + +def _load_network_ports_details(network, **kwargs): + plugin = kwargs.get('plugin', None) + tenant_id = kwargs.get('tenant_id', None) + #load network details only if required + if not 'net-ports' in network: + # Don't pass filter options, don't care about unused filters + port_list = plugin.get_all_ports(tenant_id, network['net-id']) + ports_data = [plugin.get_port_details( + tenant_id, network['net-id'], + port['port-id']) + for port in port_list] + network['net-ports'] = ports_data + + +def _filter_network_by_name(network, name, **kwargs): + return network.get('net-name', None) == name + + +def _filter_network_with_operational_port(network, port_op_status, + **kwargs): + _load_network_ports_details(network, **kwargs) + return any([port['port-op-status'] == port_op_status + for port in network['net-ports']]) + + +def _filter_network_with_active_port(network, port_state, **kwargs): + _load_network_ports_details(network, **kwargs) + return any([port['port-state'] == port_state + for port in network['net-ports']]) + + +def _filter_network_has_interface(network, has_interface, **kwargs): + _load_network_ports_details(network, **kwargs) + # convert to bool + match_has_interface = has_interface.lower() == 'true' + really_has_interface = any([port['attachment'] is not None + for port in network['net-ports']]) + return match_has_interface == really_has_interface + + +def _filter_network_by_port(network, port_id, **kwargs): + _load_network_ports_details(network, **kwargs) + return any([port['port-id'] == port_id + for port in network['net-ports']]) + + +def _filter_network_by_interface(network, interface_id, **kwargs): + _load_network_ports_details(network, **kwargs) + return any([port.get('attachment', None) == interface_id + for port in network['net-ports']]) + + +def _filter_port_by_state(port, state, **kwargs): + return port.get('port-state', None) == state + + +def _filter_network_by_op_status(network, op_status, **kwargs): + return network.get('net-op-status', None) == op_status + + +def _filter_port_by_op_status(port, op_status, **kwargs): + return port.get('port-op-status', None) == op_status + + +def _filter_port_by_interface(port, interface_id, **kwargs): + return port.get('attachment', None) == interface_id + + +def _filter_port_has_interface(port, has_interface, **kwargs): + # convert to bool + match_has_interface = has_interface.lower() == 'true' + really_has_interface = 'attachment' in port and port['attachment'] != None + return match_has_interface == really_has_interface + + +def _do_filtering(items, filters, filter_opts, plugin, + tenant_id, network_id=None): + filtered_items = [] + for item in items: + is_filter_match = False + for flt in filters: + if flt in filter_opts: + is_filter_match = filters[flt](item, + filter_opts[flt], + plugin=plugin, + tenant_id=tenant_id, + network_id=network_id) + if not is_filter_match: + break + if is_filter_match: + filtered_items.append(item) + return filtered_items + + +def filter_networks(networks, plugin, tenant_id, filter_opts): + # Do filtering only if the plugin supports it + # and if filtering options have been specific + if len(filter_opts) == 0: + return networks + + # load filter functions + filters = { + 'name': _filter_network_by_name, + 'op-status': _filter_network_by_op_status, + 'port-op-status': _filter_network_with_operational_port, + 'port-state': _filter_network_with_active_port, + 'has-attachment': _filter_network_has_interface, + 'attachment': _filter_network_by_interface, + 'port': _filter_network_by_port} + # filter networks + return _do_filtering(networks, filters, filter_opts, plugin, tenant_id) + + +def filter_ports(ports, plugin, tenant_id, network_id, filter_opts): + # Do filtering only if the plugin supports it + # and if filtering options have been specific + if len(filter_opts) == 0: + return ports + + # load filter functions + filters = { + 'state': _filter_port_by_state, + 'op-status': _filter_port_by_op_status, + 'has-attachment': _filter_port_has_interface, + 'attachment': _filter_port_by_interface} + # port details are need for filtering + ports = [plugin.get_port_details(tenant_id, network_id, + port['port-id']) + for port in ports] + # filter ports + return _do_filtering(ports, + filters, + filter_opts, + plugin, + tenant_id, + network_id) diff --git a/quantum/plugins/cisco/l2network_plugin.py b/quantum/plugins/cisco/l2network_plugin.py index fb36b8cfb1..25fa514f2b 100644 --- a/quantum/plugins/cisco/l2network_plugin.py +++ b/quantum/plugins/cisco/l2network_plugin.py @@ -54,7 +54,7 @@ class L2Network(QuantumPluginBase): """ Core API implementation """ - def get_all_networks(self, tenant_id): + def get_all_networks(self, tenant_id, **kwargs): """ Returns a dictionary containing all for @@ -154,7 +154,7 @@ class L2Network(QuantumPluginBase): []) return net_dict - def get_all_ports(self, tenant_id, net_id): + def get_all_ports(self, tenant_id, net_id, **kwargs): """ Retrieves all port identifiers belonging to the specified Virtual Network. diff --git a/quantum/plugins/openvswitch/ovs_quantum_plugin.py b/quantum/plugins/openvswitch/ovs_quantum_plugin.py index 4b09b1218a..c2eb84ee77 100644 --- a/quantum/plugins/openvswitch/ovs_quantum_plugin.py +++ b/quantum/plugins/openvswitch/ovs_quantum_plugin.py @@ -106,7 +106,7 @@ class OVSQuantumPlugin(QuantumPluginBase): # % (vlan_id, network_id)) self.vmap.set_vlan(vlan_id, network_id) - def get_all_networks(self, tenant_id): + def get_all_networks(self, tenant_id, **kwargs): nets = [] for x in db.network_list(tenant_id): LOG.debug("Adding network: %s" % x.uuid) @@ -167,9 +167,10 @@ class OVSQuantumPlugin(QuantumPluginBase): 'net-id': port.network_id, 'attachment': port.interface_id} - def get_all_ports(self, tenant_id, net_id): + def get_all_ports(self, tenant_id, net_id, **kwargs): ids = [] ports = db.port_list(net_id) + # This plugin does not perform filtering at the moment return [{'port-id': str(p.uuid)} for p in ports] def create_port(self, tenant_id, net_id, port_state=None, **kwargs): diff --git a/quantum/plugins/sample/SamplePlugin.py b/quantum/plugins/sample/SamplePlugin.py index 5fb6cbc812..1f3798c013 100644 --- a/quantum/plugins/sample/SamplePlugin.py +++ b/quantum/plugins/sample/SamplePlugin.py @@ -165,13 +165,17 @@ class FakePlugin(object): att_id=port['interface_id'], att_port_id=port['uuid']) - def get_all_networks(self, tenant_id): + def get_all_networks(self, tenant_id, **kwargs): """ Returns a dictionary containing all for the specified tenant. """ LOG.debug("FakePlugin.get_all_networks() called") + filter_opts = kwargs.get('filter_opts', None) + if not filter_opts is None and len(filter_opts) > 0: + LOG.debug("filtering options were passed to the plugin" + "but the Fake plugin does not support them") nets = [] for net in db.network_list(tenant_id): net_item = {'net-id': str(net.uuid), @@ -232,12 +236,16 @@ class FakePlugin(object): net = db.network_update(net_id, tenant_id, **kwargs) return net - def get_all_ports(self, tenant_id, net_id): + def get_all_ports(self, tenant_id, net_id, **kwargs): """ Retrieves all port identifiers belonging to the specified Virtual Network. """ LOG.debug("FakePlugin.get_all_ports() called") + filter_opts = kwargs.get('filter_opts', None) + if not filter_opts is None and len(filter_opts) > 0: + LOG.debug("filtering options were passed to the plugin" + "but the Fake plugin does not support them") port_ids = [] ports = db.port_list(net_id) for x in ports: diff --git a/quantum/quantum_plugin_base.py b/quantum/quantum_plugin_base.py index 07af86996c..3af3d0f84a 100644 --- a/quantum/quantum_plugin_base.py +++ b/quantum/quantum_plugin_base.py @@ -31,11 +31,16 @@ class QuantumPluginBase(object): __metaclass__ = ABCMeta @abstractmethod - def get_all_networks(self, tenant_id): + def get_all_networks(self, tenant_id, **kwargs): """ Returns a dictionary containing all for the specified tenant. + :param tenant_id: unique identifier for the tenant whose networks + are being retrieved by this method + :param **kwargs: options to be passed to the plugin. The following + keywork based-options can be specified: + filter_opts - options for filtering network list :returns: a list of mapping sequences with the following signature: [ {'net-id': uuid that uniquely identifies the particular quantum network, @@ -119,11 +124,17 @@ class QuantumPluginBase(object): pass @abstractmethod - def get_all_ports(self, tenant_id, net_id): + def get_all_ports(self, tenant_id, net_id, **kwargs): """ Retrieves all port identifiers belonging to the specified Virtual Network. - + :param tenant_id: unique identifier for the tenant for which this + method is going to retrieve ports + :param net_id: unique identifiers for the network whose ports are + about to be retrieved + :param **kwargs: options to be passed to the plugin. The following + keywork based-options can be specified: + filter_opts - options for filtering network list :returns: a list of mapping sequences with the following signature: [ {'port-id': uuid representing a particular port on the specified quantum network diff --git a/quantum/tests/unit/_test_api.py b/quantum/tests/unit/_test_api.py index fa01fa137e..7a444c09db 100644 --- a/quantum/tests/unit/_test_api.py +++ b/quantum/tests/unit/_test_api.py @@ -34,10 +34,7 @@ ATTS = "attachments" class AbstractAPITest(unittest.TestCase): - """Abstract base class for Quantum API unit tests - Defined according to operations defined for Quantum API v1.0 - - """ + """ Base class definiting some methods for API tests """ def _deserialize_net_response(self, content_type, response): network_data = self._net_deserializers[content_type].\ @@ -85,10 +82,57 @@ class AbstractAPITest(unittest.TestCase): if expected_res_status in (200, 202): port_data = self._deserialize_port_response(content_type, port_res) - LOG.debug("PORT RESPONSE:%s", port_res.body) - LOG.debug("PORT DATA:%s", port_data) return port_data['port']['id'] + def _set_attachment(self, network_id, port_id, interface_id, fmt, + expected_res_status=204): + put_attachment_req = testlib.put_attachment_request(self.tenant_id, + network_id, + port_id, + interface_id, + fmt) + put_attachment_res = put_attachment_req.get_response(self.api) + self.assertEqual(put_attachment_res.status_int, expected_res_status) + + def setUp(self, api_router_klass, xml_metadata_dict): + options = {} + options['plugin_provider'] = test_config['plugin_name'] + api_router_cls = utils.import_class(api_router_klass) + self.api = api_router_cls(options) + self.tenant_id = "test_tenant" + self.network_name = "test_network" + + # Prepare XML & JSON deserializers + net_xml_deserializer = XMLDeserializer(xml_metadata_dict[NETS]) + port_xml_deserializer = XMLDeserializer(xml_metadata_dict[PORTS]) + att_xml_deserializer = XMLDeserializer(xml_metadata_dict[ATTS]) + + json_deserializer = JSONDeserializer() + + self._net_deserializers = { + 'application/xml': net_xml_deserializer, + 'application/json': json_deserializer, + } + self._port_deserializers = { + 'application/xml': port_xml_deserializer, + 'application/json': json_deserializer, + } + self._att_deserializers = { + 'application/xml': att_xml_deserializer, + 'application/json': json_deserializer, + } + + def tearDown(self): + """Clear the test environment""" + # Remove database contents + db.clear_db() + + +class BaseAPIOperationsTest(AbstractAPITest): + """Abstract base class for Quantum API unit tests + Defined according to operations defined for Quantum API v1.0 + """ + def _test_create_network(self, fmt): LOG.debug("_test_create_network - fmt:%s - START", fmt) content_type = "application/%s" % fmt @@ -844,39 +888,6 @@ class AbstractAPITest(unittest.TestCase): LOG.debug("_test_unparsable_data - " \ "fmt:%s - END", fmt) - def setUp(self, api_router_klass, xml_metadata_dict): - options = {} - options['plugin_provider'] = test_config['plugin_name'] - api_router_cls = utils.import_class(api_router_klass) - self.api = api_router_cls(options) - self.tenant_id = "test_tenant" - self.network_name = "test_network" - - # Prepare XML & JSON deserializers - net_xml_deserializer = XMLDeserializer(xml_metadata_dict[NETS]) - port_xml_deserializer = XMLDeserializer(xml_metadata_dict[PORTS]) - att_xml_deserializer = XMLDeserializer(xml_metadata_dict[ATTS]) - - json_deserializer = JSONDeserializer() - - self._net_deserializers = { - 'application/xml': net_xml_deserializer, - 'application/json': json_deserializer, - } - self._port_deserializers = { - 'application/xml': port_xml_deserializer, - 'application/json': json_deserializer, - } - self._att_deserializers = { - 'application/xml': att_xml_deserializer, - 'application/json': json_deserializer, - } - - def tearDown(self): - """Clear the test environment""" - # Remove database contents - db.clear_db() - def test_list_networks_json(self): self._test_list_networks('json') diff --git a/quantum/tests/unit/test_api.py b/quantum/tests/unit/test_api.py index 57ed6503f6..ca6c50e4e2 100644 --- a/quantum/tests/unit/test_api.py +++ b/quantum/tests/unit/test_api.py @@ -16,17 +16,23 @@ # under the License. # @author: Salvatore Orlando, Citrix Systems + +import logging from webob import exc import quantum.api.attachments as atts import quantum.api.networks as nets import quantum.api.ports as ports import quantum.tests.unit._test_api as test_api +import quantum.tests.unit.testlib_api as testlib from quantum.common.test_lib import test_config -class APITestV10(test_api.AbstractAPITest): +LOG = logging.getLogger('quantum.tests.test_api') + + +class APITestV10(test_api.BaseAPIOperationsTest): def assert_network(self, **kwargs): self.assertEqual({'id': kwargs['id'], @@ -63,7 +69,7 @@ class APITestV10(test_api.AbstractAPITest): self._already_attached_code = 440 -class APITestV11(test_api.AbstractAPITest): +class APITestV11(test_api.BaseAPIOperationsTest): def assert_network(self, **kwargs): self.assertEqual({'id': kwargs['id'], @@ -107,3 +113,247 @@ class APITestV11(test_api.AbstractAPITest): self._port_state_invalid_code = exc.HTTPBadRequest.code self._port_in_use_code = exc.HTTPConflict.code self._already_attached_code = exc.HTTPConflict.code + + +class APIFiltersTest(test_api.AbstractAPITest): + """ Test case for API filters. + Uses controller for API v1.1 + """ + + def _do_filtered_network_list_request(self, flt): + list_network_req = testlib.network_list_request(self.tenant_id, + self.fmt, + query_string=flt) + list_network_res = list_network_req.get_response(self.api) + self.assertEqual(list_network_res.status_int, 200) + network_data = self._net_deserializers[self.content_type].\ + deserialize(list_network_res.body)['body'] + return network_data + + def _do_filtered_port_list_request(self, flt, network_id): + list_port_req = testlib.port_list_request(self.tenant_id, + network_id, + self.fmt, + query_string=flt) + list_port_res = list_port_req.get_response(self.api) + self.assertEqual(list_port_res.status_int, 200) + port_data = self._port_deserializers[self.content_type].\ + deserialize(list_port_res.body)['body'] + return port_data + + def setUp(self): + super(APIFiltersTest, self).setUp('quantum.api.APIRouterV11', + {test_api.NETS: nets.ControllerV11._serialization_metadata, + test_api.PORTS: ports.ControllerV11._serialization_metadata, + test_api.ATTS: atts.ControllerV11._serialization_metadata}) + self.net_op_status = test_config.get('default_net_op_status', + 'UNKNOWN') + self.port_op_status = test_config.get('default_port_op_status', + 'UNKNOWN') + self.fmt = "xml" + self.content_type = "application/%s" % self.fmt + # create data for validating filters + # Create network "test-1" + self.net1_id = self._create_network(self.fmt, name="test-1") + # Add 2 ports, 1 ACTIVE, 1 DOWN + self.port11_id = self._create_port(self.net1_id, "ACTIVE", self.fmt) + self.port12_id = self._create_port(self.net1_id, "DOWN", self.fmt) + # Put attachment "test-1-att" in active port + self._set_attachment(self.net1_id, + self.port11_id, + "test-1-att", + self.fmt) + # Create network "test-2" + # Add 2 ports, 2 ACTIVE, 0 DOWN + self.net2_id = self._create_network(self.fmt, name="test-2") + self.port21_id = self._create_port(self.net2_id, "ACTIVE", self.fmt) + self.port22_id = self._create_port(self.net2_id, "ACTIVE", self.fmt) + + def test_network_name_filter(self): + LOG.debug("test_network_name_filter - START") + flt = "name=test-1" + network_data = self._do_filtered_network_list_request(flt) + # Check network count: should return 1 + self.assertEqual(len(network_data['networks']), 1) + self.assertEqual(network_data['networks'][0]['id'], self.net1_id) + + flt = "name=non-existent" + network_data = self._do_filtered_network_list_request(flt) + # Check network count: should return 0 + self.assertEqual(len(network_data['networks']), 0) + + LOG.debug("test_network_name_filter - END") + + def test_network_op_status_filter(self): + LOG.debug("test_network_op_status_filter - START") + # First filter for networks in default status + flt = "op-status=%s" % self.net_op_status + network_data = self._do_filtered_network_list_request(flt) + # Check network count: should return 2 + self.assertEqual(len(network_data['networks']), 2) + + # And then for networks in 'DOWN' status + flt = "op-status=DOWN" + network_data = self._do_filtered_network_list_request(flt) + # Check network count: should return 0 + self.assertEqual(len(network_data['networks']), 0) + LOG.debug("test_network_op_status_filter - END") + + def test_network_port_op_status_filter(self): + LOG.debug("test_network_port_op_status_filter - START") + # First filter for networks with ports in default op status + flt = "port-op-status=%s" % self.port_op_status + network_data = self._do_filtered_network_list_request(flt) + # Check network count: should return 2 + self.assertEqual(len(network_data['networks']), 2) + LOG.debug("test_network_port_op_status_filter - END") + + def test_network_port_state_filter(self): + LOG.debug("test_network_port_state_filter - START") + # First filter for networks with ports 'ACTIVE' + flt = "port-state=ACTIVE" + network_data = self._do_filtered_network_list_request(flt) + # Check network count: should return 2 + self.assertEqual(len(network_data['networks']), 2) + + # And then for networks with ports in 'DOWN' admin state + flt = "port-state=DOWN" + network_data = self._do_filtered_network_list_request(flt) + # Check network count: should return 1 + self.assertEqual(len(network_data['networks']), 1) + LOG.debug("test_network_port_state_filter - END") + + def test_network_has_attachment_filter(self): + LOG.debug("test_network_has_attachment_filter - START") + # First filter for networks with ports 'ACTIVE' + flt = "has-attachment=True" + network_data = self._do_filtered_network_list_request(flt) + # Check network count: should return 1 + self.assertEqual(len(network_data['networks']), 1) + + # And then for networks with ports in 'DOWN' admin state + flt = "has-attachment=False" + network_data = self._do_filtered_network_list_request(flt) + # Check network count: should return 1 + self.assertEqual(len(network_data['networks']), 1) + LOG.debug("test_network_has_attachment_filter - END") + + def test_network_port_filter(self): + LOG.debug("test_network_port_filter - START") + + flt = "port=%s" % self.port11_id + network_data = self._do_filtered_network_list_request(flt) + # Check network count: should return 1 + self.assertEqual(len(network_data['networks']), 1) + self.assertEqual(network_data['networks'][0]['id'], self.net1_id) + + flt = "port=%s" % self.port21_id + network_data = self._do_filtered_network_list_request(flt) + # Check network count: should return 1 + self.assertEqual(len(network_data['networks']), 1) + self.assertEqual(network_data['networks'][0]['id'], self.net2_id) + LOG.debug("test_network_port_filter - END") + + def test_network_attachment_filter(self): + LOG.debug("test_network_attachment_filter - START") + + flt = "attachment=test-1-att" + network_data = self._do_filtered_network_list_request(flt) + # Check network count: should return 1 + self.assertEqual(len(network_data['networks']), 1) + self.assertEqual(network_data['networks'][0]['id'], self.net1_id) + + flt = "attachment=non-existent" + network_data = self._do_filtered_network_list_request(flt) + # Check network count: should return 0 + self.assertEqual(len(network_data['networks']), 0) + LOG.debug("test_network_attachment_filter - END") + + def test_network_multiple_filters(self): + LOG.debug("test_network_multiple_filters - START") + # Add some data for having more fun + another_net_id = self._create_network(self.fmt, name="test-1") + # Add 1 ACTIVE port + self._create_port(another_net_id, "ACTIVE", self.fmt) + # Do the filtering + flt = "name=test-1&port-state=ACTIVE&attachment=test-1-att" + network_data = self._do_filtered_network_list_request(flt) + # Check network count: should return 1 + self.assertEqual(len(network_data['networks']), 1) + self.assertEqual(network_data['networks'][0]['id'], self.net1_id) + LOG.debug("test_network_multiple_filters - END") + + def test_port_state_filter(self): + LOG.debug("test_port_state_filter - START") + # First filter for 'ACTIVE' ports in 1st network + flt = "state=ACTIVE" + port_data = self._do_filtered_port_list_request(flt, self.net1_id) + # Check port count: should return 1 + self.assertEqual(len(port_data['ports']), 1) + + # And then in 2nd network + port_data = self._do_filtered_port_list_request(flt, self.net2_id) + # Check port count: should return 2 + self.assertEqual(len(port_data['ports']), 2) + LOG.debug("test_port_state_filter - END") + + def test_port_op_status_filter(self): + LOG.debug("test_port_op_status_filter - START") + # First filter for 'UP' ports in 1st network + flt = "op-status=%s" % self.port_op_status + port_data = self._do_filtered_port_list_request(flt, self.net1_id) + # Check port count: should return 2 + self.assertEqual(len(port_data['ports']), 2) + LOG.debug("test_port_op_status_filter - END") + + def test_port_has_attachment_filter(self): + LOG.debug("test_port_has_attachment_filter - START") + # First search for ports with attachments in 1st network + flt = "has-attachment=True" + port_data = self._do_filtered_port_list_request(flt, self.net1_id) + # Check port count: should return 1 + self.assertEqual(len(port_data['ports']), 1) + self.assertEqual(port_data['ports'][0]['id'], self.port11_id) + + # And then for ports without attachment in 2nd network + flt = "has-attachment=False" + port_data = self._do_filtered_port_list_request(flt, self.net2_id) + # Check port count: should return 2 + self.assertEqual(len(port_data['ports']), 2) + LOG.debug("test_port_has_attachment_filter - END") + + def test_port_attachment_filter(self): + LOG.debug("test_port_attachment_filter - START") + # First search for ports with attachments in 1st network + flt = "attachment=test-1-att" + port_data = self._do_filtered_port_list_request(flt, self.net1_id) + # Check port count: should return 1 + self.assertEqual(len(port_data['ports']), 1) + self.assertEqual(port_data['ports'][0]['id'], self.port11_id) + + # And then for a non-existent attachment in 2nd network + flt = "attachment=non-existent" + port_data = self._do_filtered_port_list_request(flt, self.net2_id) + # Check port count: should return 0 + self.assertEqual(len(port_data['ports']), 0) + LOG.debug("test_port_has_attachment_filter - END") + + def test_port_multiple_filters(self): + LOG.debug("test_port_multiple_filters - START") + flt = "op-status=%s&state=DOWN" % self.port_op_status + port_data = self._do_filtered_port_list_request(flt, self.net1_id) + # Check port count: should return 1 + self.assertEqual(len(port_data['ports']), 1) + self.assertEqual(port_data['ports'][0]['id'], self.port12_id) + + flt = "state=ACTIVE&attachment=test-1-att" + port_data = self._do_filtered_port_list_request(flt, self.net1_id) + # Check port count: should return 1 + self.assertEqual(len(port_data['ports']), 1) + self.assertEqual(port_data['ports'][0]['id'], self.port11_id) + + flt = "state=ACTIVE&has-attachment=False" + port_data = self._do_filtered_port_list_request(flt, self.net2_id) + # Check port count: should return 2 + self.assertEqual(len(port_data['ports']), 2) + LOG.debug("test_port_multiple_filters - END") diff --git a/quantum/tests/unit/testlib_api.py b/quantum/tests/unit/testlib_api.py index 1b7214531a..8a43895dd6 100644 --- a/quantum/tests/unit/testlib_api.py +++ b/quantum/tests/unit/testlib_api.py @@ -3,8 +3,12 @@ import webob from quantum.common.serializer import Serializer -def create_request(path, body, content_type, method='GET'): - req = webob.Request.blank(path) +def create_request(path, body, content_type, method='GET', query_string=None): + if query_string: + url = "%s?%s" % (path, query_string) + else: + url = path + req = webob.Request.blank(url) req.method = method req.headers = {} req.headers['Accept'] = content_type @@ -12,17 +16,18 @@ def create_request(path, body, content_type, method='GET'): return req -def _network_list_request(tenant_id, format='xml', detail=False): +def _network_list_request(tenant_id, format='xml', detail=False, + query_string=None): method = 'GET' 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) + return create_request(path, None, content_type, method, query_string) -def network_list_request(tenant_id, format='xml'): - return _network_list_request(tenant_id, format) +def network_list_request(tenant_id, format='xml', query_string=None): + return _network_list_request(tenant_id, format, query_string=query_string) def network_list_detail_request(tenant_id, format='xml'): @@ -75,17 +80,21 @@ 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', detail=False): +def _port_list_request(tenant_id, network_id, format='xml', + detail=False, query_string=None): method = 'GET' detail_str = detail and '/detail' or '' path = "/tenants/%(tenant_id)s/networks/" \ "%(network_id)s/ports%(detail_str)s.%(format)s" % locals() content_type = "application/%s" % format - return create_request(path, None, content_type, method) + return create_request(path, None, content_type, method, query_string) -def port_list_request(tenant_id, network_id, format='xml'): - return _port_list_request(tenant_id, network_id, format) +def port_list_request(tenant_id, network_id, format='xml', query_string=None): + return _port_list_request(tenant_id, + network_id, + format, + query_string=query_string) def port_list_detail_request(tenant_id, network_id, format='xml'):