def create(self, req, server_id, body): """Attach a volume to an instance.""" context = req.environ['nova.context'] authorize(context) authorize_attach(context, action='create') if not self.is_valid_body(body, 'volumeAttachment'): msg = _("volumeAttachment not specified") raise exc.HTTPBadRequest(explanation=msg) try: volume_id = body['volumeAttachment']['volumeId'] except KeyError: msg = _("volumeId must be specified.") raise exc.HTTPBadRequest(explanation=msg) device = body['volumeAttachment'].get('device') #self._validate_volume_id(volume_id) LOG.info(_LI("Attach volume %(volume_id)s to instance %(server_id)s " "at %(device)s"), {'volume_id': volume_id, 'device': device, 'server_id': server_id}, context=context) #instance = common.get_instance(self.compute_api, context, server_id) try: device = conn.attach_volumes([volume_id], server_id) except exception.NotFound as e: raise exc.HTTPNotFound(explanation=e.format_message()) except exception.InstanceIsLocked as e: raise exc.HTTPConflict(explanation=e.format_message()) except exception.InstanceInvalidState as state_error: common.raise_http_conflict_for_instance_invalid_state(state_error, 'attach_volume', server_id) # The attach is async attachment = {} attachment['id'] = volume_id attachment['serverId'] = server_id attachment['volumeId'] = volume_id attachment['device'] = device # NOTE(justinsb): And now, we have a problem... # The attach is async, so there's a window in which we don't see # the attachment (until the attachment completes). We could also # get problems with concurrent requests. I think we need an # attachment state, and to write to the DB here, but that's a bigger # change. # For now, we'll probably have to rely on libraries being smart # TODO(justinsb): How do I return "accepted" here? return {'volumeAttachment': attachment}
def update(self, req, server_id, id, body): if (not self.ext_mgr or not self.ext_mgr.is_loaded('os-volume-attachment-update')): raise exc.HTTPBadRequest() context = req.environ['nova.context'] authorize(context) authorize_attach(context, action='update') if not self.is_valid_body(body, 'volumeAttachment'): msg = _("volumeAttachment not specified") raise exc.HTTPBadRequest(explanation=msg) old_volume_id = id old_volume = self.volume_api.get(context, old_volume_id) try: new_volume_id = body['volumeAttachment']['volumeId'] except KeyError: msg = _("volumeId must be specified.") raise exc.HTTPBadRequest(explanation=msg) self._validate_volume_id(new_volume_id) new_volume = self.volume_api.get(context, new_volume_id) instance = common.get_instance(self.compute_api, context, server_id) bdms = objects.BlockDeviceMappingList.get_by_instance_uuid( context, instance.uuid) found = False try: for bdm in bdms: if bdm.volume_id != old_volume_id: continue try: self.compute_api.swap_volume(context, instance, old_volume, new_volume) found = True break except exception.VolumeUnattached: # The volume is not attached. Treat it as NotFound # by falling through. pass except exception.InstanceIsLocked as e: raise exc.HTTPConflict(explanation=e.format_message()) except exception.InstanceInvalidState as state_error: common.raise_http_conflict_for_instance_invalid_state(state_error, 'swap_volume', server_id) if not found: msg = _("volume_id not found: %s") % old_volume_id raise exc.HTTPNotFound(explanation=msg) else: return webob.Response(status_int=202)
def delete(self, req, server_id, id): """Detach a volume from an instance.""" context = req.environ['nova.context'] authorize(context) authorize_attach(context, action='delete') volume_id = id LOG.info(_LI("Detach volume %s"), volume_id, context=context) instance = common.get_instance(self.compute_api, context, server_id) volume = self.volume_api.get(context, volume_id) bdms = objects.BlockDeviceMappingList.get_by_instance_uuid( context, instance.uuid) if not bdms: msg = _("Instance %s is not attached.") % server_id raise exc.HTTPNotFound(explanation=msg) found = False try: for bdm in bdms: if bdm.volume_id != volume_id: continue if bdm.is_root: msg = _("Can't detach root device volume") raise exc.HTTPForbidden(explanation=msg) try: self.compute_api.detach_volume(context, instance, volume) found = True break except exception.VolumeUnattached: # The volume is not attached. Treat it as NotFound # by falling through. pass except exception.InstanceIsLocked as e: raise exc.HTTPConflict(explanation=e.format_message()) except exception.InstanceInvalidState as state_error: common.raise_http_conflict_for_instance_invalid_state(state_error, 'detach_volume', server_id) if not found: msg = _("volume_id not found: %s") % volume_id raise exc.HTTPNotFound(explanation=msg) else: return webob.Response(status_int=202)