1a5de5d4bd
This patch adds a CI_WIKI_NAME to each driver object. The value is the exact name of the ThirdPartySystems wiki page. This allows us to create an automated tool to associated jobs to drivers and track their CI reporting status correctly. This patch also updates the generate_driver_list.py script to output the driver list as a python list of dicts that can be directly consumed. Change-Id: I0ec5f705e91f680a731648cf50738ea219565f70
317 lines
12 KiB
Python
317 lines
12 KiB
Python
# Copyright (c) 2013 Mirantis, 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 os
|
|
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
from oslo_utils import importutils
|
|
from oslo_utils import units
|
|
|
|
from cinder import context
|
|
from cinder import exception
|
|
from cinder.i18n import _, _LI, _LW
|
|
from cinder.image import image_utils
|
|
from cinder import interface
|
|
from cinder import objects
|
|
from cinder import utils
|
|
from cinder.volume import driver
|
|
from cinder.volume import utils as volutils
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
volume_opts = [
|
|
cfg.ListOpt('available_devices',
|
|
default=[],
|
|
help='List of all available devices'),
|
|
]
|
|
|
|
CONF = cfg.CONF
|
|
CONF.register_opts(volume_opts)
|
|
|
|
|
|
@interface.volumedriver
|
|
class BlockDeviceDriver(driver.BaseVD, driver.LocalVD,
|
|
driver.CloneableImageVD, driver.TransferVD):
|
|
VERSION = '2.3.0'
|
|
|
|
# ThirdPartySystems wiki page
|
|
CI_WIKI_NAME = "Cinder_Jenkins"
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(BlockDeviceDriver, self).__init__(*args, **kwargs)
|
|
self.configuration.append_config_values(volume_opts)
|
|
self.backend_name = \
|
|
self.configuration.safe_get('volume_backend_name') or "BlockDev"
|
|
target_driver =\
|
|
self.target_mapping[self.configuration.safe_get('iscsi_helper')]
|
|
self.target_driver = importutils.import_object(
|
|
target_driver,
|
|
configuration=self.configuration,
|
|
db=self.db,
|
|
executor=self._execute)
|
|
|
|
def check_for_setup_error(self):
|
|
pass
|
|
|
|
def _update_provider_location(self, obj, device):
|
|
# We update provider_location and host to mark device as used to
|
|
# avoid race with other threads.
|
|
# TODO(ynesenenko): need to remove DB access from driver
|
|
host = '{host}#{pool}'.format(host=self.host, pool=self.get_pool(obj))
|
|
obj.update({'provider_location': device, 'host': host})
|
|
obj.save()
|
|
|
|
@utils.synchronized('block_device', external=True)
|
|
def create_volume(self, volume):
|
|
device = self.find_appropriate_size_device(volume.size)
|
|
LOG.info(_LI("Creating %(volume)s on %(device)s"),
|
|
{"volume": volume.name, "device": device})
|
|
self._update_provider_location(volume, device)
|
|
|
|
def delete_volume(self, volume):
|
|
"""Deletes a logical volume."""
|
|
self._clear_block_device(volume)
|
|
|
|
def _clear_block_device(self, device):
|
|
"""Deletes a block device."""
|
|
dev_path = self.local_path(device)
|
|
if not dev_path or dev_path not in \
|
|
self.configuration.available_devices:
|
|
return
|
|
if os.path.exists(dev_path) and \
|
|
self.configuration.volume_clear != 'none':
|
|
dev_size = self._get_devices_sizes([dev_path])
|
|
volutils.clear_volume(
|
|
dev_size[dev_path], dev_path,
|
|
volume_clear=self.configuration.volume_clear,
|
|
volume_clear_size=self.configuration.volume_clear_size)
|
|
else:
|
|
LOG.warning(_LW("The device %s won't be cleared."), device)
|
|
|
|
if device.status == "error_deleting":
|
|
msg = _("Failed to delete device.")
|
|
LOG.error(msg, resource=device)
|
|
raise exception.VolumeDriverException(msg)
|
|
|
|
def local_path(self, device):
|
|
if device.provider_location:
|
|
path = device.provider_location.rsplit(" ", 1)
|
|
return path[-1]
|
|
else:
|
|
return None
|
|
|
|
def copy_image_to_volume(self, context, volume, image_service, image_id):
|
|
"""Fetch the image from image_service and write it to the volume."""
|
|
image_utils.fetch_to_raw(context,
|
|
image_service,
|
|
image_id,
|
|
self.local_path(volume),
|
|
self.configuration.volume_dd_blocksize,
|
|
size=volume.size)
|
|
|
|
def copy_volume_to_image(self, context, volume, image_service, image_meta):
|
|
"""Copy the volume to the specified image."""
|
|
image_utils.upload_volume(context,
|
|
image_service,
|
|
image_meta,
|
|
self.local_path(volume))
|
|
|
|
@utils.synchronized('block_device', external=True)
|
|
def create_cloned_volume(self, volume, src_vref):
|
|
LOG.info(_LI('Creating clone of volume: %s.'), src_vref.id)
|
|
device = self.find_appropriate_size_device(src_vref.size)
|
|
dev_size = self._get_devices_sizes([device])
|
|
volutils.copy_volume(
|
|
self.local_path(src_vref), device,
|
|
dev_size[device],
|
|
self.configuration.volume_dd_blocksize,
|
|
execute=self._execute)
|
|
self._update_provider_location(volume, device)
|
|
|
|
def get_volume_stats(self, refresh=False):
|
|
if refresh:
|
|
self._update_volume_stats()
|
|
return self._stats
|
|
|
|
def _update_volume_stats(self):
|
|
"""Retrieve stats info from volume group."""
|
|
dict_of_devices_sizes = self._devices_sizes()
|
|
used_devices = self._get_used_devices()
|
|
total_size = 0
|
|
free_size = 0
|
|
for device, size in dict_of_devices_sizes.items():
|
|
if device not in used_devices:
|
|
free_size += size
|
|
total_size += size
|
|
|
|
LOG.debug("Updating volume stats.")
|
|
data = {
|
|
'volume_backend_name': self.backend_name,
|
|
'vendor_name': "Open Source",
|
|
'driver_version': self.VERSION,
|
|
'storage_protocol': 'unknown',
|
|
'pools': []}
|
|
|
|
single_pool = {
|
|
'pool_name': data['volume_backend_name'],
|
|
'total_capacity_gb': total_size / units.Ki,
|
|
'free_capacity_gb': free_size / units.Ki,
|
|
'reserved_percentage': self.configuration.reserved_percentage,
|
|
'QoS_support': False}
|
|
|
|
data['pools'].append(single_pool)
|
|
self._stats = data
|
|
|
|
def get_pool(self, volume):
|
|
return self.backend_name
|
|
|
|
def _get_used_paths(self, lst):
|
|
used_dev = set()
|
|
for item in lst:
|
|
local_path = self.local_path(item)
|
|
if local_path:
|
|
used_dev.add(local_path)
|
|
return used_dev
|
|
|
|
def _get_used_devices(self):
|
|
lst = objects.VolumeList.get_all_by_host(context.get_admin_context(),
|
|
self.host)
|
|
used_devices = self._get_used_paths(lst)
|
|
snp_lst = objects.SnapshotList.get_by_host(context.get_admin_context(),
|
|
self.host)
|
|
return used_devices.union(self._get_used_paths(snp_lst))
|
|
|
|
def _get_devices_sizes(self, dev_paths):
|
|
"""Return devices' sizes in Mb"""
|
|
out, _err = self._execute('blockdev', '--getsize64', *dev_paths,
|
|
run_as_root=True)
|
|
dev_sizes = {}
|
|
out = out.split('\n')
|
|
# blockdev returns devices' sizes in order that
|
|
# they have been passed to it.
|
|
for n, size in enumerate(out[:-1]):
|
|
dev_sizes[dev_paths[n]] = int(size) / units.Mi
|
|
|
|
return dev_sizes
|
|
|
|
def _devices_sizes(self):
|
|
available_devices = self.configuration.available_devices
|
|
return self._get_devices_sizes(available_devices)
|
|
|
|
def find_appropriate_size_device(self, size):
|
|
dict_of_devices_sizes = self._devices_sizes()
|
|
free_devices = (set(self.configuration.available_devices) -
|
|
self._get_used_devices())
|
|
if not free_devices:
|
|
raise exception.CinderException(_("No free disk"))
|
|
possible_device = None
|
|
possible_device_size = None
|
|
for device in free_devices:
|
|
dev_size = dict_of_devices_sizes[device]
|
|
if (size * units.Ki <= dev_size and
|
|
(possible_device is None or
|
|
dev_size < possible_device_size)):
|
|
possible_device = device
|
|
possible_device_size = dev_size
|
|
|
|
if possible_device:
|
|
return possible_device
|
|
else:
|
|
raise exception.CinderException(_("No big enough free disk"))
|
|
|
|
def extend_volume(self, volume, new_size):
|
|
dev_path = self.local_path(volume)
|
|
total_size = self._get_devices_sizes([dev_path])
|
|
# Convert from Megabytes to Gigabytes
|
|
size = total_size[dev_path] / units.Ki
|
|
if size < new_size:
|
|
msg = _("Insufficient free space available to extend volume.")
|
|
LOG.error(msg, resource=volume)
|
|
raise exception.CinderException(msg)
|
|
|
|
@utils.synchronized('block_device', external=True)
|
|
def create_snapshot(self, snapshot):
|
|
volume = snapshot.volume
|
|
if volume.status != 'available':
|
|
msg = _("Volume is not available.")
|
|
LOG.error(msg, resource=volume)
|
|
raise exception.CinderException(msg)
|
|
|
|
LOG.info(_LI('Creating volume snapshot: %s.'), snapshot.id)
|
|
device = self.find_appropriate_size_device(snapshot.volume_size)
|
|
dev_size = self._get_devices_sizes([device])
|
|
volutils.copy_volume(
|
|
self.local_path(volume), device,
|
|
dev_size[device],
|
|
self.configuration.volume_dd_blocksize,
|
|
execute=self._execute)
|
|
self._update_provider_location(snapshot, device)
|
|
|
|
def delete_snapshot(self, snapshot):
|
|
self._clear_block_device(snapshot)
|
|
|
|
@utils.synchronized('block_device', external=True)
|
|
def create_volume_from_snapshot(self, volume, snapshot):
|
|
LOG.info(_LI('Creating volume %s from snapshot.'), volume.id)
|
|
device = self.find_appropriate_size_device(snapshot.volume_size)
|
|
dev_size = self._get_devices_sizes([device])
|
|
volutils.copy_volume(
|
|
self.local_path(snapshot), device,
|
|
dev_size[device],
|
|
self.configuration.volume_dd_blocksize,
|
|
execute=self._execute)
|
|
self._update_provider_location(volume, device)
|
|
|
|
# ####### Interface methods for DataPath (Target Driver) ########
|
|
|
|
def ensure_export(self, context, volume):
|
|
volume_path = self.local_path(volume)
|
|
model_update = \
|
|
self.target_driver.ensure_export(
|
|
context,
|
|
volume,
|
|
volume_path)
|
|
return model_update
|
|
|
|
def create_export(self, context, volume, connector):
|
|
volume_path = self.local_path(volume)
|
|
export_info = self.target_driver.create_export(context,
|
|
volume,
|
|
volume_path)
|
|
return {
|
|
'provider_location': export_info['location'] + ' ' + volume_path,
|
|
'provider_auth': export_info['auth'],
|
|
}
|
|
|
|
def remove_export(self, context, volume):
|
|
self.target_driver.remove_export(context, volume)
|
|
|
|
def initialize_connection(self, volume, connector):
|
|
if connector['host'] != volutils.extract_host(volume.host, 'host'):
|
|
return self.target_driver.initialize_connection(volume, connector)
|
|
else:
|
|
return {
|
|
'driver_volume_type': 'local',
|
|
'data': {'device_path': self.local_path(volume)},
|
|
}
|
|
|
|
def validate_connector(self, connector):
|
|
return self.target_driver.validate_connector(connector)
|
|
|
|
def terminate_connection(self, volume, connector, **kwargs):
|
|
pass
|