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:
parent
b971ef0e3b
commit
7c6327e062
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'
|
||||
|
Loading…
x
Reference in New Issue
Block a user