Create an LVM utility to use for local storage.
This adds a simple LVM class for performing local LVM operations. The idea is that an LVM object is instantiated based on a volume group, init can be used to instantiate and query an existing volume group, or create a new one if given a list of PV's to use in creation. See BP for information on where this is going and how it will be used in Cinder and hopefully other projects. Implements blueprint: local-lvm-storage-utils Change-Id: Iddde92af18f2317edc5f4583b2113c2b8117a4fe
This commit is contained in:
parent
aef83c437e
commit
0fbe00f70d
|
@ -0,0 +1,16 @@
|
|||
# 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.
|
|
@ -0,0 +1,16 @@
|
|||
# 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.
|
|
@ -0,0 +1,323 @@
|
|||
# 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)
|
|
@ -0,0 +1,16 @@
|
|||
# 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.
|
|
@ -0,0 +1,126 @@
|
|||
# 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')
|
Loading…
Reference in New Issue