Fix failure in multi-chunk state sync for nicira plugin

Bug 1224719

This patch ensures the 'result_count' attribute from NVP API responses
is properly handled, and adds support for '_page_cursor' request query
parameter in the fake nvp api client in order to ensure issues like this
are captured by unit tests.

Change-Id: I1d6179bd58a14d19729fc882f004b6d1afccbe3d
This commit is contained in:
Salvatore Orlando 2013-09-12 15:23:46 -07:00
parent a97c34a125
commit 2740448d03
2 changed files with 37 additions and 20 deletions

View File

@ -457,24 +457,22 @@ class NvpSynchronizer():
# API. In this case the request should be split in multiple
# requests. This is not ideal, and therefore a log warning will
# be emitted.
requests = range(0, page_size / (nvplib.MAX_PAGE_SIZE + 1) + 1)
if len(requests) > 1:
num_requests = page_size / (nvplib.MAX_PAGE_SIZE + 1) + 1
if num_requests > 1:
LOG.warn(_("Requested page size is %(cur_chunk_size)d."
"It might be necessary to do %(num_requests)d "
"round-trips to NVP for fetching data. Please "
"tune sync parameters to ensure chunk size "
"is less than %(max_page_size)d"),
{'cur_chunk_size': page_size,
'num_requests': len(requests),
'num_requests': num_requests,
'max_page_size': nvplib.MAX_PAGE_SIZE})
results = []
actual_size = 0
for _req in requests:
req_results, cursor, req_size = nvplib.get_single_query_page(
uri, self._cluster, cursor,
min(page_size, nvplib.MAX_PAGE_SIZE))
results.extend(req_results)
actual_size = actual_size + req_size
# Only the first request might return the total size,
# subsequent requests will definetely not
results, cursor, total_size = nvplib.get_single_query_page(
uri, self._cluster, cursor,
min(page_size, nvplib.MAX_PAGE_SIZE))
for _req in range(0, num_requests - 1):
# If no cursor is returned break the cycle as there is no
# actual need to perform multiple requests (all fetched)
# This happens when the overall size of resources exceeds
@ -482,9 +480,13 @@ class NvpSynchronizer():
# resource type is below this threshold
if not cursor:
break
req_results, cursor = nvplib.get_single_query_page(
uri, self._cluster, cursor,
min(page_size, nvplib.MAX_PAGE_SIZE))[:2]
results.extend(req_results)
# reset cursor before returning if we queried just to
# know the number of entities
return results, cursor if page_size else 'start', actual_size
return results, cursor if page_size else 'start', total_size
return [], cursor, None
def _fetch_nvp_data_chunk(self, sp):

View File

@ -135,7 +135,7 @@ class FakeClient:
def _get_filters(self, querystring):
if not querystring:
return (None, None, None)
return (None, None, None, None)
params = urlparse.parse_qs(querystring)
tag_filter = None
attr_filter = None
@ -144,15 +144,15 @@ class FakeClient:
'tag': params['tag'][0]}
elif 'uuid' in params:
attr_filter = {'uuid': params['uuid'][0]}
# Handle page_length
# TODO(salv-orlando): Handle page cursor too
# Handle page length and page cursor parameter
page_len = params.get('_page_length')
page_cursor = params.get('_page_cursor')
if page_len:
page_len = int(page_len[0])
else:
# Explicitly set it to None (avoid 0 or empty list)
page_len = None
return (tag_filter, attr_filter, page_len)
return (tag_filter, attr_filter, page_len, page_cursor)
def _add_lswitch(self, body):
fake_lswitch = json.loads(body)
@ -369,7 +369,11 @@ class FakeClient:
def _list(self, resource_type, response_file,
parent_uuid=None, query=None, relations=None):
(tag_filter, attr_filter, page_len) = self._get_filters(query)
(tag_filter, attr_filter,
page_len, page_cursor) = self._get_filters(query)
# result_count attribute in response should appear only when
# page_cursor is not specified
do_result_count = not page_cursor
with open("%s/%s" % (self.fake_files_path, response_file)) as f:
response_template = f.read()
res_dict = getattr(self, '_fake_%s_dict' % resource_type)
@ -410,6 +414,15 @@ class FakeClient:
return True
return False
def _cursor_match(res_uuid, page_cursor):
if not page_cursor:
return True
if page_cursor == res_uuid:
# always return True once page_cursor has been found
page_cursor = None
return True
return False
def _build_item(resource):
item = json.loads(response_template % resource)
if relations:
@ -437,7 +450,8 @@ class FakeClient:
for res_uuid in res_dict
if (parent_func(res_uuid) and
_tag_match(res_uuid) and
_attr_match(res_uuid))]
_attr_match(res_uuid) and
_cursor_match(res_uuid, page_cursor))]
# Rather inefficient, but hey this is just a mock!
next_cursor = None
total_items = len(items)
@ -447,10 +461,11 @@ class FakeClient:
except IndexError:
next_cursor = None
items = items[:page_len]
response_dict = {'results': items,
'result_count': total_items}
response_dict = {'results': items}
if next_cursor:
response_dict['page_cursor'] = next_cursor
if do_result_count:
response_dict['result_count'] = total_items
return json.dumps(response_dict)
def _show(self, resource_type, response_file,