]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
bp/api-filters
authorSalvatore Orlando <salvatore.orlando@eu.citrix.com>
Wed, 11 Jan 2012 17:26:47 +0000 (17:26 +0000)
committerSalvatore Orlando <salvatore.orlando@eu.citrix.com>
Wed, 25 Jan 2012 08:51:51 +0000 (08:51 +0000)
This changeset implements filters for core Quantum API and provides unit tests

Change-Id: I8247b3587c2cc8e53785781a45d1e457980261d2

.bzrignore
quantum/api/networks.py
quantum/api/ports.py
quantum/api/views/filters.py [new file with mode: 0644]
quantum/plugins/cisco/l2network_plugin.py
quantum/plugins/openvswitch/ovs_quantum_plugin.py
quantum/plugins/sample/SamplePlugin.py
quantum/quantum_plugin_base.py
quantum/tests/unit/_test_api.py
quantum/tests/unit/test_api.py
quantum/tests/unit/testlib_api.py

index fa741971c2dd75e7ee0d2a897df5c114d08d8902..d4e18c609327eebdf3df54fa27bf20365147e4fd 100644 (file)
@@ -9,4 +9,4 @@ ChangeLog
 *.pid
 *.log
 quantum/vcsversion.py
-.ropeproject
\ No newline at end of file
+.ropeproject
index 4229b6020cd3940875421ae405036c04a8366575..bf90c31654e5cb1188b415f1973c670abb856ee4 100644 (file)
@@ -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]
index fb5a578646597089b855b23c8c8159ac470bb3c3..d9b15c0e80d4ec8be0eb9fe279deb5c8da655599 100644 (file)
@@ -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 (file)
index 0000000..e4964f5
--- /dev/null
@@ -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)
index fb36b8cfb1c1a4a41fe61a566d3d00019ae38943..25fa514f2bb8adc2b8436ea399eb5a9baee686e0 100644 (file)
@@ -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
         <network_uuid, network_name> 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.
index 4b09b1218a7b39f9002f22debe694fcb242a7c61..c2eb84ee7735ba5709a9572beb43df10214a0925 100644 (file)
@@ -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):
index 5fb6cbc812cf745fe52d8bec3b7469dd4d17437f..1f3798c01396d219ff49c0b3ebfbfb830b3bc9dc 100644 (file)
@@ -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
         <network_uuid, network_name> 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:
index 07af86996c89d28b8789724cfd8cb1f60697731a..3af3d0f84aa6b0ce780334a9a8b8877bf59ab5b6 100644 (file)
@@ -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
         <network_uuid, network_name> 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
index fa01fa137e17b5bf781cfa63c2a18c94f5cbfd00..7a444c09db2fa95ae707e0fcbc5b79fa7e68d668 100644 (file)
@@ -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')
 
index 57ed6503f60e942b54245b44255d275b01d05172..ca6c50e4e291057256491ea9ca35238a7c8b8d8d 100644 (file)
 #    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")
index 1b7214531a72afef0c9c7d28409b529514499194..8a43895dd694363780c83aa19ad52d3be3d56ea4 100644 (file)
@@ -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'):