642 lines
26 KiB
Python
642 lines
26 KiB
Python
# Copyright 2012 OpenStack Foundation
|
|
# Copyright (c) 2013 Zelin.io
|
|
# Copyright (C) 2015 Nippon Telegraph and Telephone Corporation.
|
|
# 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.
|
|
|
|
"""
|
|
SheepDog Volume Driver.
|
|
|
|
"""
|
|
import errno
|
|
import eventlet
|
|
import io
|
|
import random
|
|
import re
|
|
|
|
from oslo_concurrency import processutils
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
from oslo_utils import excutils
|
|
from oslo_utils import units
|
|
|
|
from cinder import exception
|
|
from cinder.i18n import _
|
|
from cinder.image import image_utils
|
|
from cinder import interface
|
|
from cinder import utils
|
|
from cinder.volume import configuration
|
|
from cinder.volume import driver
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
sheepdog_opts = [
|
|
cfg.StrOpt('sheepdog_store_address',
|
|
default='127.0.0.1',
|
|
help=('IP address of sheep daemon.')),
|
|
cfg.PortOpt('sheepdog_store_port',
|
|
default=7000,
|
|
help=('Port of sheep daemon.'))
|
|
]
|
|
|
|
CONF = cfg.CONF
|
|
CONF.import_opt("image_conversion_dir", "cinder.image.image_utils")
|
|
CONF.register_opts(sheepdog_opts, group=configuration.SHARED_CONF_GROUP)
|
|
|
|
|
|
class SheepdogClient(object):
|
|
"""Sheepdog command executor."""
|
|
|
|
QEMU_SHEEPDOG_PREFIX = 'sheepdog:'
|
|
DOG_RESP_CONNECTION_ERROR = 'failed to connect to'
|
|
DOG_RESP_CLUSTER_RUNNING = 'Cluster status: running'
|
|
DOG_RESP_CLUSTER_NOT_FORMATTED = ('Cluster status: '
|
|
'Waiting for cluster to be formatted')
|
|
DOG_RESP_CLUSTER_WAITING = ('Cluster status: '
|
|
'Waiting for other nodes to join cluster')
|
|
DOG_RESP_VDI_ALREADY_EXISTS = ': VDI exists already'
|
|
DOG_RESP_VDI_NOT_FOUND = ': No VDI found'
|
|
DOG_RESP_VDI_SHRINK_NOT_SUPPORT = 'Shrinking VDIs is not implemented'
|
|
DOG_RESP_VDI_SIZE_TOO_LARGE = 'New VDI size is too large'
|
|
DOG_RESP_SNAPSHOT_VDI_NOT_FOUND = ': No VDI found'
|
|
DOG_RESP_SNAPSHOT_NOT_FOUND = ': Failed to find requested tag'
|
|
DOG_RESP_SNAPSHOT_EXISTED = 'tag (%(snapname)s) is existed'
|
|
QEMU_IMG_RESP_CONNECTION_ERROR = ('Failed to connect socket: '
|
|
'Connection refused')
|
|
QEMU_IMG_RESP_ALREADY_EXISTS = ': VDI exists already'
|
|
QEMU_IMG_RESP_SNAPSHOT_NOT_FOUND = 'Failed to find the requested tag'
|
|
QEMU_IMG_RESP_VDI_NOT_FOUND = 'No vdi found'
|
|
QEMU_IMG_RESP_SIZE_TOO_LARGE = 'An image is too large.'
|
|
|
|
def __init__(self, node_list, port):
|
|
self.node_list = node_list
|
|
self.port = port
|
|
|
|
def get_addr(self):
|
|
"""Get a random node in sheepdog cluster."""
|
|
return self.node_list[random.randint(0, len(self.node_list) - 1)]
|
|
|
|
def local_path(self, volume):
|
|
"""Return a sheepdog location path."""
|
|
return "sheepdog:%(addr)s:%(port)s:%(name)s" % {
|
|
'addr': self.get_addr(),
|
|
'port': self.port,
|
|
'name': volume['name']}
|
|
|
|
def _run_dog(self, command, subcommand, *params):
|
|
"""Execute dog command wrapper."""
|
|
addr = self.get_addr()
|
|
cmd = ('env', 'LC_ALL=C', 'LANG=C', 'dog', command, subcommand,
|
|
'-a', addr, '-p', self.port) + params
|
|
try:
|
|
(_stdout, _stderr) = utils.execute(*cmd)
|
|
if _stderr.startswith(self.DOG_RESP_CONNECTION_ERROR):
|
|
# NOTE(tishizaki)
|
|
# Dog command does not return error_code although
|
|
# dog command cannot connect to sheep process.
|
|
# That is a Sheepdog's bug.
|
|
# To avoid a Sheepdog's bug, now we need to check stderr.
|
|
# If Sheepdog has been fixed, this check logic is needed
|
|
# by old Sheepdog users.
|
|
reason = (_('Failed to connect to sheep daemon. '
|
|
'addr: %(addr)s, port: %(port)s'),
|
|
{'addr': addr, 'port': self.port})
|
|
raise exception.SheepdogError(reason=reason)
|
|
return (_stdout, _stderr)
|
|
except OSError as e:
|
|
with excutils.save_and_reraise_exception():
|
|
if e.errno == errno.ENOENT:
|
|
msg = 'Sheepdog is not installed. OSError: command is %s.'
|
|
else:
|
|
msg = 'OSError: command is %s.'
|
|
LOG.error(msg, cmd)
|
|
except processutils.ProcessExecutionError as e:
|
|
_stderr = e.stderr
|
|
if _stderr.startswith(self.DOG_RESP_CONNECTION_ERROR):
|
|
reason = (_('Failed to connect to sheep daemon. '
|
|
'addr: %(addr)s, port: %(port)s'),
|
|
{'addr': addr, 'port': self.port})
|
|
raise exception.SheepdogError(reason=reason)
|
|
raise exception.SheepdogCmdError(
|
|
cmd=e.cmd,
|
|
exit_code=e.exit_code,
|
|
stdout=e.stdout.replace('\n', '\\n'),
|
|
stderr=e.stderr.replace('\n', '\\n'))
|
|
|
|
def _run_qemu_img(self, command, *params):
|
|
"""Executes qemu-img command wrapper."""
|
|
addr = self.get_addr()
|
|
cmd = ['env', 'LC_ALL=C', 'LANG=C', 'qemu-img', command]
|
|
for param in params:
|
|
if param.startswith(self.QEMU_SHEEPDOG_PREFIX):
|
|
# replace 'sheepdog:vdiname[:snapshotname]' to
|
|
# 'sheepdog:addr:port:vdiname[:snapshotname]'
|
|
param = param.replace(self.QEMU_SHEEPDOG_PREFIX,
|
|
'%(prefix)s%(addr)s:%(port)s:' %
|
|
{'prefix': self.QEMU_SHEEPDOG_PREFIX,
|
|
'addr': addr, 'port': self.port},
|
|
1)
|
|
cmd.append(param)
|
|
try:
|
|
return utils.execute(*cmd)
|
|
except OSError as e:
|
|
with excutils.save_and_reraise_exception():
|
|
if e.errno == errno.ENOENT:
|
|
msg = ('Qemu-img is not installed. OSError: command is '
|
|
'%(cmd)s.')
|
|
else:
|
|
msg = 'OSError: command is %(cmd)s.'
|
|
LOG.error(msg, {'cmd': tuple(cmd)})
|
|
except processutils.ProcessExecutionError as e:
|
|
_stderr = e.stderr
|
|
if self.QEMU_IMG_RESP_CONNECTION_ERROR in _stderr:
|
|
reason = (_('Failed to connect to sheep daemon. '
|
|
'addr: %(addr)s, port: %(port)s'),
|
|
{'addr': addr, 'port': self.port})
|
|
raise exception.SheepdogError(reason=reason)
|
|
raise exception.SheepdogCmdError(
|
|
cmd=e.cmd,
|
|
exit_code=e.exit_code,
|
|
stdout=e.stdout.replace('\n', '\\n'),
|
|
stderr=e.stderr.replace('\n', '\\n'))
|
|
|
|
def check_cluster_status(self):
|
|
try:
|
|
(_stdout, _stderr) = self._run_dog('cluster', 'info')
|
|
except exception.SheepdogCmdError as e:
|
|
cmd = e.kwargs['cmd']
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error('Failed to check cluster status.'
|
|
'(command: %s)', cmd)
|
|
|
|
if _stdout.startswith(self.DOG_RESP_CLUSTER_RUNNING):
|
|
LOG.debug('Sheepdog cluster is running.')
|
|
return
|
|
|
|
reason = _('Invalid sheepdog cluster status.')
|
|
if _stdout.startswith(self.DOG_RESP_CLUSTER_NOT_FORMATTED):
|
|
reason = _('Cluster is not formatted. '
|
|
'You should probably perform "dog cluster format".')
|
|
elif _stdout.startswith(self.DOG_RESP_CLUSTER_WAITING):
|
|
reason = _('Waiting for all nodes to join cluster. '
|
|
'Ensure all sheep daemons are running.')
|
|
raise exception.SheepdogError(reason=reason)
|
|
|
|
def create(self, vdiname, size):
|
|
try:
|
|
self._run_dog('vdi', 'create', vdiname, '%sG' % size)
|
|
except exception.SheepdogCmdError as e:
|
|
_stderr = e.kwargs['stderr']
|
|
with excutils.save_and_reraise_exception():
|
|
if _stderr.rstrip('\\n').endswith(
|
|
self.DOG_RESP_VDI_ALREADY_EXISTS):
|
|
LOG.error('Volume already exists. %s', vdiname)
|
|
else:
|
|
LOG.error('Failed to create volume. %s', vdiname)
|
|
|
|
def delete(self, vdiname):
|
|
try:
|
|
(_stdout, _stderr) = self._run_dog('vdi', 'delete', vdiname)
|
|
if _stderr.rstrip().endswith(self.DOG_RESP_VDI_NOT_FOUND):
|
|
LOG.warning('Volume not found. %s', vdiname)
|
|
except exception.SheepdogCmdError as e:
|
|
_stderr = e.kwargs['stderr']
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error('Failed to delete volume. %s', vdiname)
|
|
|
|
def create_snapshot(self, vdiname, snapname):
|
|
try:
|
|
self._run_dog('vdi', 'snapshot', '-s', snapname, vdiname)
|
|
except exception.SheepdogCmdError as e:
|
|
cmd = e.kwargs['cmd']
|
|
_stderr = e.kwargs['stderr']
|
|
with excutils.save_and_reraise_exception():
|
|
if _stderr.rstrip('\\n').endswith(
|
|
self.DOG_RESP_SNAPSHOT_VDI_NOT_FOUND):
|
|
LOG.error('Volume "%s" not found. Please check the '
|
|
'results of "dog vdi list".',
|
|
vdiname)
|
|
elif _stderr.rstrip('\\n').endswith(
|
|
self.DOG_RESP_SNAPSHOT_EXISTED %
|
|
{'snapname': snapname}):
|
|
LOG.error('Snapshot "%s" already exists.', snapname)
|
|
else:
|
|
LOG.error('Failed to create snapshot. (command: %s)',
|
|
cmd)
|
|
|
|
def delete_snapshot(self, vdiname, snapname):
|
|
try:
|
|
(_stdout, _stderr) = self._run_dog('vdi', 'delete', '-s',
|
|
snapname, vdiname)
|
|
if _stderr.rstrip().endswith(self.DOG_RESP_SNAPSHOT_NOT_FOUND):
|
|
LOG.warning('Snapshot "%s" not found.', snapname)
|
|
elif _stderr.rstrip().endswith(self.DOG_RESP_VDI_NOT_FOUND):
|
|
LOG.warning('Volume "%s" not found.', vdiname)
|
|
except exception.SheepdogCmdError as e:
|
|
cmd = e.kwargs['cmd']
|
|
_stderr = e.kwargs['stderr']
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error('Failed to delete snapshot. (command: %s)',
|
|
cmd)
|
|
|
|
def clone(self, src_vdiname, src_snapname, dst_vdiname, size):
|
|
try:
|
|
self._run_qemu_img('create', '-b',
|
|
'sheepdog:%(src_vdiname)s:%(src_snapname)s' %
|
|
{'src_vdiname': src_vdiname,
|
|
'src_snapname': src_snapname},
|
|
'sheepdog:%s' % dst_vdiname, '%sG' % size)
|
|
except exception.SheepdogCmdError as e:
|
|
cmd = e.kwargs['cmd']
|
|
_stderr = e.kwargs['stderr']
|
|
with excutils.save_and_reraise_exception():
|
|
if self.QEMU_IMG_RESP_ALREADY_EXISTS in _stderr:
|
|
LOG.error('Clone volume "%s" already exists. '
|
|
'Please check the results of "dog vdi list".',
|
|
dst_vdiname)
|
|
elif self.QEMU_IMG_RESP_VDI_NOT_FOUND in _stderr:
|
|
LOG.error('Src Volume "%s" not found. '
|
|
'Please check the results of "dog vdi list".',
|
|
src_vdiname)
|
|
elif self.QEMU_IMG_RESP_SNAPSHOT_NOT_FOUND in _stderr:
|
|
LOG.error('Snapshot "%s" not found. '
|
|
'Please check the results of "dog vdi list".',
|
|
src_snapname)
|
|
elif self.QEMU_IMG_RESP_SIZE_TOO_LARGE in _stderr:
|
|
LOG.error('Volume size "%sG" is too large.', size)
|
|
else:
|
|
LOG.error('Failed to clone volume.(command: %s)', cmd)
|
|
|
|
def resize(self, vdiname, size):
|
|
size = int(size) * units.Gi
|
|
try:
|
|
(_stdout, _stderr) = self._run_dog('vdi', 'resize', vdiname, size)
|
|
except exception.SheepdogCmdError as e:
|
|
_stderr = e.kwargs['stderr']
|
|
with excutils.save_and_reraise_exception():
|
|
if _stderr.rstrip('\\n').endswith(
|
|
self.DOG_RESP_VDI_NOT_FOUND):
|
|
LOG.error('Failed to resize vdi. vdi not found. %s',
|
|
vdiname)
|
|
elif _stderr.startswith(self.DOG_RESP_VDI_SHRINK_NOT_SUPPORT):
|
|
LOG.error('Failed to resize vdi. '
|
|
'Shrinking vdi not supported. '
|
|
'vdi: %(vdiname)s new size: %(size)s',
|
|
{'vdiname': vdiname, 'size': size})
|
|
elif _stderr.startswith(self.DOG_RESP_VDI_SIZE_TOO_LARGE):
|
|
LOG.error('Failed to resize vdi. '
|
|
'Too large volume size. '
|
|
'vdi: %(vdiname)s new size: %(size)s',
|
|
{'vdiname': vdiname, 'size': size})
|
|
else:
|
|
LOG.error('Failed to resize vdi. '
|
|
'vdi: %(vdiname)s new size: %(size)s',
|
|
{'vdiname': vdiname, 'size': size})
|
|
|
|
def get_volume_stats(self):
|
|
try:
|
|
(_stdout, _stderr) = self._run_dog('node', 'info', '-r')
|
|
except exception.SheepdogCmdError as e:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error('Failed to get volume status. %s', e)
|
|
return _stdout
|
|
|
|
def get_vdi_info(self, vdiname):
|
|
# Get info of the specified vdi.
|
|
try:
|
|
(_stdout, _stderr) = self._run_dog('vdi', 'list', vdiname, '-r')
|
|
except exception.SheepdogCmdError as e:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error('Failed to get vdi info. %s', e)
|
|
return _stdout
|
|
|
|
def update_node_list(self):
|
|
try:
|
|
(_stdout, _stderr) = self._run_dog('node', 'list', '-r')
|
|
except exception.SheepdogCmdError as e:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error('Failed to get node list. %s', e)
|
|
node_list = []
|
|
stdout = _stdout.strip('\n')
|
|
for line in stdout.split('\n'):
|
|
node_list.append(line.split()[1].split(':')[0])
|
|
self.node_list = node_list
|
|
|
|
|
|
class SheepdogIOWrapper(io.RawIOBase):
|
|
"""File-like object with Sheepdog backend."""
|
|
|
|
def __init__(self, addr, port, volume, snapshot_name=None):
|
|
self._addr = addr
|
|
self._port = port
|
|
self._vdiname = volume['name']
|
|
self._snapshot_name = snapshot_name
|
|
self._offset = 0
|
|
# SheepdogIOWrapper instance becomes invalid if a write error occurs.
|
|
self._valid = True
|
|
|
|
def _execute(self, cmd, data=None):
|
|
try:
|
|
# NOTE(yamada-h): processutils.execute causes busy waiting
|
|
# under eventlet.
|
|
# To avoid wasting CPU resources, it should not be used for
|
|
# the command which takes long time to execute.
|
|
# For workaround, we replace a subprocess module with
|
|
# the original one while only executing a read/write command.
|
|
_processutils_subprocess = processutils.subprocess
|
|
processutils.subprocess = eventlet.patcher.original('subprocess')
|
|
return processutils.execute(*cmd, process_input=data)[0]
|
|
except (processutils.ProcessExecutionError, OSError):
|
|
self._valid = False
|
|
msg = _('Sheepdog I/O Error, command was: "%s".') % ' '.join(cmd)
|
|
raise exception.VolumeDriverException(message=msg)
|
|
finally:
|
|
processutils.subprocess = _processutils_subprocess
|
|
|
|
def read(self, length=None):
|
|
if not self._valid:
|
|
msg = _('An error occurred while reading volume "%s".'
|
|
) % self._vdiname
|
|
raise exception.VolumeDriverException(message=msg)
|
|
|
|
cmd = ['dog', 'vdi', 'read', '-a', self._addr, '-p', self._port]
|
|
if self._snapshot_name:
|
|
cmd.extend(('-s', self._snapshot_name))
|
|
cmd.extend((self._vdiname, self._offset))
|
|
if length:
|
|
cmd.append(length)
|
|
data = self._execute(cmd)
|
|
self._offset += len(data)
|
|
return data
|
|
|
|
def write(self, data):
|
|
if not self._valid:
|
|
msg = _('An error occurred while writing to volume "%s".'
|
|
) % self._vdiname
|
|
raise exception.VolumeDriverException(message=msg)
|
|
|
|
length = len(data)
|
|
cmd = ('dog', 'vdi', 'write', '-a', self._addr, '-p', self._port,
|
|
self._vdiname, self._offset, length)
|
|
self._execute(cmd, data)
|
|
self._offset += length
|
|
return length
|
|
|
|
def seek(self, offset, whence=0):
|
|
if not self._valid:
|
|
msg = _('An error occurred while seeking for volume "%s".'
|
|
) % self._vdiname
|
|
raise exception.VolumeDriverException(message=msg)
|
|
|
|
if whence == 0:
|
|
# SEEK_SET or 0 - start of the stream (the default);
|
|
# offset should be zero or positive
|
|
new_offset = offset
|
|
elif whence == 1:
|
|
# SEEK_CUR or 1 - current stream position; offset may be negative
|
|
new_offset = self._offset + offset
|
|
else:
|
|
# SEEK_END or 2 - end of the stream; offset is usually negative
|
|
# TODO(yamada-h): Support SEEK_END
|
|
raise IOError(_("Invalid argument - whence=%s not supported.") %
|
|
whence)
|
|
|
|
if new_offset < 0:
|
|
raise IOError(_("Invalid argument - negative seek offset."))
|
|
|
|
self._offset = new_offset
|
|
|
|
def tell(self):
|
|
return self._offset
|
|
|
|
def flush(self):
|
|
pass
|
|
|
|
def fileno(self):
|
|
"""Sheepdog does not have support for fileno so we raise IOError.
|
|
|
|
Raising IOError is recommended way to notify caller that interface is
|
|
not supported - see http://docs.python.org/2/library/io.html#io.IOBase
|
|
"""
|
|
raise IOError(_("fileno is not supported by SheepdogIOWrapper"))
|
|
|
|
|
|
@interface.volumedriver
|
|
class SheepdogDriver(driver.VolumeDriver):
|
|
"""Executes commands relating to Sheepdog Volumes."""
|
|
|
|
VERSION = "1.0.0"
|
|
|
|
# ThirdPartySystems wiki page
|
|
CI_WIKI_NAME = "Cinder_Jenkins"
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(SheepdogDriver, self).__init__(*args, **kwargs)
|
|
self.configuration.append_config_values(sheepdog_opts)
|
|
addr = self.configuration.sheepdog_store_address
|
|
self.port = self.configuration.sheepdog_store_port
|
|
self.stats_pattern = re.compile(r'[\w\s%]*Total\s(\d+)\s(\d+)*')
|
|
self._stats = {}
|
|
self.node_list = [addr]
|
|
self.client = SheepdogClient(self.node_list, self.port)
|
|
|
|
def check_for_setup_error(self):
|
|
"""Check cluster status and update node list."""
|
|
self.client.check_cluster_status()
|
|
self.client.update_node_list()
|
|
|
|
def _is_cloneable(self, image_location, image_meta):
|
|
"""Check the image can be clone or not."""
|
|
if image_location is None:
|
|
return False
|
|
|
|
prefix = 'sheepdog://'
|
|
if not image_location.startswith(prefix):
|
|
LOG.debug("Image is not stored in sheepdog.")
|
|
return False
|
|
|
|
if image_meta['disk_format'] != 'raw':
|
|
LOG.debug("Image clone requires image format to be "
|
|
"'raw' but image %s(%s) is '%s'.",
|
|
image_location,
|
|
image_meta['id'],
|
|
image_meta['disk_format'])
|
|
return False
|
|
|
|
# check whether volume is stored in sheepdog
|
|
# The image location would be like
|
|
# "sheepdog://192.168.10.2:7000:Alice"
|
|
(ip, port, name) = image_location[len(prefix):].split(":", 2)
|
|
|
|
stdout = self.client.get_vdi_info(name)
|
|
# Dog command return 0 and has a null output if the volume not exists
|
|
if stdout:
|
|
return True
|
|
else:
|
|
LOG.debug("Can not find vdi %(image)s, is not cloneable",
|
|
{'image': name})
|
|
return False
|
|
|
|
def clone_image(self, context, volume,
|
|
image_location, image_meta,
|
|
image_service):
|
|
"""Create a volume efficiently from an existing image."""
|
|
image_location = image_location[0] if image_location else None
|
|
if not self._is_cloneable(image_location, image_meta):
|
|
return {}, False
|
|
|
|
volume_ref = {'name': image_meta['id'], 'size': image_meta['size']}
|
|
self.create_cloned_volume(volume, volume_ref)
|
|
self.client.resize(volume.name, volume.size)
|
|
|
|
vol_path = self.client.local_path(volume)
|
|
return {'provider_location': vol_path}, True
|
|
|
|
def create_cloned_volume(self, volume, src_vref):
|
|
"""Clone a sheepdog volume from another volume."""
|
|
snapshot_name = 'tmp-snap-%s-%s' % (src_vref['name'], volume.id)
|
|
snapshot = {
|
|
'name': snapshot_name,
|
|
'volume_name': src_vref['name'],
|
|
'volume_size': src_vref['size'],
|
|
}
|
|
|
|
self.client.create_snapshot(snapshot['volume_name'], snapshot_name)
|
|
|
|
try:
|
|
self.client.clone(snapshot['volume_name'], snapshot_name,
|
|
volume.name, volume.size)
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
LOG.error('Failed to create cloned volume %s.',
|
|
volume.name)
|
|
finally:
|
|
# Delete temp Snapshot
|
|
self.client.delete_snapshot(snapshot['volume_name'], snapshot_name)
|
|
|
|
def create_volume(self, volume):
|
|
"""Create a sheepdog volume."""
|
|
self.client.create(volume.name, volume.size)
|
|
|
|
def create_volume_from_snapshot(self, volume, snapshot):
|
|
"""Create a sheepdog volume from a snapshot."""
|
|
self.client.clone(snapshot.volume_name, snapshot.name,
|
|
volume.name, volume.size)
|
|
|
|
def delete_volume(self, volume):
|
|
"""Delete a logical volume."""
|
|
self.client.delete(volume.name)
|
|
|
|
def copy_image_to_volume(self, context, volume, image_service, image_id):
|
|
with image_utils.temporary_file() as tmp:
|
|
# (wenhao): we don't need to convert to raw for sheepdog.
|
|
image_utils.fetch_verify_image(context, image_service,
|
|
image_id, tmp)
|
|
|
|
# remove the image created by import before this function.
|
|
# see volume/drivers/manager.py:_create_volume
|
|
self.client.delete(volume.name)
|
|
# convert and store into sheepdog
|
|
image_utils.convert_image(tmp, self.client.local_path(volume),
|
|
'raw')
|
|
self.client.resize(volume.name, volume.size)
|
|
|
|
def copy_volume_to_image(self, context, volume, image_service, image_meta):
|
|
"""Copy the volume to the specified image."""
|
|
image_id = image_meta['id']
|
|
with image_utils.temporary_file() as tmp:
|
|
# image_utils.convert_image doesn't support "sheepdog:" source,
|
|
# so we use the qemu-img directly.
|
|
# Sheepdog volume is always raw-formatted.
|
|
cmd = ('qemu-img',
|
|
'convert',
|
|
'-f', 'raw',
|
|
'-t', 'none',
|
|
'-O', 'raw',
|
|
self.client.local_path(volume),
|
|
tmp)
|
|
self._try_execute(*cmd)
|
|
|
|
with open(tmp, 'rb') as image_file:
|
|
image_service.update(context, image_id, {}, image_file)
|
|
|
|
def create_snapshot(self, snapshot):
|
|
"""Create a sheepdog snapshot."""
|
|
self.client.create_snapshot(snapshot.volume_name, snapshot.name)
|
|
|
|
def delete_snapshot(self, snapshot):
|
|
"""Delete a sheepdog snapshot."""
|
|
self.client.delete_snapshot(snapshot.volume_name, snapshot.name)
|
|
|
|
def ensure_export(self, context, volume):
|
|
"""Safely and synchronously recreate an export for a logical volume."""
|
|
pass
|
|
|
|
def create_export(self, context, volume, connector):
|
|
"""Export a volume."""
|
|
pass
|
|
|
|
def remove_export(self, context, volume):
|
|
"""Remove an export for a logical volume."""
|
|
pass
|
|
|
|
def initialize_connection(self, volume, connector):
|
|
return {
|
|
'driver_volume_type': 'sheepdog',
|
|
'data': {
|
|
'name': volume['name'],
|
|
'hosts': [self.client.get_addr()],
|
|
'ports': ["%d" % self.port],
|
|
}
|
|
}
|
|
|
|
def terminate_connection(self, volume, connector, **kwargs):
|
|
pass
|
|
|
|
def _update_volume_stats(self):
|
|
stats = {}
|
|
|
|
backend_name = "sheepdog"
|
|
if self.configuration:
|
|
backend_name = self.configuration.safe_get('volume_backend_name')
|
|
stats["volume_backend_name"] = backend_name or 'sheepdog'
|
|
stats['vendor_name'] = 'Open Source'
|
|
stats['driver_version'] = self.VERSION
|
|
stats['storage_protocol'] = 'sheepdog'
|
|
stats['total_capacity_gb'] = 'unknown'
|
|
stats['free_capacity_gb'] = 'unknown'
|
|
stats['reserved_percentage'] = 0
|
|
stats['QoS_support'] = False
|
|
|
|
stdout = self.client.get_volume_stats()
|
|
m = self.stats_pattern.match(stdout)
|
|
total = float(m.group(1))
|
|
used = float(m.group(2))
|
|
stats['total_capacity_gb'] = total / units.Gi
|
|
stats['free_capacity_gb'] = (total - used) / units.Gi
|
|
|
|
self._stats = stats
|
|
|
|
def get_volume_stats(self, refresh=False):
|
|
if refresh:
|
|
self._update_volume_stats()
|
|
return self._stats
|
|
|
|
def extend_volume(self, volume, new_size):
|
|
"""Extend an Existing Volume."""
|
|
self.client.resize(volume.name, new_size)
|
|
LOG.debug('Extend volume from %(old_size)s GB to %(new_size)s GB.',
|
|
{'old_size': volume.size, 'new_size': new_size})
|