195 lines
8.1 KiB
Python
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)})
|