Refactor LVM driver to use Brick VG utility

Refactor the LVM driver to utilize bricks LVM
module.  This includes significant modification to
tests and fake objects related to testing.

implements blueprint: refactor-lvm-and-iscsi-driver

Change-Id: I94e41abf90d0c5b77e732d40ed8b81b0a2f5d7dd
This commit is contained in:
John Griffith 2013-07-22 10:39:54 -06:00
parent f88732fdd5
commit b69990682b
8 changed files with 400 additions and 275 deletions

View File

@ -35,21 +35,22 @@ LOG = logging.getLogger(__name__)
class LVM(object):
"""LVM object to enable various LVM related operations."""
def __init__(self,
vg_name,
create_vg=False,
physical_volumes=None,
lvm_type='default',
def __init__(self, vg_name, root_helper, create_vg=False,
physical_volumes=None, lvm_type='default',
executor=putils.execute):
"""Initialize the LVM object.
The LVM object is based on an LVM VolumeGroup, one instantiation
for each VolumeGroup you have/use.
:param vg_name: Name of existing VG or VG to create
:param root_helper: Execution root_helper method to use
:param create_vg: Indicates the VG doesn't exist
and we want to create it
:param physical_volumes: List of PVs to build VG on
:param lvm_type: VG and Volume type (default, or thin)
:param executor: Execute method to use, None uses common/processutils
"""
self.vg_name = vg_name
@ -61,7 +62,8 @@ class LVM(object):
self.vg_uuid = None
self.vg_thin_pool = None
self.vg_thin_pool_size = 0
self._execute = executor
self.root_helper = root_helper
self._set_execute(executor)
if create_vg and physical_volumes is not None:
self.pv_list = physical_volumes
@ -86,6 +88,9 @@ class LVM(object):
else:
self.vg_thin_pool = pool_name
def _set_execute(self, execute):
self._execute = execute
def _size_str(self, size_in_g):
if '.00' in size_in_g:
size_in_g = size_in_g.replace('.00', '')
@ -103,7 +108,9 @@ class LVM(object):
"""
exists = False
cmd = ['vgs', '--noheadings', '-o', 'name']
(out, err) = self._execute(*cmd, root_helper='sudo', run_as_root=True)
(out, err) = self._execute(*cmd,
root_helper=self.root_helper,
run_as_root=True)
if out is not None:
volume_groups = out.split()
@ -114,7 +121,7 @@ class LVM(object):
def _create_vg(self, pv_list):
cmd = ['vgcreate', self.vg_name, ','.join(pv_list)]
self._execute(*cmd, root_helper='sudo', run_as_root=True)
self._execute(*cmd, root_helper=self.root_helper, run_as_root=True)
def _get_vg_uuid(self):
(out, err) = self._execute('vgs', '--noheadings',
@ -125,14 +132,17 @@ class LVM(object):
return []
@staticmethod
def supports_thin_provisioning():
def supports_thin_provisioning(root_helper):
"""Static method to check for thin LVM support on a system.
:param root_helper: root_helper to use for execute
:returns: True if supported, False otherwise
"""
cmd = ['vgs', '--version']
(out, err) = putils.execute(*cmd, root_helper='sudo', run_as_root=True)
(out, err) = putils.execute(*cmd,
root_helper=root_helper,
run_as_root=True)
lines = out.split('\n')
for line in lines:
@ -147,9 +157,10 @@ class LVM(object):
return False
@staticmethod
def get_all_volumes(vg_name=None, no_suffix=True):
def get_all_volumes(root_helper, vg_name=None, no_suffix=True):
"""Static method to get all LV's on a system.
:param root_helper: root_helper to use for execute
:param vg_name: optional, gathers info for only the specified VG
:param no_suffix: optional, reports sizes in g with no suffix
:returns: List of Dictionaries with LV info
@ -163,7 +174,9 @@ class LVM(object):
if vg_name is not None:
cmd.append(vg_name)
(out, err) = putils.execute(*cmd, root_helper='sudo', run_as_root=True)
(out, err) = putils.execute(*cmd,
root_helper=root_helper,
run_as_root=True)
lv_list = []
if out is not None:
@ -179,7 +192,7 @@ class LVM(object):
:returns: List of Dictionaries with LV info
"""
self.lv_list = self.get_all_volumes(self.vg_name)
self.lv_list = self.get_all_volumes(self.root_helper, self.vg_name)
return self.lv_list
def get_volume(self, name):
@ -194,9 +207,10 @@ class LVM(object):
return r
@staticmethod
def get_all_physical_volumes(vg_name=None, no_suffix=True):
def get_all_physical_volumes(root_helper, vg_name=None, no_suffix=True):
"""Static method to get all PVs on a system.
:param root_helper: root_helper to use for execute
:param vg_name: optional, gathers info for only the specified VG
:param no_suffix: optional, reports sizes in g with no suffix
:returns: List of Dictionaries with PV info
@ -212,7 +226,9 @@ class LVM(object):
if vg_name is not None:
cmd.append(vg_name)
(out, err) = putils.execute(*cmd, root_helper='sudo', run_as_root=True)
(out, err) = putils.execute(*cmd,
root_helper=root_helper,
run_as_root=True)
pv_list = []
if out is not None:
@ -232,13 +248,15 @@ class LVM(object):
:returns: List of Dictionaries with PV info
"""
self.pv_list = self.get_all_physical_volumes(self.vg_name)
self.pv_list = self.get_all_physical_volumes(self.root_helper,
self.vg_name)
return self.pv_list
@staticmethod
def get_all_volume_groups(vg_name=None, no_suffix=True):
def get_all_volume_groups(root_helper, vg_name=None, no_suffix=True):
"""Static method to get all VGs on a system.
:param root_helper: root_helper to use for execute
:param vg_name: optional, gathers info for only the specified VG
:param no_suffix: optional, reports sizes in g with no suffix
:returns: List of Dictionaries with VG info
@ -255,7 +273,9 @@ class LVM(object):
if vg_name is not None:
cmd.append(vg_name)
(out, err) = putils.execute(*cmd, root_helper='sudo', run_as_root=True)
(out, err) = putils.execute(*cmd,
root_helper=root_helper,
run_as_root=True)
vg_list = []
if out is not None:
@ -279,7 +299,7 @@ class LVM(object):
:returns: Dictionaries of VG info
"""
vg_list = self.get_all_volume_groups(self.vg_name)
vg_list = self.get_all_volume_groups(self.root_helper, self.vg_name)
if len(vg_list) != 1:
LOG.error(_('Unable to find VG: %s') % self.vg_name)
@ -291,9 +311,9 @@ class LVM(object):
self.vg_uuid = vg_list[0]['uuid']
if self.vg_thin_pool is not None:
for lv in self.get_all_volumes(self.vg_name):
if lv[1] == self.vg_thin_pool:
self.vg_thin_pool_size = lv[2]
for lv in self.get_all_volumes(self.root_helper, self.vg_name):
if lv['name'] == self.vg_thin_pool:
self.vg_thin_pool_size = lv['size']
def create_thin_pool(self, name=None, size_str=0):
"""Creates a thin provisioning pool for this VG.
@ -307,7 +327,7 @@ class LVM(object):
"""
if not self.supports_thin_provisioning():
if not self.supports_thin_provisioning(self.root_helper):
LOG.error(_('Requested to setup thin provisioning, '
'however current LVM version does not '
'support it.'))
@ -327,7 +347,7 @@ class LVM(object):
cmd = ['lvcreate', '-T', '-L', size_str, pool_path]
self._execute(*cmd,
root_helper='sudo',
root_helper=self.root_helper,
run_as_root=True)
self.vg_thin_pool = pool_path
@ -358,7 +378,7 @@ class LVM(object):
try:
self._execute(*cmd,
root_helper='sudo',
root_helper=self.root_helper,
run_as_root=True)
except putils.ProcessExecutionError as err:
LOG.exception(_('Error creating Volume'))
@ -387,7 +407,7 @@ class LVM(object):
try:
self._execute(*cmd,
root_helper='sudo',
root_helper=self.root_helper,
run_as_root=True)
except putils.ProcessExecutionError as err:
LOG.exception(_('Error creating snapshot'))
@ -405,7 +425,7 @@ class LVM(object):
self._execute('lvremove',
'-f',
'%s/%s' % (self.vg_name, name),
root_helper='sudo', run_as_root=True)
root_helper=self.root_helper, run_as_root=True)
def revert(self, snapshot_name):
"""Revert an LV from snapshot.
@ -414,13 +434,15 @@ class LVM(object):
"""
self._execute('lvconvert', '--merge',
snapshot_name, root_helper='sudo',
snapshot_name, root_helper=self.root_helper,
run_as_root=True)
def lv_has_snapshot(self, name):
out, err = self._execute('lvdisplay', '--noheading',
'-C', '-o', 'Attr',
'%s/%s' % (self.vg_name, name))
'%s/%s' % (self.vg_name, name),
root_helper=self.root_helper,
run_as_root=True)
if out:
out = out.strip()
if (out[0] == 'o') or (out[0] == 'O'):

View File

@ -16,6 +16,7 @@ import webob
from oslo.config import cfg
from cinder.brick.local_dev import lvm as brick_lvm
from cinder import context
from cinder import db
from cinder import exception
@ -45,6 +46,7 @@ class AdminActionsTest(test.TestCase):
self.flags(rpc_backend='cinder.openstack.common.rpc.impl_fake')
self.flags(lock_path=self.tempdir)
self.volume_api = volume_api.API()
self.stubs.Set(brick_lvm.LVM, '_vg_exists', lambda x: True)
def tearDown(self):
shutil.rmtree(self.tempdir)
@ -432,7 +434,6 @@ class AdminActionsTest(test.TestCase):
def test_attach_attaching_volume_with_different_instance(self):
"""Test that attaching volume reserved for another instance fails."""
# admin context
ctx = context.RequestContext('admin', 'fake', True)
# current status is available
volume = db.volume_create(ctx, {'status': 'available', 'host': 'test',

View File

@ -0,0 +1,69 @@
# 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.
from cinder.openstack.common import log as logging
LOG = logging.getLogger(__name__)
class FakeBrickLVM(object):
"""Logs and records calls, for unit tests."""
def __init__(self, vg_name, create, pv_list, vtype, execute=None):
super(FakeBrickLVM, self).__init__()
self.vg_size = '5.00'
self.vg_free_space = '5.00'
self.vg_name = vg_name
def supports_thin_provisioning():
return False
def get_all_volumes(vg_name=None, no_suffix=True):
if vg_name is not None:
return [vg_name]
return ['cinder-volumes', 'fake-vg-1']
def get_volumes(self):
return ['fake-volume']
def get_volume(self, name):
return ['name']
def get_all_physical_volumes(vg_name=None, no_suffix=True):
return []
def get_physical_volumes(self):
return []
def get_all_volume_groups(vg_name=None, no_suffix=True):
return ['cinder-volumes', 'fake-vg']
def update_volume_group_info(self):
pass
def create_thin_pool(self, name=None, size_str=0):
pass
def create_volume(self, name, size_str, lv_type='default', mirror_count=0):
pass
def create_lv_snapshot(self, name, source_lv_name, lv_type='default'):
pass
def delete(self, name):
pass
def revert(self, snapshot_name):
pass
def lv_has_snapshot(self, name):
return False

View File

@ -44,6 +44,7 @@ class BrickLvmTestCase(test.TestCase):
self.stubs.Set(processutils, 'execute',
self.fake_execute)
self.vg = brick.LVM(self.configuration.volume_group_name,
'sudo',
False, None,
'default',
self.fake_execute)
@ -117,7 +118,7 @@ class BrickLvmTestCase(test.TestCase):
self.assertEqual(self.vg.get_volume('fake-1')['name'], 'fake-1')
def test_get_all_physical_volumes(self):
pvs = self.vg.get_all_physical_volumes()
pvs = self.vg.get_all_physical_volumes('sudo')
self.assertEqual(len(pvs), 3)
def test_get_physical_volumes(self):
@ -125,8 +126,9 @@ class BrickLvmTestCase(test.TestCase):
self.assertEqual(len(pvs), 1)
def test_get_volume_groups(self):
self.assertEqual(len(self.vg.get_all_volume_groups()), 3)
self.assertEqual(len(self.vg.get_all_volume_groups('fake-volumes')), 1)
self.assertEqual(len(self.vg.get_all_volume_groups('sudo')), 3)
self.assertEqual(len(self.vg.get_all_volume_groups('sudo',
'fake-volumes')), 1)
def test_thin_support(self):
# lvm.supports_thin() is a static method and doesn't
@ -134,13 +136,13 @@ class BrickLvmTestCase(test.TestCase):
# so we need to stub proessutils.execute appropriately
self.stubs.Set(processutils, 'execute', self.fake_execute)
self.assertTrue(self.vg.supports_thin_provisioning())
self.assertTrue(self.vg.supports_thin_provisioning('sudo'))
self.stubs.Set(processutils, 'execute', self.fake_pretend_lvm_version)
self.assertTrue(self.vg.supports_thin_provisioning())
self.assertTrue(self.vg.supports_thin_provisioning('sudo'))
self.stubs.Set(processutils, 'execute', self.fake_old_lvm_version)
self.assertFalse(self.vg.supports_thin_provisioning())
self.assertFalse(self.vg.supports_thin_provisioning('sudo'))
def test_lv_has_snapshot(self):
self.assertTrue(self.vg.lv_has_snapshot('fake-volumes'))

View File

@ -13,6 +13,7 @@
# under the License.
from cinder.openstack.common import log as logging
from cinder.tests.brick.fake_lvm import FakeBrickLVM
from cinder.volume import driver
from cinder.volume.drivers import lvm
@ -25,6 +26,9 @@ class FakeISCSIDriver(lvm.LVMISCSIDriver):
def __init__(self, *args, **kwargs):
super(FakeISCSIDriver, self).__init__(execute=self.fake_execute,
*args, **kwargs)
self.vg = FakeBrickLVM('cinder-volumes', False,
None, 'default',
self.fake_execute)
def check_for_setup_error(self):
"""No setup necessary in fake mode."""

View File

@ -22,7 +22,6 @@ Tests for Volume Code.
import datetime
import os
import re
import shutil
import socket
import tempfile
@ -30,9 +29,8 @@ import tempfile
import mox
from oslo.config import cfg
from cinder.brick.initiator import connector as brick_conn
from cinder.brick.iscsi import iscsi
from cinder.brick.iser import iser
from cinder.brick.local_dev import lvm as brick_lvm
from cinder import context
from cinder import db
from cinder import exception
@ -45,6 +43,7 @@ from cinder.openstack.common import rpc
import cinder.policy
from cinder import quota
from cinder import test
from cinder.tests.brick.fake_lvm import FakeBrickLVM
from cinder.tests import conf_fixture
from cinder.tests.image import fake as fake_image
from cinder.tests.keymgr import fake as fake_keymgr
@ -78,8 +77,12 @@ class VolumeTestCase(test.TestCase):
self.volume = importutils.import_object(CONF.volume_manager)
self.context = context.get_admin_context()
self.stubs.Set(iscsi.TgtAdm, '_get_target', self.fake_get_target)
self.stubs.Set(brick_lvm.LVM,
'get_all_volume_groups',
self.fake_get_all_volume_groups)
fake_image.stub_out_image_service(self.stubs)
test_notifier.NOTIFICATIONS = []
self.stubs.Set(brick_lvm.LVM, '_vg_exists', lambda x: True)
def tearDown(self):
try:
@ -92,6 +95,13 @@ class VolumeTestCase(test.TestCase):
def fake_get_target(obj, iqn):
return 1
def fake_get_all_volume_groups(obj, vg_name=None, no_suffix=True):
return [{'name': 'cinder-volumes',
'size': '5.00',
'available': '2.50',
'lv_count': '2',
'uuid': 'vR1JU3-FAKE-C4A9-PQFh-Mctm-9FwA-Xwzc1m'}]
@staticmethod
def _create_volume(size=0, snapshot_id=None, image_id=None,
source_volid=None, metadata=None, status="creating",
@ -707,7 +717,6 @@ class VolumeTestCase(test.TestCase):
"""Test snapshot can be created with metadata and deleted."""
test_meta = {'fake_key': 'fake_value'}
volume = self._create_volume(0, None)
volume_id = volume['id']
snapshot = self._create_snapshot(volume['id'], metadata=test_meta)
snapshot_id = snapshot['id']
@ -877,6 +886,12 @@ class VolumeTestCase(test.TestCase):
def test_delete_busy_snapshot(self):
"""Test snapshot can be created and deleted."""
self.volume.driver.vg = FakeBrickLVM('cinder-volumes',
False,
None,
'default')
volume = self._create_volume()
volume_id = volume['id']
self.volume.create_volume(self.context, volume_id)
@ -884,6 +899,7 @@ class VolumeTestCase(test.TestCase):
self.volume.create_snapshot(self.context, volume_id, snapshot_id)
self.mox.StubOutWithMock(self.volume.driver, 'delete_snapshot')
self.volume.driver.delete_snapshot(
mox.IgnoreArg()).AndRaise(
exception.SnapshotIsBusy(snapshot_name='fake'))
@ -1099,8 +1115,6 @@ class VolumeTestCase(test.TestCase):
'disk_format': 'raw',
'container_format': 'bare'}
image_id = '70a599e0-31e7-49b7-b260-868f441e862b'
try:
volume_id = None
volume_api = cinder.volume.api.API(
@ -1125,8 +1139,6 @@ class VolumeTestCase(test.TestCase):
'disk_format': 'raw',
'container_format': 'bare'}
image_id = '70a599e0-31e7-49b7-b260-868f441e862b'
volume_api = cinder.volume.api.API(image_service=_FakeImageService())
self.assertRaises(exception.InvalidInput,
@ -1146,8 +1158,6 @@ class VolumeTestCase(test.TestCase):
'container_format': 'bare',
'min_disk': 5}
image_id = '70a599e0-31e7-49b7-b260-868f441e862b'
volume_api = cinder.volume.api.API(image_service=_FakeImageService())
self.assertRaises(exception.InvalidInput,
@ -1317,8 +1327,6 @@ class VolumeTestCase(test.TestCase):
ctx = context.get_admin_context(read_deleted="yes")
# Find all all snapshots valid within a timeframe window.
vol = db.volume_create(self.context, {'id': 1})
# Not in window
db.snapshot_create(
ctx,
@ -1699,6 +1707,7 @@ class DriverTestCase(test.TestCase):
self.context = context.get_admin_context()
self.output = ""
self.stubs.Set(iscsi.TgtAdm, '_get_target', self.fake_get_target)
self.stubs.Set(brick_lvm.LVM, '_vg_exists', lambda x: True)
def _fake_execute(_command, *_args, **_kwargs):
"""Fake _execute."""
@ -1736,14 +1745,20 @@ class LVMISCSIVolumeDriverTestCase(DriverTestCase):
lambda x: False)
self.stubs.Set(self.volume.driver, '_delete_volume',
lambda x: False)
# Want DriverTestCase._fake_execute to return 'o' so that
# volume.driver.delete_volume() raises the VolumeIsBusy exception.
self.output = 'o'
self.volume.driver.vg = FakeBrickLVM('cinder-volumes',
False,
None,
'default')
self.stubs.Set(self.volume.driver.vg, 'lv_has_snapshot',
lambda x: True)
self.assertRaises(exception.VolumeIsBusy,
self.volume.driver.delete_volume,
{'name': 'test1', 'size': 1024})
# when DriverTestCase._fake_execute returns something other than
# 'o' volume.driver.delete_volume() does not raise an exception.
self.stubs.Set(self.volume.driver.vg, 'lv_has_snapshot',
lambda x: False)
self.output = 'x'
self.volume.driver.delete_volume({'name': 'test1', 'size': 1024})
@ -1784,7 +1799,8 @@ class LVMISCSIVolumeDriverTestCase(DriverTestCase):
def test_lvm_migrate_volume_proceed(self):
hostname = socket.gethostname()
capabilities = {'location_info': 'LVMVolumeDriver:%s:bar' % hostname}
capabilities = {'location_info': 'LVMVolumeDriver:%s:'
'cinder-volumes:default:0' % hostname}
host = {'capabilities': capabilities}
vol = {'name': 'test', 'id': 1, 'size': 1}
self.stubs.Set(self.volume.driver, 'remove_export',
@ -1797,6 +1813,11 @@ class LVMISCSIVolumeDriverTestCase(DriverTestCase):
lambda x: None)
self.stubs.Set(self.volume.driver, '_create_export',
lambda x, y, vg='vg': None)
self.volume.driver.vg = FakeBrickLVM('cinder-volumes',
False,
None,
'default')
moved, model_update = self.volume.driver.migrate_volume(self.context,
vol, host)
self.assertEqual(moved, True)
@ -1884,7 +1905,18 @@ class ISCSITestCase(DriverTestCase):
out += " test2-volumes 5.52 0.52"
return out, None
def _fake_get_all_volume_groups(obj, vg_name=None, no_suffix=True):
return [{'name': 'cinder-volumes',
'size': '5.52',
'available': '0.52',
'lv_count': '2',
'uuid': 'vR1JU3-FAKE-C4A9-PQFh-Mctm-9FwA-Xwzc1m'}]
self.stubs.Set(brick_lvm.LVM,
'get_all_volume_groups',
_fake_get_all_volume_groups)
self.volume.driver.set_execute(_emulate_vgs_execute)
self.volume.driver.vg = brick_lvm.LVM('cinder-volumes', 'sudo')
self.volume.driver._update_volume_stats()
@ -1955,6 +1987,7 @@ class VolumePolicyTestCase(test.TestCase):
cinder.policy.init()
self.context = context.get_admin_context()
self.stubs.Set(brick_lvm.LVM, '_vg_exists', lambda x: True)
def tearDown(self):
super(VolumePolicyTestCase, self).tearDown()

View File

@ -20,7 +20,6 @@ Driver for Linux servers running LVM.
"""
import math
import os
import re
import socket
@ -29,6 +28,7 @@ from oslo.config import cfg
from cinder.brick.iscsi import iscsi
from cinder.brick.iser import iser
from cinder.brick.local_dev import lvm as lvm
from cinder import exception
from cinder.image import image_utils
from cinder.openstack.common import fileutils
@ -51,6 +51,10 @@ volume_opts = [
default=0,
help='If set, create lvms with multiple mirrors. Note that '
'this requires lvm_mirrors + 2 pvs with available space'),
cfg.StrOpt('lvm_type',
default='default',
help='Type of LVM volumes to deploy; (default or thin)'),
]
CONF = cfg.CONF
@ -60,84 +64,115 @@ CONF.register_opts(volume_opts)
class LVMVolumeDriver(driver.VolumeDriver):
"""Executes commands relating to Volumes."""
VERSION = '1.0.0'
VERSION = '2.0.0'
def __init__(self, *args, **kwargs):
def __init__(self, vg_obj=None, *args, **kwargs):
super(LVMVolumeDriver, self).__init__(*args, **kwargs)
self.configuration.append_config_values(volume_opts)
self.hostname = socket.gethostname()
self.vg = vg_obj
def set_execute(self, execute):
self._execute = execute
def check_for_setup_error(self):
"""Returns an error if prerequisites aren't met"""
out, err = self._execute('vgs', '--noheadings', '-o', 'name',
run_as_root=True)
volume_groups = out.split()
if self.configuration.volume_group not in volume_groups:
exception_message = (_("volume group %s doesn't exist")
% self.configuration.volume_group)
raise exception.VolumeBackendAPIException(data=exception_message)
"""Verify that requirements are in place to use LVM driver."""
if self.vg is None:
root_helper = 'sudo cinder-rootwrap %s' % CONF.rootwrap_config
try:
self.vg = lvm.LVM(self.configuration.volume_group,
root_helper,
lvm_type=self.configuration.lvm_type,
executor=self._execute)
except lvm.VolumeGroupNotFound:
message = ("Volume Group %s does not exist" %
self.configuration.volume_group)
raise exception.VolumeBackendAPIException(data=message)
def _create_volume(self, volume_name, sizestr, vg=None):
if vg is None:
vg = self.configuration.volume_group
no_retry_list = ['Insufficient free extents',
'One or more specified logical volume(s) not found']
vg_list = volutils.get_all_volume_groups(
self.configuration.volume_group)
vg_dict = \
(vg for vg in vg_list if vg['name'] == self.vg.vg_name).next()
if vg_dict is None:
message = ("Volume Group %s does not exist" %
self.configuration.volume_group)
raise exception.VolumeBackendAPIException(data=message)
cmd = ['lvcreate', '-L', sizestr, '-n', volume_name, vg]
if self.configuration.lvm_mirrors:
cmd.extend(['-m', self.configuration.lvm_mirrors, '--nosync'])
terras = int(sizestr[:-1]) / 1024.0
if terras >= 1.5:
rsize = int(2 ** math.ceil(math.log(terras) / math.log(2)))
# NOTE(vish): Next power of two for region size. See:
# http://red.ht/U2BPOD
cmd.extend(['-R', str(rsize)])
if self.configuration.lvm_type == 'thin':
# Specific checks for using Thin provisioned LV's
if not volutils.supports_thin_provisioning():
message = ("Thin provisioning not supported "
"on this version of LVM.")
raise exception.VolumeBackendAPIException(data=message)
self._try_execute(*cmd, run_as_root=True, no_retry_list=no_retry_list)
def _volume_not_present(self, volume_name):
path_name = '%s/%s' % (self.configuration.volume_group, volume_name)
try:
self._try_execute('lvdisplay', path_name, run_as_root=True)
except Exception as e:
# If the volume isn't present
return True
return False
def _delete_volume(self, volume):
"""Deletes a logical volume."""
# zero out old volumes to prevent data leaking between users
# TODO(ja): reclaiming space should be done lazy and low priority
dev_path = self.local_path(volume)
if os.path.exists(dev_path):
self.clear_volume(volume)
self._try_execute('lvremove', '-f', "%s/%s" %
(self.configuration.volume_group,
self._escape_snapshot(volume['name'])),
run_as_root=True)
pool_name = "%s-pool" % self.configuration.volume_group
if self.vg.get_volume(pool_name) is None:
try:
self.vg.create_thin_pool(pool_name)
except exception.ProcessExecutionError as exc:
exception_message = ("Failed to create thin pool, "
"error message was: %s"
% exc.stderr)
raise exception.VolumeBackendAPIException(
data=exception_message)
def _sizestr(self, size_in_g):
if int(size_in_g) == 0:
return '100M'
return '%sG' % size_in_g
return '100m'
return '%sg' % size_in_g
def _volume_not_present(self, volume_name):
return self.vg.get_volume(volume_name) is None
def _delete_volume(self, volume, is_snapshot=False):
"""Deletes a logical volume."""
# zero out old volumes to prevent data leaking between users
# TODO(ja): reclaiming space should be done lazy and low priority
dev_path = self.local_path(volume)
# TODO(jdg): Maybe we could optimize this for snaps by looking at
# the cow table and only overwriting what's necessary?
# for now we're still skipping on snaps due to hang issue
if os.path.exists(dev_path) and not is_snapshot:
self.clear_volume(volume)
name = volume['name']
if is_snapshot:
name = self._escape_snapshot(volume['name'])
self.vg.delete(name)
# Linux LVM reserves name that starts with snapshot, so that
# such volume name can't be created. Mangle it.
def _escape_snapshot(self, snapshot_name):
# Linux LVM reserves name that starts with snapshot, so that
# such volume name can't be created. Mangle it.
if not snapshot_name.startswith('snapshot'):
return snapshot_name
return '_' + snapshot_name
def _create_volume(self, name, size, lvm_type, mirror_count, vg=None):
vg_ref = self.vg
if vg is not None:
vg_ref = vg
vg_ref.create_volume(name, size, lvm_type, mirror_count)
def create_volume(self, volume):
"""Creates a logical volume. Can optionally return a Dictionary of
changes to the volume object to be persisted.
"""
self._create_volume(volume['name'], self._sizestr(volume['size']))
"""Creates a logical volume."""
mirror_count = 0
if self.configuration.lvm_mirrors:
mirror_count = self.configuration.lvm_mirrors
self._create_volume(volume['name'],
self._sizestr(volume['size']),
self.configuration.lvm_type,
mirror_count)
def create_volume_from_snapshot(self, volume, snapshot):
"""Creates a volume from a snapshot."""
self._create_volume(volume['name'], self._sizestr(volume['size']))
self._create_volume(volume['name'],
self._sizestr(volume['size']),
self.configuration.lvm_type,
self.configuration.lvm_mirrors)
volutils.copy_volume(self.local_path(snapshot),
self.local_path(volume),
snapshot['volume_size'] * 1024,
@ -149,18 +184,8 @@ class LVMVolumeDriver(driver.VolumeDriver):
# If the volume isn't present, then don't attempt to delete
return True
# TODO(yamahata): lvm can't delete origin volume only without
# deleting derived snapshots. Can we do something fancy?
out, err = self._execute('lvdisplay', '--noheading',
'-C', '-o', 'Attr',
'%s/%s' % (self.configuration.volume_group,
volume['name']),
run_as_root=True)
# fake_execute returns None resulting unit test error
if out:
out = out.strip()
if (out[0] == 'o') or (out[0] == 'O'):
raise exception.VolumeIsBusy(volume_name=volume['name'])
if self.vg.lv_has_snapshot(volume['name']):
raise exception.VolumeIsBusy(volume_name=volume['name'])
self._delete_volume(volume)
@ -202,12 +227,10 @@ class LVMVolumeDriver(driver.VolumeDriver):
def create_snapshot(self, snapshot):
"""Creates a snapshot."""
orig_lv_name = "%s/%s" % (self.configuration.volume_group,
snapshot['volume_name'])
self._try_execute('lvcreate', '-L',
self._sizestr(snapshot['volume_size']),
'--name', self._escape_snapshot(snapshot['name']),
'--snapshot', orig_lv_name, run_as_root=True)
self.vg.create_lv_snapshot(self._escape_snapshot(snapshot['name']),
snapshot['volume_name'],
self.configuration.lvm_type)
def delete_snapshot(self, snapshot):
"""Deletes a snapshot."""
@ -219,7 +242,7 @@ class LVMVolumeDriver(driver.VolumeDriver):
# TODO(yamahata): zeroing out the whole snapshot triggers COW.
# it's quite slow.
self._delete_volume(snapshot)
self._delete_volume(snapshot, is_snapshot=True)
def local_path(self, volume, vg=None):
if vg is None:
@ -245,6 +268,10 @@ class LVMVolumeDriver(driver.VolumeDriver):
def create_cloned_volume(self, volume, src_vref):
"""Creates a clone of the specified volume."""
mirror_count = 0
if self.configuration.lvm_mirrors:
mirror_count = self.configuration.lvm_mirrors
LOG.info(_('Creating clone of volume: %s') % src_vref['id'])
volume_name = src_vref['name']
temp_id = 'tmp-snap-%s' % volume['id']
@ -253,8 +280,13 @@ class LVMVolumeDriver(driver.VolumeDriver):
'volume_size': src_vref['size'],
'name': 'clone-snap-%s' % volume['id'],
'id': temp_id}
self.create_snapshot(temp_snapshot)
self._create_volume(volume['name'], self._sizestr(volume['size']))
self._create_volume(volume['name'],
self._sizestr(volume['size']),
self.configuration.lvm_type,
mirror_count)
try:
volutils.copy_volume(self.local_path(temp_snapshot),
self.local_path(volume),
@ -281,6 +313,47 @@ class LVMVolumeDriver(driver.VolumeDriver):
with fileutils.file_open(volume_path, 'wb') as volume_file:
backup_service.restore(backup, volume['id'], volume_file)
def get_volume_stats(self, refresh=False):
"""Get volume status.
If 'refresh' is True, run update the stats first.
"""
if refresh:
self._update_volume_status()
self._update_volume_status()
return self._stats
def _update_volume_status(self):
"""Retrieve status info from volume group."""
# FIXME(jdg): Fix up the duplicate code between
# LVM, LVMISCSI and ISER starting with this section
LOG.debug(_("Updating volume status"))
data = {}
backend_name = self.configuration.safe_get('volume_backend_name')
data["volume_backend_name"] = backend_name or 'LVM'
data["vendor_name"] = 'Open Source'
data["driver_version"] = self.VERSION
data["storage_protocol"] = 'local'
data['total_capacity_gb'] = float(self.vg.vg_size.replace(',', '.'))
data['free_capacity_gb'] =\
float(self.vg.vg_free_space.replace(',', '.'))
data['reserved_percentage'] = self.configuration.reserved_percentage
data['QoS_support'] = False
data['location_info'] =\
('LVMVolumeDriver:%(hostname)s:%(vg)s'
':%(lvm_type)s:%(lvm_mirrors)s' %
{'hostname': self.hostname,
'vg': self.configuration.volume_group,
'lvm_type': self.configuration.lvm_type,
'lvm_mirrors': self.configuration.lvm_mirrors})
self._stats = data
class LVMISCSIDriver(LVMVolumeDriver, driver.ISCSIDriver):
"""Executes commands relating to ISCSI volumes.
@ -322,7 +395,7 @@ class LVMISCSIDriver(LVMVolumeDriver, driver.ISCSIDriver):
auth_user,
auth_pass)
except exception.NotFound:
LOG.debug("volume_info:", volume_info)
LOG.debug(_("volume_info:%s"), volume_info)
LOG.info(_("Skipping ensure_export. No iscsi_target "
"provision for volume: %s"), volume['id'])
return
@ -359,7 +432,7 @@ class LVMISCSIDriver(LVMVolumeDriver, driver.ISCSIDriver):
volume['name'] not in volume['provider_location']):
msg = _('Detected inconsistency in provider_location id')
LOG.debug(msg)
LOG.debug(_('%s'), msg)
old_name = self._fix_id_migration(context, volume)
if 'in-use' in volume['status']:
volume_name = old_name
@ -525,35 +598,52 @@ class LVMISCSIDriver(LVMVolumeDriver, driver.ISCSIDriver):
# this export has already been removed
self.tgtadm.show_target(iscsi_target, iqn=iqn)
except Exception as e:
except Exception:
LOG.info(_("Skipping remove_export. No iscsi_target "
"is presently exported for volume: %s"), volume['id'])
return
self.tgtadm.remove_iscsi_target(iscsi_target, 0, volume['id'])
def migrate_volume(self, ctxt, volume, host):
def migrate_volume(self, ctxt, volume, host, thin=False, mirror_count=0):
"""Optimize the migration if the destination is on the same server.
If the specified host is another back-end on the same server, and
the volume is not attached, we can do the migration locally without
going through iSCSI.
"""
false_ret = (False, None)
if 'location_info' not in host['capabilities']:
return false_ret
info = host['capabilities']['location_info']
try:
(dest_type, dest_hostname, dest_vg) = info.split(':')
(dest_type, dest_hostname, dest_vg, lvm_type, lvm_mirrors) =\
info.split(':')
except ValueError:
return false_ret
if (dest_type != 'LVMVolumeDriver' or dest_hostname != self.hostname):
return false_ret
self.remove_export(ctxt, volume)
self._create_volume(volume['name'],
self._sizestr(volume['size']),
dest_vg)
if dest_vg != self.vg.vg_name:
vg_list = volutils.get_all_volume_groups()
vg_dict = \
(vg for vg in vg_list if vg['name'] == self.vg.vg_name).next()
if vg_dict is None:
message = ("Destination Volume Group %s does not exist" %
dest_vg)
LOG.error(_('%s'), message)
return false_ret
helper = 'sudo cinder-rootwrap %s' % CONF.rootwrap_config
dest_vg_ref = lvm.LVM(dest_vg, helper, lvm_type, self._execute)
self.remove_export(ctxt, volume)
self._create_volume(volume['name'],
self._sizestr(volume['size']),
lvm_type,
lvm_mirrors,
dest_vg_ref)
volutils.copy_volume(self.local_path(volume),
self.local_path(volume, vg=dest_vg),
volume['size'],
@ -582,6 +672,7 @@ class LVMISCSIDriver(LVMVolumeDriver, driver.ISCSIDriver):
"""Retrieve stats info from volume group."""
LOG.debug(_("Updating volume stats"))
self.vg.update_volume_group_info()
data = {}
# Note(zhiteng): These information are driver/backend specific,
@ -593,27 +684,17 @@ class LVMISCSIDriver(LVMVolumeDriver, driver.ISCSIDriver):
data["driver_version"] = self.VERSION
data["storage_protocol"] = 'iSCSI'
data['total_capacity_gb'] = 0
data['free_capacity_gb'] = 0
data['total_capacity_gb'] = float(self.vg.vg_size.replace(',', '.'))
data['free_capacity_gb'] = float(self.vg.vg_free_space)
data['reserved_percentage'] = self.configuration.reserved_percentage
data['QoS_support'] = False
data['location_info'] = ('LVMVolumeDriver:%(hostname)s:%(vg)s' %
{'hostname': self.hostname,
'vg': self.configuration.volume_group})
try:
out, err = self._execute('vgs', '--noheadings', '--nosuffix',
'--unit=G', '-o', 'name,size,free',
self.configuration.volume_group,
run_as_root=True)
except exception.ProcessExecutionError as exc:
LOG.error(_("Error retrieving volume stats: %s"), exc.stderr)
out = False
if out:
volume = out.split()
data['total_capacity_gb'] = float(volume[1].replace(',', '.'))
data['free_capacity_gb'] = float(volume[2].replace(',', '.'))
data['location_info'] =\
('LVMVolumeDriver:%(hostname)s:%(vg)s'
':%(lvm_type)s:%(lvm_mirrors)s' %
{'hostname': self.hostname,
'vg': self.configuration.volume_group,
'lvm_type': self.configuration.lvm_type,
'lvm_mirrors': self.configuration.lvm_mirrors})
self._stats = data
@ -764,7 +845,7 @@ class LVMISERDriver(LVMISCSIDriver, driver.ISERDriver):
self.tgtadm.show_target(iser_target, iqn=iqn)
except Exception as e:
except Exception:
LOG.info(_("Skipping remove_export. No iser_target "
"is presently exported for volume: %s"), volume['id'])
return
@ -775,6 +856,7 @@ class LVMISERDriver(LVMISCSIDriver, driver.ISERDriver):
"""Retrieve status info from volume group."""
LOG.debug(_("Updating volume status"))
self.vg.update_volume_group_info()
data = {}
# Note(zhiteng): These information are driver/backend specific,
@ -785,26 +867,13 @@ class LVMISERDriver(LVMISCSIDriver, driver.ISERDriver):
data["vendor_name"] = 'Open Source'
data["driver_version"] = self.VERSION
data["storage_protocol"] = 'iSER'
data['total_capacity_gb'] = float(self.vg.vg_size.replace(',', '.'))
data['free_capacity_gb'] =\
float(self.vg.vg_free_space.replace(',', '.'))
data['total_capacity_gb'] = 0
data['free_capacity_gb'] = 0
data['reserved_percentage'] = self.configuration.reserved_percentage
data['QoS_support'] = False
try:
out, err = self._execute('vgs', '--noheadings', '--nosuffix',
'--unit=G', '-o', 'name,size,free',
self.configuration.volume_group,
run_as_root=True)
except exception.ProcessExecutionError as exc:
LOG.error(_("Error retrieving volume status: %s"), exc.stderr)
out = False
if out:
volume = out.split()
data['total_capacity_gb'] = float(volume[1].replace(',', '.'))
data['free_capacity_gb'] = float(volume[2].replace(',', '.'))
self._stats = data
def _iser_location(self, ip, target, iqn, lun=None):
@ -813,102 +882,3 @@ class LVMISERDriver(LVMISCSIDriver, driver.ISERDriver):
def _iser_authentication(self, chap, name, password):
return "%s %s %s" % (chap, name, password)
class ThinLVMVolumeDriver(LVMISCSIDriver):
"""Subclass for thin provisioned LVM's."""
VERSION = '1.0'
def __init__(self, *args, **kwargs):
super(ThinLVMVolumeDriver, self).__init__(*args, **kwargs)
def check_for_setup_error(self):
"""Returns an error if prerequisites aren't met"""
out, err = self._execute('lvs', '--option',
'name', '--noheadings',
run_as_root=True)
pool_name = "%s-pool" % self.configuration.volume_group
if pool_name not in out:
if not self.configuration.pool_size:
out, err = self._execute('vgs',
self.configuration.volume_group,
'--noheadings',
'--options',
'name,size',
run_as_root=True)
size = re.sub(r'[\.][\d][\d]', '', out.split()[1])
else:
size = "%s" % self.configuration.pool_size
pool_path = '%s/%s' % (self.configuration.volume_group,
pool_name)
out, err = self._execute('lvcreate', '-T', '-L', size,
pool_path, run_as_root=True)
def _do_lvm_snapshot(self, src_lvm_name, dest_vref, is_cinder_snap=True):
if is_cinder_snap:
new_name = self._escape_snapshot(dest_vref['name'])
else:
new_name = dest_vref['name']
self._try_execute('lvcreate', '-s', '-n', new_name,
src_lvm_name, run_as_root=True)
def _create_volume(self, volume_name, sizestr):
vg_name = ("%s/%s-pool" % (self.configuration.volume_group,
self.configuration.volume_group))
self._try_execute('lvcreate', '-T', '-V', sizestr, '-n',
volume_name, vg_name, run_as_root=True)
def delete_volume(self, volume):
"""Deletes a logical volume."""
if self._volume_not_present(volume['name']):
return True
self._try_execute('lvremove', '-f', "%s/%s" %
(self.configuration.volume_group,
self._escape_snapshot(volume['name'])),
run_as_root=True)
def create_cloned_volume(self, volume, src_vref):
"""Creates a clone of the specified volume."""
LOG.info(_('Creating clone of volume: %s') % src_vref['id'])
orig_lv_name = "%s/%s" % (self.configuration.volume_group,
src_vref['name'])
self._do_lvm_snapshot(orig_lv_name, volume, False)
def create_snapshot(self, snapshot):
"""Creates a snapshot of a volume."""
orig_lv_name = "%s/%s" % (self.configuration.volume_group,
snapshot['volume_name'])
self._do_lvm_snapshot(orig_lv_name, snapshot)
def get_volume_stats(self, refresh=False):
"""Get volume stats.
If 'refresh' is True, run update the stats first.
"""
if refresh:
self._update_volume_stats()
return self._stats
def _update_volume_stats(self):
"""Retrieve stats info from volume group."""
LOG.debug(_("Updating volume stats"))
data = {}
backend_name = self.configuration.safe_get('volume_backend_name')
data["volume_backend_name"] = backend_name or self.__class__.__name__
data["vendor_name"] = 'Open Source'
data["driver_version"] = self.VERSION
data["storage_protocol"] = 'iSCSI'
data['reserved_percentage'] = self.configuration.reserved_percentage
data['QoS_support'] = False
data['total_capacity_gb'] = 'infinite'
data['free_capacity_gb'] = 'infinite'
data['location_info'] = ('LVMVolumeDriver:%(hostname)s:%(vg)s' %
{'hostname': self.hostname,
'vg': self.configuration.volume_group})
self._stats = data

View File

@ -23,6 +23,7 @@ import stat
from oslo.config import cfg
from cinder.brick.local_dev import lvm as brick_lvm
from cinder import exception
from cinder.openstack.common import log as logging
from cinder.openstack.common.notifier import api as notifier_api
@ -192,3 +193,26 @@ def copy_volume(srcstr, deststr, size_in_m, sync=False,
'count=%d' % count,
'bs=%s' % blocksize,
*extra_flags, run_as_root=True)
def supports_thin_provisioning():
return brick_lvm.LVM.supports_thin_provisioning(
'sudo cinder-rootwrap %s' % CONF.rootwrap_config)
def get_all_volumes(vg_name=None, no_suffix=True):
return brick_lvm.LVM.get_all_volumes(
'sudo cinder-rootwrap %s' % CONF.rootwrap_config,
vg_name, no_suffix)
def get_all_physical_volumes(vg_name=None, no_suffix=True):
return brick_lvm.LVM.get_all_physical_volumes(
'sudo cinder-rootwrap %s' % CONF.rootwrap_config,
vg_name, no_suffix)
def get_all_volume_groups(vg_name=None, no_suffix=True):
return brick_lvm.LVM.get_all_volume_groups(
'sudo cinder-rootwrap %s' % CONF.rootwrap_config,
vg_name, no_suffix)