glance_store/glance_store/common/cinder_utils.py

195 lines
8.1 KiB
Python

# Copyright 2021 RedHat Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from cinderclient.apiclient import exceptions as apiclient_exception
from cinderclient import exceptions as cinder_exception
from keystoneauth1 import exceptions as keystone_exc
from oslo_utils import excutils
import retrying
from glance_store import exceptions
from glance_store.i18n import _LE
LOG = logging.getLogger(__name__)
def handle_exceptions(method):
"""Transforms the exception for the volume but keeps its traceback intact.
"""
def wrapper(self, ctx, volume_id, *args, **kwargs):
try:
res = method(self, ctx, volume_id, *args, **kwargs)
except (keystone_exc.NotFound,
cinder_exception.NotFound,
cinder_exception.OverLimit) as e:
raise exceptions.BackendException(str(e))
return res
return wrapper
def _retry_on_internal_server_error(e):
if isinstance(e, apiclient_exception.InternalServerError):
return True
return False
class API(object):
"""API for interacting with the cinder."""
@handle_exceptions
def create(self, client, size, name,
volume_type=None, metadata=None):
kwargs = dict(volume_type=volume_type,
metadata=metadata,
name=name)
volume = client.volumes.create(size, **kwargs)
return volume
@handle_exceptions
def delete(self, client, volume_id):
client.volumes.delete(volume_id)
@handle_exceptions
def attachment_create(self, client, volume_id, connector=None,
mountpoint=None, mode=None):
"""Create a volume attachment. This requires microversion >= 3.54.
The attachment_create call was introduced in microversion 3.27. We
need 3.54 as minimum here as we need attachment_complete to finish the
attaching process and it which was introduced in version 3.44 and
we also pass the attach mode which was introduced in version 3.54.
:param client: cinderclient object
:param volume_id: UUID of the volume on which to create the attachment.
:param connector: host connector dict; if None, the attachment will
be 'reserved' but not yet attached.
:param mountpoint: Optional mount device name for the attachment,
e.g. "/dev/vdb". This is only used if a connector is provided.
:param mode: The mode in which the attachment is made i.e.
read only(ro) or read/write(rw)
:returns: a dict created from the
cinderclient.v3.attachments.VolumeAttachment object with a backward
compatible connection_info dict
"""
if connector and mountpoint and 'mountpoint' not in connector:
connector['mountpoint'] = mountpoint
try:
attachment_ref = client.attachments.create(
volume_id, connector, mode=mode)
return attachment_ref
except cinder_exception.ClientException as ex:
with excutils.save_and_reraise_exception():
LOG.error(_LE('Create attachment failed for volume '
'%(volume_id)s. Error: %(msg)s Code: %(code)s'),
{'volume_id': volume_id,
'msg': str(ex),
'code': getattr(ex, 'code', None)})
@handle_exceptions
def attachment_get(self, client, attachment_id):
"""Gets a volume attachment.
:param client: cinderclient object
:param attachment_id: UUID of the volume attachment to get.
:returns: a dict created from the
cinderclient.v3.attachments.VolumeAttachment object with a backward
compatible connection_info dict
"""
try:
attachment_ref = client.attachments.show(
attachment_id)
return attachment_ref
except cinder_exception.ClientException as ex:
with excutils.save_and_reraise_exception():
LOG.error(_LE('Show attachment failed for attachment '
'%(id)s. Error: %(msg)s Code: %(code)s'),
{'id': attachment_id,
'msg': str(ex),
'code': getattr(ex, 'code', None)})
@handle_exceptions
def attachment_update(self, client, attachment_id, connector,
mountpoint=None):
"""Updates the connector on the volume attachment. An attachment
without a connector is considered reserved but not fully attached.
:param client: cinderclient object
:param attachment_id: UUID of the volume attachment to update.
:param connector: host connector dict. This is required when updating
a volume attachment. To terminate a connection, the volume
attachment for that connection must be deleted.
:param mountpoint: Optional mount device name for the attachment,
e.g. "/dev/vdb". Theoretically this is optional per volume backend,
but in practice it's normally required so it's best to always
provide a value.
:returns: a dict created from the
cinderclient.v3.attachments.VolumeAttachment object with a backward
compatible connection_info dict
"""
if mountpoint and 'mountpoint' not in connector:
connector['mountpoint'] = mountpoint
try:
attachment_ref = client.attachments.update(
attachment_id, connector)
return attachment_ref
except cinder_exception.ClientException as ex:
with excutils.save_and_reraise_exception():
LOG.error(_LE('Update attachment failed for attachment '
'%(id)s. Error: %(msg)s Code: %(code)s'),
{'id': attachment_id,
'msg': str(ex),
'code': getattr(ex, 'code', None)})
@handle_exceptions
def attachment_complete(self, client, attachment_id):
"""Marks a volume attachment complete.
This call should be used to inform Cinder that a volume attachment is
fully connected on the host so Cinder can apply the necessary state
changes to the volume info in its database.
:param client: cinderclient object
:param attachment_id: UUID of the volume attachment to update.
"""
try:
client.attachments.complete(attachment_id)
except cinder_exception.ClientException as ex:
with excutils.save_and_reraise_exception():
LOG.error(_LE('Complete attachment failed for attachment '
'%(id)s. Error: %(msg)s Code: %(code)s'),
{'id': attachment_id,
'msg': str(ex),
'code': getattr(ex, 'code', None)})
@handle_exceptions
@retrying.retry(stop_max_attempt_number=5,
retry_on_exception=_retry_on_internal_server_error)
def attachment_delete(self, client, attachment_id):
try:
client.attachments.delete(attachment_id)
except cinder_exception.ClientException as ex:
with excutils.save_and_reraise_exception():
LOG.error(_LE('Delete attachment failed for attachment '
'%(id)s. Error: %(msg)s Code: %(code)s'),
{'id': attachment_id,
'msg': str(ex),
'code': getattr(ex, 'code', None)})