diff options
author | He Jie Xu <xuhj@linux.vnet.ibm.com> | 2013-01-17 19:38:36 +0800 |
---|---|---|
committer | He Jie Xu <xuhj@linux.vnet.ibm.com> | 2013-03-01 12:43:25 +0800 |
commit | 49982a3aeab3445b2f99bedd21935710e0ddef5a (patch) | |
tree | 131e8a00dbf7f1adf790391e2b0c31743f7d39b9 | |
parent | 4722df472ea76e39aee6536247644cf37e7b10bb (diff) | |
download | python-neutronclient-49982a3aeab3445b2f99bedd21935710e0ddef5a.tar.gz |
Add pagination support for client
Fixes bug 1130625
Add pagination params: -P, --page-size SIZE
Add sorting params: --sort-key FIELD --sort-dir {asc,desc}
Add xml support
Change-Id: I76abb6335f53176feade216413a8cabaaefe42be
25 files changed, 516 insertions, 30 deletions
diff --git a/quantumclient/common/constants.py b/quantumclient/common/constants.py index 572baa9..7e56331 100644 --- a/quantumclient/common/constants.py +++ b/quantumclient/common/constants.py @@ -22,6 +22,8 @@ XSI_NIL_ATTR = "xmlns:xsi" TYPE_XMLNS = "xmlns:quantum" TYPE_ATTR = "quantum:type" VIRTUAL_ROOT_KEY = "_v_root" +ATOM_NAMESPACE = "http://www.w3.org/2005/Atom" +ATOM_LINK_NOTATION = "{%s}link" % ATOM_NAMESPACE TYPE_BOOL = "bool" TYPE_INT = "int" diff --git a/quantumclient/common/serializer.py b/quantumclient/common/serializer.py index 7eba930..535171b 100644 --- a/quantumclient/common/serializer.py +++ b/quantumclient/common/serializer.py @@ -245,18 +245,33 @@ class XMLDeserializer(TextDeserializer): else: return tag + def _get_links(self, root_tag, node): + link_nodes = node.findall(constants.ATOM_LINK_NOTATION) + root_tag = self._get_key(node.tag) + link_key = "%s_links" % root_tag + link_list = [] + for link in link_nodes: + link_list.append({'rel': link.get('rel'), + 'href': link.get('href')}) + # Remove link node in order to avoid link node being + # processed as an item in _from_xml_node + node.remove(link) + return link_list and {link_key: link_list} or {} + def _from_xml(self, datastring): if datastring is None: return None plurals = set(self.metadata.get('plurals', {})) try: node = etree.fromstring(datastring) - result = self._from_xml_node(node, plurals) root_tag = self._get_key(node.tag) + links = self._get_links(root_tag, node) + result = self._from_xml_node(node, plurals) + # There is no case where root_tag = constants.VIRTUAL_ROOT_KEY + # and links is not None because of the way data are serialized if root_tag == constants.VIRTUAL_ROOT_KEY: return result - else: - return {root_tag: result} + return dict({root_tag: result}, **links) except Exception as e: parseError = False # Python2.7 diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index cbf87b0..6101db9 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -90,6 +90,35 @@ def add_show_list_common_argument(parser): default=[]) +def add_pagination_argument(parser): + parser.add_argument( + '-P', '--page-size', + dest='page_size', metavar='SIZE', type=int, + help=("specify retrieve unit of each request, then split one request " + "to several requests"), + default=None) + + +def add_sorting_argument(parser): + parser.add_argument( + '--sort-key', + dest='sort_key', metavar='FIELD', + action='append', + help=("sort list by specified fields (This option can be repeated), " + "The number of sort_dir and sort_key should match each other, " + "more sort_dir specified will be omitted, less will be filled " + "with asc as default direction "), + default=[]) + parser.add_argument( + '--sort-dir', + dest='sort_dir', metavar='{asc,desc}', + help=("sort list in specified directions " + "(This option can be repeated)"), + action='append', + default=[], + choices=['asc', 'desc']) + + def add_extra_argument(parser, name, _help): parser.add_argument( name, @@ -411,12 +440,18 @@ class ListCommand(QuantumCommand, lister.Lister): _formatters = {} list_columns = [] unknown_parts_flag = True + pagination_support = False + sorting_support = False def get_parser(self, prog_name): parser = super(ListCommand, self).get_parser(prog_name) add_show_list_common_argument(parser) if self.unknown_parts_flag: add_extra_argument(parser, 'filter_specs', 'filters options') + if self.pagination_support: + add_pagination_argument(parser) + if self.sorting_support: + add_sorting_argument(parser) return parser def args2search_opts(self, parsed_args): @@ -445,6 +480,22 @@ class ListCommand(QuantumCommand, lister.Lister): self.values_specs) search_opts = self.args2search_opts(parsed_args) search_opts.update(_extra_values) + if self.pagination_support: + page_size = parsed_args.page_size + if page_size: + search_opts.update({'limit': page_size}) + if self.sorting_support: + keys = parsed_args.sort_key + if keys: + search_opts.update({'sort_key': keys}) + dirs = parsed_args.sort_dir + len_diff = len(keys) - len(dirs) + if len_diff > 0: + dirs += ['asc'] * len_diff + elif len_diff < 0: + dirs = dirs[:len(keys)] + if dirs: + search_opts.update({'sort_dir': dirs}) data = self.call_server(quantum_client, search_opts, parsed_args) collection = self.resource + "s" return data.get(collection, []) diff --git a/quantumclient/quantum/v2_0/floatingip.py b/quantumclient/quantum/v2_0/floatingip.py index 3198dfe..f4141f3 100644 --- a/quantumclient/quantum/v2_0/floatingip.py +++ b/quantumclient/quantum/v2_0/floatingip.py @@ -34,6 +34,8 @@ class ListFloatingIP(ListCommand): _formatters = {} list_columns = ['id', 'fixed_ip_address', 'floating_ip_address', 'port_id'] + pagination_support = True + sorting_support = True class ShowFloatingIP(ShowCommand): diff --git a/quantumclient/quantum/v2_0/lb/healthmonitor.py b/quantumclient/quantum/v2_0/lb/healthmonitor.py index 9aa2874..3071527 100644 --- a/quantumclient/quantum/v2_0/lb/healthmonitor.py +++ b/quantumclient/quantum/v2_0/lb/healthmonitor.py @@ -29,6 +29,8 @@ class ListHealthMonitor(quantumv20.ListCommand): log = logging.getLogger(__name__ + '.ListHealthMonitor') list_columns = ['id', 'type', 'admin_state_up', 'status'] _formatters = {} + pagination_support = True + sorting_support = True class ShowHealthMonitor(quantumv20.ShowCommand): diff --git a/quantumclient/quantum/v2_0/lb/member.py b/quantumclient/quantum/v2_0/lb/member.py index c43b94b..e3da789 100644 --- a/quantumclient/quantum/v2_0/lb/member.py +++ b/quantumclient/quantum/v2_0/lb/member.py @@ -31,6 +31,8 @@ class ListMember(quantumv20.ListCommand): 'id', 'address', 'protocol_port', 'admin_state_up', 'status' ] _formatters = {} + pagination_support = True + sorting_support = True class ShowMember(quantumv20.ShowCommand): diff --git a/quantumclient/quantum/v2_0/lb/pool.py b/quantumclient/quantum/v2_0/lb/pool.py index b39c684..049ac67 100644 --- a/quantumclient/quantum/v2_0/lb/pool.py +++ b/quantumclient/quantum/v2_0/lb/pool.py @@ -30,6 +30,8 @@ class ListPool(quantumv20.ListCommand): list_columns = ['id', 'name', 'lb_method', 'protocol', 'admin_state_up', 'status'] _formatters = {} + pagination_support = True + sorting_support = True class ShowPool(quantumv20.ShowCommand): diff --git a/quantumclient/quantum/v2_0/lb/vip.py b/quantumclient/quantum/v2_0/lb/vip.py index 5c2c304..c41fafa 100644 --- a/quantumclient/quantum/v2_0/lb/vip.py +++ b/quantumclient/quantum/v2_0/lb/vip.py @@ -30,6 +30,8 @@ class ListVip(quantumv20.ListCommand): list_columns = ['id', 'name', 'algorithm', 'address', 'protocol', 'admin_state_up', 'status'] _formatters = {} + pagination_support = True + sorting_support = True class ShowVip(quantumv20.ShowCommand): diff --git a/quantumclient/quantum/v2_0/network.py b/quantumclient/quantum/v2_0/network.py index dfc0171..c7f0d8e 100644 --- a/quantumclient/quantum/v2_0/network.py +++ b/quantumclient/quantum/v2_0/network.py @@ -41,11 +41,22 @@ class ListNetwork(ListCommand): log = logging.getLogger(__name__ + '.ListNetwork') _formatters = {'subnets': _format_subnets, } list_columns = ['id', 'name', 'subnets'] + pagination_support = True + sorting_support = True def extend_list(self, data, parsed_args): """Add subnet information to a network list""" quantum_client = self.get_client() search_opts = {'fields': ['id', 'cidr']} + if self.pagination_support: + page_size = parsed_args.page_size + if page_size: + search_opts.update({'limit': page_size}) + subnet_ids = [] + for n in data: + if 'subnets' in n: + subnet_ids.extend(n['subnets']) + search_opts.update({'id': subnet_ids}) subnets = quantum_client.list_subnets(**search_opts).get('subnets', []) subnet_dict = dict([(s['id'], s) for s in subnets]) for n in data: @@ -58,6 +69,8 @@ class ListExternalNetwork(ListNetwork): """List external networks that belong to a given tenant""" log = logging.getLogger(__name__ + '.ListExternalNetwork') + pagination_support = True + sorting_support = True def retrieve_list(self, parsed_args): external = '--router:external=True' diff --git a/quantumclient/quantum/v2_0/port.py b/quantumclient/quantum/v2_0/port.py index 753df0e..e946511 100644 --- a/quantumclient/quantum/v2_0/port.py +++ b/quantumclient/quantum/v2_0/port.py @@ -41,6 +41,8 @@ class ListPort(ListCommand): log = logging.getLogger(__name__ + '.ListPort') _formatters = {'fixed_ips': _format_fixed_ips, } list_columns = ['id', 'name', 'mac_address', 'fixed_ips'] + pagination_support = True + sorting_support = True class ListRouterPort(ListCommand): @@ -50,6 +52,8 @@ class ListRouterPort(ListCommand): log = logging.getLogger(__name__ + '.ListRouterPort') _formatters = {'fixed_ips': _format_fixed_ips, } list_columns = ['id', 'name', 'mac_address', 'fixed_ips'] + pagination_support = True + sorting_support = True def get_parser(self, prog_name): parser = super(ListRouterPort, self).get_parser(prog_name) diff --git a/quantumclient/quantum/v2_0/router.py b/quantumclient/quantum/v2_0/router.py index a0de0c8..86eaa72 100644 --- a/quantumclient/quantum/v2_0/router.py +++ b/quantumclient/quantum/v2_0/router.py @@ -42,6 +42,8 @@ class ListRouter(ListCommand): log = logging.getLogger(__name__ + '.ListRouter') _formatters = {'external_gateway_info': _format_external_gateway_info, } list_columns = ['id', 'name', 'external_gateway_info'] + pagination_support = True + sorting_support = True class ShowRouter(ShowCommand): diff --git a/quantumclient/quantum/v2_0/securitygroup.py b/quantumclient/quantum/v2_0/securitygroup.py index 9442ccc..971a2c8 100644 --- a/quantumclient/quantum/v2_0/securitygroup.py +++ b/quantumclient/quantum/v2_0/securitygroup.py @@ -28,6 +28,8 @@ class ListSecurityGroup(quantumv20.ListCommand): log = logging.getLogger(__name__ + '.ListSecurityGroup') _formatters = {} list_columns = ['id', 'name', 'description'] + pagination_support = True + sorting_support = True class ShowSecurityGroup(quantumv20.ShowCommand): @@ -81,6 +83,8 @@ class ListSecurityGroupRule(quantumv20.ListCommand): 'source_ip_prefix', 'source_group_id'] replace_rules = {'security_group_id': 'security_group', 'source_group_id': 'source_group'} + pagination_support = True + sorting_support = True def get_parser(self, prog_name): parser = super(ListSecurityGroupRule, self).get_parser(prog_name) @@ -106,6 +110,15 @@ class ListSecurityGroupRule(quantumv20.ListCommand): return quantum_client = self.get_client() search_opts = {'fields': ['id', 'name']} + if self.pagination_support: + page_size = parsed_args.page_size + if page_size: + search_opts.update({'limit': page_size}) + sec_group_ids = set() + for rule in data: + for key in self.replace_rules: + sec_group_ids.add(rule[key]) + search_opts.update({"id": sec_group_ids}) secgroups = quantum_client.list_security_groups(**search_opts) secgroups = secgroups.get('security_groups', []) sg_dict = dict([(sg['id'], sg['name']) diff --git a/quantumclient/quantum/v2_0/subnet.py b/quantumclient/quantum/v2_0/subnet.py index 9339ff5..4442b72 100644 --- a/quantumclient/quantum/v2_0/subnet.py +++ b/quantumclient/quantum/v2_0/subnet.py @@ -61,6 +61,8 @@ class ListSubnet(ListCommand): 'dns_nameservers': _format_dns_nameservers, 'host_routes': _format_host_routes, } list_columns = ['id', 'name', 'cidr', 'allocation_pools'] + pagination_support = True + sorting_support = True class ShowSubnet(ShowCommand): diff --git a/quantumclient/tests/unit/lb/test_cli20_healthmonitor.py b/quantumclient/tests/unit/lb/test_cli20_healthmonitor.py index 657b24e..ab5327c 100644 --- a/quantumclient/tests/unit/lb/test_cli20_healthmonitor.py +++ b/quantumclient/tests/unit/lb/test_cli20_healthmonitor.py @@ -92,6 +92,31 @@ class CLITestV20LbHealthmonitor(test_cli20.CLITestV20Base): None) self._test_list_resources(resources, cmd, True) + def test_list_healthmonitors_pagination(self): + """lb-healthmonitor-list""" + resources = "health_monitors" + cmd = healthmonitor.ListHealthMonitor(test_cli20.MyApp(sys.stdout), + None) + self._test_list_resources_with_pagination(resources, cmd) + + def test_list_healthmonitors_sort(self): + """lb-healthmonitor-list --sort-key name --sort-key id --sort-key asc + --sort-key desc + """ + resources = "health_monitors" + cmd = healthmonitor.ListHealthMonitor(test_cli20.MyApp(sys.stdout), + None) + self._test_list_resources(resources, cmd, + sort_key=["name", "id"], + sort_dir=["asc", "desc"]) + + def test_list_healthmonitors_limit(self): + """lb-healthmonitor-list -P""" + resources = "health_monitors" + cmd = healthmonitor.ListHealthMonitor(test_cli20.MyApp(sys.stdout), + None) + self._test_list_resources(resources, cmd, page_size=1000) + def test_show_healthmonitor_id(self): """lb-healthmonitor-show test_id""" resource = 'health_monitor' diff --git a/quantumclient/tests/unit/lb/test_cli20_member.py b/quantumclient/tests/unit/lb/test_cli20_member.py index 609bba1..2913d4c 100644 --- a/quantumclient/tests/unit/lb/test_cli20_member.py +++ b/quantumclient/tests/unit/lb/test_cli20_member.py @@ -72,6 +72,28 @@ class CLITestV20LbMember(test_cli20.CLITestV20Base): cmd = member.ListMember(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) + def test_list_members_pagination(self): + """lb-member-list""" + resources = "members" + cmd = member.ListMember(test_cli20.MyApp(sys.stdout), None) + self._test_list_resources_with_pagination(resources, cmd) + + def test_list_members_sort(self): + """lb-member-list --sort-key name --sort-key id --sort-key asc + --sort-key desc + """ + resources = "members" + cmd = member.ListMember(test_cli20.MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, + sort_key=["name", "id"], + sort_dir=["asc", "desc"]) + + def test_list_members_limit(self): + """lb-member-list -P""" + resources = "members" + cmd = member.ListMember(test_cli20.MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, page_size=1000) + def test_show_member_id(self): """lb-member-show test_id""" resource = 'member' diff --git a/quantumclient/tests/unit/lb/test_cli20_pool.py b/quantumclient/tests/unit/lb/test_cli20_pool.py index ee2f4e3..4bf6d3c 100644 --- a/quantumclient/tests/unit/lb/test_cli20_pool.py +++ b/quantumclient/tests/unit/lb/test_cli20_pool.py @@ -80,6 +80,28 @@ class CLITestV20LbPool(test_cli20.CLITestV20Base): cmd = pool.ListPool(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) + def test_list_pools_pagination(self): + """lb-pool-list""" + resources = "pools" + cmd = pool.ListPool(test_cli20.MyApp(sys.stdout), None) + self._test_list_resources_with_pagination(resources, cmd) + + def test_list_pools_sort(self): + """lb-pool-list --sort-key name --sort-key id --sort-key asc + --sort-key desc + """ + resources = "pools" + cmd = pool.ListPool(test_cli20.MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, + sort_key=["name", "id"], + sort_dir=["asc", "desc"]) + + def test_list_pools_limit(self): + """lb-pool-list -P""" + resources = "pools" + cmd = pool.ListPool(test_cli20.MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, page_size=1000) + def test_show_pool_id(self): """lb-pool-show test_id""" resource = 'pool' diff --git a/quantumclient/tests/unit/lb/test_cli20_vip.py b/quantumclient/tests/unit/lb/test_cli20_vip.py index 880c20a..878e99f 100644 --- a/quantumclient/tests/unit/lb/test_cli20_vip.py +++ b/quantumclient/tests/unit/lb/test_cli20_vip.py @@ -125,6 +125,28 @@ class CLITestV20LbVip(test_cli20.CLITestV20Base): cmd = vip.ListVip(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) + def test_list_vips_pagination(self): + """lb-vip-list""" + resources = "vips" + cmd = vip.ListVip(test_cli20.MyApp(sys.stdout), None) + self._test_list_resources_with_pagination(resources, cmd) + + def test_list_vips_sort(self): + """lb-vip-list --sort-key name --sort-key id --sort-key asc + --sort-key desc + """ + resources = "vips" + cmd = vip.ListVip(test_cli20.MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, + sort_key=["name", "id"], + sort_dir=["asc", "desc"]) + + def test_list_vips_limit(self): + """lb-vip-list -P""" + resources = "vips" + cmd = vip.ListVip(test_cli20.MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, page_size=1000) + def test_show_vip_id(self): """lb-vip-show test_id""" resource = 'vip' diff --git a/quantumclient/tests/unit/test_cli20.py b/quantumclient/tests/unit/test_cli20.py index 4e62eda..21b1b51 100644 --- a/quantumclient/tests/unit/test_cli20.py +++ b/quantumclient/tests/unit/test_cli20.py @@ -225,7 +225,8 @@ class CLITestV20Base(testtools.TestCase): self.mox.UnsetStubs() def _test_list_resources(self, resources, cmd, detail=False, tags=[], - fields_1=[], fields_2=[]): + fields_1=[], fields_2=[], page_size=None, + sort_key=[], sort_dir=[]): self.mox.StubOutWithMock(cmd, "get_client") self.mox.StubOutWithMock(self.client.httpclient, "request") cmd.get_client().MultipleTimes().AndReturn(self.client) @@ -263,6 +264,32 @@ class CLITestV20Base(testtools.TestCase): query += "&fields=" + field else: query = "fields=" + field + if page_size: + args.append("--page-size") + args.append(str(page_size)) + if query: + query += "&limit=%s" % page_size + else: + query = "limit=%s" % page_size + if sort_key: + for key in sort_key: + args.append('--sort-key') + args.append(key) + if query: + query += '&' + query += 'sort_key=%s' % key + if sort_dir: + len_diff = len(sort_key) - len(sort_dir) + if len_diff > 0: + sort_dir += ['asc'] * len_diff + elif len_diff < 0: + sort_dir = sort_dir[:len(sort_key)] + for dir in sort_dir: + args.append('--sort-dir') + args.append(dir) + if query: + query += '&' + query += 'sort_dir=%s' % dir path = getattr(self.client, resources + "_path") self.client.httpclient.request( MyUrlComparator(end_url(path, query), self.client), 'GET', @@ -277,6 +304,38 @@ class CLITestV20Base(testtools.TestCase): _str = self.fake_stdout.make_string() self.assertTrue('myid1' in _str) + def _test_list_resources_with_pagination(self, resources, cmd): + self.mox.StubOutWithMock(cmd, "get_client") + self.mox.StubOutWithMock(self.client.httpclient, "request") + cmd.get_client().MultipleTimes().AndReturn(self.client) + path = getattr(self.client, resources + "_path") + fake_query = "marker=myid2&limit=2" + reses1 = {resources: [{'id': 'myid1', }, + {'id': 'myid2', }], + '%s_links' % resources: [{'href': end_url(path, fake_query), + 'rel': 'next'}]} + reses2 = {resources: [{'id': 'myid3', }, + {'id': 'myid4', }]} + resstr1 = self.client.serialize(reses1) + resstr2 = self.client.serialize(reses2) + self.client.httpclient.request( + end_url(path, ""), 'GET', + body=None, + headers=ContainsKeyValue('X-Auth-Token', + TOKEN)).AndReturn((MyResp(200), resstr1)) + self.client.httpclient.request( + end_url(path, fake_query), 'GET', + body=None, + headers=ContainsKeyValue('X-Auth-Token', + TOKEN)).AndReturn((MyResp(200), resstr2)) + self.mox.ReplayAll() + cmd_parser = cmd.get_parser("list_" + resources) + + parsed_args = cmd_parser.parse_args("") + cmd.run(parsed_args) + self.mox.VerifyAll() + self.mox.UnsetStubs() + def _test_update_resource(self, resource, cmd, myid, args, extrafields): self.mox.StubOutWithMock(cmd, "get_client") self.mox.StubOutWithMock(self.client.httpclient, "request") diff --git a/quantumclient/tests/unit/test_cli20_floatingips.py b/quantumclient/tests/unit/test_cli20_floatingips.py index 7b5912c..620470d 100644 --- a/quantumclient/tests/unit/test_cli20_floatingips.py +++ b/quantumclient/tests/unit/test_cli20_floatingips.py @@ -86,6 +86,27 @@ class CLITestV20FloatingIps(CLITestV20Base): cmd = ListFloatingIP(MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) + def test_list_floatingips_pagination(self): + resources = 'floatingips' + cmd = ListFloatingIP(MyApp(sys.stdout), None) + self._test_list_resources_with_pagination(resources, cmd) + + def test_list_floatingips_sort(self): + """list floatingips: --sort-key name --sort-key id --sort-key asc + --sort-key desc + """ + resources = 'floatingips' + cmd = ListFloatingIP(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, + sort_key=["name", "id"], + sort_dir=["asc", "desc"]) + + def test_list_floatingips_limit(self): + """list floatingips: -P.""" + resources = 'floatingips' + cmd = ListFloatingIP(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, page_size=1000) + def test_delete_floatingip(self): """Delete floatingip: fip1""" resource = 'floatingip' diff --git a/quantumclient/tests/unit/test_cli20_network.py b/quantumclient/tests/unit/test_cli20_network.py index 6bc9ebd..a0a7c90 100644 --- a/quantumclient/tests/unit/test_cli20_network.py +++ b/quantumclient/tests/unit/test_cli20_network.py @@ -128,12 +128,47 @@ class CLITestV20Network(CLITestV20Base): self.assertEquals('\n', _str) def _test_list_networks(self, cmd, detail=False, tags=[], - fields_1=[], fields_2=[]): + fields_1=[], fields_2=[], page_size=None, + sort_key=[], sort_dir=[]): resources = "networks" self.mox.StubOutWithMock(ListNetwork, "extend_list") ListNetwork.extend_list(IsA(list), IgnoreArg()) self._test_list_resources(resources, cmd, detail, tags, - fields_1, fields_2) + fields_1, fields_2, page_size=page_size, + sort_key=sort_key, sort_dir=sort_dir) + + def test_list_nets_pagination(self): + cmd = ListNetwork(MyApp(sys.stdout), None) + self.mox.StubOutWithMock(ListNetwork, "extend_list") + ListNetwork.extend_list(IsA(list), IgnoreArg()) + self._test_list_resources_with_pagination("networks", cmd) + + def test_list_nets_sort(self): + """list nets: --sort-key name --sort-key id --sort-dir asc + --sort-dir desc + """ + cmd = ListNetwork(MyApp(sys.stdout), None) + self._test_list_networks(cmd, sort_key=['name', 'id'], + sort_dir=['asc', 'desc']) + + def test_list_nets_sort_with_keys_more_than_dirs(self): + """list nets: --sort-key name --sort-key id --sort-dir desc + """ + cmd = ListNetwork(MyApp(sys.stdout), None) + self._test_list_networks(cmd, sort_key=['name', 'id'], + sort_dir=['desc']) + + def test_list_nets_sort_with_dirs_more_than_keys(self): + """list nets: --sort-key name --sort-dir desc --sort-dir asc + """ + cmd = ListNetwork(MyApp(sys.stdout), None) + self._test_list_networks(cmd, sort_key=['name'], + sort_dir=['desc', 'asc']) + + def test_list_nets_limit(self): + """list nets: -P""" + cmd = ListNetwork(MyApp(sys.stdout), None) + self._test_list_networks(cmd, page_size=1000) def test_list_nets_detail(self): """list nets: -D.""" @@ -170,11 +205,15 @@ class CLITestV20Network(CLITestV20Base): cmd.get_client().AndReturn(self.client) setup_list_stub('networks', data, '') cmd.get_client().AndReturn(self.client) + filters = '' + for n in data: + for s in n['subnets']: + filters = filters + "&id=%s" % s setup_list_stub('subnets', [{'id': 'mysubid1', 'cidr': '192.168.1.0/24'}, {'id': 'mysubid2', 'cidr': '172.16.0.0/24'}, {'id': 'mysubid3', 'cidr': '10.1.1.0/24'}], - query='fields=id&fields=cidr') + query='fields=id&fields=cidr' + filters) self.mox.ReplayAll() args = [] diff --git a/quantumclient/tests/unit/test_cli20_port.py b/quantumclient/tests/unit/test_cli20_port.py index faec572..65cfc99 100644 --- a/quantumclient/tests/unit/test_cli20_port.py +++ b/quantumclient/tests/unit/test_cli20_port.py @@ -140,6 +140,27 @@ class CLITestV20Port(CLITestV20Base): cmd = ListPort(MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) + def test_list_ports_pagination(self): + resources = "ports" + cmd = ListPort(MyApp(sys.stdout), None) + self._test_list_resources_with_pagination(resources, cmd) + + def test_list_ports_sort(self): + """list ports: --sort-key name --sort-key id --sort-key asc + --sort-key desc + """ + resources = "ports" + cmd = ListPort(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, + sort_key=["name", "id"], + sort_dir=["asc", "desc"]) + + def test_list_ports_limit(self): + """list ports: -P""" + resources = "ports" + cmd = ListPort(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, page_size=1000) + def test_list_ports_tags(self): """List ports: -- --tags a b.""" resources = "ports" diff --git a/quantumclient/tests/unit/test_cli20_router.py b/quantumclient/tests/unit/test_cli20_router.py index 502365d..1875b8a 100644 --- a/quantumclient/tests/unit/test_cli20_router.py +++ b/quantumclient/tests/unit/test_cli20_router.py @@ -76,6 +76,27 @@ class CLITestV20Router(CLITestV20Base): cmd = ListRouter(MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) + def test_list_routers_pagination(self): + resources = "routers" + cmd = ListRouter(MyApp(sys.stdout), None) + self._test_list_resources_with_pagination(resources, cmd) + + def test_list_routers_sort(self): + """list routers: --sort-key name --sort-key id --sort-key asc + --sort-key desc + """ + resources = "routers" + cmd = ListRouter(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, + sort_key=["name", "id"], + sort_dir=["asc", "desc"]) + + def test_list_routers_limit(self): + """list routers: -P""" + resources = "routers" + cmd = ListRouter(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, page_size=1000) + def test_update_router_exception(self): """Update router: myid.""" resource = 'router' diff --git a/quantumclient/tests/unit/test_cli20_securitygroup.py b/quantumclient/tests/unit/test_cli20_securitygroup.py index 7a2e941..b5891f4 100644 --- a/quantumclient/tests/unit/test_cli20_securitygroup.py +++ b/quantumclient/tests/unit/test_cli20_securitygroup.py @@ -73,6 +73,26 @@ class CLITestV20SecurityGroups(test_cli20.CLITestV20Base): test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) + def test_list_security_groups_pagination(self): + resources = "security_groups" + cmd = securitygroup.ListSecurityGroup( + test_cli20.MyApp(sys.stdout), None) + self._test_list_resources_with_pagination(resources, cmd) + + def test_list_security_groups_sort(self): + resources = "security_groups" + cmd = securitygroup.ListSecurityGroup( + test_cli20.MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, + sort_key=["name", "id"], + sort_dir=["asc", "desc"]) + + def test_list_security_groups_limit(self): + resources = "security_groups" + cmd = securitygroup.ListSecurityGroup( + test_cli20.MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, page_size=1000) + def test_show_security_group_id(self): resource = 'security_group' cmd = securitygroup.ShowSecurityGroup( @@ -145,6 +165,38 @@ class CLITestV20SecurityGroups(test_cli20.CLITestV20Base): mox.IgnoreArg()) self._test_list_resources(resources, cmd, True) + def test_list_security_group_rules_pagination(self): + resources = "security_group_rules" + cmd = securitygroup.ListSecurityGroupRule( + test_cli20.MyApp(sys.stdout), None) + self.mox.StubOutWithMock(securitygroup.ListSecurityGroupRule, + "extend_list") + securitygroup.ListSecurityGroupRule.extend_list(mox.IsA(list), + mox.IgnoreArg()) + self._test_list_resources_with_pagination(resources, cmd) + + def test_list_security_group_rules_sort(self): + resources = "security_group_rules" + cmd = securitygroup.ListSecurityGroupRule( + test_cli20.MyApp(sys.stdout), None) + self.mox.StubOutWithMock(securitygroup.ListSecurityGroupRule, + "extend_list") + securitygroup.ListSecurityGroupRule.extend_list(mox.IsA(list), + mox.IgnoreArg()) + self._test_list_resources(resources, cmd, + sort_key=["name", "id"], + sort_dir=["asc", "desc"]) + + def test_list_security_group_rules_limit(self): + resources = "security_group_rules" + cmd = securitygroup.ListSecurityGroupRule( + test_cli20.MyApp(sys.stdout), None) + self.mox.StubOutWithMock(securitygroup.ListSecurityGroupRule, + "extend_list") + securitygroup.ListSecurityGroupRule.extend_list(mox.IsA(list), + mox.IgnoreArg()) + self._test_list_resources(resources, cmd, page_size=1000) + def test_show_security_group_rule(self): resource = 'security_group_rule' cmd = securitygroup.ShowSecurityGroupRule( @@ -196,11 +248,18 @@ class CLITestV20SecurityGroups(test_cli20.CLITestV20Base): setup_list_stub('security_group_rules', list_data, query) if conv: cmd.get_client().AndReturn(self.client) + sec_ids = set() + for n in data['data']: + sec_ids.add(n[1]) + sec_ids.add(n[2]) + filters = '' + for id in sec_ids: + filters = filters + "&id=%s" % id setup_list_stub('security_groups', [{'id': 'myid1', 'name': 'group1'}, {'id': 'myid2', 'name': 'group2'}, {'id': 'myid3', 'name': 'group3'}], - query='fields=id&fields=name') + query='fields=id&fields=name' + filters) self.mox.ReplayAll() cmd_parser = cmd.get_parser('list_security_group_rules') diff --git a/quantumclient/tests/unit/test_cli20_subnet.py b/quantumclient/tests/unit/test_cli20_subnet.py index 9b9650e..ca27945 100644 --- a/quantumclient/tests/unit/test_cli20_subnet.py +++ b/quantumclient/tests/unit/test_cli20_subnet.py @@ -331,6 +331,27 @@ class CLITestV20Subnet(CLITestV20Base): self._test_list_resources(resources, cmd, fields_1=['a', 'b'], fields_2=['c', 'd']) + def test_list_subnets_pagination(self): + resources = "subnets" + cmd = ListSubnet(MyApp(sys.stdout), None) + self._test_list_resources_with_pagination(resources, cmd) + + def test_list_subnets_sort(self): + """List subnets: --sort-key name --sort-key id --sort-key asc + --sort-key desc + """ + resources = "subnets" + cmd = ListSubnet(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, + sort_key=["name", "id"], + sort_dir=["asc", "desc"]) + + def test_list_subnets_limit(self): + """List subnets: -P.""" + resources = "subnets" + cmd = ListSubnet(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, page_size=1000) + def test_update_subnet(self): """Update subnet: myid --name myname --tags a b.""" resource = 'subnet' diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index aa40a76..3010354 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -19,6 +19,7 @@ import httplib import logging import time import urllib +import urlparse from quantumclient.client import HTTPClient from quantumclient.common import _ @@ -245,12 +246,13 @@ class Client(object): return self.get(self.ext_path % ext_alias, params=_params) @APIParamsCall - def list_ports(self, **_params): + def list_ports(self, retrieve_all=True, **_params): """ Fetches a list of all networks for a tenant """ # Pass filters in "params" argument to do_request - return self.get(self.ports_path, params=_params) + return self.list('ports', self.ports_path, retrieve_all, + **_params) @APIParamsCall def show_port(self, port, **_params): @@ -281,12 +283,13 @@ class Client(object): return self.delete(self.port_path % (port)) @APIParamsCall - def list_networks(self, **_params): + def list_networks(self, retrieve_all=True, **_params): """ Fetches a list of all networks for a tenant """ # Pass filters in "params" argument to do_request - return self.get(self.networks_path, params=_params) + return self.list('networks', self.networks_path, retrieve_all, + **_params) @APIParamsCall def show_network(self, network, **_params): @@ -317,11 +320,12 @@ class Client(object): return self.delete(self.network_path % (network)) @APIParamsCall - def list_subnets(self, **_params): + def list_subnets(self, retrieve_all=True, **_params): """ Fetches a list of all networks for a tenant """ - return self.get(self.subnets_path, params=_params) + return self.list('subnets', self.subnets_path, retrieve_all, + **_params) @APIParamsCall def show_subnet(self, subnet, **_params): @@ -352,12 +356,13 @@ class Client(object): return self.delete(self.subnet_path % (subnet)) @APIParamsCall - def list_routers(self, **_params): + def list_routers(self, retrieve_all=True, **_params): """ Fetches a list of all routers for a tenant """ # Pass filters in "params" argument to do_request - return self.get(self.routers_path, params=_params) + return self.list('routers', self.routers_path, retrieve_all, + **_params) @APIParamsCall def show_router(self, router, **_params): @@ -420,12 +425,13 @@ class Client(object): body={'router': {'external_gateway_info': {}}}) @APIParamsCall - def list_floatingips(self, **_params): + def list_floatingips(self, retrieve_all=True, **_params): """ Fetches a list of all floatingips for a tenant """ # Pass filters in "params" argument to do_request - return self.get(self.floatingips_path, params=_params) + return self.list('floatingips', self.floatingips_path, retrieve_all, + **_params) @APIParamsCall def show_floatingip(self, floatingip, **_params): @@ -463,11 +469,12 @@ class Client(object): return self.post(self.security_groups_path, body=body) @APIParamsCall - def list_security_groups(self, **_params): + def list_security_groups(self, retrieve_all=True, **_params): """ Fetches a list of all security groups for a tenant """ - return self.get(self.security_groups_path, params=_params) + return self.list('security_groups', self.security_groups_path, + retrieve_all, **_params) @APIParamsCall def show_security_group(self, security_group, **_params): @@ -500,11 +507,13 @@ class Client(object): (security_group_rule)) @APIParamsCall - def list_security_group_rules(self, **_params): + def list_security_group_rules(self, retrieve_all=True, **_params): """ Fetches a list of all security group rules for a tenant """ - return self.get(self.security_group_rules_path, params=_params) + return self.list('security_group_rules', + self.security_group_rules_path, + retrieve_all, **_params) @APIParamsCall def show_security_group_rule(self, security_group_rule, **_params): @@ -515,12 +524,13 @@ class Client(object): params=_params) @APIParamsCall - def list_vips(self, **_params): + def list_vips(self, retrieve_all=True, **_params): """ Fetches a list of all load balancer vips for a tenant """ # Pass filters in "params" argument to do_request - return self.get(self.vips_path, params=_params) + return self.list('vips', self.vips_path, retrieve_all, + **_params) @APIParamsCall def show_vip(self, vip, **_params): @@ -551,12 +561,13 @@ class Client(object): return self.delete(self.vip_path % (vip)) @APIParamsCall - def list_pools(self, **_params): + def list_pools(self, retrieve_all=True, **_params): """ Fetches a list of all load balancer pools for a tenant """ # Pass filters in "params" argument to do_request - return self.get(self.pools_path, params=_params) + return self.list('pools', self.pools_path, retrieve_all, + **_params) @APIParamsCall def show_pool(self, pool, **_params): @@ -594,12 +605,13 @@ class Client(object): return self.get(self.pool_path_stats % (pool), params=_params) @APIParamsCall - def list_members(self, **_params): + def list_members(self, retrieve_all=True, **_params): """ Fetches a list of all load balancer members for a tenant """ # Pass filters in "params" argument to do_request - return self.get(self.members_path, params=_params) + return self.list('members', self.members_path, retrieve_all, + **_params) @APIParamsCall def show_member(self, member, **_params): @@ -630,12 +642,13 @@ class Client(object): return self.delete(self.member_path % (member)) @APIParamsCall - def list_health_monitors(self, **_params): + def list_health_monitors(self, retrieve_all=True, **_params): """ Fetches a list of all load balancer health monitors for a tenant """ # Pass filters in "params" argument to do_request - return self.get(self.health_monitors_path, params=_params) + return self.list('health_monitors', self.health_monitors_path, + retrieve_all, **_params) @APIParamsCall def show_health_monitor(self, health_monitor, **_params): @@ -976,3 +989,32 @@ class Client(object): def put(self, action, body=None, headers=None, params=None): return self.retry_request("PUT", action, body=body, headers=headers, params=params) + + def list(self, collection, path, retrieve_all=True, **params): + if retrieve_all: + res = [] + for r in self._pagination(collection, path, **params): + res.extend(r[collection]) + return {collection: res} + else: + return self._pagination(collection, path, **params) + + def _pagination(self, collection, path, **params): + if params.get('page_reverse', False): + linkrel = 'previous' + else: + linkrel = 'next' + next = True + while next: + res = self.get(path, params=params) + yield res + next = False + try: + for link in res['%s_links' % collection]: + if link['rel'] == linkrel: + query_str = urlparse.urlparse(link['href']).query + params = urlparse.parse_qs(query_str) + next = True + break + except KeyError: + break |