From 62a9553cac9dfab1d735897f192a4f81456df569 Mon Sep 17 00:00:00 2001 From: Michal Galet Date: Wed, 19 Jun 2013 13:50:17 +0100 Subject: [PATCH 1/4] LIBCLOUD-344: vCloud: ex_query() should return result parameters (total, page, page_size, etc.), add more metadata to Vdc object --- libcloud/compute/drivers/vcloud.py | 56 ++++++++++++++++++---- .../compute/fixtures/vcloud_1_5/api_query_user.xml | 2 +- ...pi_vdc_3d9ae28c_1de9_4307_8107_9356ff8ba6d0.xml | 4 +- libcloud/test/compute/test_vcloud.py | 19 ++++++-- 4 files changed, 64 insertions(+), 17 deletions(-) diff --git a/libcloud/compute/drivers/vcloud.py b/libcloud/compute/drivers/vcloud.py index adae38f..c837a13 100644 --- a/libcloud/compute/drivers/vcloud.py +++ b/libcloud/compute/drivers/vcloud.py @@ -77,15 +77,17 @@ class Vdc(object): Virtual datacenter (vDC) representation """ - def __init__(self, id, name, driver, allocation_model=None, cpu=None, - memory=None, storage=None): + def __init__(self, id, name, driver, enabled=True, allocation_model=None, + cpu=None, memory=None, storage=None, vm_quota=None): self.id = id self.name = name self.driver = driver + self.enabled = enabled self.allocation_model = allocation_model self.cpu = cpu self.memory = memory self.storage = storage + self.vm_quota = vm_quota def __repr__(self): return ('' @@ -143,6 +145,29 @@ class Subject(object): % (self.type, self.name, self.access_level)) +class QueryResult(object): + """ + Query result object. Is iterable. + """ + def __init__(self, id, total, page, page_size, name, records): + self.id = id + self.total = total + self.page = page + self.page_size = page_size + self.name = name + self.records = records + + def __len__(self): + return len(self.records) + + def __getitem__(self, item): + return self.records[item] + + def __repr__(self): + return ('' + % (self.id, self.total, self.page)) + + class InstantiateVAppXML(object): def __init__(self, name, template, net_href, cpus, memory, password=None, row=None, group=None): @@ -1248,8 +1273,8 @@ class VCloud_1_5_NodeDriver(VCloudNodeDriver): sort_desc=None): """ Queries vCloud for specified type. See http://www.vmware.com/pdf/vcd_15_api_guide.pdf - for details. Each element of the returned list is a dictionary with all - attributes from the record. + for details. Result returned as QueryResult object. Elements of the + returned list are dictionary with all attributes from the record. @param type: type to query (r.g. user, group, vApp etc.) @type type: C{str} @@ -1269,7 +1294,7 @@ class VCloud_1_5_NodeDriver(VCloudNodeDriver): @param sort_desc: sort in descending order by specified field @type sort_desc: C{str} - @rtype: C{list} of dict + @rtype: C{QueryResult} """ # This is a workaround for filter parameter encoding # the urllib encodes (name==Developers%20Only) into @@ -1290,14 +1315,19 @@ class VCloud_1_5_NodeDriver(VCloudNodeDriver): filter = '(' + filter + ')' url += '&filter=' + filter.replace(' ', '+') - results = [] + records = [] res = self.connection.request(url) for elem in res.object: if not elem.tag.endswith('Link'): result = elem.attrib result['type'] = elem.tag.split('}')[1] - results.append(result) - return results + records.append(result) + return QueryResult(id=res.object.get('href'), + total=int(res.object.get('total')), + page=int(res.object.get('page')), + page_size=int(res.object.get('pageSize')), + name=res.object.get('name'), + records=records) def create_node(self, **kwargs): """Creates and returns node. If the source image is: @@ -1922,13 +1952,21 @@ class VCloud_1_5_NodeDriver(VCloudNodeDriver): memory = get_capacity_values(vdc_elm.find(fixxpath(vdc_elm, 'ComputeCapacity/Memory'))) storage = get_capacity_values(vdc_elm.find(fixxpath(vdc_elm, 'StorageCapacity'))) + vm_quota = vdc_elm.findtext(fixxpath(vdc_elm, 'VmQuota')) + vm_quota = int(vm_quota) + + enabled = vdc_elm.findtext(fixxpath(vdc_elm, 'IsEnabled')) + enabled = enabled == 'true' + return Vdc(id=vdc_elm.get('href'), name=vdc_elm.get('name'), driver=self, + enabled=enabled, allocation_model=vdc_elm.findtext(fixxpath(vdc_elm, 'AllocationModel')), cpu=cpu, memory=memory, - storage=storage) + storage=storage, + vm_quota=vm_quota) class VCloud_5_1_NodeDriver(VCloud_1_5_NodeDriver): diff --git a/libcloud/test/compute/fixtures/vcloud_1_5/api_query_user.xml b/libcloud/test/compute/fixtures/vcloud_1_5/api_query_user.xml index 13c89db..228b5c0 100644 --- a/libcloud/test/compute/fixtures/vcloud_1_5/api_query_user.xml +++ b/libcloud/test/compute/fixtures/vcloud_1_5/api_query_user.xml @@ -1,5 +1,5 @@ - + diff --git a/libcloud/test/compute/fixtures/vcloud_1_5/api_vdc_3d9ae28c_1de9_4307_8107_9356ff8ba6d0.xml b/libcloud/test/compute/fixtures/vcloud_1_5/api_vdc_3d9ae28c_1de9_4307_8107_9356ff8ba6d0.xml index 3e55b5d..b216777 100644 --- a/libcloud/test/compute/fixtures/vcloud_1_5/api_vdc_3d9ae28c_1de9_4307_8107_9356ff8ba6d0.xml +++ b/libcloud/test/compute/fixtures/vcloud_1_5/api_vdc_3d9ae28c_1de9_4307_8107_9356ff8ba6d0.xml @@ -52,6 +52,6 @@ 0 1024 - 150 - true + 7 + false diff --git a/libcloud/test/compute/test_vcloud.py b/libcloud/test/compute/test_vcloud.py index b118255..250e0e3 100644 --- a/libcloud/test/compute/test_vcloud.py +++ b/libcloud/test/compute/test_vcloud.py @@ -229,6 +229,7 @@ class VCloud_1_5_Tests(unittest.TestCase, TestCaseMixin): self.assertEqual(len(vdcs), 1) self.assertEqual(vdcs[0].id, 'https://vm-vcloud/api/vdc/3d9ae28c-1de9-4307-8107-9356ff8ba6d0') self.assertEqual(vdcs[0].name, 'MyVdc') + self.assertEqual(vdcs[0].enabled, False) self.assertEqual(vdcs[0].allocation_model, 'AllocationPool') self.assertEqual(vdcs[0].storage.limit, 5120000) self.assertEqual(vdcs[0].storage.used, 1984512) @@ -239,6 +240,7 @@ class VCloud_1_5_Tests(unittest.TestCase, TestCaseMixin): self.assertEqual(vdcs[0].memory.limit, 527360) self.assertEqual(vdcs[0].memory.used, 130752) self.assertEqual(vdcs[0].memory.units, 'MB') + self.assertEqual(vdcs[0].vm_quota, 7) def test_ex_list_nodes(self): self.assertEqual(len(self.driver.ex_list_nodes()), len(self.driver.list_nodes())) @@ -255,11 +257,18 @@ class VCloud_1_5_Tests(unittest.TestCase, TestCaseMixin): self.driver.ex_power_off_node(node) def test_ex_query(self): - results = self.driver.ex_query('user', filter='name==jrambo', page=2, page_size=30, sort_desc='startDate') - self.assertEqual(len(results), 1) - self.assertEqual(results[0]['type'], 'UserRecord') - self.assertEqual(results[0]['name'], 'jrambo') - self.assertEqual(results[0]['isLdapUser'], 'true') + result = self.driver.ex_query('user', filter='name==jrambo', page=2, page_size=30, sort_desc='startDate') + self.assertEqual(len(result), 1) + self.assertEqual(result[0]['type'], 'UserRecord') + self.assertEqual(result[0]['name'], 'jrambo') + self.assertEqual(result[0]['isLdapUser'], 'true') + self.assertEqual(result.page_size, 30) + self.assertEqual(result.total, 133) + self.assertEqual(result.page, 2) + self.assertEqual(result.name, 'user') + self.assertEqual(len([x for x in result]), 1) + + def test_ex_get_control_access(self): node = Node('https://vm-vcloud/api/vApp/vapp-8c57a5b6-e61b-48ca-8a78-3b70ee65ef6b', 'testNode', NodeState.RUNNING, [], [], self.driver) -- 1.8.1.msysgit.1 From 6c72395071ae9a47fd6a6ffbc4d377ec101f4483 Mon Sep 17 00:00:00 2001 From: Michal Galet Date: Mon, 24 Jun 2013 14:16:03 +0100 Subject: [PATCH 2/4] LIBCLOUD-344: QueryResult class returns generator and handles pagination. Suggested on GitHub pull request: https://github.com/apache/libcloud/pull/112 --- libcloud/compute/drivers/vcloud.py | 113 ++++++++++++--------- .../compute/fixtures/vcloud_1_5/api_query_user.xml | 6 +- .../fixtures/vcloud_1_5/api_query_user__page2.xml | 7 ++ .../vcloud_1_5/api_query_user__stop_iteration.xml | 6 ++ libcloud/test/compute/test_vcloud.py | 34 ++++--- 5 files changed, 104 insertions(+), 62 deletions(-) create mode 100644 libcloud/test/compute/fixtures/vcloud_1_5/api_query_user__page2.xml create mode 100644 libcloud/test/compute/fixtures/vcloud_1_5/api_query_user__stop_iteration.xml diff --git a/libcloud/compute/drivers/vcloud.py b/libcloud/compute/drivers/vcloud.py index c837a13..99d1c11 100644 --- a/libcloud/compute/drivers/vcloud.py +++ b/libcloud/compute/drivers/vcloud.py @@ -147,21 +147,65 @@ class Subject(object): class QueryResult(object): """ - Query result object. Is iterable. + Query result object. Is iterable with lazy loading. """ - def __init__(self, id, total, page, page_size, name, records): - self.id = id - self.total = total - self.page = page + def __init__(self, driver, type, filter, sort_asc, sort_desc, page_size): + self.driver = driver self.page_size = page_size - self.name = name - self.records = records + + self.params = { + 'type': type, + 'pageSize': page_size + } + if sort_asc: + self.params['sortAsc'] = sort_asc + if sort_desc: + self.params['sortDesc'] = sort_desc + + self.url = '/api/query?' + urlencode(self.params) + if filter: + if not filter.startswith('('): + filter = '(' + filter + ')' + self.url += '&filter=' + filter.replace(' ', '+') + + self.id = self.url + self.page = 0 + self._fetch_page() def __len__(self): - return len(self.records) + return self.total + + def __iter__(self): + return self.iterate_records() - def __getitem__(self, item): - return self.records[item] + def iterate_records(self): + while self.records: + item = self.records.pop(0) + yield item + if not self.records: + try: + self._fetch_page() + except Exception: + e = sys.exc_info()[1] + if (isinstance(e.args[0], _ElementInterface) and + e.args[0].tag.endswith('Error') and + e.args[0].get('minorErrorCode') == 'BAD_REQUEST' and + re.match('Invalid parameter page=\\d+ must be less or equal to \\d+', e.args[0].get('message'))): + raise StopIteration + else: + raise + + def _fetch_page(self): + self.records = [] + self.page += 1 + res = self.driver.connection.request('{0}&page={1}'.format(self.url, self.page)) + for elem in res.object: + if not elem.tag.endswith('Link'): + result = elem.attrib + result['type'] = elem.tag.split('}')[1] + self.records.append(result) + self.total = int(res.object.get('total')) + self.name = res.object.get('name') def __repr__(self): return ('' @@ -1204,7 +1248,7 @@ class VCloud_1_5_NodeDriver(VCloudNodeDriver): if not res: raise LibcloudError('Specified subject "%s %s" not found ' % (subject.type, subject.name)) - href = res[0]['href'] + href = res.iterate_records().next()['href'] ET.SubElement(setting, 'Subject', {'href': href}) ET.SubElement(setting, 'AccessLevel').text = subject.access_level @@ -1269,8 +1313,8 @@ class VCloud_1_5_NodeDriver(VCloudNodeDriver): method='POST') self._wait_for_task_completion(res.object.get('href')) - def ex_query(self, type, filter=None, page=1, page_size=100, sort_asc=None, - sort_desc=None): + def ex_query(self, type, filter=None, sort_asc=None, + sort_desc=None, page_size=100): """ Queries vCloud for specified type. See http://www.vmware.com/pdf/vcd_15_api_guide.pdf for details. Result returned as QueryResult object. Elements of the @@ -1282,52 +1326,27 @@ class VCloud_1_5_NodeDriver(VCloudNodeDriver): @param filter: filter expression (see documentation for syntax) @type filter: C{str} - @param page: page number - @type page: C{int} - - @param page_size: page size - @type page_size: C{int} - @param sort_asc: sort in ascending order by specified field @type sort_asc: C{str} @param sort_desc: sort in descending order by specified field @type sort_desc: C{str} + @param page_size: page size for internal pagination (for optimizations) + @type page_size: C{int} + @rtype: C{QueryResult} """ # This is a workaround for filter parameter encoding # the urllib encodes (name==Developers%20Only) into # %28name%3D%3DDevelopers%20Only%29) which is not accepted by vCloud - params = { - 'type': type, - 'pageSize': page_size, - 'page': page, - } - if sort_asc: - params['sortAsc'] = sort_asc - if sort_desc: - params['sortDesc'] = sort_desc - - url = '/api/query?' + urlencode(params) - if filter: - if not filter.startswith('('): - filter = '(' + filter + ')' - url += '&filter=' + filter.replace(' ', '+') - records = [] - res = self.connection.request(url) - for elem in res.object: - if not elem.tag.endswith('Link'): - result = elem.attrib - result['type'] = elem.tag.split('}')[1] - records.append(result) - return QueryResult(id=res.object.get('href'), - total=int(res.object.get('total')), - page=int(res.object.get('page')), - page_size=int(res.object.get('pageSize')), - name=res.object.get('name'), - records=records) + return QueryResult(driver=self, + type=type, + filter=filter, + sort_asc=sort_asc, + sort_desc=sort_desc, + page_size=page_size) def create_node(self, **kwargs): """Creates and returns node. If the source image is: diff --git a/libcloud/test/compute/fixtures/vcloud_1_5/api_query_user.xml b/libcloud/test/compute/fixtures/vcloud_1_5/api_query_user.xml index 228b5c0..2ab3cf3 100644 --- a/libcloud/test/compute/fixtures/vcloud_1_5/api_query_user.xml +++ b/libcloud/test/compute/fixtures/vcloud_1_5/api_query_user.xml @@ -1,6 +1,8 @@ - + - + + + \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/vcloud_1_5/api_query_user__page2.xml b/libcloud/test/compute/fixtures/vcloud_1_5/api_query_user__page2.xml new file mode 100644 index 0000000..410e0b8 --- /dev/null +++ b/libcloud/test/compute/fixtures/vcloud_1_5/api_query_user__page2.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/vcloud_1_5/api_query_user__stop_iteration.xml b/libcloud/test/compute/fixtures/vcloud_1_5/api_query_user__stop_iteration.xml new file mode 100644 index 0000000..5bf2a78 --- /dev/null +++ b/libcloud/test/compute/fixtures/vcloud_1_5/api_query_user__stop_iteration.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/libcloud/test/compute/test_vcloud.py b/libcloud/test/compute/test_vcloud.py index 250e0e3..ff32a6a 100644 --- a/libcloud/test/compute/test_vcloud.py +++ b/libcloud/test/compute/test_vcloud.py @@ -257,18 +257,21 @@ class VCloud_1_5_Tests(unittest.TestCase, TestCaseMixin): self.driver.ex_power_off_node(node) def test_ex_query(self): - result = self.driver.ex_query('user', filter='name==jrambo', page=2, page_size=30, sort_desc='startDate') - self.assertEqual(len(result), 1) - self.assertEqual(result[0]['type'], 'UserRecord') - self.assertEqual(result[0]['name'], 'jrambo') - self.assertEqual(result[0]['isLdapUser'], 'true') - self.assertEqual(result.page_size, 30) - self.assertEqual(result.total, 133) - self.assertEqual(result.page, 2) - self.assertEqual(result.name, 'user') - self.assertEqual(len([x for x in result]), 1) - + result = self.driver.ex_query('user', filter='name==jrambo', page_size=3, sort_desc='startDate') + self.assertEqual(result.page_size, 3) + self.assertEqual(result.total, 5) + self.assertEqual(result.name, 'user') + self.assertEqual(len(result), 5) + items = [x for x in result] + self.assertEqual(len(items), 5) + self.assertEqual(items[0]['type'], 'UserRecord') + self.assertEqual(items[0]['name'], 'jrambo1') + self.assertEqual(items[0]['isLdapUser'], 'true') + self.assertEqual(items[1]['name'], 'jrambo2') + self.assertEqual(items[2]['name'], 'jrambo3') + self.assertEqual(items[3]['name'], 'jrambo4') + self.assertEqual(items[4]['name'], 'jrambo5') def test_ex_get_control_access(self): node = Node('https://vm-vcloud/api/vApp/vapp-8c57a5b6-e61b-48ca-8a78-3b70ee65ef6b', 'testNode', NodeState.RUNNING, [], [], self.driver) @@ -604,10 +607,15 @@ class VCloud_1_5_MockHttp(MockHttp, unittest.TestCase): def _api_query(self, method, url, body, headers): assert method == 'GET' if 'type=user' in url: - self.assertTrue('page=2' in url) self.assertTrue('filter=(name==jrambo)' in url) self.assertTrue('sortDesc=startDate') - body = self.fixtures.load('api_query_user.xml') + if 'page=1' in url: + body = self.fixtures.load('api_query_user.xml') + elif 'page=2' in url: + body = self.fixtures.load('api_query_user__page2.xml') + else: + body = self.fixtures.load('api_query_user__stop_iteration.xml') + return httplib.BAD_REQUEST, body, headers, httplib.responses[httplib.BAD_REQUEST] elif 'type=group' in url: body = self.fixtures.load('api_query_group.xml') else: -- 1.8.1.msysgit.1 From 4b56d9938c7fc96d3f93e39ebabac8afc0bb11ec Mon Sep 17 00:00:00 2001 From: Michal Galet Date: Mon, 24 Jun 2013 14:42:14 +0100 Subject: [PATCH 3/4] LIBCLOUD-344: Hide sensitive fields in QueryResult class. --- libcloud/compute/drivers/vcloud.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/libcloud/compute/drivers/vcloud.py b/libcloud/compute/drivers/vcloud.py index 99d1c11..9f61326 100644 --- a/libcloud/compute/drivers/vcloud.py +++ b/libcloud/compute/drivers/vcloud.py @@ -169,7 +169,7 @@ class QueryResult(object): self.url += '&filter=' + filter.replace(' ', '+') self.id = self.url - self.page = 0 + self._page = 0 self._fetch_page() def __len__(self): @@ -179,10 +179,10 @@ class QueryResult(object): return self.iterate_records() def iterate_records(self): - while self.records: - item = self.records.pop(0) + while self._records: + item = self._records.pop(0) yield item - if not self.records: + if not self._records: try: self._fetch_page() except Exception: @@ -196,20 +196,20 @@ class QueryResult(object): raise def _fetch_page(self): - self.records = [] - self.page += 1 - res = self.driver.connection.request('{0}&page={1}'.format(self.url, self.page)) + self._records = [] + self._page += 1 + res = self.driver.connection.request('{0}&page={1}'.format(self.url, self._page)) for elem in res.object: if not elem.tag.endswith('Link'): result = elem.attrib result['type'] = elem.tag.split('}')[1] - self.records.append(result) + self._records.append(result) self.total = int(res.object.get('total')) self.name = res.object.get('name') def __repr__(self): return ('' - % (self.id, self.total, self.page)) + % (self.id, self.total, self._page)) class InstantiateVAppXML(object): -- 1.8.1.msysgit.1 From bd047c77c2d3a4cf134cf1042499e3d7a1378f23 Mon Sep 17 00:00:00 2001 From: Michal Galet Date: Mon, 24 Jun 2013 22:37:47 +0100 Subject: [PATCH 4/4] LIBCLOUD-344: More code improvements based on pull code review comments. * Fixed compatibility with Python 2.5 string contatenation * Improved logic to stop generator iteration when reached end * More docstring --- libcloud/compute/drivers/vcloud.py | 37 ++++++++++++++++++---- .../vcloud_1_5/api_query_user__stop_iteration.xml | 6 ---- libcloud/test/compute/test_vcloud.py | 5 +-- 3 files changed, 31 insertions(+), 17 deletions(-) delete mode 100644 libcloud/test/compute/fixtures/vcloud_1_5/api_query_user__stop_iteration.xml diff --git a/libcloud/compute/drivers/vcloud.py b/libcloud/compute/drivers/vcloud.py index 9f61326..b25ef5b 100644 --- a/libcloud/compute/drivers/vcloud.py +++ b/libcloud/compute/drivers/vcloud.py @@ -148,6 +148,24 @@ class Subject(object): class QueryResult(object): """ Query result object. Is iterable with lazy loading. + @param driver: driver object + @type driver: L{VCloudDriver} + + @param type: type of record to query (ex. vApp, vm, user etc.) + @type type: C{str} + + @param filter: filter expression. + See http://www.vmware.com/pdf/vcd_15_api_guide.pdf + @type type: C{str} + + @param sort_asc: sort in ascending order by specified field + @type sort_asc: C{str} + + @param sort_desc: sort in descending order by specified field + @type sort_desc: C{str} + + @param page_size: page size for internal pagination + @type page_size: C{int} """ def __init__(self, driver, type, filter, sort_asc, sort_desc, page_size): self.driver = driver @@ -169,6 +187,7 @@ class QueryResult(object): self.url += '&filter=' + filter.replace(' ', '+') self.id = self.url + self.total = 0 self._page = 0 self._fetch_page() @@ -179,26 +198,34 @@ class QueryResult(object): return self.iterate_records() def iterate_records(self): + current_index = 0 while self._records: item = self._records.pop(0) yield item + + # Are we at the end? + current_index += 1 + if current_index >= self.total: + raise StopIteration + if not self._records: try: self._fetch_page() except Exception: e = sys.exc_info()[1] - if (isinstance(e.args[0], _ElementInterface) and + if (len(e.args) > 0 and + isinstance(e.args[0], _ElementInterface) and e.args[0].tag.endswith('Error') and e.args[0].get('minorErrorCode') == 'BAD_REQUEST' and re.match('Invalid parameter page=\\d+ must be less or equal to \\d+', e.args[0].get('message'))): raise StopIteration else: - raise + raise e def _fetch_page(self): self._records = [] self._page += 1 - res = self.driver.connection.request('{0}&page={1}'.format(self.url, self._page)) + res = self.driver.connection.request('%s&page=%s' % (self.url, self._page)) for elem in res.object: if not elem.tag.endswith('Link'): result = elem.attrib @@ -1337,10 +1364,6 @@ class VCloud_1_5_NodeDriver(VCloudNodeDriver): @rtype: C{QueryResult} """ - # This is a workaround for filter parameter encoding - # the urllib encodes (name==Developers%20Only) into - # %28name%3D%3DDevelopers%20Only%29) which is not accepted by vCloud - return QueryResult(driver=self, type=type, filter=filter, diff --git a/libcloud/test/compute/fixtures/vcloud_1_5/api_query_user__stop_iteration.xml b/libcloud/test/compute/fixtures/vcloud_1_5/api_query_user__stop_iteration.xml deleted file mode 100644 index 5bf2a78..0000000 --- a/libcloud/test/compute/fixtures/vcloud_1_5/api_query_user__stop_iteration.xml +++ /dev/null @@ -1,6 +0,0 @@ - - \ No newline at end of file diff --git a/libcloud/test/compute/test_vcloud.py b/libcloud/test/compute/test_vcloud.py index ff32a6a..0076ac7 100644 --- a/libcloud/test/compute/test_vcloud.py +++ b/libcloud/test/compute/test_vcloud.py @@ -263,7 +263,7 @@ class VCloud_1_5_Tests(unittest.TestCase, TestCaseMixin): self.assertEqual(result.total, 5) self.assertEqual(result.name, 'user') self.assertEqual(len(result), 5) - items = [x for x in result] + items = list(result) self.assertEqual(len(items), 5) self.assertEqual(items[0]['type'], 'UserRecord') self.assertEqual(items[0]['name'], 'jrambo1') @@ -613,9 +613,6 @@ class VCloud_1_5_MockHttp(MockHttp, unittest.TestCase): body = self.fixtures.load('api_query_user.xml') elif 'page=2' in url: body = self.fixtures.load('api_query_user__page2.xml') - else: - body = self.fixtures.load('api_query_user__stop_iteration.xml') - return httplib.BAD_REQUEST, body, headers, httplib.responses[httplib.BAD_REQUEST] elif 'type=group' in url: body = self.fixtures.load('api_query_group.xml') else: -- 1.8.1.msysgit.1