179c2e3771
The official service type name for cinder is not volume (or volumevN) but block-storage. Use the block-storage type to detect availability of cinder, in addition to legacy volume/volumev3 service type. 'block-store' is also a valid alias and should be added as well. Closes-Bug: #2084794 Change-Id: Ifbeaba033c6dae0fa704a2be568b2f4e2cb7426a
1246 lines
40 KiB
Python
1246 lines
40 KiB
Python
# Copyright 2012 United States Government as represented by the
|
|
# Administrator of the National Aeronautics and Space Administration.
|
|
# All Rights Reserved.
|
|
#
|
|
# Copyright 2012 OpenStack Foundation
|
|
# Copyright 2012 Nebula, Inc.
|
|
# Copyright (c) 2012 X.commerce, a business unit of eBay Inc.
|
|
#
|
|
# 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
|
|
import math
|
|
|
|
from django.conf import settings
|
|
from django.utils.translation import gettext_lazy as _
|
|
from django.utils.translation import pgettext_lazy
|
|
|
|
from cinderclient import api_versions
|
|
from cinderclient import client as cinder_client
|
|
from cinderclient import exceptions as cinder_exception
|
|
from cinderclient.v3.contrib import list_extensions as cinder_list_extensions
|
|
|
|
from horizon import exceptions
|
|
from horizon.utils.memoized import memoized
|
|
|
|
from openstack_dashboard.api import _nova
|
|
from openstack_dashboard.api import base
|
|
from openstack_dashboard.api import microversions
|
|
from openstack_dashboard.contrib.developer.profiler import api as profiler
|
|
from openstack_dashboard.utils import settings as utils
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
# API static values
|
|
VOLUME_STATE_AVAILABLE = "available"
|
|
DEFAULT_QUOTA_NAME = 'default'
|
|
|
|
# Available consumer choices associated with QOS Specs
|
|
CONSUMER_CHOICES = (
|
|
('back-end', _('back-end')),
|
|
('front-end', _('front-end')),
|
|
('both', pgettext_lazy('Both of front-end and back-end', 'both')),
|
|
)
|
|
|
|
VERSIONS = base.APIVersionManager("volume", preferred_version='3')
|
|
|
|
try:
|
|
# pylint: disable=ungrouped-imports
|
|
from cinderclient.v3 import client as cinder_client_v3
|
|
VERSIONS.load_supported_version('3', {"client": cinder_client_v3,
|
|
"version": '3'})
|
|
except ImportError:
|
|
pass
|
|
|
|
|
|
class BaseCinderAPIResourceWrapper(base.APIResourceWrapper):
|
|
|
|
@property
|
|
def name(self):
|
|
# If a volume doesn't have a name, use its id.
|
|
return (getattr(self._apiresource, 'name', None) or
|
|
getattr(self._apiresource, 'id', None))
|
|
|
|
@property
|
|
def description(self):
|
|
return (getattr(self._apiresource, 'description', None) or
|
|
getattr(self._apiresource, 'display_description', None))
|
|
|
|
|
|
class Volume(BaseCinderAPIResourceWrapper):
|
|
|
|
_attrs = ['id', 'name', 'description', 'size', 'status', 'created_at',
|
|
'volume_type', 'availability_zone', 'imageRef', 'bootable',
|
|
'snapshot_id', 'source_volid', 'attachments', 'tenant_name',
|
|
'group_id', 'consistencygroup_id', 'os-vol-host-attr:host',
|
|
'os-vol-tenant-attr:tenant_id', 'metadata',
|
|
'volume_image_metadata', 'encrypted', 'transfer',
|
|
'multiattach']
|
|
|
|
@property
|
|
def is_bootable(self):
|
|
return self.bootable == 'true'
|
|
|
|
@property
|
|
def tenant_id(self):
|
|
return getattr(self, 'os-vol-tenant-attr:tenant_id', "")
|
|
|
|
|
|
class VolumeSnapshot(BaseCinderAPIResourceWrapper):
|
|
|
|
_attrs = ['id', 'name', 'description', 'size', 'status',
|
|
'created_at', 'volume_id', 'group_snapshot_id',
|
|
'os-extended-snapshot-attributes:project_id',
|
|
'metadata']
|
|
|
|
@property
|
|
def project_id(self):
|
|
return getattr(self, 'os-extended-snapshot-attributes:project_id', "")
|
|
|
|
|
|
class VolumeType(BaseCinderAPIResourceWrapper):
|
|
|
|
_attrs = ['id', 'name', 'extra_specs', 'created_at', 'encryption',
|
|
'associated_qos_spec', 'description',
|
|
'os-extended-snapshot-attributes:project_id']
|
|
|
|
|
|
class VolumeBackup(BaseCinderAPIResourceWrapper):
|
|
|
|
_attrs = ['id', 'name', 'description', 'container', 'size', 'status',
|
|
'created_at', 'volume_id', 'availability_zone', 'snapshot_id',
|
|
'os-backup-project-attr:project_id', 'fail_reason']
|
|
_volume = None
|
|
_snapshot = None
|
|
|
|
@property
|
|
def volume(self):
|
|
return self._volume
|
|
|
|
@volume.setter
|
|
def volume(self, value):
|
|
self._volume = value
|
|
|
|
@property
|
|
def snapshot(self):
|
|
return self._snapshot
|
|
|
|
@snapshot.setter
|
|
def snapshot(self, value):
|
|
self._snapshot = value
|
|
|
|
@property
|
|
def project_id(self):
|
|
return getattr(self, 'os-backup-project-attr:project_id', "")
|
|
|
|
|
|
class QosSpecs(BaseCinderAPIResourceWrapper):
|
|
|
|
_attrs = ['id', 'name', 'consumer', 'specs']
|
|
|
|
|
|
class VolTypeExtraSpec(object):
|
|
def __init__(self, type_id, key, val):
|
|
self.type_id = type_id
|
|
self.id = key
|
|
self.key = key
|
|
self.value = val
|
|
|
|
|
|
class GroupTypeSpec(object):
|
|
def __init__(self, group_type_id, key, val):
|
|
self.group_type_id = group_type_id
|
|
self.id = key
|
|
self.key = key
|
|
self.value = val
|
|
|
|
|
|
class QosSpec(object):
|
|
def __init__(self, id, key, val):
|
|
self.id = id
|
|
self.key = key
|
|
self.value = val
|
|
|
|
|
|
class VolumeTransfer(base.APIResourceWrapper):
|
|
|
|
_attrs = ['id', 'name', 'created_at', 'volume_id', 'auth_key']
|
|
|
|
|
|
class VolumePool(base.APIResourceWrapper):
|
|
|
|
_attrs = ['name', 'pool_name', 'total_capacity_gb', 'free_capacity_gb',
|
|
'allocated_capacity_gb', 'QoS_support', 'reserved_percentage',
|
|
'volume_backend_name', 'vendor_name', 'driver_version',
|
|
'storage_protocol', 'extra_specs']
|
|
|
|
|
|
class Message(base.APIResourceWrapper):
|
|
_attrs = ['id', 'event_id', 'created_at', 'resource_type', 'user_message']
|
|
|
|
|
|
class Group(base.APIResourceWrapper):
|
|
_attrs = ['id', 'status', 'availability_zone', 'created_at', 'name',
|
|
'description', 'group_type', 'volume_types',
|
|
'group_snapshot_id', 'source_group_id', 'replication_status',
|
|
'project_id']
|
|
|
|
|
|
class GroupSnapshot(base.APIResourceWrapper):
|
|
_attrs = ['id', 'name', 'description', 'status', 'created_at',
|
|
'group_id', 'group_type_id', 'project_id']
|
|
|
|
|
|
class GroupType(base.APIResourceWrapper):
|
|
_attrs = ['id', 'name', 'description', 'is_public', 'group_specs']
|
|
|
|
|
|
def _find_cinder_url(request, version=None):
|
|
if version is None:
|
|
api_version = VERSIONS.get_active_version()
|
|
version = api_version['version']
|
|
version = base.Version(version)
|
|
|
|
# We support only cinder v3.
|
|
candidates = ['block-storage', 'block-store', 'volumev3', 'volume']
|
|
|
|
for service_name in candidates:
|
|
try:
|
|
return version, base.url_for(request, service_name)
|
|
except exceptions.ServiceCatalogException:
|
|
pass
|
|
else:
|
|
raise exceptions.ServiceCatalogException(
|
|
("Cinder %(version)s requested but no '%(service)s' service "
|
|
"type available in Keystone catalog.") %
|
|
{'version': version, 'service': candidates})
|
|
|
|
|
|
@memoized
|
|
def cinderclient(request, version=None):
|
|
version, cinder_url = _find_cinder_url(request, version)
|
|
|
|
insecure = settings.OPENSTACK_SSL_NO_VERIFY
|
|
cacert = settings.OPENSTACK_SSL_CACERT
|
|
|
|
c = cinder_client.Client(
|
|
version,
|
|
request.user.username,
|
|
request.user.token.id,
|
|
project_id=request.user.tenant_id,
|
|
auth_url=base.url_for(request, 'identity'),
|
|
insecure=insecure,
|
|
cacert=cacert,
|
|
http_log_debug=settings.DEBUG,
|
|
)
|
|
c.client.auth_token = request.user.token.id
|
|
c.client.management_url = cinder_url
|
|
return c
|
|
|
|
|
|
def get_microversion(request, features):
|
|
try:
|
|
version, cinder_url = _find_cinder_url(request)
|
|
except exceptions.ServiceCatalogException:
|
|
return None
|
|
|
|
insecure = settings.OPENSTACK_SSL_NO_VERIFY
|
|
cacert = settings.OPENSTACK_SSL_CACERT
|
|
|
|
min_ver, max_ver = cinder_client.get_server_version(cinder_url,
|
|
insecure, cacert)
|
|
return microversions.get_microversion_for_features(
|
|
'cinder', features, api_versions.APIVersion, min_ver, max_ver)
|
|
|
|
|
|
def _cinderclient_with_features(request, features,
|
|
raise_exc=False, message=False):
|
|
version = get_microversion(request, features)
|
|
if version is None:
|
|
if message:
|
|
versions = microversions.get_requested_versions('cinder', features)
|
|
if message is True:
|
|
message = ('Insufficient microversion for cinder feature(s) '
|
|
'%(features)s. One of the following API '
|
|
'microversion(s) is required: %(versions).')
|
|
LOG.warning(message,
|
|
{'features': features, 'versions': versions})
|
|
if raise_exc:
|
|
raise microversions.MicroVersionNotFound(features)
|
|
if version is not None:
|
|
version = version.get_string()
|
|
return cinderclient(request, version=version)
|
|
|
|
|
|
def _cinderclient_with_generic_groups(request):
|
|
return _cinderclient_with_features(request, 'groups')
|
|
|
|
|
|
def version_get():
|
|
api_version = VERSIONS.get_active_version()
|
|
return api_version['version']
|
|
|
|
|
|
def volume_list(request, search_opts=None, marker=None, sort_dir="desc"):
|
|
volumes, _, __ = volume_list_paged(
|
|
request, search_opts=search_opts, marker=marker, paginate=False,
|
|
sort_dir=sort_dir)
|
|
return volumes
|
|
|
|
|
|
def update_pagination(entities, page_size, marker, sort_dir):
|
|
has_more_data, has_prev_data = False, False
|
|
if len(entities) > page_size:
|
|
has_more_data = True
|
|
entities.pop()
|
|
if marker is not None:
|
|
has_prev_data = True
|
|
# first page condition when reached via prev back
|
|
elif sort_dir == 'asc' and marker is not None:
|
|
has_more_data = True
|
|
# last page condition
|
|
elif marker is not None:
|
|
has_prev_data = True
|
|
|
|
if sort_dir == 'asc':
|
|
entities.reverse()
|
|
|
|
return entities, has_more_data, has_prev_data
|
|
|
|
|
|
@profiler.trace
|
|
def volume_list_paged(request, search_opts=None, marker=None, paginate=False,
|
|
sort_dir="desc"):
|
|
"""List volumes with pagination.
|
|
|
|
To see all volumes in the cloud as an admin you can pass in a special
|
|
search option: {'all_tenants': 1}
|
|
"""
|
|
has_more_data = False
|
|
has_prev_data = False
|
|
volumes = []
|
|
|
|
# To support filtering with group_id, we need to use the microversion.
|
|
c_client = _cinderclient_with_generic_groups(request)
|
|
if c_client is None:
|
|
return volumes, has_more_data, has_prev_data
|
|
|
|
# build a dictionary of volume_id -> transfer
|
|
transfers = {t.volume_id: t
|
|
for t in transfer_list(request, search_opts=search_opts)}
|
|
|
|
if paginate:
|
|
page_size = utils.get_page_size(request)
|
|
# sort_key and sort_dir deprecated in kilo, use sort
|
|
# if pagination is true, we use a single sort parameter
|
|
# by default, it is "created_at"
|
|
sort = 'created_at:' + sort_dir
|
|
for v in c_client.volumes.list(search_opts=search_opts,
|
|
limit=page_size + 1,
|
|
marker=marker,
|
|
sort=sort):
|
|
v.transfer = transfers.get(v.id)
|
|
volumes.append(Volume(v))
|
|
volumes, has_more_data, has_prev_data = update_pagination(
|
|
volumes, page_size, marker, sort_dir)
|
|
else:
|
|
for v in c_client.volumes.list(search_opts=search_opts):
|
|
v.transfer = transfers.get(v.id)
|
|
volumes.append(Volume(v))
|
|
|
|
return volumes, has_more_data, has_prev_data
|
|
|
|
|
|
@profiler.trace
|
|
def volume_get(request, volume_id):
|
|
client = _cinderclient_with_generic_groups(request)
|
|
volume_data = client.volumes.get(volume_id)
|
|
|
|
for attachment in volume_data.attachments:
|
|
if "server_id" in attachment:
|
|
instance = _nova.server_get(request, attachment['server_id'])
|
|
attachment['instance_name'] = instance.name
|
|
else:
|
|
# Nova volume can occasionally send back error'd attachments
|
|
# the lack a server_id property; to work around that we'll
|
|
# give the attached instance a generic name.
|
|
attachment['instance_name'] = _("Unknown instance")
|
|
|
|
volume_data.transfer = None
|
|
if volume_data.status == 'awaiting-transfer':
|
|
for transfer in transfer_list(request):
|
|
if transfer.volume_id == volume_id:
|
|
volume_data.transfer = transfer
|
|
break
|
|
|
|
return Volume(volume_data)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_create(request, size, name, description, volume_type,
|
|
snapshot_id=None, metadata=None, image_id=None,
|
|
availability_zone=None, source_volid=None,
|
|
group_id=None):
|
|
client = _cinderclient_with_generic_groups(request)
|
|
data = {'name': name,
|
|
'description': description,
|
|
'volume_type': volume_type,
|
|
'snapshot_id': snapshot_id,
|
|
'metadata': metadata,
|
|
'imageRef': image_id,
|
|
'availability_zone': availability_zone,
|
|
'source_volid': source_volid,
|
|
'group_id': group_id}
|
|
|
|
volume = client.volumes.create(size, **data)
|
|
return Volume(volume)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_extend(request, volume_id, new_size):
|
|
client = _cinderclient_with_features(request,
|
|
'extend_in_use_volume')
|
|
return client.volumes.extend(volume_id, new_size)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_delete(request, volume_id):
|
|
return cinderclient(request).volumes.delete(volume_id)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_retype(request, volume_id, new_type, migration_policy):
|
|
return cinderclient(request).volumes.retype(volume_id,
|
|
new_type,
|
|
migration_policy)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_set_bootable(request, volume_id, bootable):
|
|
return cinderclient(request).volumes.set_bootable(volume_id,
|
|
bootable)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_update(request, volume_id, name, description):
|
|
vol_data = {'name': name,
|
|
'description': description}
|
|
return cinderclient(request).volumes.update(volume_id,
|
|
**vol_data)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_set_metadata(request, volume_id, metadata):
|
|
return cinderclient(request).volumes.set_metadata(volume_id, metadata)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_delete_metadata(request, volume_id, keys):
|
|
return cinderclient(request).volumes.delete_metadata(volume_id, keys)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_reset_state(request, volume_id, state):
|
|
cinderclient(request).volumes.reset_state(volume_id, state)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_upload_to_image(request, volume_id, force, image_name,
|
|
container_format, disk_format):
|
|
return cinderclient(request).volumes.upload_to_image(volume_id,
|
|
force,
|
|
image_name,
|
|
container_format,
|
|
disk_format)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_get_encryption_metadata(request, volume_id):
|
|
return cinderclient(request).volumes.get_encryption_metadata(volume_id)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_migrate(request, volume_id, host, force_host_copy=False,
|
|
lock_volume=False):
|
|
return cinderclient(request).volumes.migrate_volume(volume_id,
|
|
host,
|
|
force_host_copy,
|
|
lock_volume)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_snapshot_get(request, snapshot_id):
|
|
client = _cinderclient_with_generic_groups(request)
|
|
snapshot = client.volume_snapshots.get(snapshot_id)
|
|
return VolumeSnapshot(snapshot)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_snapshot_list(request, search_opts=None):
|
|
snapshots, _, __ = volume_snapshot_list_paged(request,
|
|
search_opts=search_opts,
|
|
paginate=False)
|
|
return snapshots
|
|
|
|
|
|
@profiler.trace
|
|
def volume_snapshot_list_paged(request, search_opts=None, marker=None,
|
|
paginate=False, sort_dir="desc"):
|
|
has_more_data = False
|
|
has_prev_data = False
|
|
snapshots = []
|
|
c_client = _cinderclient_with_generic_groups(request)
|
|
if c_client is None:
|
|
return snapshots, has_more_data, has_more_data
|
|
|
|
if paginate:
|
|
page_size = utils.get_page_size(request)
|
|
# sort_key and sort_dir deprecated in kilo, use sort
|
|
# if pagination is true, we use a single sort parameter
|
|
# by default, it is "created_at"
|
|
sort = 'created_at:' + sort_dir
|
|
for s in c_client.volume_snapshots.list(search_opts=search_opts,
|
|
limit=page_size + 1,
|
|
marker=marker,
|
|
sort=sort):
|
|
snapshots.append(VolumeSnapshot(s))
|
|
|
|
snapshots, has_more_data, has_prev_data = update_pagination(
|
|
snapshots, page_size, marker, sort_dir)
|
|
else:
|
|
for s in c_client.volume_snapshots.list(search_opts=search_opts):
|
|
snapshots.append(VolumeSnapshot(s))
|
|
|
|
return snapshots, has_more_data, has_prev_data
|
|
|
|
|
|
@profiler.trace
|
|
def volume_snapshot_create(request, volume_id, name,
|
|
description=None, force=False):
|
|
data = {'name': name,
|
|
'description': description,
|
|
'force': force}
|
|
|
|
return VolumeSnapshot(cinderclient(request).volume_snapshots.create(
|
|
volume_id, **data))
|
|
|
|
|
|
@profiler.trace
|
|
def volume_snapshot_delete(request, snapshot_id):
|
|
return cinderclient(request).volume_snapshots.delete(snapshot_id)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_snapshot_update(request, snapshot_id, name, description):
|
|
snapshot_data = {'name': name,
|
|
'description': description}
|
|
return cinderclient(request).volume_snapshots.update(snapshot_id,
|
|
**snapshot_data)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_snapshot_set_metadata(request, snapshot_id, metadata):
|
|
return cinderclient(request).volume_snapshots.set_metadata(
|
|
snapshot_id, metadata)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_snapshot_delete_metadata(request, snapshot_id, keys):
|
|
return cinderclient(request).volume_snapshots.delete_metadata(
|
|
snapshot_id, keys)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_snapshot_reset_state(request, snapshot_id, state):
|
|
return cinderclient(request).volume_snapshots.reset_state(
|
|
snapshot_id, state)
|
|
|
|
|
|
@memoized
|
|
def volume_backup_supported(request):
|
|
"""This method will determine if cinder supports backup."""
|
|
# TODO(lcheng) Cinder does not expose the information if cinder
|
|
# backup is configured yet. This is a workaround until that
|
|
# capability is available.
|
|
# https://bugs.launchpad.net/cinder/+bug/1334856
|
|
return utils.get_dict_config('OPENSTACK_CINDER_FEATURES', 'enable_backup')
|
|
|
|
|
|
@profiler.trace
|
|
def volume_backup_get(request, backup_id):
|
|
backup = cinderclient(request, '3.18').backups.get(backup_id)
|
|
return VolumeBackup(backup)
|
|
|
|
|
|
def volume_backup_list(request, search_opts=None):
|
|
backups, _, __ = volume_backup_list_paged(request, paginate=False,
|
|
search_opts=search_opts)
|
|
return backups
|
|
|
|
|
|
@profiler.trace
|
|
def volume_backup_list_paged_with_page_menu(request, search_opts=None,
|
|
page_number=1,
|
|
sort_dir="desc",
|
|
all_tenants=False):
|
|
backups = []
|
|
count = 0
|
|
pages_count = 0
|
|
if search_opts is None:
|
|
search_opts = {}
|
|
page_size = utils.get_page_size(request)
|
|
c_client = cinderclient(request, '3.45')
|
|
|
|
if c_client is None:
|
|
return backups, 0, count, pages_count
|
|
|
|
offset = (page_number - 1) * page_size
|
|
sort = 'created_at:' + sort_dir
|
|
search_opts['all_tenants'] = all_tenants
|
|
search_opts['offset'] = offset
|
|
search_opts['with_count'] = True
|
|
bkps, count = c_client.backups.list(limit=page_size,
|
|
sort=sort,
|
|
search_opts=search_opts)
|
|
if not bkps:
|
|
return backups, page_size, count, pages_count
|
|
|
|
if isinstance(bkps[0], list):
|
|
bkps = bkps[0]
|
|
pages_count = int(math.ceil(float(count) / float(page_size)))
|
|
for b in bkps:
|
|
backups.append(VolumeBackup(b))
|
|
|
|
return backups, page_size, count, pages_count
|
|
|
|
|
|
@profiler.trace
|
|
def volume_backup_list_paged(request, marker=None, paginate=False,
|
|
sort_dir="desc", search_opts=None):
|
|
has_more_data = False
|
|
has_prev_data = False
|
|
backups = []
|
|
|
|
c_client = cinderclient(request)
|
|
if c_client is None:
|
|
return backups, has_more_data, has_prev_data
|
|
|
|
if paginate:
|
|
page_size = utils.get_page_size(request)
|
|
# sort_key and sort_dir deprecated in kilo, use sort
|
|
# if pagination is true, we use a single sort parameter
|
|
# by default, it is "created_at"
|
|
sort = 'created_at:' + sort_dir
|
|
for b in c_client.backups.list(limit=page_size + 1,
|
|
marker=marker,
|
|
sort=sort, search_opts=search_opts):
|
|
backups.append(VolumeBackup(b))
|
|
|
|
backups, has_more_data, has_prev_data = update_pagination(
|
|
backups, page_size, marker, sort_dir)
|
|
else:
|
|
for b in c_client.backups.list(search_opts=search_opts):
|
|
backups.append(VolumeBackup(b))
|
|
|
|
return backups, has_more_data, has_prev_data
|
|
|
|
|
|
@profiler.trace
|
|
def volume_backup_create(request,
|
|
volume_id,
|
|
container_name,
|
|
name,
|
|
description,
|
|
force=False,
|
|
incremental=False,
|
|
snapshot_id=None):
|
|
# need to ensure the container name is not an empty
|
|
# string, but pass None to get the container name
|
|
# generated correctly
|
|
backup = cinderclient(request).backups.create(
|
|
volume_id,
|
|
container=container_name if container_name else None,
|
|
name=name,
|
|
description=description,
|
|
snapshot_id=snapshot_id,
|
|
incremental=incremental,
|
|
force=force)
|
|
return VolumeBackup(backup)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_backup_delete(request, backup_id, force=None):
|
|
return cinderclient(request).backups.delete(backup_id, force=force)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_backup_restore(request, backup_id, volume_id):
|
|
return cinderclient(request).restores.restore(backup_id=backup_id,
|
|
volume_id=volume_id)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_backup_reset_state(request, backup_id, state):
|
|
return cinderclient(request).backups.reset_state(
|
|
backup_id, state)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_manage(request,
|
|
host,
|
|
identifier,
|
|
id_type,
|
|
name,
|
|
description,
|
|
volume_type,
|
|
availability_zone,
|
|
metadata,
|
|
bootable):
|
|
source = {id_type: identifier}
|
|
cinderclient(request).volumes.manage(
|
|
host=host,
|
|
ref=source,
|
|
name=name,
|
|
description=description,
|
|
volume_type=volume_type,
|
|
availability_zone=availability_zone,
|
|
metadata=metadata,
|
|
bootable=bootable)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_unmanage(request, volume_id):
|
|
return cinderclient(request).volumes.unmanage(volume=volume_id)
|
|
|
|
|
|
@profiler.trace
|
|
def tenant_quota_get(request, tenant_id):
|
|
c_client = cinderclient(request)
|
|
if c_client is None:
|
|
return base.QuotaSet()
|
|
return base.QuotaSet(c_client.quotas.get(tenant_id))
|
|
|
|
|
|
@profiler.trace
|
|
def tenant_quota_update(request, tenant_id, **kwargs):
|
|
return cinderclient(request).quotas.update(tenant_id, **kwargs)
|
|
|
|
|
|
@profiler.trace
|
|
def default_quota_get(request, tenant_id):
|
|
return base.QuotaSet(cinderclient(request).quotas.defaults(tenant_id))
|
|
|
|
|
|
def volume_type_list_with_qos_associations(request):
|
|
vol_types = volume_type_list(request)
|
|
vol_types_dict = {}
|
|
|
|
# initialize and build a dictionary for lookup access below
|
|
for vol_type in vol_types:
|
|
vol_type.associated_qos_spec = ""
|
|
vol_types_dict[vol_type.id] = vol_type
|
|
|
|
# get all currently defined qos specs
|
|
qos_specs = qos_spec_list(request)
|
|
for qos_spec in qos_specs:
|
|
# get all volume types this qos spec is associated with
|
|
assoc_vol_types = qos_spec_get_associations(request, qos_spec.id)
|
|
for assoc_vol_type in assoc_vol_types:
|
|
# update volume type to hold this association info
|
|
vol_type = vol_types_dict[assoc_vol_type.id]
|
|
vol_type.associated_qos_spec = qos_spec.name
|
|
|
|
return vol_types
|
|
|
|
|
|
def volume_type_get_with_qos_association(request, volume_type_id):
|
|
vol_type = volume_type_get(request, volume_type_id)
|
|
vol_type.associated_qos_spec = ""
|
|
|
|
# get all currently defined qos specs
|
|
qos_specs = qos_spec_list(request)
|
|
for qos_spec in qos_specs:
|
|
# get all volume types this qos spec is associated with
|
|
assoc_vol_types = qos_spec_get_associations(request, qos_spec.id)
|
|
for assoc_vol_type in assoc_vol_types:
|
|
if vol_type.id == assoc_vol_type.id:
|
|
# update volume type to hold this association info
|
|
vol_type.associated_qos_spec = qos_spec.name
|
|
return vol_type
|
|
|
|
return vol_type
|
|
|
|
|
|
@profiler.trace
|
|
def default_quota_update(request, **kwargs):
|
|
cinderclient(request).quota_classes.update(DEFAULT_QUOTA_NAME, **kwargs)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_type_list(request):
|
|
return cinderclient(request).volume_types.list()
|
|
|
|
|
|
@profiler.trace
|
|
def volume_type_create(request, name, description=None, is_public=True):
|
|
return cinderclient(request).volume_types.create(name, description,
|
|
is_public)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_type_update(request, volume_type_id, name=None, description=None,
|
|
is_public=None):
|
|
return cinderclient(request).volume_types.update(volume_type_id,
|
|
name,
|
|
description,
|
|
is_public)
|
|
|
|
|
|
@profiler.trace
|
|
@memoized
|
|
def volume_type_default(request):
|
|
return cinderclient(request).volume_types.default()
|
|
|
|
|
|
@profiler.trace
|
|
def volume_type_delete(request, volume_type_id):
|
|
try:
|
|
return cinderclient(request).volume_types.delete(volume_type_id)
|
|
except cinder_exception.BadRequest:
|
|
raise exceptions.BadRequest(_(
|
|
"This volume type is used by one or more volumes."))
|
|
|
|
|
|
@profiler.trace
|
|
def volume_type_get(request, volume_type_id):
|
|
return cinderclient(request).volume_types.get(volume_type_id)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_encryption_type_create(request, volume_type_id, data):
|
|
return cinderclient(request).volume_encryption_types.create(volume_type_id,
|
|
specs=data)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_encryption_type_delete(request, volume_type_id):
|
|
return cinderclient(request).volume_encryption_types.delete(volume_type_id)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_encryption_type_get(request, volume_type_id):
|
|
return cinderclient(request).volume_encryption_types.get(volume_type_id)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_encryption_type_list(request):
|
|
return cinderclient(request).volume_encryption_types.list()
|
|
|
|
|
|
@profiler.trace
|
|
def volume_encryption_type_update(request, volume_type_id, data):
|
|
return cinderclient(request).volume_encryption_types.update(volume_type_id,
|
|
specs=data)
|
|
|
|
|
|
@profiler.trace
|
|
def volume_type_extra_get(request, type_id, raw=False):
|
|
vol_type = volume_type_get(request, type_id)
|
|
extras = vol_type.get_keys()
|
|
if raw:
|
|
return extras
|
|
return [VolTypeExtraSpec(type_id, key, value) for
|
|
key, value in extras.items()]
|
|
|
|
|
|
def volume_type_extra_set(request, type_id, metadata):
|
|
vol_type = volume_type_get(request, type_id)
|
|
if not metadata:
|
|
return None
|
|
return vol_type.set_keys(metadata)
|
|
|
|
|
|
def volume_type_extra_delete(request, type_id, keys):
|
|
vol_type = volume_type_get(request, type_id)
|
|
return vol_type.unset_keys(keys)
|
|
|
|
|
|
@profiler.trace
|
|
def qos_spec_list(request):
|
|
return cinderclient(request).qos_specs.list()
|
|
|
|
|
|
@profiler.trace
|
|
def qos_spec_get(request, qos_spec_id):
|
|
return cinderclient(request).qos_specs.get(qos_spec_id)
|
|
|
|
|
|
@profiler.trace
|
|
def qos_spec_delete(request, qos_spec_id):
|
|
return cinderclient(request).qos_specs.delete(qos_spec_id, force=True)
|
|
|
|
|
|
@profiler.trace
|
|
def qos_spec_create(request, name, specs):
|
|
return cinderclient(request).qos_specs.create(name, specs)
|
|
|
|
|
|
def qos_spec_get_keys(request, qos_spec_id, raw=False):
|
|
spec = qos_spec_get(request, qos_spec_id)
|
|
qos_specs = spec.specs
|
|
if raw:
|
|
return spec
|
|
return [QosSpec(qos_spec_id, key, value) for
|
|
key, value in qos_specs.items()]
|
|
|
|
|
|
@profiler.trace
|
|
def qos_spec_set_keys(request, qos_spec_id, specs):
|
|
return cinderclient(request).qos_specs.set_keys(qos_spec_id, specs)
|
|
|
|
|
|
@profiler.trace
|
|
def qos_spec_unset_keys(request, qos_spec_id, specs):
|
|
return cinderclient(request).qos_specs.unset_keys(qos_spec_id, specs)
|
|
|
|
|
|
@profiler.trace
|
|
def qos_spec_associate(request, qos_specs, vol_type_id):
|
|
return cinderclient(request).qos_specs.associate(qos_specs, vol_type_id)
|
|
|
|
|
|
@profiler.trace
|
|
def qos_spec_disassociate(request, qos_specs, vol_type_id):
|
|
return cinderclient(request).qos_specs.disassociate(qos_specs, vol_type_id)
|
|
|
|
|
|
@profiler.trace
|
|
def qos_spec_get_associations(request, qos_spec_id):
|
|
return cinderclient(request).qos_specs.get_associations(qos_spec_id)
|
|
|
|
|
|
def qos_specs_list(request):
|
|
return [QosSpecs(s) for s in qos_spec_list(request)]
|
|
|
|
|
|
@profiler.trace
|
|
@memoized
|
|
def tenant_absolute_limits(request, tenant_id=None):
|
|
_cinderclient = _cinderclient_with_features(
|
|
request, ['limits_project_id_query'],
|
|
message=('Insufficient microversion for GET limits with '
|
|
'project_id query. One of the following API micro '
|
|
'version is required: %(versions)s. '
|
|
'This causes bug 1810309 on updating quotas.'))
|
|
limits = _cinderclient.limits.get(tenant_id=tenant_id).absolute
|
|
limits_dict = {}
|
|
for limit in limits:
|
|
if limit.value < 0:
|
|
# In some cases, the absolute limits data in Cinder can get
|
|
# out of sync causing the total.*Used limits to return
|
|
# negative values instead of 0. For such cases, replace
|
|
# negative values with 0.
|
|
if limit.name.startswith('total') and limit.name.endswith('Used'):
|
|
limits_dict[limit.name] = 0
|
|
else:
|
|
# -1 is used to represent unlimited quotas
|
|
limits_dict[limit.name] = float("inf")
|
|
else:
|
|
limits_dict[limit.name] = limit.value
|
|
return limits_dict
|
|
|
|
|
|
@profiler.trace
|
|
def service_list(request):
|
|
return cinderclient(request).services.list()
|
|
|
|
|
|
@profiler.trace
|
|
def availability_zone_list(request, detailed=False):
|
|
return cinderclient(request).availability_zones.list(detailed=detailed)
|
|
|
|
|
|
@profiler.trace
|
|
@memoized
|
|
def list_extensions(request):
|
|
cinder_api = cinderclient(request)
|
|
return tuple(cinder_list_extensions.ListExtManager(cinder_api).show_all())
|
|
|
|
|
|
@memoized
|
|
def extension_supported(request, extension_name):
|
|
"""This method will determine if Cinder supports a given extension name."""
|
|
for extension in list_extensions(request):
|
|
if extension.name == extension_name:
|
|
return True
|
|
return False
|
|
|
|
|
|
@profiler.trace
|
|
def transfer_list(request, detailed=True, search_opts=None):
|
|
"""List volume transfers.
|
|
|
|
To see all volumes transfers as an admin pass in a special
|
|
search option: {'all_tenants': 1}
|
|
"""
|
|
c_client = cinderclient(request)
|
|
try:
|
|
return [VolumeTransfer(v) for v in c_client.transfers.list(
|
|
detailed=detailed, search_opts=search_opts)]
|
|
except cinder_exception.Forbidden as error:
|
|
LOG.error(error)
|
|
return []
|
|
|
|
|
|
@profiler.trace
|
|
def transfer_get(request, transfer_id):
|
|
transfer_data = cinderclient(request).transfers.get(transfer_id)
|
|
return VolumeTransfer(transfer_data)
|
|
|
|
|
|
@profiler.trace
|
|
def transfer_create(request, transfer_id, name):
|
|
volume = cinderclient(request).transfers.create(transfer_id, name)
|
|
return VolumeTransfer(volume)
|
|
|
|
|
|
@profiler.trace
|
|
def transfer_accept(request, transfer_id, auth_key):
|
|
return cinderclient(request).transfers.accept(transfer_id, auth_key)
|
|
|
|
|
|
@profiler.trace
|
|
def transfer_delete(request, transfer_id):
|
|
return cinderclient(request).transfers.delete(transfer_id)
|
|
|
|
|
|
@profiler.trace
|
|
def pool_list(request, detailed=False):
|
|
c_client = cinderclient(request)
|
|
if c_client is None:
|
|
return []
|
|
|
|
return [VolumePool(v) for v in c_client.pools.list(
|
|
detailed=detailed)]
|
|
|
|
|
|
@profiler.trace
|
|
def message_list(request, search_opts=None):
|
|
try:
|
|
c_client = _cinderclient_with_features(request, ['message_list'],
|
|
raise_exc=True, message=True)
|
|
except microversions.MicroVersionNotFound:
|
|
LOG.warning("Insufficient microversion for message_list")
|
|
return []
|
|
return c_client.messages.list(search_opts)
|
|
|
|
|
|
def is_volume_service_enabled(request):
|
|
return bool(
|
|
base.is_service_enabled(request, 'block-storage') or
|
|
base.is_service_enabled(request, 'block-store') or
|
|
base.is_service_enabled(request, 'volumev3') or
|
|
base.is_service_enabled(request, 'volume')
|
|
)
|
|
|
|
|
|
def volume_type_access_list(request, volume_type):
|
|
return cinderclient(request).volume_type_access.list(volume_type)
|
|
|
|
|
|
def volume_type_add_project_access(request, volume_type, project_id):
|
|
return cinderclient(request).volume_type_access.add_project_access(
|
|
volume_type, project_id)
|
|
|
|
|
|
def volume_type_remove_project_access(request, volume_type, project_id):
|
|
return cinderclient(request).volume_type_access.remove_project_access(
|
|
volume_type, project_id)
|
|
|
|
|
|
@profiler.trace
|
|
def group_type_list(request):
|
|
client = _cinderclient_with_generic_groups(request)
|
|
return [GroupType(t) for t in client.group_types.list()]
|
|
|
|
|
|
@profiler.trace
|
|
def group_type_get(request, group_type_id):
|
|
client = _cinderclient_with_generic_groups(request)
|
|
return GroupType(client.group_types.get(group_type_id))
|
|
|
|
|
|
@profiler.trace
|
|
def group_type_create(request, name, description=None, is_public=None):
|
|
client = _cinderclient_with_generic_groups(request)
|
|
params = {'name': name}
|
|
if description is not None:
|
|
params['description'] = description
|
|
if is_public is not None:
|
|
params['is_public'] = is_public
|
|
return GroupType(client.group_types.create(**params))
|
|
|
|
|
|
@profiler.trace
|
|
def group_type_update(request, group_type_id, name=None, description=None,
|
|
is_public=None):
|
|
client = _cinderclient_with_generic_groups(request)
|
|
return GroupType(client.group_types.update(group_type_id,
|
|
name,
|
|
description,
|
|
is_public))
|
|
|
|
|
|
@profiler.trace
|
|
def group_type_delete(request, group_type_id):
|
|
client = _cinderclient_with_generic_groups(request)
|
|
client.group_types.delete(group_type_id)
|
|
|
|
|
|
@profiler.trace
|
|
def group_type_spec_list(request, group_type_id, raw=False):
|
|
group_type = group_type_get(request, group_type_id)
|
|
specs = group_type._apiresource.get_keys()
|
|
if raw:
|
|
return specs
|
|
return [GroupTypeSpec(group_type_id, key, value) for
|
|
key, value in specs.items()]
|
|
|
|
|
|
@profiler.trace
|
|
def group_type_spec_set(request, group_type_id, metadata):
|
|
group_type = group_type_get(request, group_type_id)
|
|
if not metadata:
|
|
return None
|
|
return group_type._apiresource.set_keys(metadata)
|
|
|
|
|
|
@profiler.trace
|
|
def group_type_spec_unset(request, group_type_id, keys):
|
|
group_type = group_type_get(request, group_type_id)
|
|
return group_type._apiresource.unset_keys(keys)
|
|
|
|
|
|
@profiler.trace
|
|
def group_list(request, search_opts=None):
|
|
client = _cinderclient_with_generic_groups(request)
|
|
return [Group(g) for g in client.groups.list(search_opts=search_opts)]
|
|
|
|
|
|
@profiler.trace
|
|
def group_list_with_vol_type_names(request, search_opts=None):
|
|
groups = group_list(request, search_opts)
|
|
vol_types = volume_type_list(request)
|
|
for group in groups:
|
|
group.volume_type_names = []
|
|
for vol_type_id in group.volume_types:
|
|
for vol_type in vol_types:
|
|
if vol_type.id == vol_type_id:
|
|
group.volume_type_names.append(vol_type.name)
|
|
break
|
|
|
|
return groups
|
|
|
|
|
|
@profiler.trace
|
|
def group_get(request, group_id):
|
|
client = _cinderclient_with_generic_groups(request)
|
|
group = client.groups.get(group_id)
|
|
return Group(group)
|
|
|
|
|
|
@profiler.trace
|
|
def group_get_with_vol_type_names(request, group_id):
|
|
group = group_get(request, group_id)
|
|
vol_types = volume_type_list(request)
|
|
group.volume_type_names = []
|
|
for vol_type_id in group.volume_types:
|
|
for vol_type in vol_types:
|
|
if vol_type.id == vol_type_id:
|
|
group.volume_type_names.append(vol_type.name)
|
|
break
|
|
return group
|
|
|
|
|
|
@profiler.trace
|
|
def group_create(request, name, group_type, volume_types,
|
|
description=None, availability_zone=None):
|
|
client = _cinderclient_with_generic_groups(request)
|
|
params = {'name': name,
|
|
'group_type': group_type,
|
|
# cinderclient expects a comma-separated list of volume types.
|
|
'volume_types': ','.join(volume_types)}
|
|
if description is not None:
|
|
params['description'] = description
|
|
if availability_zone is not None:
|
|
params['availability_zone'] = availability_zone
|
|
return Group(client.groups.create(**params))
|
|
|
|
|
|
@profiler.trace
|
|
def group_create_from_source(request, name, group_snapshot_id=None,
|
|
source_group_id=None, description=None,
|
|
user_id=None, project_id=None):
|
|
client = _cinderclient_with_generic_groups(request)
|
|
return Group(client.groups.create_from_src(
|
|
group_snapshot_id, source_group_id, name, description,
|
|
user_id, project_id))
|
|
|
|
|
|
@profiler.trace
|
|
def group_delete(request, group_id, delete_volumes=False):
|
|
client = _cinderclient_with_generic_groups(request)
|
|
client.groups.delete(group_id, delete_volumes)
|
|
|
|
|
|
@profiler.trace
|
|
def group_update(request, group_id, name=None, description=None,
|
|
add_volumes=None, remove_volumes=None):
|
|
data = {}
|
|
if name is not None:
|
|
data['name'] = name
|
|
if description is not None:
|
|
data['description'] = description
|
|
if add_volumes:
|
|
# cinderclient expects a comma-separated list of volume types.
|
|
data['add_volumes'] = ','.join(add_volumes)
|
|
if remove_volumes:
|
|
# cinderclient expects a comma-separated list of volume types.
|
|
data['remove_volumes'] = ','.join(remove_volumes)
|
|
client = _cinderclient_with_generic_groups(request)
|
|
return client.groups.update(group_id, **data)
|
|
|
|
|
|
def group_snapshot_create(request, group_id, name, description=None):
|
|
client = _cinderclient_with_generic_groups(request)
|
|
return GroupSnapshot(client.group_snapshots.create(group_id, name,
|
|
description))
|
|
|
|
|
|
def group_snapshot_get(request, group_snapshot_id):
|
|
client = _cinderclient_with_generic_groups(request)
|
|
return GroupSnapshot(client.group_snapshots.get(group_snapshot_id))
|
|
|
|
|
|
def group_snapshot_list(request, search_opts=None):
|
|
client = _cinderclient_with_generic_groups(request)
|
|
return [GroupSnapshot(s) for s
|
|
in client.group_snapshots.list(search_opts=search_opts)]
|
|
|
|
|
|
def group_snapshot_delete(request, group_snapshot_id):
|
|
client = _cinderclient_with_generic_groups(request)
|
|
client.group_snapshots.delete(group_snapshot_id)
|