Logical Volume management

OpenLMI storage provider supports basic LVM operations, incl. thin pool management. See Red Hat documentation for LVM terminology, architecture and usage from command line (note that the information there apply also to other Linux distributions).

Volume Groups (VG) and Thin Pools (TP) are represented by LMI_VGStoragePool class. To differentiate between the two, SpaceLimitDetermination and SpaceLimit are both set or both empty.

If both are set, an instance of the class is a thin pool. SpaceLimitDetermination is always set to 4 (limitless thin pool, meaning that it can be overcommited) and SpaceLimit is set to the capacity of the storage allocated to the pool. Also, RemainingManagedSpace will be set to the remaining space on the pool. Due to the current limitation of the underlying storage library, if the pool is overcommited, its RemainingManagedSpace value is set to 0.

If both SpaceLimitDetermination and SpaceLimit are empty, the instance of the LMI_VGStoragePool class is a regular volume group.

Every LMI_VGStoragePool instance has associated one instance of LMI_VGStorageSetting representing its configuration (e.g. volume group extent size) and one instance of LMI_LVStorageCapabilities, representing its ability to create logical volumes (for SMI-S applications). Every LMI_VGStoragePool instance, if it is a thin pool, is associated with its thin logical volumes (if they exist) using LMI_VGAllocatedFromStoragePool.

Physical Volumes (PV) are associated to VGs using LMI_VGAssociatedComponentExtent association.

Logical Volumes (LV) and Thin Logical Volumes (TLV) are represented by LMI_LVStorageExtent class. If an instance of the class is a thin logical volume, ThinlyProvisioned is set to True.

Each LMI_LVStorageExtent instance is associated to its respective VG/TP using LMI_LVAllocatedFromStoragePool association.

In addition, LVs are associated to all PVs using LMI_LVBasedOn association.

Following instance diagram shows one Volume Group /dev/myGroup based on three Physical Volumes /dev/sda1, /dev/sdb1 and /dev/sdc1 and two Logical Volumes myVol1 and myVol2.

Note that the diagram is simplified and does not show LMI_LVBasedOn association, which associates every myVolY to /dev/sdX1.

The next instance diagram displays the Volume Group /dev/myGroup (see previous diagram) that has myThinPool, sized 100 MiB, associated to it. This Thin Pool is used to provision the 10 GiB Thin Logical Volume /dev/mapper/myGroup-myThinVolume. The VG/TP pair is connected with an LMI_VGAllocatedFromStoragePool association. LMI_LVAllocatedFromStoragePool association joins the TP/TLV pair.

Currently the LVM support is limited to creation and removal of VGs and LVs and to adding/removing devices to/from a VG. It is not possible to modify existing LV, e.g. or resize LVs. In future OpenLMI may be extended to have more configuration options in LMI_VGStorageSetting and LMI_LVStorageSetting.

Useful methods

CreateOrModifyVG

Creates a Volume Group with given devices. The devices are automatically formatted with Physical Volume metadata. Optionally, the Volume Group extent size can be specified by using Goal parameter of the method.

This method can be also used to add/remove PVs to/from VG.

CreateOrModifyThinPool
Creates or modifies a Thin Pool.
CreateOrModifyThinLV
Create or modifies a Thin Logical Volume.
CreateOrModifyStoragePool
Creates a Volume Group in SMI-S way.
CreateVGStorageSetting
This is helper method to calculate LMI_VGStorageSetting for given list of devices for CreateOrModifyStoragePool method.
CreateOrModifyLV
Creates a Logical Volume from given VG.
CreateOrModifyElementFromStoragePool
Creates a Logical Volume in SMI-S way.
DeleteLV
Destroys a Logical Volume or a Thin Logical Volume.
ReturnToStoragePool
Destroys a Logical Volume in SMI-S way.
DeleteVG
Destroys a Volume Group or a Thin Pool.
DeleteStoragePool
Destroys a Volume Group in SMI-S way.

Use cases

Note

All example scripts expect properly initialized lmishell.

Create Volume Group

Use CreateOrModifyVG method. Following example creates a VG ‘/dev/myGroup’ with three members and with default extent size (4MiB):

storage_service = ns.LMI_StorageConfigurationService.first_instance()

# Find the devices we want to add to VG
# (filtering one CIM_StorageExtent.instances()
# call would be faster, but this is easier to read)
sda1 = ns.CIM_StorageExtent.first_instance({"Name": "/dev/sda1"})
sdb1 = ns.CIM_StorageExtent.first_instance({"Name": "/dev/sdb1"})
sdc1 = ns.CIM_StorageExtent.first_instance({"Name": "/dev/sdc1"})

# Create the VG
(ret, outparams, err) = storage_service.SyncCreateOrModifyVG(
        ElementName="myGroup",
        InExtents=[sda1, sdb1, sdc1])
vg = outparams['Pool'].to_instance()
print "VG", vg.PoolID, \
        "with extent size", vg.ExtentSize, \
        "and",  vg.RemainingExtents, "free extents created."

