Skip to content

Commit 7c6327e

Browse files
committed
Avoid error 414 when retrieving subnet cidrs for ListNetworks
Bug 1172537 In order to avoif 414 the list subnet requests will be split in multiple requests. The total URI len for each of these requests will be lower than 8K (the default maximum for eventlet.wsgi.server). The patch tries to submit a single request, and if the URI is too long an exception is raised before the request is sent over the wire; the exception handler will split the subnet id list, and submit the new requests. This patch does not address the case in which the server is configured with a maximum URI length different from 8K. Change-Id: Ia2414cd5374a91d3d12215807037a5d46b836ad6
1 parent b971ef0 commit 7c6327e

5 files changed

Lines changed: 110 additions & 4 deletions

File tree

quantumclient/client.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,14 @@ def _cs_request(self, *args, **kwargs):
135135
raise exceptions.Forbidden(message=body)
136136
return resp, body
137137

138-
def do_request(self, url, method, **kwargs):
138+
def authenticate_and_fetch_endpoint_url(self):
139139
if not self.auth_token:
140140
self.authenticate()
141141
elif not self.endpoint_url:
142142
self.endpoint_url = self._get_endpoint_url()
143143

144+
def do_request(self, url, method, **kwargs):
145+
self.authenticate_and_fetch_endpoint_url()
144146
# Perform the request once. If we get a 401 back then it
145147
# might be because the auth token expired, so try to
146148
# re-authenticate and try again. If it still fails, bail.

quantumclient/common/exceptions.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,14 @@ class QuantumCLIError(QuantumClientException):
123123
pass
124124

125125

126+
class RequestURITooLong(QuantumClientException):
127+
"""Raised when a request fails with HTTP error 414."""
128+
129+
def __init__(self, **kwargs):
130+
self.excess = kwargs.get('excess', 0)
131+
super(RequestURITooLong, self).__init__(**kwargs)
132+
133+
126134
class ConnectionFailed(QuantumClientException):
127135
message = _("Connection to quantum failed: %(reason)s")
128136

quantumclient/quantum/v2_0/network.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import argparse
1919
import logging
2020

21+
from quantumclient.common import exceptions
2122
from quantumclient.quantum.v2_0 import CreateCommand
2223
from quantumclient.quantum.v2_0 import DeleteCommand
2324
from quantumclient.quantum.v2_0 import ListCommand
@@ -36,6 +37,9 @@ def _format_subnets(network):
3637
class ListNetwork(ListCommand):
3738
"""List networks that belong to a given tenant."""
3839

40+
# Length of a query filter on subnet id
41+
# id=<uuid>& (with len(uuid)=36)
42+
subnet_id_filter_len = 40
3943
resource = 'network'
4044
log = logging.getLogger(__name__ + '.ListNetwork')
4145
_formatters = {'subnets': _format_subnets, }
@@ -55,8 +59,27 @@ def extend_list(self, data, parsed_args):
5559
for n in data:
5660
if 'subnets' in n:
5761
subnet_ids.extend(n['subnets'])
58-
search_opts.update({'id': subnet_ids})
59-
subnets = quantum_client.list_subnets(**search_opts).get('subnets', [])
62+
63+
def _get_subnet_list(sub_ids):
64+
search_opts['id'] = sub_ids
65+
return quantum_client.list_subnets(
66+
**search_opts).get('subnets', [])
67+
68+
try:
69+
subnets = _get_subnet_list(subnet_ids)
70+
except exceptions.RequestURITooLong as uri_len_exc:
71+
# The URI is too long because of too many subnet_id filters
72+
# Use the excess attribute of the exception to know how many
73+
# subnet_id filters can be inserted into a single request
74+
subnet_count = len(subnet_ids)
75+
max_size = ((self.subnet_id_filter_len * subnet_count) -
76+
uri_len_exc.excess)
77+
chunk_size = max_size / self.subnet_id_filter_len
78+
subnets = []
79+
for i in xrange(0, subnet_count, chunk_size):
80+
subnets.extend(
81+
_get_subnet_list(subnet_ids[i: i + chunk_size]))
82+
6083
subnet_dict = dict([(s['id'], s) for s in subnets])
6184
for n in data:
6285
if 'subnets' in n:

quantumclient/v2_0/client.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,8 @@ class Client(object):
196196
'health_monitors': 'health_monitor',
197197
'quotas': 'quota',
198198
}
199+
# 8192 Is the default max URI len for eventlet.wsgi.server
200+
MAX_URI_LEN = 8192
199201

