--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation.
+# 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.
+
+"""
+LVM class for performing LVM operations.
+"""
+
+import math
+
+from itertools import izip
+
+from cinder.openstack.common.gettextutils import _
+from cinder.openstack.common import log as logging
+from cinder.openstack.common import processutils as putils
+
+LOG = logging.getLogger(__name__)
+
+
+class VolumeGroupNotFound(Exception):
+ def __init__(self, vg_name):
+ message = (_('Unable to find Volume Group: %s') % vg_name)
+ super(VolumeGroupNotFound, self).__init__(message)
+
+
+class VolumeGroupCreationFailed(Exception):
+ def __init__(self, vg_name):
+ message = (_('Failed to create Volume Group: %s') % vg_name)
+ super(VolumeGroupCreationFailed, self).__init__(message)
+
+
+class LVM(object):
+ """LVM object to enable various LVM related operations."""
+
+ def __init__(self, vg_name, create_vg=False,
+ physical_volumes=None):
+ """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 create_vg: Indicates the VG doesn't exist
+ and we want to create it
+ :param physical_volumes: List of PVs to build VG on
+
+ """
+ self.vg_name = vg_name
+ self.pv_list = []
+ self.lv_list = []
+ self.vg_size = 0
+ self.vg_available_space = 0
+ self.vg_lv_count = 0
+ self.vg_uuid = None
+
+ if create_vg and physical_volumes is not None:
+ self.pv_list = physical_volumes
+
+ try:
+ self._create_vg(physical_volumes)
+ except putils.ProcessExecutionError as err:
+ LOG.exception(_('Error creating Volume Group'))
+ LOG.error(_('Cmd :%s') % err.cmd)
+ LOG.error(_('StdOut :%s') % err.stdout)
+ LOG.error(_('StdErr :%s') % err.stderr)
+ raise VolumeGroupCreationFailed(vg_name=self.vg_name)
+
+ if self._vg_exists() is False:
+ LOG.error(_('Unable to locate Volume Group %s') % vg_name)
+ raise VolumeGroupNotFound(vg_name=vg_name)
+
+ def _size_str(self, size_in_g):
+ if '.00' in size_in_g:
+ size_in_g = size_in_g.replace('.00', '')
+
+ if int(size_in_g) == 0:
+ return '100M'
+
+ return '%sG' % size_in_g
+
+ def _vg_exists(self):
+ """Simple check to see if VG exists.
+
+ :returns: True if vg specified in object exists, else False
+
+ """
+ exists = False
+ cmd = ['vgs', '--noheadings', '-o', 'name']
+ (out, err) = putils.execute(*cmd, root_helper='sudo', run_as_root=True)
+
+ if out is not None:
+ volume_groups = out.split()
+ if self.vg_name in volume_groups:
+ exists = True
+
+ return exists
+
+ def _create_vg(self, pv_list):
+ cmd = ['vgcreate', self.vg_name, ','.join(pv_list)]
+ putils.execute(*cmd, root_helper='sudo', run_as_root=True)
+
+ def _get_vg_uuid(self):
+ (out, err) = putils.execute('vgs', '--noheadings',
+ '-o uuid', self.vg_name)
+ if out is not None:
+ return out.split()
+ else:
+ return []
+
+ @staticmethod
+ def get_all_volumes(vg_name=None):
+ """Static method to get all LV's on a system.
+
+ :param vg_name: optional, gathers info for only the specified VG
+ :returns: List of Dictionaries with LV info
+
+ """
+ cmd = ['lvs', '--noheadings', '-o', 'vg_name,name,size']
+ if vg_name is not None:
+ cmd += [vg_name]
+
+ (out, err) = putils.execute(*cmd, root_helper='sudo', run_as_root=True)
+
+ lv_list = []
+ if out is not None:
+ volumes = out.split()
+ for vg, name, size in izip(*[iter(volumes)] * 3):
+ lv_list.append({"vg": vg, "name": name, "size": size})
+
+ return lv_list
+
+ def get_volumes(self):
+ """Get all LV's associated with this instantiation (VG).
+
+ :returns: List of Dictionaries with LV info
+
+ """
+ self.lv_list = self.get_all_volumes(self.vg_name)
+ return self.lv_list
+
+ def get_volume(self, name):
+ """Get reference object of volume specified by name.
+
+ :returns: dict representation of Logical Volume if exists
+
+ """
+ ref_list = self.get_volumes()
+ for r in ref_list:
+ if r['name'] == name:
+ return r
+
+ @staticmethod
+ def get_all_physical_volumes(vg_name=None):
+ """Static method to get all PVs on a system.
+
+ :param vg_name: optional, gathers info for only the specified VG
+ :returns: List of Dictionaries with PV info
+
+ """
+ cmd = ['pvs', '--noheadings',
+ '-o', 'vg_name,name,size,free',
+ '--separator', ':']
+ if vg_name is not None:
+ cmd += [vg_name]
+
+ (out, err) = putils.execute(*cmd, root_helper='sudo', run_as_root=True)
+
+ pv_list = []
+ if out is not None:
+ pvs = out.split()
+ for pv in pvs:
+ fields = pv.split(':')
+ pv_list.append({'vg': fields[0],
+ 'name': fields[1],
+ 'size': fields[2],
+ 'available': fields[3]})
+
+ return pv_list
+
+ def get_physical_volumes(self):
+ """Get all PVs associated with this instantiation (VG).
+
+ :returns: List of Dictionaries with PV info
+
+ """
+ self.pv_list = self.get_all_physical_volumes(self.vg_name)
+ return self.pv_list
+
+ @staticmethod
+ def get_all_volume_groups(vg_name=None):
+ """Static method to get all VGs on a system.
+
+ :param vg_name: optional, gathers info for only the specified VG
+ :returns: List of Dictionaries with VG info
+
+ """
+ cmd = ['vgs', '--noheadings',
+ '-o', 'name,size,free,lv_count,uuid',
+ '--separator', ':']
+ if vg_name is not None:
+ cmd += [vg_name]
+
+ (out, err) = putils.execute(*cmd, root_helper='sudo', run_as_root=True)
+
+ vg_list = []
+ if out is not None:
+ vgs = out.split()
+ for vg in vgs:
+ fields = vg.split(':')
+ vg_list.append({'name': fields[0],
+ 'size': fields[1],
+ 'available': fields[2],
+ 'lv_count': fields[3],
+ 'uuid': fields[4]})
+
+ return vg_list
+
+ def update_volume_group_info(self):
+ """Update VG info for this instantiation.
+
+ Used to update member fields of object and
+ provide a dict of info for caller.
+
+ :returns: Dictionaries of VG info
+
+ """
+ vg_list = self.get_all_volume_groups(self.vg_name)
+
+ if len(vg_list) != 1:
+ LOG.error(_('Unable to find VG: %s') % self.vg_name)
+ raise VolumeGroupNotFound(vg_name=self.vg_name)
+
+ self.vg_size = vg_list[0]['size']
+ self.vg_available_space = vg_list[0]['available']
+ self.vg_lv_count = vg_list[0]['lv_count']
+ self.vg_uuid = vg_list[0]['uuid']
+
+ return vg_list[0]
+
+ def create_volume(self, name, size_str, lv_type='default', mirror_count=0):
+ """Creates a logical volume on the object's VG.
+
+ :param name: Name to use when creating Logical Volume
+ :param size_str: Size to use when creating Logical Volume
+ :param lv_type: Type of Volume (default or thin)
+ :param mirror_count: Use LVM mirroring with specified count
+
+ """
+ size = self._size_str(size_str)
+ cmd = ['lvcreate', '-n', name, self.vg_name]
+ if lv_type == 'thin':
+ cmd += ['-T', '-V', size]
+ else:
+ cmd += ['-L', size]
+
+ if mirror_count > 0:
+ cmd += ['-m', mirror_count, '--nosync']
+ terras = int(size[:-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 += ['-R', str(rsize)]
+
+ putils.execute(*cmd,
+ root_helper='sudo',
+ run_as_root=True)
+
+ def create_lv_snapshot(self, name, source_lv_name, lv_type='default'):
+ """Creates a snapshot of a logical volume.
+
+ :param name: Name to assign to new snapshot
+ :param source_lv_name: Name of Logical Volume to snapshot
+ :param lv_type: Type of LV (default or thin)
+
+ """
+ source_lvref = self.get_volume(source_lv_name)
+ if source_lvref is None:
+ LOG.error(_("Unable to find LV: %s") % source_lv_name)
+ return False
+ cmd = ['lvcreate', '--name', name,
+ '--snapshot', '%s/%s' % (self.vg_name, source_lv_name)]
+ if lv_type != 'thin':
+ size = source_lvref['size']
+ cmd += ['-L', size]
+
+ putils.execute(*cmd,
+ root_helper='sudo',
+ run_as_root=True)
+
+ def delete(self, name):
+ """Delete logical volume or snapshot.
+
+ :param name: Name of LV to delete
+
+ """
+ putils.execute('lvremove',
+ '-f',
+ '%s/%s' % (self.vg_name, name),
+ root_helper='sudo', run_as_root=True)
+
+ def revert(self, snapshot_name):
+ """Revert an LV from snapshot.
+
+ :param snapshot_name: Name of snapshot to revert
+
+ """
+ putils.execute('lvconvert', '--merge',
+ snapshot_name, root_helper='sudo',
+ run_as_root=True)
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack LLC.
+# 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 mox
+
+
+from cinder.brick.local_dev import lvm as brick
+from cinder.openstack.common import log as logging
+from cinder.openstack.common import processutils
+from cinder import test
+from cinder.volume import configuration as conf
+
+LOG = logging.getLogger(__name__)
+
+
+def create_configuration():
+ configuration = mox.MockObject(conf.Configuration)
+ configuration.append_config_values(mox.IgnoreArg())
+ return configuration
+
+
+class BrickLvmTestCase(test.TestCase):
+ def setUp(self):
+ self._mox = mox.Mox()
+ self.configuration = mox.MockObject(conf.Configuration)
+ self.configuration.volume_group_name = 'fake-volumes'
+ super(BrickLvmTestCase, self).setUp()
+ self.stubs.Set(processutils, 'execute',
+ self.fake_execute)
+ self.vg = brick.LVM(self.configuration.volume_group_name)
+
+ def failed_fake_execute(obj, *cmd, **kwargs):
+ return ("\n", "fake-error")
+
+ def fake_execute(obj, *cmd, **kwargs):
+ cmd_string = ', '.join(cmd)
+ data = "\n"
+
+ if 'vgs, --noheadings, -o, name' == cmd_string:
+ data = " fake-volumes\n"
+ elif 'vgs, --noheadings, -o uuid, fake-volumes' in cmd_string:
+ data = " kVxztV-dKpG-Rz7E-xtKY-jeju-QsYU-SLG6Z1\n"
+ elif 'vgs, --noheadings, -o, name,size,free,lv_count,uuid' in\
+ cmd_string:
+ data = " fake-volumes:10.00g:10.00g:0:"\
+ "kVxztV-dKpG-Rz7E-xtKY-jeju-QsYU-SLG6Z1\n"
+ if 'fake-volumes' in cmd_string:
+ return (data, "")
+ data += " fake-volumes-2:10.00g:10.00g:0:"\
+ "lWyauW-dKpG-Rz7E-xtKY-jeju-QsYU-SLG7Z2\n"
+ data += " fake-volumes-3:10.00g:10.00g:0:"\
+ "mXzbuX-dKpG-Rz7E-xtKY-jeju-QsYU-SLG8Z3\n"
+ elif 'lvs, --noheadings, -o, vg_name,name,size' in cmd_string:
+ data = " fake-volumes fake-1 1.00g\n"
+ data += " fake-volumes fake-2 1.00g\n"
+ elif 'lvs, --noheadings, -o, vg_name,name,size' in cmd_string:
+ data = " fake-volumes fake-1 1.00g\n"
+ data += " fake-volumes fake-2 1.00g\n"
+ elif 'pvs, --noheadings' and 'fake-volumes' in cmd_string:
+ data = " fake-volumes:/dev/sda:10.00g:8.99g\n"
+ elif 'pvs, --noheadings' in cmd_string:
+ data = " fake-volumes:/dev/sda:10.00g:8.99g\n"
+ data += " fake-volumes-2:/dev/sdb:10.00g:8.99g\n"
+ data += " fake-volumes-3:/dev/sdc:10.00g:8.99g\n"
+ else:
+ pass
+
+ return (data, "")
+
+ def test_vg_exists(self):
+ self.stubs.Set(processutils, 'execute', self.fake_execute)
+ self.assertEqual(self.vg._vg_exists(), True)
+
+ self.stubs.Set(processutils, 'execute', self.failed_fake_execute)
+ self.assertEqual(self.vg._vg_exists(), False)
+
+ def test_get_vg_uuid(self):
+ self.stubs.Set(processutils, 'execute', self.fake_execute)
+ self.assertEqual(self.vg._get_vg_uuid()[0],
+ 'kVxztV-dKpG-Rz7E-xtKY-jeju-QsYU-SLG6Z1')
+
+ def test_get_all_volumes(self):
+ self.stubs.Set(processutils, 'execute', self.fake_execute)
+ out = self.vg.get_volumes()
+
+ self.assertEqual(out[0]['name'], 'fake-1')
+ self.assertEqual(out[0]['size'], '1.00g')
+ self.assertEqual(out[0]['vg'], 'fake-volumes')
+
+ def test_get_volume(self):
+ self.stubs.Set(processutils, 'execute', self.fake_execute)
+ self.assertEqual(self.vg.get_volume('fake-1')['name'], 'fake-1')
+
+ def test_get_all_physical_volumes(self):
+ self.stubs.Set(processutils, 'execute', self.fake_execute)
+ pvs = self.vg.get_all_physical_volumes()
+ self.assertEqual(len(pvs), 3)
+
+ def test_get_physical_volumes(self):
+ self.stubs.Set(processutils, 'execute', self.fake_execute)
+ pvs = self.vg.get_physical_volumes()
+ self.assertEqual(len(pvs), 1)
+
+ def test_get_volume_groups(self):
+ self.stubs.Set(processutils, 'execute', self.fake_execute)
+ self.assertEqual(len(self.vg.get_all_volume_groups()), 3)
+ self.assertEqual(len(self.vg.get_all_volume_groups('fake-volumes')), 1)
+
+ def test_update_vg_info(self):
+ self.stubs.Set(processutils, 'execute', self.fake_execute)
+ self.assertEqual(self.vg.update_volume_group_info()['name'],
+ 'fake-volumes')