Merge "Add support for storing volumes on GPFS"

This commit is contained in:
Jenkins 2013-07-11 13:31:58 +00:00 committed by Gerrit Code Review
commit d4be2c5bb0
4 changed files with 948 additions and 1 deletions

429
cinder/tests/test_gpfs.py Normal file
View File

@ -0,0 +1,429 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright IBM Corp. 2013 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
import tempfile
from oslo.config import cfg
from cinder import context
from cinder import db
from cinder import exception
from cinder.image import image_utils
from cinder.openstack.common import importutils
from cinder.openstack.common import log as logging
from cinder import test
from cinder import utils
from cinder.volume import configuration as conf
from cinder.volume.drivers.gpfs import GPFSDriver
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class FakeImageService():
def update(self, context, image_id, path):
pass
def download(self, context, image_id, image_fd):
for b in range(256):
image_fd.write('some_image_data')
image_fd.close()
class FakeQemuImgInfo(object):
def __init__(self):
self.file_format = None
self.backing_file = None
class GPFSDriverTestCase(test.TestCase):
driver_name = "cinder.volume.drivers.gpfs.GPFSDriver"
context = context.get_admin_context()
def _execute_wrapper(self, cmd, *args, **kwargs):
try:
kwargs.pop('run_as_root')
except KeyError:
pass
return utils.execute(cmd, *args, **kwargs)
def setUp(self):
super(GPFSDriverTestCase, self).setUp()
self.volumes_path = tempfile.mkdtemp(prefix="gpfs_")
self.images_dir = '%s/images' % self.volumes_path
if not os.path.exists(self.volumes_path):
os.mkdir(self.volumes_path)
if not os.path.exists(self.images_dir):
os.mkdir(self.images_dir)
self.image_id = '70a599e0-31e7-49b7-b260-868f441e862b'
self.image_path = self.images_dir + "/" + self.image_id
utils.execute('truncate', '-s', 1024, self.image_path)
self.driver = GPFSDriver(configuration=conf.Configuration(None))
self.driver.set_execute(self._execute_wrapper)
self.flags(volume_driver=self.driver_name,
gpfs_mount_point_base=self.volumes_path)
self.volume = importutils.import_object(CONF.volume_manager)
self.volume.driver.set_execute(self._execute_wrapper)
self.stubs.Set(GPFSDriver, '_create_gpfs_snap',
self._fake_gpfs_snap)
self.stubs.Set(GPFSDriver, '_create_gpfs_copy',
self._fake_gpfs_copy)
self.stubs.Set(GPFSDriver, '_gpfs_redirect',
self._fake_gpfs_redirect)
self.stubs.Set(GPFSDriver, '_is_gpfs_parent_file',
self._fake_is_gpfs_parent)
self.stubs.Set(GPFSDriver, '_delete_gpfs_file',
self._fake_delete_gpfs_file)
self.stubs.Set(image_utils, 'qemu_img_info',
self._fake_qemu_qcow2_image_info)
self.stubs.Set(image_utils, 'convert_image',
self._fake_convert_image)
self.context = context.get_admin_context()
CONF.gpfs_images_dir = self.images_dir
def tearDown(self):
try:
os.remove(self.image_path)
os.rmdir(self.images_dir)
os.rmdir(self.volumes_path)
except OSError:
pass
super(GPFSDriverTestCase, self).tearDown()
def _create_volume(self, size=0, snapshot_id=None, image_id=None,
metadata=None):
"""Create a volume object."""
vol = {}
vol['size'] = size
vol['snapshot_id'] = snapshot_id
vol['image_id'] = image_id
vol['user_id'] = 'fake'
vol['project_id'] = 'fake'
vol['availability_zone'] = CONF.storage_availability_zone
vol['status'] = "creating"
vol['attach_status'] = "detached"
vol['host'] = CONF.host
if metadata is not None:
vol['metadata'] = metadata
return db.volume_create(context.get_admin_context(), vol)
def test_create_delete_volume_full_backing_file(self):
"""create and delete vol with full creation method"""
CONF.gpfs_sparse_volumes = False
vol = self._create_volume(size=1)
volume_id = vol['id']
self.assertTrue(os.path.exists(self.volumes_path))
self.volume.create_volume(self.context, volume_id)
path = self.volumes_path + '/' + vol['name']
self.assertTrue(os.path.exists(self.volumes_path))
self.assertTrue(os.path.exists(path))
self.volume.delete_volume(self.context, volume_id)
self.assertFalse(os.path.exists(path))
self.assertTrue(os.path.exists(self.volumes_path))
def test_create_delete_volume_sparse_backing_file(self):
"""create and delete vol with default sparse creation method"""
CONF.gpfs_sparse_volumes = True
vol = self._create_volume(size=1)
volume_id = vol['id']
self.assertTrue(os.path.exists(self.volumes_path))
self.volume.create_volume(self.context, volume_id)
path = self.volumes_path + '/' + vol['name']
self.assertTrue(os.path.exists(self.volumes_path))
self.assertTrue(os.path.exists(path))
self.volume.delete_volume(self.context, volume_id)
self.assertFalse(os.path.exists(path))
self.assertTrue(os.path.exists(self.volumes_path))
def _create_snapshot(self, volume_id, size='0'):
"""Create a snapshot object."""
snap = {}
snap['volume_size'] = size
snap['user_id'] = 'fake'
snap['project_id'] = 'fake'
snap['volume_id'] = volume_id
snap['status'] = "creating"
return db.snapshot_create(context.get_admin_context(), snap)
def test_create_delete_snapshot(self):
volume_src = self._create_volume()
self.volume.create_volume(self.context, volume_src['id'])
snapCount = len(db.snapshot_get_all_for_volume(self.context,
volume_src['id']))
self.assertTrue(snapCount == 0)
snapshot = self._create_snapshot(volume_src['id'])
snapshot_id = snapshot['id']
self.volume.create_snapshot(self.context, volume_src['id'],
snapshot_id)
self.assertTrue(os.path.exists(os.path.join(self.volumes_path,
snapshot['name'])))
snapCount = len(db.snapshot_get_all_for_volume(self.context,
volume_src['id']))
self.assertTrue(snapCount == 1)
self.volume.delete_volume(self.context, volume_src['id'])
self.volume.delete_snapshot(self.context, snapshot_id)
self.assertFalse(os.path.exists(os.path.join(self.volumes_path,
snapshot['name'])))
snapCount = len(db.snapshot_get_all_for_volume(self.context,
volume_src['id']))
self.assertTrue(snapCount == 0)
def test_create_volume_from_snapshot(self):
volume_src = self._create_volume()
self.volume.create_volume(self.context, volume_src['id'])
snapshot = self._create_snapshot(volume_src['id'])
snapshot_id = snapshot['id']
self.volume.create_snapshot(self.context, volume_src['id'],
snapshot_id)
self.assertTrue(os.path.exists(os.path.join(self.volumes_path,
snapshot['name'])))
volume_dst = self._create_volume(0, snapshot_id)
self.volume.create_volume(self.context, volume_dst['id'], snapshot_id)
self.assertEqual(volume_dst['id'], db.volume_get(
context.get_admin_context(),
volume_dst['id']).id)
self.assertEqual(snapshot_id, db.volume_get(
context.get_admin_context(),
volume_dst['id']).snapshot_id)
self.volume.delete_volume(self.context, volume_dst['id'])
self.volume.delete_volume(self.context, volume_src['id'])
self.volume.delete_snapshot(self.context, snapshot_id)
def test_create_cloned_volume(self):
volume_src = self._create_volume()
self.volume.create_volume(self.context, volume_src['id'])
volume_dst = self._create_volume()
volumepath = os.path.join(self.volumes_path, volume_dst['name'])
self.assertFalse(os.path.exists(volumepath))
self.driver.create_cloned_volume(volume_dst, volume_src)
self.assertEqual(volume_dst['id'], db.volume_get(
context.get_admin_context(),
volume_dst['id']).id)
self.assertTrue(os.path.exists(volumepath))
self.volume.delete_volume(self.context, volume_src['id'])
self.volume.delete_volume(self.context, volume_dst['id'])
def test_create_volume_from_snapshot_method(self):
volume_src = self._create_volume()
self.volume.create_volume(self.context, volume_src['id'])
snapshot = self._create_snapshot(volume_src['id'])
snapshot_id = snapshot['id']
self.volume.create_snapshot(self.context, volume_src['id'],
snapshot_id)
volume_dst = self._create_volume()
self.driver.create_volume_from_snapshot(volume_dst, snapshot)
self.assertEqual(volume_dst['id'], db.volume_get(
context.get_admin_context(),
volume_dst['id']).id)
volumepath = os.path.join(self.volumes_path, volume_dst['name'])
self.assertTrue(os.path.exists(volumepath))
self.volume.delete_volume(self.context, volume_dst['id'])
self.volume.delete_volume(self.context, volume_src['id'])
self.volume.delete_snapshot(self.context, snapshot_id)
def test_copy_image_to_volume_with_copy_on_write_mode(self):
"""Test the function of copy_image_to_volume
focusing on the integretion of the image_util
using copy_on_write image sharing mode.
"""
# specify image file format is raw
self.stubs.Set(image_utils, 'qemu_img_info',
self._fake_qemu_raw_image_info)
volume = self._create_volume()
volumepath = os.path.join(self.volumes_path, volume['name'])
CONF.gpfs_images_share_mode = 'copy_on_write'
self.driver.copy_image_to_volume(self.context,
volume,
FakeImageService(),
self.image_id)
self.assertTrue(os.path.exists(volumepath))
self.volume.delete_volume(self.context, volume['id'])
self.assertFalse(os.path.exists(volumepath))
def test_copy_image_to_volume_with_copy_mode(self):
"""Test the function of copy_image_to_volume
focusing on the integretion of the image_util
using copy image sharing mode.
"""
# specify image file format is raw
self.stubs.Set(image_utils, 'qemu_img_info',
self._fake_qemu_raw_image_info)
volume = self._create_volume()
volumepath = os.path.join(self.volumes_path, volume['name'])
CONF.gpfs_images_share_mode = 'copy'
self.driver.copy_image_to_volume(self.context,
volume,
FakeImageService(),
self.image_id)
self.assertTrue(os.path.exists(volumepath))
self.volume.delete_volume(self.context, volume['id'])
def test_copy_image_to_volume_with_non_gpfs_image_dir(self):
"""Test the function of copy_image_to_volume
focusing on the integretion of the image_util
using a non gpfs glance images directory
"""
# specify image file format is raw
self.stubs.Set(image_utils, 'qemu_img_info',
self._fake_qemu_raw_image_info)
for share_mode in ['copy_on_write', 'copy']:
volume = self._create_volume()
volumepath = os.path.join(self.volumes_path, volume['name'])
CONF.gpfs_images_share_mode = share_mode
CONF.gpfs_images_dir = None
self.driver.copy_image_to_volume(self.context,
volume,
FakeImageService(),
self.image_id)
self.assertTrue(os.path.exists(volumepath))
self.volume.delete_volume(self.context, volume['id'])
def test_copy_image_to_volume_with_illegal_image_format(self):
"""Test the function of copy_image_to_volume
focusing on the integretion of the image_util
using an illegal image file format
"""
# specify image file format is qcow2
self.stubs.Set(image_utils, 'qemu_img_info',
self._fake_qemu_qcow2_image_info)
volume = self._create_volume()
CONF.gpfs_images_share_mode = 'copy'
CONF.gpfs_images_dir = self.images_dir
self.assertRaises(exception.ImageUnacceptable,
self.driver.copy_image_to_volume,
self.context,
volume,
FakeImageService(),
self.image_id)
self.volume.delete_volume(self.context, volume['id'])
def test_get_volume_stats(self):
stats = self.driver.get_volume_stats()
self.assertEquals(stats['volume_backend_name'], 'GPFS')
self.assertEquals(stats['storage_protocol'], 'file')
def test_check_for_setup_error_ok(self):
self.stubs.Set(GPFSDriver, '_get_gpfs_state',
self._fake_gpfs_get_state_active)
self.stubs.Set(GPFSDriver, '_is_gpfs_path',
self._fake_is_gpfs_path)
self.driver.check_for_setup_error()
def test_check_for_setup_error_gpfs_not_active(self):
self.stubs.Set(GPFSDriver, '_get_gpfs_state',
self._fake_gpfs_get_state_not_active)
self.stubs.Set(GPFSDriver, '_is_gpfs_path',
self._fake_is_gpfs_path)
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.check_for_setup_error)
def test_check_for_setup_error_not_gpfs_path(self):
self.stubs.Set(GPFSDriver, '_get_gpfs_state',
self._fake_gpfs_get_state_active)
self.stubs.Set(GPFSDriver, '_is_gpfs_path',
self._fake_is_not_gpfs_path)
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.check_for_setup_error)
def _fake_gpfs_snap(self, src, dest=None, modebits='644'):
if dest is None:
dest = src
utils.execute('truncate', '-s', 0, dest)
utils.execute('chmod', '644', dest)
def _fake_gpfs_copy(self, src, dest):
utils.execute('truncate', '-s', 0, dest)
utils.execute('chmod', '666', dest)
def _fake_gpfs_redirect(self, src):
return True
def _fake_is_gpfs_parent(self, gpfs_file):
return False
def _fake_gpfs_get_state_active(self):
active_txt = ('mmgetstate::HEADER:version:reserved:reserved:'
'nodeName:nodeNumber:state:quorum:nodesUp:totalNodes:'
'remarks:cnfsState:\n'
'mmgetstate::0:1:::hostname:1:active:1:1:'
'1:quorum node:(undefined):')
return active_txt
def _fake_gpfs_get_state_not_active(self):
inactive_txt = ('mmgetstate::HEADER:version:reserved:reserved:'
'nodeName:nodeNumber:state:quorum:nodesUp:totalNodes:'
'remarks:cnfsState:\n'
'mmgetstate::0:1:::hostname:1:down:1:1:'
'1:quorum node:(undefined):')
return inactive_txt
def _fake_is_gpfs_path(self, path):
pass
def _fake_is_not_gpfs_path(self, path):
raise(exception.ProcessExecutionError('invalid gpfs path'))
def _fake_convert_image(self, source, dest, out_format):
utils.execute('cp', source, dest)
def _fake_qemu_qcow2_image_info(self, path):
data = FakeQemuImgInfo()
data.file_format = 'qcow2'
data.backing_file = None
data.virtual_size = 1024 * 1024 * 1024
return data
def _fake_qemu_raw_image_info(self, path):
data = FakeQemuImgInfo()
data.file_format = 'raw'
data.backing_file = None
data.virtual_size = 1024 * 1024 * 1024
return data
def _fake_delete_gpfs_file(self, fchild):
volume_path = fchild
vol_name = os.path.basename(fchild)
vol_id = vol_name.split('volume-').pop()
utils.execute('rm', '-f', volume_path)
utils.execute('rm', '-f', volume_path + '.snap')
all_snaps = db.snapshot_get_all_for_volume(self.context, vol_id)
for snap in all_snaps:
snap_path = self.volumes_path + '/' + snap['name']
utils.execute('rm', '-f', snap_path)