200202
def get_attr_metadata(self):
201203
if self.format == 'json':
@@ -901,6 +903,12 @@ def do_request(self, method, action, body=None, headers=None, params=None):
901903
if type(params) is dict and params:
902904
params = utils.safe_encode_dict(params)
903905
action += '?' + urllib.urlencode(params, doseq=1)
906+
# Ensure client always has correct uri - do not guesstimate anything
907+
self.httpclient.authenticate_and_fetch_endpoint_url()
908+
uri_len = len(self.httpclient.endpoint_url) + len(action)
909+
if uri_len > self.MAX_URI_LEN:
910+
raise exceptions.RequestURITooLong(
911+
excess=uri_len - self.MAX_URI_LEN)
904912
if body:
905913
body = self.serialize(body)
906914
self.httpclient.content_type = self.content_type()

tests/unit/test_cli20_network.py

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# Copyright 2012 OpenStack LLC.
21
# All Rights Reserved
32
#
43
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -473,6 +472,72 @@ def test_delete_network(self):
473472
args = [myid]
474473
self._test_delete_resource(resource, cmd, myid, args)
475474

475+
def _test_extend_list(self, mox_calls):
476+
data = [{'id': 'netid%d' % i, 'name': 'net%d' % i,
477+
'subnets': ['mysubid%d' % i]}
478+
for i in range(0, 10)]
479+
self.mox.StubOutWithMock(self.client.httpclient, "request")
480+
path = getattr(self.client, 'subnets_path')
481+
cmd = ListNetwork(MyApp(sys.stdout), None)
482+
self.mox.StubOutWithMock(cmd, "get_client")
483+
cmd.get_client().MultipleTimes().AndReturn(self.client)
484+
mox_calls(path, data)
485+
self.mox.ReplayAll()
486+
known_args, _vs = cmd.get_parser('create_subnets').parse_known_args()
487+
cmd.extend_list(data, known_args)
488+
self.mox.VerifyAll()
489+
490+
def _build_test_data(self, data):
491+
subnet_ids = []
492+
response = []
493+
filters = ""
494+
for n in data:
495+
if 'subnets' in n:
496+
subnet_ids.extend(n['subnets'])
497+
for subnet_id in n['subnets']:
498+
filters = "%s&id=%s" % (filters, subnet_id)
499+
response.append({'id': subnet_id,
500+
'cidr': '192.168.0.0/16'})
501+
resp_str = self.client.serialize({'subnets': response})
502+
resp = (test_cli20.MyResp(200), resp_str)
503+
return filters, resp
504+
505+
def test_extend_list(self):
506+
def mox_calls(path, data):
507+
filters, response = self._build_test_data(data)
508+
self.client.httpclient.request(
509+
test_cli20.end_url(path, 'fields=id&fields=cidr' + filters),
510+
'GET',
511+
body=None,
512+
headers=ContainsKeyValue(
513+
'X-Auth-Token', test_cli20.TOKEN)).AndReturn(response)
514+
515+
self._test_extend_list(mox_calls)
516+
517+
def test_extend_list_exceed_max_uri_len(self):
518+
def mox_calls(path, data):
519+
sub_data_lists = [data[:len(data) - 1], data[len(data) - 1:]]
520+
filters, response = self._build_test_data(data)
521+
# 1 char of extra URI len will cause a split in 2 requests
522+
self.client.httpclient.request(
523+
test_cli20.end_url(path, 'fields=id&fields=cidr%s' % filters),
524+
'GET',
525+
body=None,
526+
headers=ContainsKeyValue(
527+
'X-Auth-Token', test_cli20.TOKEN)).AndRaise(
528+
exceptions.RequestURITooLong(excess=1))
529+
for data in sub_data_lists:
530+
filters, response = self._build_test_data(data)
531+
self.client.httpclient.request(
532+
test_cli20.end_url(path,
533+
'fields=id&fields=cidr%s' % filters),
534+
'GET',
535+
body=None,
536+
headers=ContainsKeyValue(
537+
'X-Auth-Token', test_cli20.TOKEN)).AndReturn(response)
538+
539+
self._test_extend_list(mox_calls)
540+
476541

477542
class CLITestV20NetworkXML(CLITestV20NetworkJSON):
478543
format = 'xml'

0 commit comments

Comments
 (0)