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
This commit is contained in:
Salvatore Orlando 2013-05-03 19:46:11 +02:00
parent b971ef0e3b
commit 7c6327e062
5 changed files with 110 additions and 4 deletions
quantumclient
tests/unit

@ -135,12 +135,14 @@ class HTTPClient(httplib2.Http):
raise exceptions.Forbidden(message=body)
return resp, body
def do_request(self, url, method, **kwargs):
def authenticate_and_fetch_endpoint_url(self):
if not self.auth_token:
self.authenticate()
elif not self.endpoint_url:
self.endpoint_url = self._get_endpoint_url()
def do_request(self, url, method, **kwargs):
self.authenticate_and_fetch_endpoint_url()
# Perform the request once. If we get a 401 back then it
# might be because the auth token expired, so try to
# re-authenticate and try again. If it still fails, bail.

@ -123,6 +123,14 @@ class QuantumCLIError(QuantumClientException):
pass
class RequestURITooLong(QuantumClientException):
"""Raised when a request fails with HTTP error 414."""
def __init__(self, **kwargs):
self.excess = kwargs.get('excess', 0)
super(RequestURITooLong, self).__init__(**kwargs)
class ConnectionFailed(QuantumClientException):
message = _("Connection to quantum failed: %(reason)s")

@ -18,6 +18,7 @@
import argparse
import logging
from quantumclient.common import exceptions
from quantumclient.quantum.v2_0 import CreateCommand
from quantumclient.quantum.v2_0 import DeleteCommand
from quantumclient.quantum.v2_0 import ListCommand
@ -36,6 +37,9 @@ def _format_subnets(network):
class ListNetwork(ListCommand):
"""List networks that belong to a given tenant."""
# Length of a query filter on subnet id
# id=<uuid>& (with len(uuid)=36)
subnet_id_filter_len = 40
resource = 'network'
log = logging.getLogger(__name__ + '.ListNetwork')
_formatters = {'subnets': _format_subnets, }
@ -55,8 +59,27 @@ class ListNetwork(ListCommand):
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', [])
def _get_subnet_list(sub_ids):
search_opts['id'] = sub_ids
return quantum_client.list_subnets(
**search_opts).get('subnets', [])
try:
subnets = _get_subnet_list(subnet_ids)
except exceptions.RequestURITooLong as uri_len_exc:
# The URI is too long because of too many subnet_id filters
# Use the excess attribute of the exception to know how many
# subnet_id filters can be inserted into a single request
subnet_count = len(subnet_ids)
max_size = ((self.subnet_id_filter_len * subnet_count) -
uri_len_exc.excess)
chunk_size = max_size / self.subnet_id_filter_len
subnets = []
for i in xrange(0, subnet_count, chunk_size):
subnets.extend(
_get_subnet_list(subnet_ids[i: i + chunk_size]))
subnet_dict = dict([(s['id'], s) for s in subnets])
for n in data:
if 'subnets' in n:

@ -196,6 +196,8 @@ class Client(object):
'health_monitors': 'health_monitor',
'quotas': 'quota',
}
# 8192 Is the default max URI len for eventlet.wsgi.server
MAX_URI_LEN = 8192
def get_attr_metadata(self):
if self.format == 'json':
@ -901,6 +903,12 @@ class Client(object):
if type(params) is dict and params:
params = utils.safe_encode_dict(params)
action += '?' + urllib.urlencode(params, doseq=1)
# Ensure client always has correct uri - do not guesstimate anything
self.httpclient.authenticate_and_fetch_endpoint_url()
uri_len = len(self.httpclient.endpoint_url) + len(action)
if uri_len > self.MAX_URI_LEN:
raise exceptions.RequestURITooLong(
excess=uri_len - self.MAX_URI_LEN)
if body:
body = self.serialize(body)
self.httpclient.content_type = self.content_type()

@ -1,4 +1,3 @@
# Copyright 2012 OpenStack LLC.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -473,6 +472,72 @@ class CLITestV20NetworkJSON(CLITestV20Base):
args = [myid]
self._test_delete_resource(resource, cmd, myid, args)
def _test_extend_list(self, mox_calls):
data = [{'id': 'netid%d' % i, 'name': 'net%d' % i,
'subnets': ['mysubid%d' % i]}
for i in range(0, 10)]
self.mox.StubOutWithMock(self.client.httpclient, "request")
path = getattr(self.client, 'subnets_path')
cmd = ListNetwork(MyApp(sys.stdout), None)
self.mox.StubOutWithMock(cmd, "get_client")
cmd.get_client().MultipleTimes().AndReturn(self.client)
mox_calls(path, data)
self.mox.ReplayAll()
known_args, _vs = cmd.get_parser('create_subnets').parse_known_args()
cmd.extend_list(data, known_args)
self.mox.VerifyAll()
def _build_test_data(self, data):
subnet_ids = []
response = []
filters = ""
for n in data:
if 'subnets' in n:
subnet_ids.extend(n['subnets'])
for subnet_id in n['subnets']:
filters = "%s&id=%s" % (filters, subnet_id)
response.append({'id': subnet_id,
'cidr': '192.168.0.0/16'})
resp_str = self.client.serialize({'subnets': response})
resp = (test_cli20.MyResp(200), resp_str)
return filters, resp
def test_extend_list(self):
def mox_calls(path, data):
filters, response = self._build_test_data(data)
self.client.httpclient.request(
test_cli20.end_url(path, 'fields=id&fields=cidr' + filters),
'GET',
body=None,
headers=ContainsKeyValue(
'X-Auth-Token', test_cli20.TOKEN)).AndReturn(response)
self._test_extend_list(mox_calls)
def test_extend_list_exceed_max_uri_len(self):
def mox_calls(path, data):
sub_data_lists = [data[:len(data) - 1], data[len(data) - 1:]]
filters, response = self._build_test_data(data)
# 1 char of extra URI len will cause a split in 2 requests
self.client.httpclient.request(
test_cli20.end_url(path, 'fields=id&fields=cidr%s' % filters),
'GET',
body=None,
headers=ContainsKeyValue(
'X-Auth-Token', test_cli20.TOKEN)).AndRaise(
exceptions.RequestURITooLong(excess=1))
for data in sub_data_lists:
filters, response = self._build_test_data(data)
self.client.httpclient.request(
test_cli20.end_url(path,
'fields=id&fields=cidr%s' % filters),
'GET',
body=None,
headers=ContainsKeyValue(
'X-Auth-Token', test_cli20.TOKEN)).AndReturn(response)
self._test_extend_list(mox_calls)
class CLITestV20NetworkXML(CLITestV20NetworkJSON):
format = 'xml'