View File

@ -0,0 +1,479 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright IBM Corp. 2013 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.
"""
GPFS Volume Driver.
"""
import math
import os
import re
from oslo.config import cfg
from cinder import exception
from cinder.image import image_utils
from cinder.openstack.common import fileutils
from cinder.openstack.common import log as logging
from cinder import units
from cinder.volume import driver
VERSION = 1.0
LOG = logging.getLogger(__name__)
gpfs_opts = [
cfg.StrOpt('gpfs_mount_point_base',
default=None,
help='Path to the directory on GPFS mount point where '
'volumes are stored'),
cfg.StrOpt('gpfs_images_dir',
default=None,
help='Path to GPFS Glance repository as mounted on '
'Nova nodes'),
cfg.StrOpt('gpfs_images_share_mode',
default=None,
help='Set this if Glance image repo is on GPFS as well '
'so that the image bits can be transferred efficiently '
'between Glance and Cinder. Valid values are copy or '
'copy_on_write. copy performs a full copy of the image, '
'copy_on_write efficiently shares unmodified blocks of '
'the image.'),
cfg.IntOpt('gpfs_max_clone_depth',
default=0,
help='A lengthy chain of copy-on-write snapshots or clones '
'could have impact on performance. This option limits '
'the number of indirections required to reach a specific '
'block. 0 indicates unlimited.'),
cfg.BoolOpt('gpfs_sparse_volumes',
default=True,
help=('Create volumes as sparse files which take no space. '
'If set to False volume is created as regular file. '
'In this case volume creation may take a significantly '
'longer time.')),
]
CONF = cfg.CONF
CONF.register_opts(gpfs_opts)
class GPFSDriver(driver.VolumeDriver):
"""Implements volume functions using GPFS primitives."""
def __init__(self, *args, **kwargs):
super(GPFSDriver, self).__init__(*args, **kwargs)
self.configuration.append_config_values(gpfs_opts)
def _get_gpfs_state(self):
(out, _) = self._execute('mmgetstate', '-Y', run_as_root=True)
return out
def _check_gpfs_state(self):
out = self._get_gpfs_state()
lines = out.splitlines()
state_token = lines[0].split(':').index('state')
gpfs_state = lines[1].split(':')[state_token]
if gpfs_state != 'active':
LOG.error(_('GPFS is not active. Detailed output: %s') % out)
exception_message = (_("GPFS is not running - state: %s") %
gpfs_state)
raise exception.VolumeBackendAPIException(data=exception_message)
def _is_gpfs_path(self, directory):
self._execute('mmlsattr', directory, run_as_root=True)
def _is_samefs(self, p1, p2):
if os.lstat(p1).st_dev == os.lstat(p2).st_dev:
return True
return False
def check_for_setup_error(self):
"""Returns an error if prerequisites aren't met."""
self._check_gpfs_state()
if(self.configuration.gpfs_mount_point_base is None):
msg = _('Option gpfs_mount_point_base is not set correctly.')
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
if(self.configuration.gpfs_images_share_mode and
self.configuration.gpfs_images_share_mode not in ['copy_on_write',
'copy']):
msg = _('Option gpfs_images_share_mode is not set correctly.')
LOG.warn(msg)
raise exception.VolumeBackendAPIException(data=msg)
if(self.configuration.gpfs_images_share_mode and
self.configuration.gpfs_images_dir is None):
msg = _('Option gpfs_images_dir is not set correctly.')
LOG.warn(msg)
raise exception.VolumeBackendAPIException(data=msg)
if(self.configuration.gpfs_images_share_mode == 'copy_on_write' and
not self._is_samefs(self.configuration.gpfs_mount_point_base,
self.configuration.gpfs_images_dir)):
msg = (_('gpfs_images_share_mode is set to copy_on_write, but '
'%(vol)s and %(img)s belong to different file systems') %
{'vol': self.configuration.gpfs_mount_point_base,
'img': self.configuration.gpfs_images_dir})
LOG.warn(msg)
raise exception.VolumeBackendAPIException(data=msg)
for directory in [self.configuration.gpfs_mount_point_base,
self.configuration.gpfs_images_dir]:
if directory is None:
continue
if not directory.startswith('/'):
msg = (_('%s must be an absolute path.') % directory)
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
if not os.path.isdir(directory):
msg = (_('%s is not a directory.') % directory)
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
try:
# check that configured directories are on GPFS
self._is_gpfs_path(directory)
except exception.ProcessExecutionError:
msg = (_('%s is not on GPFS. Perhaps GPFS not mounted.') %
directory)
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
def _create_sparse_file(self, path, size):
"""Creates file with 0 disk usage."""
sizestr = self._sizestr(size)
self._execute('truncate', '-s', sizestr, path, run_as_root=True)
self._execute('chmod', '666', path, run_as_root=True)
def _create_regular_file(self, path, size):
"""Creates regular file of given size."""
block_size_mb = 1
block_count = size * units.GiB / (block_size_mb * units.MiB)
self._execute('dd', 'if=/dev/zero', 'of=%s' % path,
'bs=%dM' % block_size_mb,
'count=%d' % block_count,
run_as_root=True)
self._execute('chmod', '666', path, run_as_root=True)
def create_volume(self, volume):
"""Creates a GPFS volume."""
volume_path = self.local_path(volume)
volume_size = volume['size']
if self.configuration.gpfs_sparse_volumes:
self._create_sparse_file(volume_path, volume_size)
else:
self._create_regular_file(volume_path, volume_size)
v_metadata = volume.get('volume_metadata')
fstype = None
fslabel = None
for item in v_metadata:
if item['key'] == 'fstype':
fstype = item['value']
elif item['key'] == 'fslabel':
fslabel = item['value']
if fstype:
self._mkfs(volume, fstype, fslabel)
def create_volume_from_snapshot(self, volume, snapshot):
"""Creates a GPFS volume from a snapshot."""
volume_path = self.local_path(volume)
snapshot_path = self.local_path(snapshot)
self._create_gpfs_copy(src=snapshot_path, dest=volume_path)
self._gpfs_redirect(volume_path)
data = image_utils.qemu_img_info(volume_path)
return {'size': math.ceil(data.virtual_size / 1024.0 ** 3)}
def create_cloned_volume(self, volume, src_vref):
src = self.local_path(src_vref)
dest = self.local_path(volume)
self._create_gpfs_clone(src, dest)
data = image_utils.qemu_img_info(dest)
return {'size': math.ceil(data.virtual_size / 1024.0 ** 3)}
def _delete_gpfs_file(self, fchild):
if not os.path.exists(fchild):
return
(out, err) = self._execute('mmclone', 'show', fchild, run_as_root=True)
fparent = None
reInode = re.compile(
'.*\s+(?:yes|no)\s+\d+\s+(?P<inode>\d+)', re.M | re.S)
match = reInode.match(out)
if match:
inode = match.group('inode')
path = os.path.dirname(fchild)
(out, err) = self._execute('find', path, '-maxdepth', '1',
'-inum', inode, run_as_root=True)
if out:
fparent = out.split('\n', 1)[0]
self._execute(
'rm', '-f', fchild, check_exit_code=False, run_as_root=True)
# There is no need to check for volume references on this snapshot
# because 'rm -f' itself serves as a simple and implicit check. If the
# parent is referenced by another volume, GPFS doesn't allow deleting
# it. 'rm -f' silently fails and the subsequent check on the path
# indicates whether there are any volumes derived from that snapshot.
# If there are such volumes, we quit recursion and let the other
# volumes delete the snapshot later. If there are no references, rm
# would succeed and the snapshot is deleted.
if not os.path.exists(fchild) and fparent:
fpbase = os.path.basename(fparent)
if (fpbase.startswith('snapshot-') or fpbase.endswith('.snap')):
self._delete_gpfs_file(fparent)
def delete_volume(self, volume):
"""Deletes a logical volume."""
volume_path = self.local_path(volume)
self._delete_gpfs_file(volume_path)
def _gpfs_redirect(self, src):
"""Removes the copy_on_write dependency between src and parent.
Remove the copy_on_write dependency between the src file and its
immediate parent such that the length of dependency chain is reduced
by 1.
"""
max_depth = self.configuration.gpfs_max_clone_depth
if max_depth == 0:
return False
(out, err) = self._execute('mmclone', 'show', src, run_as_root=True)
reDepth = re.compile('.*\s+no\s+(?P<depth>\d+)', re.M | re.S)
match = reDepth.match(out)
if match:
depth = int(match.group('depth'))
if depth > max_depth:
self._execute('mmclone', 'redirect', src, run_as_root=True)
return True
return False
def _create_gpfs_clone(self, src, dest):
snap = dest + ".snap"
self._create_gpfs_snap(src, snap)
self._create_gpfs_copy(snap, dest)
if(self._gpfs_redirect(src) and self._gpfs_redirect(dest)):
self._execute('rm', '-f', snap, run_as_root=True)
def _create_gpfs_copy(self, src, dest, modebits='666'):
self._execute('mmclone', 'copy', src, dest, run_as_root=True)
self._execute('chmod', modebits, dest, run_as_root=True)
def _create_gpfs_snap(self, src, dest=None, modebits='644'):
if dest is None:
self._execute('mmclone', 'snap', src, run_as_root=True)
self._execute('chmod', modebits, src, run_as_root=True)
else:
self._execute('mmclone', 'snap', src, dest, run_as_root=True)
self._execute('chmod', modebits, dest, run_as_root=True)
def _is_gpfs_parent_file(self, gpfs_file):
out, _ = self._execute('mmclone', 'show', gpfs_file, run_as_root=True)
ptoken = out.splitlines().pop().split()[0]
return ptoken == 'yes'
def create_snapshot(self, snapshot):
"""Creates a GPFS snapshot."""
snapshot_path = self.local_path(snapshot)
volume_path = os.path.join(self.configuration.gpfs_mount_point_base,
snapshot['volume_name'])
self._create_gpfs_snap(src=volume_path, dest=snapshot_path)
def delete_snapshot(self, snapshot):
"""Deletes a GPFS snapshot."""
# A snapshot file is deleted as a part of delete_volume when
# all volumes derived from it are deleted.
def local_path(self, volume):
return os.path.join(self.configuration.gpfs_mount_point_base,
volume['name'])
def ensure_export(self, context, volume):
"""Synchronously recreates an export for a logical volume."""
pass
def create_export(self, context, volume):
"""Exports the volume."""
pass
def remove_export(self, context, volume):
"""Removes an export for a logical volume."""
pass
def initialize_connection(self, volume, connector):
return {
'driver_volume_type': 'local',
'data': {
'name': volume['name'],
'device_path': self.local_path(volume),
}
}
def terminate_connection(self, volume, connector, **kwargs):
pass
def get_volume_stats(self, refresh=False):
"""Get volume status.
If 'refresh' is True, or stats have never been updated, run update
the stats first.
"""
if not self._stats or refresh:
self._update_volume_status()
return self._stats
def _update_volume_status(self):
"""Retrieve status info from volume group."""
LOG.debug("Updating volume status")
data = {}
backend_name = self.configuration.safe_get('volume_backend_name')
data["volume_backend_name"] = backend_name or 'GPFS'
data["vendor_name"] = 'IBM'
data["driver_version"] = '1.0'
data["storage_protocol"] = 'file'
free, capacity = self._get_available_capacity(self.configuration.
gpfs_mount_point_base)
data['total_capacity_gb'] = math.ceil(capacity / 1024.0 ** 3)
data['free_capacity_gb'] = math.ceil(free / 1024.0 ** 3)
data['reserved_percentage'] = 0
data['QoS_support'] = False
self._stats = data
def _sizestr(self, size_in_g):
if int(size_in_g) == 0:
return '100M'
return '%sG' % size_in_g
def copy_image_to_volume(self, context, volume, image_service, image_id):
"""Fetch the image from image_service and write it to the volume."""
return self._gpfs_fetch_to_raw(context, image_service, image_id,
self.local_path(volume))
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))
def _mkfs(self, volume, fs, label=None):
if fs == 'swap':
cmd = ['mkswap']
else:
cmd = ['mkfs', '-t', fs]
if fs in ('ext3', 'ext4'):
cmd.append('-F')
if label:
if fs in ('msdos', 'vfat'):
label_opt = '-n'
else:
label_opt = '-L'
cmd.extend([label_opt, label])
path = self.local_path(volume)
cmd.append(path)
try:
self._execute(*cmd, run_as_root=True)
except exception.ProcessExecutionError as exc:
exception_message = (_("mkfs failed on volume %(vol)s, "
"error message was: %(err)s")
% {'vol': volume['name'], 'err': exc.stderr})
LOG.error(exception_message)
raise exception.VolumeBackendAPIException(
data=exception_message)
def _get_available_capacity(self, path):
"""Calculate available space on path."""
out, _ = self._execute('df', '-P', '-B', '1', path,
run_as_root=True)
out = out.splitlines()[1]
size = int(out.split()[1])
available = int(out.split()[3])
return available, size
def _gpfs_fetch(self, context, image_service, image_id, path, _user_id,
_project_id):
if not (self.configuration.gpfs_images_share_mode and
self.configuration.gpfs_images_dir and
os.path.exists(
os.path.join(self.configuration.gpfs_images_dir,
image_id))):
with fileutils.remove_path_on_error(path):
with open(path, "wb") as image_file:
image_service.download(context, image_id, image_file)
else:
image_path = os.path.join(self.configuration.gpfs_images_dir,
image_id)
if self.configuration.gpfs_images_share_mode == 'copy_on_write':
# check if the image is a GPFS snap file
if not self._is_gpfs_parent_file(image_path):
self._create_gpfs_snap(image_path, modebits='666')
self._execute('ln', '-s', image_path, path, run_as_root=True)
else: # copy
self._execute('cp', image_path, path, run_as_root=True)
self._execute('chmod', '666', path, run_as_root=True)
def _gpfs_fetch_to_raw(self, context, image_service, image_id, dest,
user_id=None, project_id=None):
if (self.configuration.image_conversion_dir and not
os.path.exists(self.configuration.image_conversion_dir)):
os.makedirs(self.configuration.image_conversion_dir)
tmp = "%s.part" % dest
with fileutils.remove_path_on_error(tmp):
self._gpfs_fetch(context, image_service, image_id, tmp, user_id,
project_id)
data = image_utils.qemu_img_info(tmp)
fmt = data.file_format
if fmt is None:
msg = _("'qemu-img info' parsing failed.")
LOG.error(msg)
raise exception.ImageUnacceptable(
reason=msg,
image_id=image_id)
backing_file = data.backing_file
if backing_file is not None:
msg = (_("fmt = %(fmt)s backed by: %(backing_file)s") %
{'fmt': fmt, 'backing_file': backing_file})
LOG.error(msg)
raise exception.ImageUnacceptable(
image_id=image_id,
reason=msg)
LOG.debug("%s was %s, converting to raw" % (image_id, fmt))
image_utils.convert_image(tmp, dest, 'raw')
data = image_utils.qemu_img_info(dest)
if data.file_format != "raw":
msg = (_("Converted to raw, but format is now %s") %
data.file_format)
LOG.error(msg)
raise exception.ImageUnacceptable(
image_id=image_id,
reason=msg)
os.unlink(tmp)
return {'size': math.ceil(data.virtual_size / 1024.0 ** 3)}

