Add new "_paginated_request_next" method and update list_images() method
to utilize it.
This method includes infinite loop protection and makes sure driver
doesn't hang / end up in an infinite loop if the API returns an
invalid response or similar.
@@ -223,6 +223,52 @@ class OpenStackNodeDriver(NodeDriver, OpenStackDriverMixin):
)
return {obj: objects}
+ def _paginated_request_next(self, path, request_method, response_key):
+ """
+ Perform multiple calls and retrieve all the elements for a paginated
+ response.
+
+ This method utilizes "next" attribute in the response object. It also
+ includes infinite loop protection (if the "next" value matches the
+ current path, it will abort).
+
+ :param request_method: Method to call which will send the request and
+ return a response. This method will get passed
+ in "path" as a first argument.
+
+ :param response_key: Key in the response object dictionary which
+ contains actual objects we are interested in.
+ """
+ iteration_count = 0
+
+ result = []
+ while path:
+ response = request_method(path)
+ items = response.object.get(response_key, [])
+ result.extend(items)
+
+ # Retrieve next path
+ next_path = response.object.get('next', None)
+
+ if next_path == path or iteration_count > PAGINATION_LIMIT:
+ if iteration_count > PAGINATION_LIMIT:
+ raise OpenStackException(
+ 'Pagination limit reached for %s, the limit is %d. '
+ 'This might indicate that your API is returning a '
+ 'looping next target for pagination!' % (
+ path, PAGINATION_LIMIT
+ ), None
+ )
+
+ # Likely an infinite loop since the next path matches the
+ # current one
+ break
+
+ path = next_path
+ iteration_count += 1
+
+ return result
+
def destroy_node(self, node):
uri = '/servers/%s' % (node.id)
resp = self.connection.request(uri, method='DELETE')
@@ -2862,14 +2908,16 @@ class OpenStack_2_NodeDriver(OpenStack_1_1_NodeDriver):
raise NotImplementedError(
"ex_only_active in list_images is not implemented "
"in the OpenStack_2_NodeDriver")
+
+ result = self._paginated_request_next(
+ path='/v2/images',
+ request_method=self.image_connection.request,
+ response_key='images')
+
images = []
- path = '/v2/images'
- while path:
- response = self.image_connection.request(path)
- for image in response.object['images']:
- images.append(self._to_image(image))
- # get next bunch of images (pagination)
- path = response.object.get('next', None)
+ for item in result:
+ images.append(self._to_image(item))
+
return images
def ex_update_image(self, image_id, data):
