common.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. # Copyright 2016 Cloudbase Solutions Srl
  2. # All Rights Reserved.
  3. import collections
  4. import math
  5. import time
  6. import uuid
  7. from oslo_log import log as logging
  8. from oslo_utils import units
  9. from coriolis import exception
  10. from coriolis import utils
  11. MIGRATION_TMP_FORMAT = "migration_tmp_%s"
  12. NOVA_API_VERSION = 2
  13. GLANCE_API_VERSION = 1
  14. NEUTRON_API_VERSION = '2.0'
  15. CINDER_API_VERSION = 2
  16. LOG = logging.getLogger(__name__)
  17. GlanceImage = collections.namedtuple(
  18. "GlanceImage", "id format size path os_type")
  19. def get_unique_name():
  20. return MIGRATION_TMP_FORMAT % str(uuid.uuid4())
  21. def create_image(glance, name, disk_path, disk_format, container_format,
  22. hypervisor_type):
  23. properties = {}
  24. if hypervisor_type:
  25. properties["hypervisor_type"] = hypervisor_type
  26. if glance.version == 1:
  27. return _create_image_v1(glance, name, disk_path, disk_format,
  28. container_format, properties)
  29. elif glance.version == 2:
  30. return _create_image_v2(glance, name, disk_path, disk_format,
  31. container_format, properties)
  32. else:
  33. raise NotImplementedError("Unsupported Glance version")
  34. @utils.retry_on_error()
  35. def _create_image_v2(glance, name, disk_path, disk_format, container_format,
  36. properties):
  37. image = glance.images.create(
  38. name=name,
  39. disk_format=disk_format,
  40. container_format=container_format,
  41. **properties)
  42. try:
  43. with open(disk_path, 'rb') as f:
  44. glance.images.upload(image.id, f)
  45. return image
  46. except:
  47. glance.images.delete(image.id)
  48. raise
  49. @utils.retry_on_error()
  50. def _create_image_v1(glance, name, disk_path, disk_format, container_format,
  51. properties):
  52. with open(disk_path, 'rb') as f:
  53. return glance.images.create(
  54. name=name,
  55. disk_format=disk_format,
  56. container_format=container_format,
  57. properties=properties,
  58. data=f)
  59. @utils.retry_on_error()
  60. def wait_for_image(nova, image_id, expected_status='ACTIVE'):
  61. image = nova.images.get(image_id)
  62. while image.status not in [expected_status, 'ERROR']:
  63. LOG.debug('Image "%(id)s" in status: "%(status)s". '
  64. 'Waiting for status: "%(expected_status)s".',
  65. {'id': image_id, 'status': image.status,
  66. 'expected_status': expected_status})
  67. time.sleep(2)
  68. image = nova.images.get(image.id)
  69. if image.status != expected_status:
  70. raise exception.CoriolisException(
  71. "Image is in status: %s" % image.status)
  72. @utils.retry_on_error()
  73. def wait_for_instance(nova, instance_id, expected_status='ACTIVE'):
  74. instance = nova.servers.get(instance_id)
  75. while instance.status not in [expected_status, 'ERROR']:
  76. LOG.debug('Instance %(id)s status: %(status)s. '
  77. 'Waiting for status: "%(expected_status)s".',
  78. {'id': instance_id, 'status': instance.status,
  79. 'expected_status': expected_status})
  80. time.sleep(2)
  81. instance = nova.servers.get(instance.id)
  82. if instance.status != expected_status:
  83. raise exception.CoriolisException(
  84. "VM is in status: %s" % instance.status)
  85. @utils.retry_on_error(terminal_exceptions=[exception.CoriolisException])
  86. def wait_for_instance_deletion(
  87. nova, instance_id, max_retries=150, retry_period=2):
  88. instances = nova.servers.findall(id=instance_id)
  89. i = 0
  90. while i < max_retries and instances:
  91. i = i + 1
  92. instance = utils.get_single_result(instances)
  93. if instance.status == 'error':
  94. raise exception.CoriolisException(
  95. "Instance \"%s\" has reached invalid state \"%s\" while "
  96. "deleting." % (instance_id, instance.status))
  97. LOG.debug('Instance %(id)s status: %(status)s. '
  98. 'Waiting %(period)s seconds for its deletion.',
  99. {'id': instance_id, 'status': instance.status,
  100. 'period': retry_period})
  101. time.sleep(retry_period)
  102. instances = nova.servers.findall(id=instance_id)
  103. if instances:
  104. instance = utils.get_single_result(instances)
  105. raise exception.CoriolisException(
  106. "Max attempts of %(attempts)s reached while waiting for VM "
  107. "\"%(instance_id)s\" deletion. Last known status: \"%(status)s\"" %
  108. {'attempts': max_retries, 'status': instance.status,
  109. 'instance_id': instance_id})
  110. @utils.retry_on_error()
  111. def find_volume(cinder, volume_id):
  112. volumes = cinder.volumes.findall(id=volume_id)
  113. if volumes:
  114. return utils.get_single_result(volumes)
  115. @utils.retry_on_error()
  116. def extend_volume(cinder, volume_id, new_size):
  117. volume_size_gb = math.ceil(new_size / units.Gi)
  118. cinder.volumes.extend(volume_id, volume_size_gb)
  119. @utils.retry_on_error()
  120. def get_volume_from_snapshot(cinder, snapshot_id):
  121. snapshot = cinder.volume_snapshots.get(snapshot_id)
  122. return cinder.volumes.get(snapshot.volume_id)
  123. @utils.retry_on_error()
  124. def create_volume(cinder, size, name, image_ref=None,
  125. snapshot_id=None, volume_type=None):
  126. if snapshot_id:
  127. volume_size_gb = None
  128. else:
  129. volume_size_gb = math.ceil(size / units.Gi)
  130. return cinder.volumes.create(
  131. size=volume_size_gb,
  132. name=name,
  133. volume_type=volume_type,
  134. imageRef=image_ref,
  135. snapshot_id=snapshot_id)
  136. @utils.retry_on_error(terminal_exceptions=[exception.NotFound])
  137. def get_flavor(nova, flavor_name):
  138. flavors = nova.flavors.findall(name=flavor_name)
  139. if not flavors:
  140. raise exception.FlavorNotFound(flavor_name=flavor_name)
  141. return flavors[0]
  142. @utils.retry_on_error(terminal_exceptions=[exception.NotFound])
  143. def get_image(glance, image_name):
  144. images = glance.images.findall(name=image_name)
  145. if not images:
  146. raise exception.ImageNotFound(image_name=image_name)
  147. return images[0]
  148. @utils.retry_on_error(terminal_exceptions=[exception.NotFound])
  149. def check_floating_ip_pool(nova, pool_name):
  150. if not nova.floating_ip_pools.findall(name=pool_name):
  151. raise exception.FloatingIPPoolNotFound(pool_name=pool_name)
  152. @utils.retry_on_error(terminal_exceptions=[exception.NotFound])
  153. def wait_for_volume(cinder, volume_id, expected_status='available'):
  154. volumes = cinder.volumes.findall(id=volume_id)
  155. if not volumes:
  156. raise exception.VolumeNotFound(volume_id=volume_id)
  157. volume = utils.get_single_result(volumes)
  158. terminal_statuses = [expected_status, 'error']
  159. if expected_status == 'in-use':
  160. # if we're waiting for a volume to become attached, we are guaranteed
  161. # that its status would no longer be 'available' the moment the
  162. # attachment request is accepted.
  163. terminal_statuses.append('available')
  164. while volume.status not in terminal_statuses:
  165. LOG.debug('Volume %(id)s status: %(status)s. '
  166. 'Waiting for status: "%(expected_status)s".',
  167. {'id': volume_id, 'status': volume.status,
  168. 'expected_status': expected_status})
  169. time.sleep(2)
  170. volume = cinder.volumes.get(volume.id)
  171. if volume.status != expected_status:
  172. raise exception.CoriolisException(
  173. "Volume is in status: %s" % volume.status)
  174. @utils.retry_on_error()
  175. def delete_volume(cinder, volume_id):
  176. volumes = cinder.volumes.findall(id=volume_id)
  177. for volume in volumes:
  178. volume.delete()
  179. @utils.retry_on_error()
  180. def create_volume_snapshot(cinder, volume_id, name, force=False):
  181. return cinder.volume_snapshots.create(volume_id, name=name, force=force)
  182. @utils.retry_on_error(terminal_exceptions=[exception.NotFound])
  183. def wait_for_volume_snapshot(cinder, snapshot_id,
  184. expected_status='available'):
  185. snapshots = cinder.volume_snapshots.findall(id=snapshot_id)
  186. if not snapshots:
  187. if expected_status == 'deleted':
  188. return
  189. raise exception.VolumeSnapshotNotFound(snapshot_id=snapshot_id)
  190. snapshot = utils.get_single_result(snapshots)
  191. while snapshot.status not in [expected_status, 'error']:
  192. if expected_status == 'deleted' and snapshot.status == 'available':
  193. LOG.debug("Cinder volume snapshot '%s' became 'available' while "
  194. "waiting for its deletion. This may be the behavior "
  195. "of the Cinder driver being used, so no further "
  196. "deletion attempts will be made.",
  197. snapshot_id)
  198. return
  199. LOG.debug('Volume snapshot %(id)s status: %(status)s. '
  200. 'Waiting for status: "%(expected_status)s".',
  201. {'id': snapshot_id, 'status': snapshot.status,
  202. 'expected_status': expected_status})
  203. time.sleep(2)
  204. if expected_status == 'deleted':
  205. snapshots = cinder.volume_snapshots.findall(id=snapshot_id)
  206. if not snapshots:
  207. return
  208. snapshot = utils.get_single_result(snapshots)
  209. else:
  210. snapshot = cinder.volume_snapshots.get(snapshot.id)
  211. if snapshot.status != expected_status:
  212. raise exception.CoriolisException(
  213. "Volume snapshot is in status: %s" % snapshot.status)
  214. @utils.retry_on_error()
  215. def delete_volume_snapshot(cinder, snapshot_id):
  216. snapshots = cinder.volume_snapshots.findall(id=snapshot_id)
  217. for snapshot in snapshots:
  218. return cinder.volume_snapshots.delete(snapshot.id)
  219. @utils.retry_on_error()
  220. def create_volume_backup(cinder, volume_id, snapshot_id, name, container,
  221. incremental, force=False):
  222. return cinder.backups.create(
  223. volume_id=volume_id,
  224. snapshot_id=snapshot_id,
  225. container=container,
  226. name=name,
  227. incremental=incremental,
  228. force=force)
  229. @utils.retry_on_error(terminal_exceptions=[exception.NotFound])
  230. def wait_for_volume_backup(cinder, backup_id, expected_status='available'):
  231. backups = cinder.backups.findall(id=backup_id)
  232. if not backups:
  233. if expected_status == 'deleted':
  234. return
  235. raise exception.VolumeBackupNotFound(backup_id=backup_id)
  236. backup = utils.get_single_result(backups)
  237. while backup.status not in [expected_status, 'error']:
  238. LOG.debug('Volume backup %(id)s status: %(status)s. '
  239. 'Waiting for status: "%(expected_status)s".',
  240. {'id': backup_id, 'status': backup.status,
  241. 'expected_status': expected_status})
  242. time.sleep(2)
  243. if expected_status == 'deleted':
  244. backups = cinder.backups.findall(id=backup_id)
  245. if not backups:
  246. return
  247. backup = utils.get_single_result(backups)
  248. else:
  249. backup = cinder.backups.get(backup.id)
  250. if backup.status != expected_status:
  251. raise exception.CoriolisException(
  252. "Volume backup is in status: %s" % backup.status)
  253. @utils.retry_on_error()
  254. def delete_volume_backup(cinder, backup_id):
  255. cinder.backups.delete(backup_id)
  256. @utils.retry_on_error()
  257. def find_volume_backups(cinder, volume_id=None, container=None):
  258. return cinder.backups.list(search_opts={
  259. "volume_id": volume_id,
  260. "container": container})
  261. @utils.retry_on_error()
  262. def get_instance_volumes(nova, instance_id):
  263. return nova.volumes.get_server_volumes(instance_id)