View File

@ -1401,4 +1401,36 @@
# Driver to use for volume creation (string value)
#volume_driver=cinder.volume.drivers.lvm.LVMISCSIDriver
# Total option count: 300
#
# Options defined in cinder.volume.drivers.gpfs
#
# Path to the directory on GPFS mount point where
# volumes are stored (string value)
# gpfs_mount_point_base=$state_path/mnt
# Path to GPFS Glance repository as mounted on
# Nova nodes (string value)
# gpfs_images_dir=None
# Set this if Glance image repo is on GPFS as well
# so that the image bits can be transferred efficiently
# between Glance and Cinder. Valid values are copy or
# copy_on_write. copy performs a full copy of the image,
# copy_on_write efficiently shares unmodified blocks of
# the image. (string value)
# gpfs_images_share_mode=None
# A lengthy chain of copy-on-write snapshots or clones
# could have impact on performance. This option limits
# the number of indirections required to reach a specific
# block. 0 indicates unlimited. (integer value)
# gpfs_max_clone_depth=0
# Create volumes as sparse files which take no space.
# If set to False volume is created as regular file.
# In this case volume creation may take a significantly
# longer time. (boolean value)
# gpfs_sparse_volumes=True
# Total option count: 305

View File

@ -61,3 +61,10 @@ hus_cmd: CommandFilter, hus_cmd, root
ls: CommandFilter, ls, root
tee: CommandFilter, tee, root
multipath: CommandFilter, multipath, root
# cinder/volume/drivers/gpfs.py
mmgetstate: CommandFilter, /usr/lpp/mmfs/bin/mmgetstate, root
mmclone: CommandFilter, /usr/lpp/mmfs/bin/mmclone, root
mmlsattr: CommandFilter, /usr/lpp/mmfs/bin/mmlsattr, root
find: CommandFilter, find, root
mkfs: CommandFilter, mkfs, root