__init__.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. import functools
  2. import operator
  3. import os
  4. import sys
  5. import traceback
  6. import unittest
  7. import uuid
  8. from contextlib import contextmanager
  9. import six
  10. from cloudbridge.cloud.base.helpers import get_env
  11. from cloudbridge.cloud.factory import CloudProviderFactory
  12. from cloudbridge.cloud.interfaces import InstanceState
  13. from cloudbridge.cloud.interfaces import TestMockHelperMixin
  14. from cloudbridge.cloud.interfaces.resources import FloatingIpState
  15. from cloudbridge.cloud.interfaces.resources import NetworkState
  16. from cloudbridge.cloud.interfaces.resources import SubnetState
  17. def parse_bool(val):
  18. if val:
  19. return str(val).upper() in ['TRUE', 'YES']
  20. else:
  21. return False
  22. @contextmanager
  23. def cleanup_action(cleanup_func):
  24. """
  25. Context manager to carry out a given
  26. cleanup action after carrying out a set
  27. of tasks, or when an exception occurs.
  28. If any errors occur during the cleanup
  29. action, those are ignored, and the original
  30. traceback is preserved.
  31. :params func: This function is called if
  32. an exception occurs or at the end of the
  33. context block. If any exceptions raised
  34. by func are ignored.
  35. Usage:
  36. with cleanup_action(lambda e: print("Oops!")):
  37. do_something()
  38. """
  39. try:
  40. yield
  41. except Exception:
  42. ex_class, ex_val, ex_traceback = sys.exc_info()
  43. try:
  44. cleanup_func()
  45. except Exception as e:
  46. print("Error during exception cleanup: {0}".format(e))
  47. traceback.print_exc()
  48. six.reraise(ex_class, ex_val, ex_traceback)
  49. try:
  50. cleanup_func()
  51. except Exception as e:
  52. print("Error during cleanup: {0}".format(e))
  53. traceback.print_exc()
  54. def skipIfNoService(services):
  55. """
  56. A decorator for skipping tests if the provider
  57. does not implement a given service.
  58. """
  59. def wrap(func):
  60. """
  61. The actual wrapper
  62. """
  63. @functools.wraps(func)
  64. def wrapper(self, *args, **kwargs):
  65. provider = getattr(self, 'provider')
  66. if provider:
  67. for service in services:
  68. if not provider.has_service(service):
  69. self.skipTest("Skipping test because '%s' service is"
  70. " not implemented" % (service,))
  71. func(self, *args, **kwargs)
  72. return wrapper
  73. return wrap
  74. def skipIfPython(op, major, minor):
  75. """
  76. A decorator for skipping tests if the python
  77. version doesn't match
  78. """
  79. def stringToOperator(op):
  80. op_map = {
  81. "=": operator.eq,
  82. "==": operator.eq,
  83. "<": operator.lt,
  84. "<=": operator.le,
  85. ">": operator.gt,
  86. ">=": operator.ge,
  87. }
  88. return op_map.get(op)
  89. def wrap(func):
  90. """
  91. The actual wrapper
  92. """
  93. @functools.wraps(func)
  94. def wrapper(self, *args, **kwargs):
  95. op_func = stringToOperator(op)
  96. if op_func(sys.version_info, (major, minor)):
  97. self.skipTest(
  98. "Skipping test because python version {0} is {1} expected"
  99. " version {2}".format(sys.version_info[:2],
  100. op, (major, minor)))
  101. func(self, *args, **kwargs)
  102. return wrapper
  103. return wrap
  104. TEST_DATA_CONFIG = {
  105. "AWSCloudProvider": {
  106. # Match the ami value with entry in custom_amis.json for use with moto
  107. "image": get_env('CB_IMAGE_AWS', 'ami-aa2ea6d0'),
  108. "vm_type": get_env('CB_VM_TYPE_AWS', 't2.nano'),
  109. "placement": get_env('CB_PLACEMENT_AWS', 'us-east-1a'),
  110. },
  111. 'OpenStackCloudProvider': {
  112. 'image': os.environ.get('CB_IMAGE_OS',
  113. 'c66bdfa1-62b1-43be-8964-e9ce208ac6a5'),
  114. "vm_type": os.environ.get('CB_VM_TYPE_OS', 'm1.tiny'),
  115. "placement": os.environ.get('CB_PLACEMENT_OS', 'nova'),
  116. },
  117. 'GCECloudProvider': {
  118. 'image': ('https://www.googleapis.com/compute/v1/'
  119. 'projects/ubuntu-os-cloud/global/images/'
  120. 'ubuntu-1710-artful-v20180126'),
  121. 'vm_type': 'f1-micro',
  122. 'placement': os.environ.get('GCE_DEFAULT_ZONE', 'us-central1-a'),
  123. },
  124. "AzureCloudProvider": {
  125. "placement":
  126. get_env('CB_PLACEMENT_AZURE', 'eastus'),
  127. "image":
  128. get_env('CB_IMAGE_AZURE',
  129. 'Canonical:UbuntuServer:16.04.0-LTS:latest'),
  130. "vm_type":
  131. get_env('CB_VM_TYPE_AZURE', 'Basic_A2'),
  132. }
  133. }
  134. def get_provider_test_data(provider, key):
  135. if "AWSCloudProvider" in provider.name:
  136. return TEST_DATA_CONFIG.get("AWSCloudProvider").get(key)
  137. elif "OpenStackCloudProvider" in provider.name:
  138. return TEST_DATA_CONFIG.get("OpenStackCloudProvider").get(key)
  139. elif "GCECloudProvider" in provider.name:
  140. return TEST_DATA_CONFIG.get("GCECloudProvider").get(key)
  141. elif "AzureCloudProvider" in provider.name:
  142. return TEST_DATA_CONFIG.get("AzureCloudProvider").get(key)
  143. return None
  144. def get_or_create_default_subnet(provider):
  145. """
  146. Return the default subnet to be used for tests
  147. """
  148. return provider.networking.subnets.get_or_create_default(
  149. zone=get_provider_test_data(provider, 'placement'))
  150. def cleanup_subnet(subnet):
  151. if subnet:
  152. subnet.delete()
  153. subnet.wait_for([SubnetState.UNKNOWN],
  154. terminal_states=[SubnetState.ERROR])
  155. def cleanup_network(network):
  156. """
  157. Delete the supplied network, first deleting any contained subnets.
  158. """
  159. if network:
  160. try:
  161. for sn in network.subnets:
  162. with cleanup_action(lambda: cleanup_subnet(sn)):
  163. pass
  164. finally:
  165. network.delete()
  166. network.wait_for([NetworkState.UNKNOWN],
  167. terminal_states=[NetworkState.ERROR])
  168. def cleanup_fip(fip):
  169. if fip:
  170. fip.delete()
  171. fip.wait_for([FloatingIpState.UNKNOWN],
  172. terminal_states=[FloatingIpState.ERROR])
  173. def get_test_gateway(provider):
  174. """
  175. Get an internet gateway for testing.
  176. This includes creating a network for the gateway, which is also returned.
  177. """
  178. sn = get_or_create_default_subnet(provider)
  179. net = sn.network
  180. return net.gateways.get_or_create_inet_gateway()
  181. def cleanup_gateway(gateway):
  182. """
  183. Delete the supplied network and gateway.
  184. """
  185. with cleanup_action(lambda: gateway.delete()):
  186. pass
  187. def create_test_instance(
  188. provider, instance_label, subnet, launch_config=None,
  189. key_pair=None, vm_firewalls=None, user_data=None):
  190. instance = provider.compute.instances.create(
  191. instance_label, get_provider_test_data(provider, 'image'),
  192. get_provider_test_data(provider, 'vm_type'),
  193. subnet=subnet,
  194. zone=get_provider_test_data(provider, 'placement'),
  195. key_pair=key_pair,
  196. vm_firewalls=vm_firewalls,
  197. launch_config=launch_config,
  198. user_data=user_data)
  199. return instance
  200. def get_test_instance(provider, label, key_pair=None, vm_firewalls=None,
  201. subnet=None, user_data=None):
  202. launch_config = None
  203. instance = create_test_instance(
  204. provider,
  205. label,
  206. subnet=subnet,
  207. key_pair=key_pair,
  208. vm_firewalls=vm_firewalls,
  209. launch_config=launch_config,
  210. user_data=user_data)
  211. instance.wait_till_ready()
  212. return instance
  213. def get_test_fixtures_folder():
  214. return os.path.join(os.path.dirname(__file__), '../fixtures/')
  215. def delete_instance(instance):
  216. if instance:
  217. instance.delete()
  218. instance.wait_for([InstanceState.DELETED, InstanceState.UNKNOWN],
  219. terminal_states=[InstanceState.ERROR])
  220. def cleanup_test_resources(instance=None, vm_firewall=None,
  221. key_pair=None, network=None):
  222. """Clean up any combination of supplied resources."""
  223. with cleanup_action(lambda: cleanup_network(network)
  224. if network else None):
  225. with cleanup_action(lambda: key_pair.delete() if key_pair else None):
  226. with cleanup_action(lambda: vm_firewall.delete()
  227. if vm_firewall else None):
  228. delete_instance(instance)
  229. def get_uuid():
  230. return str(uuid.uuid4())[:6]
  231. class ProviderTestBase(unittest.TestCase):
  232. _provider = None
  233. def setUp(self):
  234. if isinstance(self.provider, TestMockHelperMixin):
  235. self.provider.setUpMock()
  236. def tearDown(self):
  237. if isinstance(self.provider, TestMockHelperMixin):
  238. self.provider.tearDownMock()
  239. self._provider = None
  240. def get_provider_wait_interval(self, provider_class):
  241. if issubclass(provider_class, TestMockHelperMixin):
  242. return 0
  243. else:
  244. return 1
  245. def create_provider_instance(self):
  246. provider_name = get_env("CB_TEST_PROVIDER", "aws")
  247. use_mock_drivers = parse_bool(
  248. os.environ.get("CB_USE_MOCK_PROVIDERS", "True"))
  249. factory = CloudProviderFactory()
  250. provider_class = factory.get_provider_class(provider_name,
  251. get_mock=use_mock_drivers)
  252. config = {'default_wait_interval':
  253. self.get_provider_wait_interval(provider_class),
  254. 'default_result_limit': 5}
  255. return provider_class(config)
  256. @property
  257. def provider(self):
  258. if not self._provider:
  259. self._provider = self.create_provider_instance()
  260. return self._provider