Manage a pool of nodes for a distributed test infrastructure
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

provider.py 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. # Copyright (C) 2011-2013 OpenStack Foundation
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
  12. # implied.
  13. #
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. import logging
  17. import threading
  18. import time
  19. import uuid
  20. import openstack.exceptions
  21. from nodepool import exceptions
  22. from nodepool.driver.openstack.provider import OpenStackProvider
  23. from nodepool.driver.fake.handler import FakeNodeRequestHandler
  24. from openstack.cloud.exc import OpenStackCloudCreateException
  25. class Dummy(object):
  26. IMAGE = 'Image'
  27. INSTANCE = 'Instance'
  28. FLAVOR = 'Flavor'
  29. LOCATION = 'Server.Location'
  30. PORT = 'Port'
  31. def __init__(self, kind, **kw):
  32. self.__kind = kind
  33. self.__kw = kw
  34. for k, v in kw.items():
  35. setattr(self, k, v)
  36. try:
  37. if self.should_fail:
  38. raise openstack.exceptions.OpenStackCloudException(
  39. 'This image has SHOULD_FAIL set to True.')
  40. if self.over_quota:
  41. raise openstack.exceptions.HttpException(
  42. message='Quota exceeded for something', http_status=403)
  43. except AttributeError:
  44. pass
  45. def __repr__(self):
  46. args = []
  47. for k in self.__kw.keys():
  48. args.append('%s=%s' % (k, getattr(self, k)))
  49. args = ' '.join(args)
  50. return '<%s %s %s>' % (self.__kind, id(self), args)
  51. def __getitem__(self, key, default=None):
  52. return getattr(self, key, default)
  53. def __setitem__(self, key, value):
  54. setattr(self, key, value)
  55. def get(self, key, default=None):
  56. return getattr(self, key, default)
  57. def set(self, key, value):
  58. setattr(self, key, value)
  59. class FakeOpenStackCloud(object):
  60. log = logging.getLogger("nodepool.FakeOpenStackCloud")
  61. @staticmethod
  62. def _get_quota():
  63. return 100, 20, 1000000
  64. def __init__(self, images=None, networks=None):
  65. self.pause_creates = False
  66. self._image_list = images
  67. if self._image_list is None:
  68. self._image_list = [
  69. Dummy(
  70. Dummy.IMAGE,
  71. id='fake-image-id',
  72. status='READY',
  73. name='Fake Precise',
  74. metadata={})
  75. ]
  76. if networks is None:
  77. networks = [dict(id='fake-public-network-uuid',
  78. name='fake-public-network-name'),
  79. dict(id='fake-private-network-uuid',
  80. name='fake-private-network-name'),
  81. dict(id='fake-ipv6-network-uuid',
  82. name='fake-ipv6-network-name')]
  83. self.networks = networks
  84. self._flavor_list = [
  85. Dummy(Dummy.FLAVOR, id='f1', ram=8192, name='Fake Flavor',
  86. vcpus=4),
  87. Dummy(Dummy.FLAVOR, id='f2', ram=8192, name='Unreal Flavor',
  88. vcpus=4),
  89. ]
  90. self._azs = ['az1', 'az2']
  91. self._server_list = []
  92. self.max_cores, self.max_instances, self.max_ram = FakeOpenStackCloud.\
  93. _get_quota()
  94. self._down_ports = [
  95. Dummy(Dummy.PORT, id='1a', status='DOWN',
  96. device_owner="compute:nova"),
  97. Dummy(Dummy.PORT, id='2b', status='DOWN',
  98. device_owner=None),
  99. ]
  100. def _get(self, name_or_id, instance_list):
  101. self.log.debug("Get %s in %s" % (name_or_id, repr(instance_list)))
  102. for instance in instance_list:
  103. if isinstance(name_or_id, dict):
  104. if instance.id == name_or_id['id']:
  105. return instance
  106. elif instance.name == name_or_id or instance.id == name_or_id:
  107. return instance
  108. return None
  109. def get_network(self, name_or_id, filters=None):
  110. for net in self.networks:
  111. if net['id'] == name_or_id or net['name'] == name_or_id:
  112. return net
  113. return self.networks[0]
  114. def _create(self, instance_list, instance_type=Dummy.INSTANCE,
  115. done_status='ACTIVE', max_quota=-1, **kw):
  116. should_fail = kw.get('SHOULD_FAIL', '').lower() == 'true'
  117. nics = kw.get('nics', [])
  118. security_groups = kw.get('security_groups', [])
  119. addresses = None
  120. # if keyword 'ipv6-uuid' is found in provider config,
  121. # ipv6 address will be available in public addr dict.
  122. for nic in nics:
  123. if nic['net-id'] != 'fake-ipv6-network-uuid':
  124. continue
  125. addresses = dict(
  126. public=[dict(version=4, addr='fake'),
  127. dict(version=6, addr='fake_v6')],
  128. private=[dict(version=4, addr='fake')]
  129. )
  130. public_v6 = 'fake_v6'
  131. public_v4 = 'fake'
  132. private_v4 = 'fake'
  133. host_id = 'fake_host_id'
  134. interface_ip = 'fake_v6'
  135. break
  136. if not addresses:
  137. addresses = dict(
  138. public=[dict(version=4, addr='fake')],
  139. private=[dict(version=4, addr='fake')]
  140. )
  141. public_v6 = ''
  142. public_v4 = 'fake'
  143. private_v4 = 'fake'
  144. host_id = 'fake'
  145. interface_ip = 'fake'
  146. over_quota = False
  147. if (instance_type == Dummy.INSTANCE and
  148. self.max_instances > -1 and
  149. len(instance_list) >= self.max_instances):
  150. over_quota = True
  151. az = kw.get('availability_zone')
  152. if az and az not in self._azs:
  153. raise openstack.exceptions.BadRequestException(
  154. message='The requested availability zone is not available',
  155. http_status=400)
  156. s = Dummy(instance_type,
  157. id=uuid.uuid4().hex,
  158. name=kw['name'],
  159. status='BUILD',
  160. adminPass='fake',
  161. addresses=addresses,
  162. public_v4=public_v4,
  163. public_v6=public_v6,
  164. private_v4=private_v4,
  165. host_id=host_id,
  166. interface_ip=interface_ip,
  167. security_groups=security_groups,
  168. location=Dummy(Dummy.LOCATION, zone=kw.get('az')),
  169. metadata=kw.get('meta', {}),
  170. manager=self,
  171. key_name=kw.get('key_name', None),
  172. should_fail=should_fail,
  173. over_quota=over_quota,
  174. event=threading.Event())
  175. instance_list.append(s)
  176. t = threading.Thread(target=self._finish,
  177. name='FakeProvider create',
  178. args=(s, 0.1, done_status))
  179. t.start()
  180. return s
  181. def _delete(self, name_or_id, instance_list):
  182. self.log.debug("Delete from %s" % (repr(instance_list),))
  183. instance = None
  184. for maybe in instance_list:
  185. if maybe.name == name_or_id or maybe.id == name_or_id:
  186. instance = maybe
  187. if instance:
  188. instance_list.remove(instance)
  189. self.log.debug("Deleted from %s" % (repr(instance_list),))
  190. def _finish(self, obj, delay, status):
  191. self.log.debug("Pause creates %s", self.pause_creates)
  192. if self.pause_creates:
  193. self.log.debug("Pausing")
  194. obj.event.wait()
  195. self.log.debug("Continuing")
  196. else:
  197. time.sleep(delay)
  198. obj.status = status
  199. def create_image(self, **kwargs):
  200. return self._create(
  201. self._image_list, instance_type=Dummy.IMAGE,
  202. done_status='READY', **kwargs)
  203. def get_image(self, name_or_id):
  204. return self._get(name_or_id, self._image_list)
  205. def list_images(self):
  206. return self._image_list
  207. def delete_image(self, name_or_id):
  208. if not name_or_id:
  209. raise Exception('name_or_id is Empty')
  210. self._delete(name_or_id, self._image_list)
  211. def create_image_snapshot(self, name, server, **metadata):
  212. # XXX : validate metadata?
  213. return self._create(
  214. self._image_list, instance_type=Dummy.IMAGE,
  215. name=name, **metadata)
  216. def list_flavors(self, get_extra=False):
  217. return self._flavor_list
  218. def get_openstack_vars(self, server):
  219. server.public_v4 = 'fake'
  220. server.public_v6 = 'fake'
  221. server.private_v4 = 'fake'
  222. server.host_id = 'fake'
  223. server.interface_ip = 'fake'
  224. return server
  225. def create_server(self, **kw):
  226. return self._create(self._server_list, **kw)
  227. def get_server(self, name_or_id):
  228. result = self._get(name_or_id, self._server_list)
  229. return result
  230. def _clean_floating_ip(self, server):
  231. server.public_v4 = ''
  232. server.public_v6 = ''
  233. server.interface_ip = server.private_v4
  234. return server
  235. def wait_for_server(self, server, **kwargs):
  236. while server.status == 'BUILD':
  237. time.sleep(0.1)
  238. auto_ip = kwargs.get('auto_ip')
  239. if not auto_ip:
  240. server = self._clean_floating_ip(server)
  241. return server
  242. def list_servers(self):
  243. return self._server_list
  244. def delete_server(self, name_or_id, delete_ips=True):
  245. self._delete(name_or_id, self._server_list)
  246. def list_availability_zone_names(self):
  247. return self._azs.copy()
  248. def get_compute_limits(self):
  249. return Dummy(
  250. 'limits',
  251. max_total_cores=self.max_cores,
  252. max_total_instances=self.max_instances,
  253. max_total_ram_size=self.max_ram,
  254. total_cores_used=4 * len(self._server_list),
  255. total_instances_used=len(self._server_list),
  256. total_ram_used=8192 * len(self._server_list)
  257. )
  258. def list_ports(self, filters=None):
  259. if filters and filters.get('status') == 'DOWN':
  260. return self._down_ports
  261. return []
  262. def delete_port(self, port_id):
  263. tmp_ports = []
  264. for port in self._down_ports:
  265. if port.id != port_id:
  266. tmp_ports.append(port)
  267. else:
  268. self.log.debug("Deleted port ID: %s", port_id)
  269. self._down_ports = tmp_ports
  270. class FakeUploadFailCloud(FakeOpenStackCloud):
  271. log = logging.getLogger("nodepool.FakeUploadFailCloud")
  272. def __init__(self, times_to_fail=None):
  273. super(FakeUploadFailCloud, self).__init__()
  274. self.times_to_fail = times_to_fail
  275. self.times_failed = 0
  276. def create_image(self, **kwargs):
  277. if self.times_to_fail is None:
  278. raise exceptions.BuilderError("Test fail image upload.")
  279. self.times_failed += 1
  280. if self.times_failed <= self.times_to_fail:
  281. raise exceptions.BuilderError("Test fail image upload.")
  282. else:
  283. return super(FakeUploadFailCloud, self).create_image(**kwargs)
  284. class FakeLaunchAndDeleteFailCloud(FakeOpenStackCloud):
  285. log = logging.getLogger("nodepool.FakeLaunchAndDeleteFailCloud")
  286. def __init__(self, times_to_fail=None):
  287. super(FakeLaunchAndDeleteFailCloud, self).__init__()
  288. self.times_to_fail_delete = times_to_fail
  289. self.times_to_fail_launch = times_to_fail
  290. self.times_failed_delete = 0
  291. self.times_failed_launch = 0
  292. self.launch_success = False
  293. self.delete_success = False
  294. def wait_for_server(self, **kwargs):
  295. if self.times_to_fail_launch is None:
  296. raise Exception("Test fail server launch.")
  297. if self.times_failed_launch < self.times_to_fail_launch:
  298. self.times_failed_launch += 1
  299. raise exceptions.ServerDeleteException("Test fail server launch.")
  300. else:
  301. self.launch_success = True
  302. return super(FakeLaunchAndDeleteFailCloud,
  303. self).wait_for_server(**kwargs)
  304. def delete_server(self, *args, **kwargs):
  305. if self.times_to_fail_delete is None:
  306. raise exceptions.ServerDeleteException("Test fail server delete.")
  307. if self.times_failed_delete < self.times_to_fail_delete:
  308. self.times_failed_delete += 1
  309. raise exceptions.ServerDeleteException("Test fail server delete.")
  310. else:
  311. self.delete_success = True
  312. return super(FakeLaunchAndDeleteFailCloud,
  313. self).delete_server(*args, **kwargs)
  314. class FakeProvider(OpenStackProvider):
  315. fake_cloud = FakeOpenStackCloud
  316. def __init__(self, provider, use_taskmanager):
  317. self.createServer_fails = 0
  318. self.createServer_fails_with_external_id = 0
  319. self.__client = FakeProvider.fake_cloud()
  320. super(FakeProvider, self).__init__(provider, use_taskmanager)
  321. def _getClient(self):
  322. return self.__client
  323. def createServer(self, *args, **kwargs):
  324. while self.createServer_fails:
  325. self.createServer_fails -= 1
  326. raise Exception("Expected createServer exception")
  327. while self.createServer_fails_with_external_id:
  328. self.createServer_fails_with_external_id -= 1
  329. raise OpenStackCloudCreateException('server', 'fakeid')
  330. return super(FakeProvider, self).createServer(*args, **kwargs)
  331. def getRequestHandler(self, poolworker, request):
  332. return FakeNodeRequestHandler(poolworker, request)