diff --git a/cloudcafe/compute/volume_attachments_api/behaviors.py b/cloudcafe/compute/volume_attachments_api/behaviors.py index 660a3c54..fd38c92d 100644 --- a/cloudcafe/compute/volume_attachments_api/behaviors.py +++ b/cloudcafe/compute/volume_attachments_api/behaviors.py @@ -21,20 +21,38 @@ class VolumeAttachmentsAPI_Behaviors(BaseComputeBehavior): self.config = volume_attachments_config or VolumeAttachmentsAPIConfig() self.volumes_client = volumes_client - def wait_for_attachment_to_propagate( - self, attachment_id, server_id, timeout=None, poll_rate=5): + def _validated_volume_attach(self, server_id, volume_id, device=None): + """Performs a volume attach with response validation""" - timeout = timeout or self.config.attachment_propagation_timeout - poll_rate = poll_rate or self.config.api_poll_rate - endtime = time() + int(timeout) - while time() < endtime: - resp = self.client.get_volume_attachment_details( - attachment_id, server_id) - if resp.ok: - return True - sleep(poll_rate) - else: - return False + resp = self.client.attach_volume(server_id, volume_id, device=device) + + if not resp.ok: + raise VolumeAttachmentBehaviorError( + "Volume attachment failed in attach_volume_to_server " + "with a '{0}'. Could not attach volume {1} to server {2}" + .format(resp.status_code, volume_id, server_id)) + + if resp.entity is None: + raise VolumeAttachmentBehaviorError( + "Volume attachment failed in auto_attach_volume_to_server. " + "Could not deserialize volume attachment response body. " + "Could not attach volume '{1}' to server '{2}'".format( + volume_id, server_id)) + + return resp + + def _validated_volume_detach(self, attachment_id, server_id): + + resp = self.client.delete_volume_attachment( + attachment_id, server_id) + + if not resp.ok: + raise VolumeAttachmentBehaviorError( + "Volume attachment DELETE failed in delete_volume_attachment " + "with a {0}. Could not delete attachment '{1}' on server " + "'{2}'".format(resp.status_code, attachment_id, server_id)) + + return resp def _get_volume_status(self, volume_id): resp = self.volumes_client.get_volume_info(volume_id=volume_id) @@ -54,6 +72,41 @@ class VolumeAttachmentsAPI_Behaviors(BaseComputeBehavior): return resp.entity.status + def wait_for_attachment_to_propagate( + self, attachment_id, server_id, timeout=None, poll_rate=5): + + timeout = timeout or self.config.attachment_propagation_timeout + poll_rate = poll_rate or self.config.api_poll_rate + endtime = time() + int(timeout) + while time() < endtime: + resp = self.client.get_volume_attachment_details( + attachment_id, server_id) + if resp.ok: + return True + sleep(poll_rate) + else: + return False + + def wait_for_attachment_to_delete( + self, attachment_id, server_id, timeout=None, poll_rate=None): + + timeout = timeout or self.config.attachment_propagation_timeout + poll_rate = poll_rate or self.config.api_poll_rate + endtime = time() + int(timeout) + + while time() < endtime: + resp = self.client.get_volume_attachment_details( + attachment_id, server_id) + if resp.status_code == 404: + return None + sleep(poll_rate) + else: + raise VolumeAttachmentBehaviorError( + "Volume Attachment {0} still exists on server '{1}', {2} " + "seconds after a successful DELETE. Could not verify that " + "attachment was deleted.".format( + attachment_id, server_id, timeout)) + def verify_volume_status_progression_during_attachment( self, volume_id, state_list=None): @@ -115,7 +168,8 @@ class VolumeAttachmentsAPI_Behaviors(BaseComputeBehavior): verifier.add_state( expected_statuses=['available'], - error_statuses=['error', 'attaching', 'creating', 'deleting'], + error_statuses=[ + 'error', 'attaching', 'creating', 'deleting', 'in-use'], poll_rate=self.config.api_poll_rate, poll_failure_retry_limit=3) @@ -125,62 +179,23 @@ class VolumeAttachmentsAPI_Behaviors(BaseComputeBehavior): if raise_on_error: raise exception - def delete_volume_attachment( - self, attachment_id, server_id, timeout=None, poll_rate=None): - """Waits timeout seconds for volume attachment to 404 after issuing - a delete to it - """ - - timeout = timeout or self.config.attachment_propagation_timeout - poll_rate = poll_rate or self.config.api_poll_rate - endtime = time() + int(timeout) - - resp = self.client.delete_volume_attachment( - attachment_id, server_id) - - if not resp.ok: - raise VolumeAttachmentBehaviorError( - "Volume attachment DELETE failed in delete_volume_attachment " - "with a {0}. Could not delete attachment '{1}' on server " - "'{2}'".format(resp.status_code, attachment_id, server_id)) - - while time() < endtime: - resp = self.client.get_volume_attachment_details( - attachment_id, server_id) - if resp.status_code == 404: - return None - sleep(poll_rate) - else: - raise VolumeAttachmentBehaviorError( - "Volume Attachment {0} still exists on server '{1}', {2} " - "seconds after a successful DELETE. Could not verify that " - "attachment was deleted.".format( - attachment_id, server_id, timeout)) - def attach_volume_to_server( self, server_id, volume_id, device=None, attachment_propagation_timeout=60): - """Returns a VolumeAttachment object""" + """ + Performs response validation on attach call, confirms volume attachment + propogation, and verifies volume status progression during the attach + process. + + :rtype: VolumeAttachment model + """ attachment_propagation_timeout = ( attachment_propagation_timeout or self.config.attachment_propagation_timeout) - resp = self.client.attach_volume(server_id, volume_id, device=device) - - if not resp.ok: - raise VolumeAttachmentBehaviorError( - "Volume attachment failed in attach_volume_to_server " - "with a '{0}'. Could not attach volume {1} to server {2}" - .format(resp.status_code, volume_id, server_id)) - - if resp.entity is None: - raise VolumeAttachmentBehaviorError( - "Volume attachment failed in auto_attach_volume_to_server. " - "Could not deserialize volume attachment response body. " - "Could not attach volume '{1}' to server '{2}'".format( - volume_id, server_id)) - + # Attach volume to server (validate response) + resp = self._validated_volume_attach(server_id, volume_id, device) attachment = resp.entity # Confirm volume attachment propagation @@ -197,3 +212,13 @@ class VolumeAttachmentsAPI_Behaviors(BaseComputeBehavior): self.verify_volume_status_progression_during_attachment(volume_id) return attachment + + def delete_volume_attachment( + self, attachment_id, server_id, timeout=None, poll_rate=None): + """Waits timeout seconds for volume attachment to 404 after issuing + a delete to it. + Raises exception on error. + """ + self._validated_volume_detach(attachment_id, server_id) + self.wait_for_attachment_to_delete( + attachment_id, server_id, timeout, poll_rate)