The resulting VG is the same as shown in diagram above, except it does not have any LVs yet.

Create Thin Pool

The VG from the previous example can be used to create a TP on. This example script creates a Thin Pool ‘myThinPool’ on the VG ‘myGroup’. The TP is 100 MiB in size:

storage_service = ns.LMI_StorageConfigurationService.first_instance()
MEGABYTE = 1024*1024

# Find the volume group
vg = ns.LMI_VGStoragePool.first_instance({"InstanceID":"LMI:VG:myGroup"})

# Allocate a thin pool out of it
(ret, outparams, err) = storage_service.SyncCreateOrModifyThinPool(
        ElementName="myThinPool",
        InPool=vg.path,
        # 100 MiB
        Size=100 * MEGABYTE)
tp = outparams["Pool"].to_instance()
print "TP %s with %d MiB remaining" % \
        (tp.Name, tp.RemainingManagedSpace / MEGABYTE)

Create Volume Group in SMI-S way

SMI-S applications can use CreateOrModifyStoragePool method. Following example creates a VG ‘/dev/myGroup’ with three members and with default extent size (4MiB):

storage_service = ns.LMI_StorageConfigurationService.first_instance()

# Find the devices we want to add to VG
# (filtering one CIM_StorageExtent.instances()
# call would be faster, but this is easier to read)
sda1 = ns.CIM_StorageExtent.first_instance({"Name": "/dev/sda1"})
sdb1 = ns.CIM_StorageExtent.first_instance({"Name": "/dev/sdb1"})
sdc1 = ns.CIM_StorageExtent.first_instance({"Name": "/dev/sdc1"})

# Create the VG
(ret, outparams, err) = storage_service.SyncCreateOrModifyStoragePool(
        InExtents=[sda1, sdb1, sdc1],
        ElementName="myGroup")
vg = outparams['Pool'].to_instance()
print "VG", vg.PoolID, \
        "with extent size", vg.ExtentSize, \
        "and",  vg.RemainingExtents, "free extents created."

The resulting VG is the same as shown in diagram above, except it does not have any LVs yet.

Add and remove devices to/from a Volume Group

CreateOrModifyStoragePool can be used to modify exising VG. Its ‘InExtents’ parameter specifies new list of Physical Volumes of the VG. When an PV is being removed from a VG, all its data are safely moved to a free PV.

Continuing with previous example, let’s remove ‘/dev/sda1’ from the VG and add ‘/dev/sdd1’ to it:

storage_service = ns.LMI_StorageConfigurationService.first_instance()

# Find all the devices we want to be in VG
# (filtering one CIM_StorageExtent.instances()
# call would be faster, but this is easier to read)
sdb1 = ns.CIM_StorageExtent.first_instance({"Name": "/dev/sdb1"})
sdc1 = ns.CIM_StorageExtent.first_instance({"Name": "/dev/sdc1"})
sdd1 = ns.CIM_StorageExtent.first_instance({"Name": "/dev/sdd1"})

new_pvs = [sdb1, sdc1, sdd1]              # Without sda1!

# Find the VG
vg = ns.LMI_VGStoragePool.first_instance({"Name": "/dev/mapper/myGroup"})

# Set the list of PVs of the VG.
# All existing PVs, which are not listed in InExtents parameter will
# be removed from the VG. All new devices listed in InExtents parameter
# are added to the VG. All data in the VG are moved from the PVs being
# removed to a free PV, no data is lost.

(ret, outparams, err) = storage_service.SyncCreateOrModifyVG(
        InExtents=new_pvs,
        pool=vg.path)

Create Volume Group with specific extent size

Use CreateVGStorageSetting to create LMI_VGStorageSetting, modify its ExtentSize property with desired extent size and finally call CreateOrModifyVG with the setting as Goal parameter. Following example creates a VG ‘/dev/myGroup’ with three members and with 1MiB extent size (4MiB):

storage_service = ns.LMI_StorageConfigurationService.first_instance()
MEGABYTE = 1024*1024

# Find the devices we want to add to VG
# (filtering one CIM_StorageExtent.instances()
# call would be faster, but this is easier to read)
sda1 = ns.CIM_StorageExtent.first_instance({"Name": "/dev/sda1"})
sdb1 = ns.CIM_StorageExtent.first_instance({"Name": "/dev/sdb1"})
sdc1 = ns.CIM_StorageExtent.first_instance({"Name": "/dev/sdc1"})

# Create the LMI_VGStorageSetting
vg_caps = ns.LMI_VGStorageCapabilities.first_instance()
(ret, outparams, err) = vg_caps.CreateVGStorageSetting(
        InExtents = [sda1, sdb1, sdc1])
setting = outparams['Setting'].to_instance()
# Modify the LMI_VGStorageSetting
setting.ExtentSize = MEGABYTE
settinh.push()

