157 lines
6.2 KiB
Python
157 lines
6.2 KiB
Python
# 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 six
|
|
|
|
from cinderclient import exceptions as cinder_exception
|
|
from oslo_log import log as logging
|
|
from oslo_utils import excutils
|
|
from oslo_utils import uuidutils
|
|
|
|
from zun.common import clients
|
|
from zun.common import exception
|
|
from zun.common.i18n import _
|
|
import zun.conf
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
CONF = zun.conf.CONF
|
|
|
|
|
|
class CinderAPI(object):
|
|
|
|
def __init__(self, context):
|
|
self.context = context
|
|
self.cinder = clients.OpenStackClients(self.context).cinder()
|
|
|
|
def __getattr__(self, key):
|
|
return getattr(self.cinder, key)
|
|
|
|
def get(self, volume_id):
|
|
return self.cinder.volumes.get(volume_id)
|
|
|
|
def search_volume(self, volume):
|
|
if uuidutils.is_uuid_like(volume):
|
|
try:
|
|
volume = self.cinder.volumes.get(volume)
|
|
except cinder_exception.NotFound:
|
|
raise exception.VolumeNotFound(volume=volume)
|
|
else:
|
|
try:
|
|
volume = self.cinder.volumes.find(name=volume)
|
|
except cinder_exception.NotFound:
|
|
raise exception.VolumeNotFound(volume=volume)
|
|
except cinder_exception.NoUniqueMatch:
|
|
raise exception.Conflict(_(
|
|
'Multiple cinder volumes exist with same name. '
|
|
'Please use the uuid instead.'))
|
|
|
|
return volume
|
|
|
|
def ensure_volume_usable(self, volume):
|
|
# Make sure the container has access to the volume.
|
|
if hasattr(volume, 'os-vol-tenant-attr:tenant_id'):
|
|
project_id = self.context.project_id
|
|
if getattr(volume, 'os-vol-tenant-attr:tenant_id') != project_id:
|
|
raise exception.VolumeNotUsable(volume=volume.id)
|
|
|
|
if volume.attachments and not volume.multiattach:
|
|
raise exception.VolumeInUse(volume=volume.id)
|
|
|
|
def reserve_volume(self, volume_id):
|
|
return self.cinder.volumes.reserve(volume_id)
|
|
|
|
def unreserve_volume(self, volume_id):
|
|
return self.cinder.volumes.unreserve(volume_id)
|
|
|
|
def initialize_connection(self, volume_id, connector):
|
|
try:
|
|
connection_info = self.cinder.volumes.initialize_connection(
|
|
volume_id, connector)
|
|
return connection_info
|
|
except cinder_exception.ClientException as ex:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error('Initialize connection failed for volume '
|
|
'%(vol)s on host %(host)s. Error: %(msg)s '
|
|
'Code: %(code)s. Attempting to terminate '
|
|
'connection.',
|
|
{'vol': volume_id,
|
|
'host': connector.get('host'),
|
|
'msg': six.text_type(ex),
|
|
'code': ex.code})
|
|
try:
|
|
self.terminate_connection(volume_id, connector)
|
|
except Exception as exc:
|
|
LOG.error('Connection between volume %(vol)s and host '
|
|
'%(host)s might have succeeded, but attempt '
|
|
'to terminate connection has failed. '
|
|
'Validate the connection and determine if '
|
|
'manual cleanup is needed. Error: %(msg)s '
|
|
'Code: %(code)s.',
|
|
{'vol': volume_id,
|
|
'host': connector.get('host'),
|
|
'msg': six.text_type(exc),
|
|
'code': (exc.code
|
|
if hasattr(exc, 'code') else None)})
|
|
|
|
def terminate_connection(self, volume_id, connector):
|
|
return self.cinder.volumes.terminate_connection(volume_id, connector)
|
|
|
|
def attach(self, volume_id, mountpoint, hostname):
|
|
return self.cinder.volumes.attach(volume=volume_id,
|
|
instance_uuid=None,
|
|
mountpoint=mountpoint,
|
|
host_name=hostname)
|
|
|
|
def detach(self, volume_id):
|
|
attachment_id = None
|
|
volume = self.get(volume_id)
|
|
attachments = volume.attachments or {}
|
|
for am in attachments:
|
|
if am['host_name'].lower() == CONF.host.lower():
|
|
attachment_id = am['attachment_id']
|
|
break
|
|
|
|
if attachment_id is None and volume.multiattach:
|
|
LOG.warning("attachment_id couldn't be retrieved for "
|
|
"volume %(volume_id)s. The volume has the "
|
|
"'multiattach' flag enabled, without the "
|
|
"attachment_id Cinder most probably "
|
|
"cannot perform the detach.",
|
|
{'volume_id': volume_id})
|
|
|
|
return self.cinder.volumes.detach(volume_id, attachment_id)
|
|
|
|
def begin_detaching(self, volume_id):
|
|
self.cinder.volumes.begin_detaching(volume_id)
|
|
|
|
def roll_detaching(self, volume_id):
|
|
self.cinder.volumes.roll_detaching(volume_id)
|
|
|
|
def create_volume(self, size):
|
|
try:
|
|
volume = self.cinder.volumes.create(size)
|
|
except cinder_exception.ClientException as ex:
|
|
LOG.error('Volume creation failed: %(ex)s', {'ex': ex})
|
|
raise exception.VolumeCreateFailed(creation_failed=ex)
|
|
|
|
return volume
|
|
|
|
def delete_volume(self, volume_id):
|
|
try:
|
|
self.cinder.volumes.delete(volume_id)
|
|
except cinder_exception.ClientException as ex:
|
|
LOG.error('Volume deletion failed: %(ex)s',
|
|
{'ex': ex})
|
|
raise exception.VolumeDeleteFailed(deletion_failed=ex)
|