diff --git a/heat/engine/clients/os/cinder.py b/heat/engine/clients/os/cinder.py index 368ca78e9b..11f9813133 100644 --- a/heat/engine/clients/os/cinder.py +++ b/heat/engine/clients/os/cinder.py @@ -22,6 +22,7 @@ from heat.common.i18n import _ from heat.common.i18n import _LI from heat.engine.clients import client_plugin from heat.engine import constraints +from heat.engine import resource LOG = logging.getLogger(__name__) @@ -127,6 +128,87 @@ class CinderClientPlugin(client_plugin.ClientPlugin): return (isinstance(ex, exceptions.ClientException) and ex.code == 409) + def check_detach_volume_complete(self, vol_id): + try: + vol = self.client().volumes.get(vol_id) + except Exception as ex: + self.ignore_not_found(ex) + return True + + if vol.status in ('in-use', 'detaching'): + LOG.debug('%s - volume still in use' % vol_id) + return False + + LOG.debug('Volume %(id)s - status: %(status)s' % { + 'id': vol.id, 'status': vol.status}) + + if vol.status not in ('available', 'deleting'): + LOG.debug("Detachment failed - volume %(vol)s " + "is in %(status)s status" % {"vol": vol.id, + "status": vol.status}) + raise resource.ResourceUnknownStatus( + resource_status=vol.status, + result=_('Volume detachment failed')) + else: + return True + + def check_attach_volume_complete(self, vol_id): + vol = self.client().volumes.get(vol_id) + if vol.status in ('available', 'attaching'): + LOG.debug("Volume %(id)s is being attached - " + "volume status: %(status)s" % {'id': vol_id, + 'status': vol.status}) + return False + + if vol.status != 'in-use': + LOG.debug("Attachment failed - volume %(vol)s is " + "in %(status)s status" % {"vol": vol_id, + "status": vol.status}) + raise resource.ResourceUnknownStatus( + resource_status=vol.status, + result=_('Volume attachment failed')) + + LOG.info(_LI('Attaching volume %(id)s complete'), {'id': vol_id}) + return True + + +# NOTE(pshchelo): these Volume*Progress classes are simple key-value storages +# meant to be passed between handle_ and check__complete, +# being mutated during subsequent check__complete calls. +class VolumeDetachProgress(object): + def __init__(self, srv_id, vol_id, attach_id, val=False): + self.called = val + self.cinder_complete = val + self.nova_complete = val + self.srv_id = srv_id + self.vol_id = vol_id + self.attach_id = attach_id + + +class VolumeAttachProgress(object): + def __init__(self, srv_id, vol_id, device, val=False): + self.called = val + self.complete = val + self.srv_id = srv_id + self.vol_id = vol_id + self.device = device + + +class VolumeDeleteProgress(object): + def __init__(self, val=False): + self.backup = {'called': val, + 'complete': val} + self.delete = {'called': val, + 'complete': val} + self.backup_id = None + + +class VolumeResizeProgress(object): + def __init__(self, val=False, size=None): + self.called = val + self.complete = val + self.size = size + class VolumeConstraint(constraints.BaseCustomConstraint): diff --git a/heat/engine/clients/os/nova.py b/heat/engine/clients/os/nova.py index ee46c43ad1..0c9a0b4111 100644 --- a/heat/engine/clients/os/nova.py +++ b/heat/engine/clients/os/nova.py @@ -32,6 +32,7 @@ from six.moves.urllib import parse as urlparse from heat.common import exception from heat.common.i18n import _ +from heat.common.i18n import _LI from heat.common.i18n import _LW from heat.engine.clients import client_plugin from heat.engine import constraints @@ -495,6 +496,55 @@ echo -e '%s\tALL=(ALL)\tNOPASSWD: ALL' >> /etc/sudoers return net_id + def attach_volume(self, server_id, volume_id, device): + try: + va = self.client().volumes.create_server_volume( + server_id=server_id, + volume_id=volume_id, + device=device) + except Exception as ex: + if self.is_client_exception(ex): + raise exception.Error(_( + "Failed to attach volume %(vol)s to server %(srv)s " + "- %(err)s") % {'vol': volume_id, + 'srv': server_id, + 'err': ex}) + else: + raise + return va.id + + def detach_volume(self, server_id, attach_id): + # detach the volume using volume_attachment + try: + self.client().volumes.delete_server_volume(server_id, attach_id) + except Exception as ex: + if not (self.is_not_found(ex) + or self.is_bad_request(ex)): + raise exception.Error( + _("Could not detach attachment %(att)s " + "from server %(srv)s.") % {'srv': server_id, + 'att': attach_id}) + + def check_detach_volume_complete(self, server_id, attach_id): + """Check that nova server lost attachment. + + This check is needed for immediate reattachment when updating: + there might be some time between cinder marking volume as 'available' + and nova removing attachment from its own objects, so we + check that nova already knows that the volume is detached. + """ + try: + self.client().volumes.get_server_volume(server_id, attach_id) + except Exception as ex: + self.ignore_not_found(ex) + LOG.info(_LI("Volume %(vol)s is detached from server %(srv)s"), + {'vol': attach_id, 'srv': server_id}) + return True + else: + LOG.debug("Server %(srv)s still has attachment %(att)s." % { + 'att': attach_id, 'srv': server_id}) + return False + class ServerConstraint(constraints.BaseCustomConstraint): diff --git a/heat/engine/resources/openstack/cinder/volume.py b/heat/engine/resources/openstack/cinder/volume.py index 1520fde7f7..8c70e12ef8 100644 --- a/heat/engine/resources/openstack/cinder/volume.py +++ b/heat/engine/resources/openstack/cinder/volume.py @@ -19,12 +19,12 @@ from heat.common import exception from heat.common.i18n import _ from heat.common.i18n import _LI from heat.engine import attributes +from heat.engine.clients.os import cinder as heat_cinder from heat.engine import constraints from heat.engine import properties +from heat.engine import resource from heat.engine.resources import volume_base as vb -from heat.engine import scheduler from heat.engine import support -from heat.engine import volume_tasks as vol_task LOG = logging.getLogger(__name__) @@ -236,10 +236,41 @@ class CinderVolume(vb.BaseVolume): return vol_id + def _extend_volume(self, new_size): + try: + self.client().volumes.extend(self.resource_id, new_size) + except Exception as ex: + if self.client_plugin().is_client_exception(ex): + raise exception.Error(_( + "Failed to extend volume %(vol)s - %(err)s") % { + 'vol': self.resource_id, 'err': str(ex)}) + else: + raise + return True + + def _check_extend_volume_complete(self): + vol = self.client().volumes.get(self.resource_id) + if vol.status == 'extending': + LOG.debug("Volume %s is being extended" % vol.id) + return False + + if vol.status != 'available': + LOG.info(_LI("Resize failed: Volume %(vol)s " + "is in %(status)s state."), + {'vol': vol.id, 'status': vol.status}) + raise resource.ResourceUnknownStatus( + resource_status=vol.status, + result=_('Volume resize failed')) + + LOG.info(_LI('Volume %(id)s resize complete'), {'id': vol.id}) + return True + def handle_update(self, json_snippet, tmpl_diff, prop_diff): vol = None - checkers = [] cinder = self.client() + prg_resize = None + prg_attach = None + prg_detach = None # update the name and description for cinder volume if self.NAME in prop_diff or self.DESCRIPTION in prop_diff: vol = cinder.volumes.get(self.resource_id) @@ -268,6 +299,10 @@ class CinderVolume(vb.BaseVolume): vol = cinder.volumes.get(self.resource_id) new_vol_type = prop_diff.get(self.VOLUME_TYPE) cinder.volumes.retype(vol, new_vol_type, 'never') + # update read_only access mode + if self.READ_ONLY in prop_diff: + flag = prop_diff.get(self.READ_ONLY) + cinder.volumes.update_readonly_flag(self.resource_id, flag) # extend volume size if self.SIZE in prop_diff: if not vol: @@ -278,6 +313,7 @@ class CinderVolume(vb.BaseVolume): raise exception.NotSupported(feature=_("Shrinking volume")) elif new_size > vol.size: + prg_resize = heat_cinder.VolumeResizeProgress(size=new_size) if vol.attachments: # NOTE(pshchelo): # this relies on current behavior of cinder attachments, @@ -289,35 +325,59 @@ class CinderVolume(vb.BaseVolume): server_id = vol.attachments[0]['server_id'] device = vol.attachments[0]['device'] attachment_id = vol.attachments[0]['id'] - detach_task = vol_task.VolumeDetachTask( - self.stack, server_id, attachment_id) - checkers.append(scheduler.TaskRunner(detach_task)) - extend_task = vol_task.VolumeExtendTask( - self.stack, vol.id, new_size) - checkers.append(scheduler.TaskRunner(extend_task)) - attach_task = vol_task.VolumeAttachTask( - self.stack, server_id, vol.id, device) - checkers.append(scheduler.TaskRunner(attach_task)) + prg_detach = heat_cinder.VolumeDetachProgress( + server_id, vol.id, attachment_id) + prg_attach = heat_cinder.VolumeAttachProgress( + server_id, vol.id, device) - else: - extend_task = vol_task.VolumeExtendTask( - self.stack, vol.id, new_size) - checkers.append(scheduler.TaskRunner(extend_task)) - # update read_only access mode - if self.READ_ONLY in prop_diff: - flag = prop_diff.get(self.READ_ONLY) - cinder.volumes.update_readonly_flag(self.resource_id, flag) + return prg_detach, prg_resize, prg_attach - if checkers: - checkers[0].start() - return checkers + def _detach_volume_to_complete(self, prg_detach): + if not prg_detach.called: + self.client_plugin('nova').detach_volume(prg_detach.srv_id, + prg_detach.attach_id) + prg_detach.called = True + return False + if not prg_detach.cinder_complete: + cinder_complete_res = self.client_plugin( + ).check_detach_volume_complete(prg_detach.vol_id) + prg_detach.cinder_complete = cinder_complete_res + return False + if not prg_detach.nova_complete: + prg_detach.nova_complete = self.client_plugin( + 'nova').check_detach_volume_complete(prg_detach.srv_id, + prg_detach.attach_id) + return False + + def _attach_volume_to_complete(self, prg_attach): + if not prg_attach.called: + prg_attach.called = self.client_plugin('nova').attach_volume( + prg_attach.srv_id, prg_attach.vol_id, prg_attach.device) + return False + if not prg_attach.complete: + prg_attach.complete = self.client_plugin( + ).check_attach_volume_complete(prg_attach.vol_id) + return prg_attach.complete def check_update_complete(self, checkers): - for checker in checkers: - if not checker.started(): - checker.start() - if not checker.step(): + prg_detach, prg_resize, prg_attach = checkers + if not prg_resize: + return True + # detach volume + if prg_detach: + if not prg_detach.nova_complete: + self._detach_volume_to_complete(prg_detach) return False + # resize volume + if not prg_resize.called: + prg_resize.called = self._extend_volume(prg_resize.size) + return False + if not prg_resize.complete: + prg_resize.complete = self._check_extend_volume_complete() + return prg_resize.complete and not prg_attach + # reattach volume back + if prg_attach: + return self._attach_volume_to_complete(prg_attach) return True def handle_snapshot(self): @@ -334,24 +394,25 @@ class CinderVolume(vb.BaseVolume): raise exception.Error(backup.fail_reason) def handle_delete_snapshot(self, snapshot): - backup_id = snapshot['resource_data'].get('backup_id') + backup_id = snapshot['resource_data']['backup_id'] + try: + self.client().backups.delete(backup_id) + except Exception as ex: + self.client_plugin().ignore_not_found(ex) + return + else: + return backup_id - def delete(): - cinder = self.client() - try: - cinder.backups.delete(backup_id) - while True: - yield - cinder.backups.get(backup_id) - except Exception as ex: - self.client_plugin().ignore_not_found(ex) - - delete_task = scheduler.TaskRunner(delete) - delete_task.start() - return delete_task - - def check_delete_snapshot_complete(self, delete_task): - return delete_task.step() + def check_delete_snapshot_complete(self, backup_id): + if not backup_id: + return True + try: + self.client().backups.get(backup_id) + except Exception as ex: + self.client_plugin().ignore_not_found(ex) + return True + else: + return False def _build_exclusive_options(self): exclusive_options = [] @@ -463,13 +524,21 @@ class CinderVolumeAttachment(vb.BaseVolumeAttachment): } def handle_update(self, json_snippet, tmpl_diff, prop_diff): - checkers = [] + prg_attach = None + prg_detach = None if prop_diff: # Even though some combinations of changed properties # could be updated in UpdateReplace manner, # we still first detach the old resource so that # self.resource_id is not replaced prematurely volume_id = self.properties[self.VOLUME_ID] + server_id = self._stored_properties_data.get(self.INSTANCE_ID) + self.client_plugin('nova').detach_volume(server_id, + self.resource_id) + prg_detach = heat_cinder.VolumeDetachProgress( + server_id, volume_id, self.resource_id) + prg_detach.called = True + if self.VOLUME_ID in prop_diff: volume_id = prop_diff.get(self.VOLUME_ID) @@ -477,29 +546,36 @@ class CinderVolumeAttachment(vb.BaseVolumeAttachment): if self.DEVICE in prop_diff: device = prop_diff.get(self.DEVICE) - server_id = self._stored_properties_data.get(self.INSTANCE_ID) - detach_task = vol_task.VolumeDetachTask( - self.stack, server_id, self.resource_id) - checkers.append(scheduler.TaskRunner(detach_task)) - if self.INSTANCE_ID in prop_diff: server_id = prop_diff.get(self.INSTANCE_ID) - attach_task = vol_task.VolumeAttachTask( - self.stack, server_id, volume_id, device) + prg_attach = heat_cinder.VolumeAttachProgress( + server_id, volume_id, device) - checkers.append(scheduler.TaskRunner(attach_task)) - - if checkers: - checkers[0].start() - return checkers + return prg_detach, prg_attach def check_update_complete(self, checkers): - for checker in checkers: - if not checker.started(): - checker.start() - if not checker.step(): - return False - self.resource_id_set(checkers[-1]._task.attachment_id) + prg_detach, prg_attach = checkers + if not (prg_detach and prg_attach): + return True + if not prg_detach.cinder_complete: + prg_detach.cinder_complete = self.client_plugin( + ).check_detach_volume_complete(prg_detach.vol_id) + return False + if not prg_detach.nova_complete: + prg_detach.nova_complete = self.client_plugin( + 'nova').check_detach_volume_complete(prg_detach.srv_id, + self.resource_id) + return False + if not prg_attach.called: + prg_attach.called = self.client_plugin('nova').attach_volume( + prg_attach.srv_id, prg_attach.vol_id, prg_attach.device) + return False + if not prg_attach.complete: + prg_attach.complete = self.client_plugin( + ).check_attach_volume_complete(prg_attach.vol_id) + if prg_attach.complete: + self.resource_id_set(prg_attach.called) + return prg_attach.complete return True diff --git a/heat/engine/resources/volume_base.py b/heat/engine/resources/volume_base.py index ed4aeaa7e9..7172735010 100644 --- a/heat/engine/resources/volume_base.py +++ b/heat/engine/resources/volume_base.py @@ -13,9 +13,8 @@ from heat.common import exception from heat.common.i18n import _ +from heat.engine.clients.os import cinder as heat_cinder from heat.engine import resource -from heat.engine import scheduler -from heat.engine import volume_tasks as vol_task class BaseVolume(resource.Resource): @@ -82,55 +81,80 @@ class BaseVolume(resource.Resource): ] self._verify_check_conditions(checks) - def _backup(self): - cinder = self.client() - backup = cinder.backups.create(self.resource_id) - while backup.status == 'creating': - yield - backup = cinder.backups.get(backup.id) - if backup.status != 'available': + def handle_snapshot_delete(self, state): + backup = state not in ((self.CREATE, self.FAILED), + (self.UPDATE, self.FAILED)) + progress = heat_cinder.VolumeDeleteProgress() + progress.backup['called'] = not backup + progress.backup['complete'] = not backup + return progress + + def handle_delete(self): + if self.resource_id is None: + return heat_cinder.VolumeDeleteProgress(True) + progress = heat_cinder.VolumeDeleteProgress() + progress.backup['called'] = True + progress.backup['complete'] = True + return progress + + def _create_backup(self): + backup = self.client().backups.create(self.resource_id) + return backup.id + + def _check_create_backup_complete(self, prg): + backup = self.client().backups.get(prg.backup_id) + if backup.status == 'creating': + return False + if backup.status == 'available': + return True + else: raise resource.ResourceUnknownStatus( resource_status=backup.status, result=_('Volume backup failed')) - @scheduler.wrappertask - def _delete(self, backup=False): - if self.resource_id is not None: + def _delete_volume(self): + try: cinder = self.client() + vol = cinder.volumes.get(self.resource_id) + if vol.status == 'in-use': + raise exception.Error(_('Volume in use')) + # if the volume is already in deleting status, + # just wait for the deletion to complete + if vol.status != 'deleting': + cinder.volumes.delete(self.resource_id) + else: + return True + except Exception as ex: + self.client_plugin().ignore_not_found(ex) + return True + else: + return False + + def check_delete_complete(self, prg): + if not prg.backup['called']: + prg.backup_id = self._create_backup() + prg.backup['called'] = True + return False + + if not prg.backup['complete']: + prg.backup['complete'] = self._check_create_backup_complete(prg) + return False + + if not prg.delete['called']: + prg.delete['complete'] = self._delete_volume() + prg.delete['called'] = True + return False + + if not prg.delete['complete']: try: - vol = cinder.volumes.get(self.resource_id) - - if backup: - yield self._backup() - vol = cinder.volumes.get(self.resource_id) - - if vol.status == 'in-use': - raise exception.Error(_('Volume in use')) - # if the volume is already in deleting status, - # just wait for the deletion to complete - if vol.status != 'deleting': - cinder.volumes.delete(self.resource_id) - while True: - yield - vol = cinder.volumes.get(self.resource_id) + self.client().volumes.get(self.resource_id) except Exception as ex: self.client_plugin().ignore_not_found(ex) - - def handle_snapshot_delete(self, state): - backup = state not in ((self.CREATE, self.FAILED), - (self.UPDATE, self.FAILED)) - - delete_task = scheduler.TaskRunner(self._delete, backup=backup) - delete_task.start() - return delete_task - - def handle_delete(self): - delete_task = scheduler.TaskRunner(self._delete) - delete_task.start() - return delete_task - - def check_delete_complete(self, delete_task): - return delete_task.step() + prg.delete['complete'] = True + return True + else: + return False + return True class BaseVolumeAttachment(resource.Resource): @@ -138,31 +162,41 @@ class BaseVolumeAttachment(resource.Resource): Base Volume Attachment Manager. ''' + default_client_name = 'cinder' + def handle_create(self): server_id = self.properties[self.INSTANCE_ID] volume_id = self.properties[self.VOLUME_ID] dev = self.properties[self.DEVICE] - attach_task = vol_task.VolumeAttachTask( - self.stack, server_id, volume_id, dev) - attach_runner = scheduler.TaskRunner(attach_task) + attach_id = self.client_plugin('nova').attach_volume( + server_id, volume_id, dev) - attach_runner.start() + self.resource_id_set(attach_id) - self.resource_id_set(attach_task.attachment_id) + return volume_id - return attach_runner - - def check_create_complete(self, attach_runner): - return attach_runner.step() + def check_create_complete(self, volume_id): + return self.client_plugin().check_attach_volume_complete(volume_id) def handle_delete(self): server_id = self.properties[self.INSTANCE_ID] - detach_task = vol_task.VolumeDetachTask( - self.stack, server_id, self.resource_id) - detach_runner = scheduler.TaskRunner(detach_task) - detach_runner.start() - return detach_runner + vol_id = self.properties[self.VOLUME_ID] + self.client_plugin('nova').detach_volume(server_id, + self.resource_id) + prg = heat_cinder.VolumeDetachProgress( + server_id, vol_id, self.resource_id) + prg.called = True + return prg - def check_delete_complete(self, detach_runner): - return detach_runner.step() + def check_delete_complete(self, prg): + if not prg.cinder_complete: + prg.cinder_complete = self.client_plugin( + ).check_detach_volume_complete(prg.vol_id) + return False + if not prg.nova_complete: + prg.nova_complete = self.client_plugin( + 'nova').check_detach_volume_complete(prg.srv_id, + prg.attach_id) + return prg.nova_complete + return True diff --git a/heat/engine/volume_tasks.py b/heat/engine/volume_tasks.py index 0a0c4f4947..afe709ce80 100644 --- a/heat/engine/volume_tasks.py +++ b/heat/engine/volume_tasks.py @@ -13,7 +13,6 @@ from oslo_log import log as logging -from heat.common import exception from heat.common.i18n import _ from heat.common.i18n import _LI from heat.engine import resource @@ -21,56 +20,6 @@ from heat.engine import resource LOG = logging.getLogger(__name__) -class VolumeExtendTask(object): - """A task to resize volume using Cinder API.""" - - def __init__(self, stack, volume_id, size): - self.clients = stack.clients - self.volume_id = volume_id - self.size = size - - def __str__(self): - return _("Resizing volume %(vol)s to size %(size)i") % { - 'vol': self.volume_id, 'size': self.size} - - def __repr__(self): - return "%s(%s +-> %i)" % (type(self).__name__, self.volume_id, - self.size) - - def __call__(self): - LOG.debug(str(self)) - - cinder = self.clients.client('cinder').volumes - vol = cinder.get(self.volume_id) - - try: - cinder.extend(self.volume_id, self.size) - except Exception as ex: - if self.clients.client_plugin('cinder').is_client_exception(ex): - raise exception.Error(_( - "Failed to extend volume %(vol)s - %(err)s") % { - 'vol': vol.id, 'err': ex}) - else: - raise - - yield - - vol = cinder.get(self.volume_id) - while vol.status == 'extending': - LOG.debug("Volume %s is being extended" % self.volume_id) - yield - vol = cinder.get(self.volume_id) - - if vol.status != 'available': - LOG.info(_LI("Resize failed: Volume %(vol)s is in %(status)s " - "state."), {'vol': vol.id, 'status': vol.status}) - raise resource.ResourceUnknownStatus( - resource_status=vol.status, - result=_('Volume resize failed')) - - LOG.info(_LI('%s - complete'), str(self)) - - class VolumeAttachTask(object): """A task for attaching a volume to a Nova server.""" @@ -128,102 +77,3 @@ class VolumeAttachTask(object): result=_('Volume attachment failed')) LOG.info(_LI('%s - complete'), str(self)) - - -class VolumeDetachTask(object): - """A task for detaching a volume from a Nova server.""" - - def __init__(self, stack, server_id, attachment_id): - """ - Initialise with the stack (for obtaining the clients), and the IDs of - the server and volume. - """ - self.clients = stack.clients - self.server_id = server_id - self.attachment_id = attachment_id - - def __str__(self): - """Return a human-readable string description of the task.""" - return _('Removing attachment %(att)s from Instance %(srv)s') % { - 'att': self.attachment_id, 'srv': self.server_id} - - def __repr__(self): - """Return a brief string description of the task.""" - return '%s(%s -/> %s)' % (type(self).__name__, - self.attachment_id, - self.server_id) - - def __call__(self): - """Return a co-routine which runs the task.""" - LOG.debug(str(self)) - - nova_plugin = self.clients.client_plugin('nova') - cinder_plugin = self.clients.client_plugin('cinder') - server_api = self.clients.client('nova').volumes - cinder = self.clients.client('cinder') - # get reference to the volume while it is attached - try: - nova_vol = server_api.get_server_volume(self.server_id, - self.attachment_id) - vol = cinder.volumes.get(nova_vol.id) - except Exception as ex: - if (cinder_plugin.is_not_found(ex) or - nova_plugin.is_not_found(ex) or - nova_plugin.is_bad_request(ex)): - return - else: - raise - - if vol.status == 'deleting': - return - - # detach the volume using volume_attachment - try: - server_api.delete_server_volume(self.server_id, self.attachment_id) - except Exception as ex: - if nova_plugin.is_not_found(ex) or nova_plugin.is_bad_request(ex): - pass - else: - raise - - yield - - try: - while vol.status in ('in-use', 'detaching'): - LOG.debug('%s - volume still in use' % str(self)) - yield - vol = cinder.volumes.get(nova_vol.id) - - LOG.info(_LI('%(name)s - status: %(status)s'), - {'name': str(self), 'status': vol.status}) - if vol.status not in ['available', 'deleting']: - LOG.info(_LI("Detachment failed - volume %(vol)s " - "is in %(status)s status"), - {"vol": vol.id, - "status": vol.status}) - raise resource.ResourceUnknownStatus( - resource_status=vol.status, - result=_('Volume detachment failed')) - - except Exception as ex: - cinder_plugin.ignore_not_found(ex) - - # The next check is needed for immediate reattachment when updating: - # there might be some time between cinder marking volume as 'available' - # and nova removing attachment from its own objects, so we - # check that nova already knows that the volume is detached - def server_has_attachment(server_id, attachment_id): - try: - server_api.get_server_volume(server_id, attachment_id) - except Exception as ex: - nova_plugin.ignore_not_found(ex) - return False - return True - - while server_has_attachment(self.server_id, self.attachment_id): - LOG.info(_LI("Server %(srv)s still has attachment %(att)s."), - {'att': self.attachment_id, 'srv': self.server_id}) - yield - - LOG.info(_LI("Volume %(vol)s is detached from server %(srv)s"), - {'vol': vol.id, 'srv': self.server_id}) diff --git a/heat/tests/aws/test_volume.py b/heat/tests/aws/test_volume.py index 892c7f3f04..acd43afcad 100644 --- a/heat/tests/aws/test_volume.py +++ b/heat/tests/aws/test_volume.py @@ -308,10 +308,13 @@ class VolumeTest(vt_base.BaseVolumeTest): self._mock_create_server_volume_script(fva) self.stub_VolumeConstraint_validate() # delete script - self.fc.volumes.get_server_volume(u'WikiDatabase', - 'vol-123').AndReturn(fva) + self.fc.volumes.delete_server_volume(u'WikiDatabase', + 'vol-123').AndReturn(None) self.cinder_fc.volumes.get(fva.id).AndRaise( cinder_exp.NotFound('Not found')) + self.fc.volumes.get_server_volume(u'WikiDatabase', 'vol-123' + ).AndRaise( + fakes_nova.fake_exception()) self.m.ReplayAll() @@ -333,9 +336,12 @@ class VolumeTest(vt_base.BaseVolumeTest): self._mock_create_server_volume_script(fva) self.stub_VolumeConstraint_validate() # delete script - self.fc.volumes.get_server_volume(u'WikiDatabase', - 'vol-123').AndReturn(fva) + self.fc.volumes.delete_server_volume(u'WikiDatabase', + 'vol-123').AndReturn(None) self.cinder_fc.volumes.get(fva.id).AndReturn(fva) + self.fc.volumes.get_server_volume(u'WikiDatabase', 'vol-123' + ).AndRaise( + fakes_nova.fake_exception()) self.m.ReplayAll() @@ -395,11 +401,8 @@ class VolumeTest(vt_base.BaseVolumeTest): self.stub_VolumeConstraint_validate() # delete script fva = vt_base.FakeVolume('in-use') - self.fc.volumes.get_server_volume(u'WikiDatabase', - 'vol-123').AndReturn(fva) - self.cinder_fc.volumes.get(fva.id).AndReturn(fva) self.fc.volumes.delete_server_volume( - 'WikiDatabase', 'vol-123').MultipleTimes().AndReturn(None) + 'WikiDatabase', 'vol-123').AndReturn(None) self.cinder_fc.volumes.get(fva.id).AndReturn( vt_base.FakeVolume('error', id=fva.id)) self.m.ReplayAll() @@ -444,13 +447,9 @@ class VolumeTest(vt_base.BaseVolumeTest): fv = self._mock_create_volume(vt_base.FakeVolume('creating'), stack_name) - self.cinder_fc.volumes.get(fv.id).AndReturn( - vt_base.FakeVolume('available')) - self.cinder_fc.volumes.delete(fv.id).AndReturn(True) self.cinder_fc.volumes.get(fv.id).AndReturn( vt_base.FakeVolume('deleting')) - self.cinder_fc.volumes.get(fv.id).AndRaise( - cinder_exp.NotFound('Not found')) + self.m.ReplayAll() stack = utils.parse_stack(self.t, stack_name=stack_name) @@ -533,8 +532,10 @@ class VolumeTest(vt_base.BaseVolumeTest): # snapshot script self.m.StubOutWithMock(self.cinder_fc.backups, 'create') - self.cinder_fc.backups.create(fv.id).AndReturn( - vt_base.FakeBackup('available')) + self.m.StubOutWithMock(self.cinder_fc.backups, 'get') + fb = vt_base.FakeBackup('available') + self.cinder_fc.backups.create(fv.id).AndReturn(fb) + self.cinder_fc.backups.get(fb.id).AndReturn(fb) self.cinder_fc.volumes.get(fv.id).AndReturn(fv) self._mock_delete_volume(fv) @@ -555,10 +556,11 @@ class VolumeTest(vt_base.BaseVolumeTest): stack_name) # snapshot script - self.cinder_fc.volumes.get(fv.id).AndReturn(fv) self.m.StubOutWithMock(self.cinder_fc.backups, 'create') + self.m.StubOutWithMock(self.cinder_fc.backups, 'get') fb = vt_base.FakeBackup('error') self.cinder_fc.backups.create(fv.id).AndReturn(fb) + self.cinder_fc.backups.get(fb.id).AndReturn(fb) self.m.ReplayAll() self.t['Resources']['DataVolume']['DeletionPolicy'] = 'Snapshot' diff --git a/heat/tests/openstack/test_volume.py b/heat/tests/openstack/test_volume.py index b905faf7c0..0bd1e87470 100644 --- a/heat/tests/openstack/test_volume.py +++ b/heat/tests/openstack/test_volume.py @@ -337,7 +337,6 @@ class CinderVolumeTest(vt_base.BaseVolumeTest): fv = vt_base.FakeVolume('available', size=1, attachments=[]) self.cinder_fc.volumes.get(fv.id).AndReturn(fv) - self.cinder_fc.volumes.get(fv.id).AndReturn(fv) self.cinder_fc.volumes.extend(fv.id, 2) self.cinder_fc.volumes.get(fv.id).AndReturn( vt_base.FakeVolume('extending')) @@ -371,7 +370,6 @@ class CinderVolumeTest(vt_base.BaseVolumeTest): fv = vt_base.FakeVolume('available', size=1, attachments=[]) self.cinder_fc.volumes.get(fv.id).AndReturn(fv) - self.cinder_fc.volumes.get(fv.id).AndReturn(fv) self.cinder_fc.volumes.extend(fv.id, 2).AndRaise( cinder_exp.OverLimit(413)) self.m.ReplayAll() @@ -400,7 +398,6 @@ class CinderVolumeTest(vt_base.BaseVolumeTest): fv = vt_base.FakeVolume('available', size=1, attachments=[]) self.cinder_fc.volumes.get(fv.id).AndReturn(fv) - self.cinder_fc.volumes.get(fv.id).AndReturn(fv) self.cinder_fc.volumes.extend(fv.id, 2) self.cinder_fc.volumes.get(fv.id).AndReturn( vt_base.FakeVolume('extending'))