# Create the VG
# (either of CreateOrModifyStoragePool or CreateOrModifyVG
# can be used with the same result)
(ret, outparams, err) = storage_service.SyncCreateOrModifyStoragePool(
        InExtents=[sda1, sdb1, sdc1],
        ElementName="myGroup",
        Goal=setting)
vg = outparams['Pool'].to_instance()
print "VG", vg.PoolID, \
        "with extent size", vg.ExtentSize, \
        "and",  vg.RemainingExtents, "free extents created."

List Physical Volumes of a Volume Group

Enumerate VGAssociatedComponentExtent associations of the VG.

Following code lists all PVs of /dev/myGroup:

# Find the VG
vg = ns.LMI_VGStoragePool.first_instance({"Name": "/dev/mapper/myGroup"})
pvs = vg.associators(AssocClass="LMI_VGAssociatedComponentExtent")
for pv in pvs:
    print "Found PV", pv.DeviceID

Create Logical Volume

Use CreateOrModifyLV method. Following example creates two 100MiB volumes:

storage_service = ns.LMI_StorageConfigurationService.first_instance()
MEGABYTE = 1024*1024

# Find the VG
vg = ns.LMI_VGStoragePool.first_instance({"Name": "/dev/mapper/myGroup"})

# Create the LV
(ret, outparams, err) = storage_service.SyncCreateOrModifyLV(
        ElementName="Vol1",
        InPool=vg,
        Size=100 * MEGABYTE)
lv = outparams['TheElement'].to_instance()
print "LV", lv.DeviceID, \
        "with", lv.BlockSize * lv.NumberOfBlocks,\
        "bytes created."

# Create the second LV
(ret, outparams, err) = storage_service.SyncCreateOrModifyLV(
        ElementName="Vol2",
        InPool=vg,
        Size=100 * MEGABYTE)
lv = outparams['TheElement'].to_instance()
print "LV", lv.DeviceID, \
        "with", lv.BlockSize * lv.NumberOfBlocks, \
        "bytes created."

The resulting LVs are the same as shown in diagram above.

Create Thin Logical Volume

The following example assumes that a TP was already created (see Create Thin Pool).

There already is a TP (100 MiB) in the system. This snippet of code creates a 10 GiB Thin Logical Volume and prints some information about it. Note that this TLV causes the underlying TP to be overcommited:

storage_service = ns.LMI_StorageConfigurationService.first_instance()

# Find the thin pool
tp = ns.LMI_VGStoragePool.first_instance({"ElementName":"myThinPool"})

(ret, outparams, err) = storage_service.SyncCreateOrModifyThinLV(
        ElementName="myThinLV",
        ThinPool=tp.path,
        # 10 GiB
        Size=10 * GIGABYTE)
tlv = outparams["TheElement"].to_instance()
print "TLV %s of size %d GiB" % \
       (tlv.Name, tlv.BlockSize * tlv.NumberOfBlocks / GIGABYTE)

Create Logical Volume in SMI-S way

Use CreateOrModifyElementFromStoragePool method. The code is the same as in previous sample, just different method is used:

storage_service = ns.LMI_StorageConfigurationService.first_instance()
MEGABYTE = 1024*1024

# Find the VG
vg = ns.LMI_VGStoragePool.first_instance({"Name": "/dev/mapper/myGroup"})

# Create the LV
(ret, outparams, err) = storage_service.SyncCreateOrModifyElementFromStoragePool(
        ElementName="Vol1",
        InPool=vg,
        Size=100 * MEGABYTE)
lv = outparams['TheElement'].to_instance()
print "LV", lv.DeviceID, \
        "with", lv.BlockSize * lv.NumberOfBlocks,\
        "bytes created."

# Create the second LV
(ret, outparams, err) = storage_service.SyncCreateOrModifyElementFromStoragePool(
        ElementName="Vol2",
        InPool=vg,
        Size=100 * MEGABYTE)
lv = outparams['TheElement'].to_instance()
print "LV", lv.DeviceID, \
        "with", lv.BlockSize * lv.NumberOfBlocks, \
        "bytes created."

Delete VG

Call DeleteVG method:

storage_service = ns.LMI_StorageConfigurationService.first_instance()

vg = ns.LMI_VGStoragePool.first_instance({"Name": "/dev/mapper/myGroup"})
(ret, outparams, err) = storage_service.SyncDeleteVG(
        Pool = vg)

Delete LV

Call DeleteLV method:

storage_service = ns.LMI_StorageConfigurationService.first_instance()

lv = ns.LMI_LVStorageExtent.first_instance({"Name": "/dev/mapper/myGroup-Vol2"})
(ret, outparams, err) = storage_service.SyncDeleteLV(
        TheElement=lv)

Future direction

In future, we might implement:

  • Modification of existing VGs and LVs, for example renaming VGs and LVs and resizing LVs.
  • LVs with stripping and mirroring.
  • Clustered VGs and LVs.
  • Snapshots.
  • Indications of various events.