standard_interface_tests.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. """
  2. Standard tests for behaviour common across the whole of cloudbridge.
  3. This includes:
  4. 1. Checking that every resource has an id property
  5. 2. Checking for object equality and repr
  6. 3. Checking standard behaviour for list, iter, find, get, delete
  7. """
  8. import test.helpers as helpers
  9. import uuid
  10. from cloudbridge.cloud.interfaces.exceptions \
  11. import InvalidNameException
  12. from cloudbridge.cloud.interfaces.resources import ObjectLifeCycleMixin
  13. from cloudbridge.cloud.interfaces.resources import ResultList
  14. def check_repr(test, obj):
  15. test.assertTrue(
  16. obj.id in repr(obj),
  17. "repr(obj) for %s contain the object id so that the object"
  18. " can be reconstructed, but does not. eval(repr(obj)) == obj"
  19. % (type(obj).__name__,))
  20. def check_json(test, obj):
  21. val = obj.to_json()
  22. test.assertEqual(val.get('id'), obj.id)
  23. test.assertEqual(val.get('name'), obj.name)
  24. def check_obj_properties(test, obj):
  25. test.assertEqual(obj, obj, "Object should be equal to itself")
  26. test.assertFalse(obj != obj, "Object inequality should be false")
  27. check_obj_name(test, obj)
  28. def check_list(test, service, obj):
  29. list_objs = service.list()
  30. test.assertIsInstance(list_objs, ResultList)
  31. all_records = list_objs
  32. while list_objs.is_truncated:
  33. list_objs = service.list(marker=list_objs.marker)
  34. all_records += list_objs
  35. match_objs = [o for o in all_records if o.id == obj.id]
  36. test.assertTrue(
  37. len(match_objs) == 1,
  38. "List objects for %s does not return the expected object id %s. Got %s"
  39. % (type(obj).__name__, obj.id, match_objs))
  40. return match_objs
  41. def check_iter(test, service, obj):
  42. # check iteration
  43. iter_objs = list(service)
  44. iter_ids = [o.id for o in service]
  45. test.assertEqual(len(set(iter_ids)), len(iter_ids),
  46. "Iteration should not return duplicates")
  47. match_objs = [o for o in iter_objs if o.id == obj.id]
  48. test.assertTrue(
  49. len(match_objs) == 1,
  50. "Iter objects for %s does not return the expected object id %s. Got %s"
  51. % (type(obj).__name__, obj.id, match_objs))
  52. return match_objs
  53. def check_find(test, service, obj):
  54. # check find
  55. find_objs = service.find(name=obj.name)
  56. test.assertTrue(
  57. len(find_objs) == 1,
  58. "Find objects for %s does not return the expected object: %s. Got %s"
  59. % (type(obj).__name__, obj.name, find_objs))
  60. test.assertEqual(find_objs[0], obj)
  61. return find_objs
  62. def check_find_non_existent(test, service):
  63. # check find
  64. find_objs = service.find(name="random_imagined_obj_name")
  65. test.assertTrue(
  66. len(find_objs) == 0,
  67. "Find non-existent object for %s returned unexpected objects: %s"
  68. % (type(service).__name__, find_objs))
  69. def check_get(test, service, obj):
  70. get_obj = service.get(obj.id)
  71. print("Actual - " + str(obj.__dict__))
  72. print("Get - " + str(get_obj.__dict__))
  73. test.assertEqual(get_obj.name, obj.name)
  74. test.assertEqual(get_obj._provider, obj._provider)
  75. test.assertEqual(get_obj.id, obj.id)
  76. test.assertEqual(get_obj.state, obj.state)
  77. test.assertEqual(get_obj, obj)
  78. test.assertIsInstance(get_obj, type(obj))
  79. return get_obj
  80. def check_get_non_existent(test, service):
  81. # check get
  82. get_objs = service.get(str(uuid.uuid4()))
  83. test.assertIsNone(
  84. get_objs,
  85. "Get non-existent object for %s returned unexpected objects: %s"
  86. % (type(service).__name__, get_objs))
  87. def check_delete(test, service, obj, perform_delete=False):
  88. if perform_delete:
  89. obj.delete()
  90. objs = service.list()
  91. found_objs = [o for o in objs if o.id == obj.id]
  92. test.assertTrue(
  93. len(found_objs) == 0,
  94. "Object %s in service %s should have been deleted but still exists."
  95. % (found_objs, type(service).__name__))
  96. def check_obj_name(test, obj):
  97. """
  98. Cloudbridge identifiers must be 1-63 characters long, and comply with
  99. RFC1035. In addition, identifiers should contain only lowercase letters,
  100. numeric characters, underscores, and dashes. International
  101. characters are allowed.
  102. """
  103. # if name has a setter, make sure invalid values cannot be set
  104. name_property = getattr(type(obj), 'name', None)
  105. if isinstance(name_property, property) and name_property.fset:
  106. # setting letters, numbers and international characters should succeed
  107. # TODO: Unicode characters trip up Moto. Add following: \u0D85\u0200
  108. VALID_NAME = u"hello_world-123"
  109. original_name = obj.name
  110. obj.name = VALID_NAME
  111. # setting spaces should raise an exception
  112. with test.assertRaises(InvalidNameException):
  113. obj.name = "hello world"
  114. # setting upper case characters should raise an exception
  115. with test.assertRaises(InvalidNameException):
  116. obj.name = "helloWorld"
  117. # setting special characters should raise an exception
  118. with test.assertRaises(InvalidNameException):
  119. obj.name = "hello.world:how_goes_it"
  120. # setting a length > 63 should result in an exception
  121. with test.assertRaises(InvalidNameException,
  122. msg="Name of length > 64 should be disallowed"):
  123. obj.name = "a" * 64
  124. # refreshing should yield the last successfully set name
  125. obj.refresh()
  126. test.assertEqual(obj.name, VALID_NAME)
  127. obj.name = original_name
  128. def check_standard_behaviour(test, service, obj):
  129. """
  130. Checks standard behaviour in a given cloudbridge resource
  131. of a given service.
  132. """
  133. obj.wait_till_ready()
  134. check_repr(test, obj)
  135. check_json(test, obj)
  136. check_obj_properties(test, obj)
  137. objs_list = check_list(test, service, obj)
  138. objs_iter = check_iter(test, service, obj)
  139. objs_find = check_find(test, service, obj)
  140. check_find_non_existent(test, service)
  141. obj_get = check_get(test, service, obj)
  142. check_get_non_existent(test, service)
  143. test.assertTrue(
  144. obj == objs_list[0] == objs_iter[0] == objs_find[0] == obj_get,
  145. "Objects returned by list: {0}, iter: {1}, find: {2} and get: {3} "
  146. " are not as expected: {4}" .format(objs_list[0].id, objs_iter[0].id,
  147. objs_find[0].id, obj_get.id,
  148. obj.id))
  149. test.assertTrue(
  150. obj.id == objs_list[0].id == objs_iter[0].id ==
  151. objs_find[0].id == obj_get.id,
  152. "Object Ids returned by list: {0}, iter: {1}, find: {2} and get: {3} "
  153. " are not as expected: {4}" .format(objs_list[0].id, objs_iter[0].id,
  154. objs_find[0].id, obj_get.id,
  155. obj.id))
  156. test.assertTrue(
  157. obj.name == objs_list[0].name == objs_iter[0].name ==
  158. objs_find[0].name == obj_get.name,
  159. "Names returned by list: {0}, iter: {1}, find: {2} and get: {3} "
  160. " are not as expected: {4}" .format(objs_list[0].id, objs_iter[0].id,
  161. objs_find[0].id, obj_get.id,
  162. obj.id))
  163. def check_create(test, service, iface, name_prefix,
  164. create_func, cleanup_func):
  165. # check create with invalid name
  166. with test.assertRaises(InvalidNameException):
  167. # spaces should raise an exception
  168. create_func("hello world")
  169. # check create with invalid name
  170. with test.assertRaises(InvalidNameException):
  171. # uppercase characters should raise an exception
  172. create_func("helloWorld")
  173. # setting special characters should raise an exception
  174. with test.assertRaises(InvalidNameException):
  175. create_func("hello.world:how_goes_it")
  176. # setting a length > 63 should result in an exception
  177. with test.assertRaises(InvalidNameException,
  178. msg="Name of length > 64 should be disallowed"):
  179. create_func("a" * 64)
  180. def check_crud(test, service, iface, name_prefix,
  181. create_func, cleanup_func, extra_test_func=None,
  182. custom_check_delete=None, skip_name_check=False):
  183. """
  184. Checks crud behaviour of a given cloudbridge service. The create_func will
  185. be used as a factory function to create a service object and the
  186. cleanup_func will be used to destroy the object. Once an object is created
  187. using the create_func, all other standard behavioural tests can be run
  188. against that object.
  189. :type test: ``TestCase``
  190. :param test: The TestCase object to use
  191. :type service: ``CloudService``
  192. :param service: The CloudService object under test. For example,
  193. a VolumeService object.
  194. :type iface: ``type``
  195. :param iface: The type to test behaviour against. This type must be a
  196. subclass of ``CloudResource``.
  197. :type name_prefix: ``str``
  198. :param name_prefix: The name to prefix all created objects with. This
  199. function will generated a new name with the
  200. specified name_prefix for each test object created
  201. and pass that name into the create_func
  202. :type create_func: ``func``
  203. :param create_func: The create_func must accept the name of the object to
  204. create as a parameter and return the constructed
  205. object.
  206. :type cleanup_func: ``func``
  207. :param cleanup_func: The cleanup_func must accept the created object
  208. and perform all cleanup tasks required to delete the
  209. object.
  210. :type extra_test_func: ``func``
  211. :param extra_test_func: This function will be called to perform additional
  212. tests after object construction and initialization,
  213. but before object cleanup. It will receive the
  214. created object as a parameter.
  215. :type custom_check_delete: ``func``
  216. :param custom_check_delete: If provided, this function will be called
  217. instead of the standard check_delete function
  218. to make sure that the object has been deleted.
  219. :type skip_name_check: ``boolean``
  220. :param skip_name_check: If True, the invalid name checking will be
  221. skipped.
  222. """
  223. obj = None
  224. with helpers.cleanup_action(lambda: cleanup_func(obj)):
  225. if not skip_name_check:
  226. check_create(test, service, iface, name_prefix,
  227. create_func, cleanup_func)
  228. name = "{0}-{1}".format(name_prefix, helpers.get_uuid())
  229. obj = create_func(name)
  230. if issubclass(iface, ObjectLifeCycleMixin):
  231. obj.wait_till_ready()
  232. check_standard_behaviour(test, service, obj)
  233. if extra_test_func:
  234. extra_test_func(obj)
  235. if custom_check_delete:
  236. custom_check_delete(obj)
  237. else:
  238. check_delete(test, service, obj)