test_aws_pagination.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. """
  2. Unit tests for ``BotoEC2Service`` server-side pagination.
  3. ``_get_paginated_results`` hands boto3 a ``PaginationConfig`` built from two
  4. separate numbers: ``MaxItems``, how many results the caller asked for, and
  5. ``PageSize``, how many the service returns per request. They used to be the
  6. same value, which made a small result limit walk a large scan in tiny
  7. increments - a filtered ``describe_images`` measured 977.6s at a page size of
  8. 5 against 10.0s at 1000, for the same single result.
  9. Separating them moves where page boundaries fall, and the resume token
  10. handed back to callers is defined in terms of those boundaries. These tests
  11. pin the round trip: paging a collection with a small limit must still visit
  12. every object exactly once and terminate, whatever the transport page size.
  13. Exercised against moto so they run in CI without cloud credentials.
  14. """
  15. import unittest
  16. from moto import mock_aws
  17. from cloudbridge.factory import CloudProviderFactory
  18. from cloudbridge.factory import ProviderList
  19. from cloudbridge.providers.aws.helpers import DEFAULT_PAGE_SIZE
  20. # More objects than one page at the result limit below, so paging is forced.
  21. OBJECT_COUNT = 7
  22. RESULT_LIMIT = 2
  23. class AWSPaginationTestCase(unittest.TestCase):
  24. def setUp(self):
  25. self.mock = mock_aws()
  26. self.mock.start()
  27. self.provider = CloudProviderFactory().create_provider(
  28. ProviderList.AWS,
  29. {'aws_access_key': 'a', 'aws_secret_key': 'b',
  30. 'aws_region_name': 'us-east-1',
  31. 'default_result_limit': RESULT_LIMIT})
  32. def tearDown(self):
  33. self.mock.stop()
  34. def _create_volumes(self):
  35. created = []
  36. for i in range(OBJECT_COUNT):
  37. vol = self.provider.storage.volumes.create(
  38. 'cb-page-%d' % i, 1)
  39. created.append(vol.id)
  40. return created
  41. def test_paging_visits_every_object_exactly_once(self):
  42. expected = self._create_volumes()
  43. seen = []
  44. page = self.provider.storage.volumes.list()
  45. seen.extend(o.id for o in page)
  46. pages = 1
  47. while page.is_truncated:
  48. page = self.provider.storage.volumes.list(marker=page.marker)
  49. seen.extend(o.id for o in page)
  50. pages += 1
  51. self.assertLess(pages, OBJECT_COUNT + 5,
  52. "Paging failed to terminate; the resume token is "
  53. "probably not advancing.")
  54. self.assertEqual(sorted(seen), sorted(expected),
  55. "Paging must visit every object exactly once.")
  56. self.assertEqual(len(seen), len(set(seen)),
  57. "Paging returned duplicates across pages.")
  58. def test_a_page_holds_no_more_than_the_result_limit(self):
  59. # The caller's limit bounds what comes back, independently of how
  60. # much was fetched per request to produce it.
  61. self._create_volumes()
  62. page = self.provider.storage.volumes.list()
  63. self.assertLessEqual(len(page), RESULT_LIMIT)
  64. def test_transport_page_size_is_independent_of_the_result_limit(self):
  65. # The regression this guards: PageSize tracking the result limit is
  66. # what made a sparse scan pathological.
  67. # pylint:disable=protected-access
  68. svc = self.provider.storage.volumes.svc
  69. client = self.provider.ec2_conn.meta.client
  70. page_size = svc._page_size(client, 'describe_volumes', RESULT_LIMIT)
  71. self.assertEqual(page_size, DEFAULT_PAGE_SIZE)
  72. self.assertNotEqual(page_size, RESULT_LIMIT)
  73. def test_page_size_is_clamped_to_the_operations_ceiling(self):
  74. # DescribeRouteTables allows 100 where most EC2 describes allow more,
  75. # and exceeding a ceiling is a hard InvalidParameterValue.
  76. # pylint:disable=protected-access
  77. svc = self.provider.storage.volumes.svc
  78. client = self.provider.ec2_conn.meta.client
  79. self.assertEqual(
  80. svc._page_size(client, 'describe_route_tables', RESULT_LIMIT),
  81. 100)
  82. def test_page_size_survives_an_unknown_operation(self):
  83. # pylint:disable=protected-access
  84. svc = self.provider.storage.volumes.svc
  85. client = self.provider.ec2_conn.meta.client
  86. self.assertEqual(
  87. svc._page_size(client, 'not_an_operation', RESULT_LIMIT),
  88. DEFAULT_PAGE_SIZE)
  89. if __name__ == "__main__":
  90. unittest.main()