[Kimchi-devel] [PATCH] Create logical pool from existing VG
Daniel Henrique Barboza
dhbarboza82 at gmail.com
Mon Nov 23 14:41:27 UTC 2015
Patch looks good. Just a few comments:
- if we're not making stuff like shell pipe (using the output of one
command as stdin of another), there is no reason to not use
wok's run_command instead of instancing subprocess.Popen()
by hand
- every new feature should have unit tests to cover them. We are now
packaging 'python-mock' due to the Live Migration backend and we can
use it to create complex test scenarios. Just a FYI for the future
patches
On 11/18/2015 01:16 PM, Aline Manera wrote:
> Signed-off-by: Aline Manera <alinefm at linux.vnet.ibm.com>
> Signed-off-by: Jose Ricardo Ziviani <joserz at linux.vnet.ibm.com>
> ---
> src/wok/plugins/kimchi/API.json | 5 +
> src/wok/plugins/kimchi/control/host.py | 22 ++++
> src/wok/plugins/kimchi/disks.py | 123 +++++++++++++++++++++
> src/wok/plugins/kimchi/docs/API.md | 25 ++++-
> src/wok/plugins/kimchi/i18n.py | 5 +
> src/wok/plugins/kimchi/mockmodel.py | 27 +++++
> src/wok/plugins/kimchi/model/host.py | 27 +++++
> src/wok/plugins/kimchi/model/libvirtstoragepool.py | 9 +-
> src/wok/plugins/kimchi/model/storagepools.py | 30 +++--
> .../plugins/kimchi/tests/test_mock_storagepool.py | 8 +-
> 10 files changed, 264 insertions(+), 17 deletions(-)
>
> diff --git a/src/wok/plugins/kimchi/API.json b/src/wok/plugins/kimchi/API.json
> index 961f35f..22ddd28 100644
> --- a/src/wok/plugins/kimchi/API.json
> +++ b/src/wok/plugins/kimchi/API.json
> @@ -141,6 +141,11 @@
> "error": "KCHPOOL0025E"
> }
> }
> + },
> + "from_vg": {
> + "description": "Indicate if a logical pool will be created from an existing VG or not",
> + "type": "boolean",
> + "error": "KCHPOOL0026E"
> }
> }
> }
> diff --git a/src/wok/plugins/kimchi/control/host.py b/src/wok/plugins/kimchi/control/host.py
> index 9bc703a..39df0d0 100644
> --- a/src/wok/plugins/kimchi/control/host.py
> +++ b/src/wok/plugins/kimchi/control/host.py
> @@ -35,12 +35,34 @@ class Host(Resource):
> self.devices = Devices(self.model)
> self.cpuinfo = CPUInfo(self.model)
> self.partitions = Partitions(self.model)
> + self.vgs = VolumeGroups(self.model)
>
> @property
> def data(self):
> return {}
>
>
> +class VolumeGroups(Collection):
> + def __init__(self, model):
> + super(VolumeGroups, self).__init__(model)
> + self.role_key = 'host'
> + self.uri_fmt = "/host/vgs"
> + self.admin_methods = ['GET']
> + self.resource = VolumeGroup
> +
> +
> +class VolumeGroup(Resource):
> + def __init__(self, model, id=None):
> + super(VolumeGroup, self).__init__(model, id)
> + self.role_key = 'host'
> + self.uri_fmt = "/host/vgs/%s"
> + self.admin_methods = ['GET']
> +
> + @property
> + def data(self):
> + return self.info
> +
> +
> class VMHolders(SimpleCollection):
> def __init__(self, model, device_id):
> super(VMHolders, self).__init__(model)
> diff --git a/src/wok/plugins/kimchi/disks.py b/src/wok/plugins/kimchi/disks.py
> index eb40e3a..a36a8c7 100644
> --- a/src/wok/plugins/kimchi/disks.py
> +++ b/src/wok/plugins/kimchi/disks.py
> @@ -194,3 +194,126 @@ def get_partition_details(name):
> dev['path'] = dev_path
> dev['name'] = name
> return dev
> +
> +
> +def vgs():
> + """
> + lists all volume groups in the system. All size units are in bytes.
> +
> + [{'vgname': 'vgtest', 'size': 999653638144L, 'free': 0}]
> + """
> + cmd = ['vgs',
> + '--units',
> + 'b',
> + '--nosuffix',
> + '--noheading',
> + '--unbuffered',
> + '--options',
> + 'vg_name,vg_size,vg_free']
> +
> + proc = subprocess.Popen(cmd,
> + stdout=subprocess.PIPE,
> + stderr=subprocess.PIPE)
> +
> + out, err = proc.communicate()
> +
> + if proc.returncode != 0:
> + raise OperationFailed("KCHLVM0001E", {'err': err})
> +
> + if not out:
> + return []
> +
> + # remove blank spaces and create a list of VGs
> + vgs = map(lambda v: v.strip(), out.strip('\n').split('\n'))
> +
> + # create a dict based on data retrieved from vgs
> + return map(lambda l: {'vgname': l[0],
> + 'size': long(l[1]),
> + 'free': long(l[2])},
> + [fields.split() for fields in vgs])
> +
> +
> +def lvs(vgname=None):
> + """
> + lists all logical volumes found in the system. It can be filtered by
> + the volume group. All size units are in bytes.
> +
> + [{'lvname': 'lva', 'path': '/dev/vgtest/lva', 'size': 12345L},
> + {'lvname': 'lvb', 'path': '/dev/vgtest/lvb', 'size': 12345L}]
> + """
> + cmd = ['lvs',
> + '--units',
> + 'b',
> + '--nosuffix',
> + '--noheading',
> + '--unbuffered',
> + '--options',
> + 'lv_name,lv_path,lv_size,vg_name']
> +
> + proc = subprocess.Popen(cmd,
> + stdout=subprocess.PIPE,
> + stderr=subprocess.PIPE)
> +
> + out, err = proc.communicate()
> +
> + if proc.returncode != 0:
> + raise OperationFailed("KCHLVM0001E", {'err': err})
> +
> + if not out:
> + return []
> +
> + # remove blank spaces and create a list of LVs filtered by vgname, if
> + # provided
> + lvs = filter(lambda f: vgname is None or vgname in f,
> + map(lambda v: v.strip(), out.strip('\n').split('\n')))
> +
> + # create a dict based on data retrieved from lvs
> + return map(lambda l: {'lvname': l[0],
> + 'path': l[1],
> + 'size': long(l[2])},
> + [fields.split() for fields in lvs])
> +
> +
> +def pvs(vgname=None):
> + """
> + lists all physical volumes in the system. It can be filtered by the
> + volume group. All size units are in bytes.
> +
> + [{'pvname': '/dev/sda3',
> + 'size': 469502001152L,
> + 'uuid': 'kkon5B-vnFI-eKHn-I5cG-Hj0C-uGx0-xqZrXI'},
> + {'pvname': '/dev/sda2',
> + 'size': 21470642176L,
> + 'uuid': 'CyBzhK-cQFl-gWqr-fyWC-A50Y-LMxu-iHiJq4'}]
> + """
> + cmd = ['pvs',
> + '--units',
> + 'b',
> + '--nosuffix',
> + '--noheading',
> + '--unbuffered',
> + '--options',
> + 'pv_name,pv_size,pv_uuid,vg_name']
> +
> + proc = subprocess.Popen(cmd,
> + stdout=subprocess.PIPE,
> + stderr=subprocess.PIPE)
> +
> + out, err = proc.communicate()
> +
> + if proc.returncode != 0:
> + raise OperationFailed("KCHLVM0001E", {'err': err})
> +
> + if not out:
> + return []
> +
> + # remove blank spaces and create a list of PVs filtered by vgname, if
> + # provided
> + pvs = filter(lambda f: vgname is None or vgname in f,
> + map(lambda v: v.strip(), out.strip('\n').split('\n')))
> +
> + # create a dict based on data retrieved from pvs
> + return map(lambda l: {'pvname': l[0],
> + 'size': long(l[1]),
> + 'uuid': l[2]},
> + [fields.split() for fields in pvs])
> diff --git a/src/wok/plugins/kimchi/docs/API.md b/src/wok/plugins/kimchi/docs/API.md
> index 52368b7..14f014d 100644
> --- a/src/wok/plugins/kimchi/docs/API.md
> +++ b/src/wok/plugins/kimchi/docs/API.md
> @@ -444,7 +444,8 @@ A interface represents available network interface on VM.
>
> * **GET**: Retrieve a summarized list of all defined Storage Pools
> * **POST**: Create a new Storage Pool
> - * name: The name of the Storage Pool.
> + * name: The name of the Storage Pool. It corresponds to the VG name while
> + creating a logical storage pool from an existing VG.
> * type: The type of the defined Storage Pool.
> Supported types: 'dir', 'kimchi-iso', 'netfs', 'logical', 'iscsi', 'scsi'
> * path: The path of the defined Storage Pool.
> @@ -466,6 +467,8 @@ A interface represents available network interface on VM.
> * username: Login username of the iSCSI target.
> * password: Login password of the iSCSI target.
> * adapter_name: SCSI host name.
> + * from_vg: Indicate if a logical pool will be created from an
> + existing VG or not.
>
> ### Resource: Storage Pool
>
> @@ -897,6 +900,26 @@ List of available groups.
> Otherwise blank.
> * available: false, if the partition is in use by system; true, otherwise.
>
> +### Collection: Volume Groups
> +
> +**URI:** /plugins/kimchi/host/vgs
> +
> +**Methods:**
> +
> +* **GET**: Retrieves a detailed list of all volume groups in the host.
> +
> +### Resource: Volume Group
> +
> +**URI:** /plugins/kimchi/host/vgs/*:name*
> +
> +**Methods:**
> +
> +* **GET**: Retrieve the description of a single Volume Group:
> + * name: The volume group name. Used to identify it in this API.
> + * lvs: The logical volumes associated to this volume group.
> + * pvs: The phisical volumes associated to this volume group.
> + * free: Amount of free space in the volume group.
> + * size: Total size of the volume group.
>
> ### Collection: Peers
>
> diff --git a/src/wok/plugins/kimchi/i18n.py b/src/wok/plugins/kimchi/i18n.py
> index 5e9eee4..f8089cc 100644
> --- a/src/wok/plugins/kimchi/i18n.py
> +++ b/src/wok/plugins/kimchi/i18n.py
> @@ -30,6 +30,8 @@ messages = {
> "KCHDISKS0001E": _("Error while getting block devices. Details: %(err)s"),
> "KCHDISKS0002E": _("Error while getting block device information for %(device)s."),
>
> + "KCHLVM0001E": _("Unable to retrieve LVM information. Details: %(err)s"),
> +
> "KCHPART0001E": _("Partition %(name)s does not exist in the host"),
>
> "KCHDEVS0001E": _('Unknown "_cap" specified'),
> @@ -203,6 +205,7 @@ messages = {
> "KCHPOOL0035E": _("Unable to delete pool %(name)s as it is associated with some templates"),
> "KCHPOOL0036E": _("A volume group named '%(name)s' already exists. Please, choose another name to create the logical pool."),
> "KCHPOOL0037E": _("Unable to update database with deep scan information due error: %(err)s"),
> + "KCHPOOL0038E": _("No volume group '%(name)s' found. Please, specify an existing volume group to create the logical pool from."),
>
> "KCHVOL0001E": _("Storage volume %(name)s already exists"),
> "KCHVOL0002E": _("Storage volume %(name)s does not exist in storage pool %(pool)s"),
> @@ -298,4 +301,6 @@ messages = {
> "KCHCPUINF0002E": _("Invalid vCPU/topology combination."),
> "KCHCPUINF0003E": _("This host (or current configuration) does not allow CPU topology."),
>
> + "KCHLVMS0001E": _("Invalid volume group name parameter: %(name)s."),
> +
> }
> diff --git a/src/wok/plugins/kimchi/mockmodel.py b/src/wok/plugins/kimchi/mockmodel.py
> index 895401e..9186f78 100644
> --- a/src/wok/plugins/kimchi/mockmodel.py
> +++ b/src/wok/plugins/kimchi/mockmodel.py
> @@ -39,6 +39,7 @@ from wok.plugins.kimchi.model.libvirtstoragepool import NetfsPoolDef
> from wok.plugins.kimchi.model.libvirtstoragepool import StoragePoolDef
> from wok.plugins.kimchi.model.model import Model
> from wok.plugins.kimchi.model.storagepools import StoragePoolModel
> +from wok.plugins.kimchi.model.storagepools import StoragePoolsModel
> from wok.plugins.kimchi.model.storagevolumes import StorageVolumeModel
> from wok.plugins.kimchi.model.storagevolumes import StorageVolumesModel
> from wok.plugins.kimchi.model import storagevolumes
> @@ -73,6 +74,7 @@ class MockModel(Model):
> defaults.update(mockmodel_defaults)
> osinfo.defaults = dict(defaults)
>
> + self._mock_vgs = MockVolumeGroups()
> self._mock_partitions = MockPartitions()
> self._mock_devices = MockDevices()
> self._mock_storagevolumes = MockStorageVolumes()
> @@ -113,6 +115,7 @@ class MockModel(Model):
> setattr(self, m, mock_method)
>
> DeviceModel.lookup = self._mock_device_lookup
> + StoragePoolsModel._check_lvm = self._check_lvm
> StoragePoolModel._update_lvm_disks = self._update_lvm_disks
> StorageVolumesModel.get_list = self._mock_storagevolumes_get_list
> StorageVolumeModel.doUpload = self._mock_storagevolume_doUpload
> @@ -252,6 +255,10 @@ class MockModel(Model):
>
> return MockModel._libvirt_get_vol_path(pool, vol)
>
> + def _check_lvm(self, name, from_vg):
> + # do not do any verification while using MockModel
> + pass
> +
> def _update_lvm_disks(self, pool_name, disks):
> conn = self.conn.get()
> pool = conn.storagePoolLookupByName(pool_name.encode('utf-8'))
> @@ -334,6 +341,12 @@ class MockModel(Model):
> def _mock_partition_lookup(self, name):
> return self._mock_partitions.partitions[name]
>
> + def _mock_volumegroups_get_list(self):
> + return self._mock_vgs.data.keys()
> +
> + def _mock_volumegroup_lookup(self, name):
> + return self._mock_vgs.data[name]
> +
> def _mock_vm_clone(self, name):
> new_name = get_next_clone_name(self.vms_get_list(), name)
> snapshots = MockModel._mock_snapshots.get(name, [])
> @@ -418,6 +431,20 @@ class MockStorageVolumes(object):
> 'isvalid': True}}
>
>
> +class MockVolumeGroups(object):
> + def __init__(self):
> + self.data = {"hostVG": {"lvs": [],
> + "name": "hostVG",
> + "pvs": ["/dev/vdx"],
> + "free": 5347737600,
> + "size": 5347737600},
> + "kimchiVG": {"lvs": [],
> + "name": "kimchiVG",
> + "pvs": ["/dev/vdz", "/dev/vdw"],
> + "free": 10695475200,
> + "size": 10695475200}}
> +
> +
> class MockPartitions(object):
> def __init__(self):
> self.partitions = {"vdx": {"available": True, "name": "vdx",
> diff --git a/src/wok/plugins/kimchi/model/host.py b/src/wok/plugins/kimchi/model/host.py
> index 96f4bea..f3ca946 100644
> --- a/src/wok/plugins/kimchi/model/host.py
> +++ b/src/wok/plugins/kimchi/model/host.py
> @@ -241,3 +241,30 @@ class PartitionModel(object):
>
> def lookup(self, name):
> return disks.get_partition_details(name)
> +
> +
> +class VolumeGroupsModel(object):
> + def __init__(self, **kargs):
> + pass
> +
> + def get_list(self):
> + return [vg['vgname'] for vg in disks.vgs()]
> +
> +
> +class VolumeGroupModel(object):
> + def __init__(self, **kargs):
> + pass
> +
> + def lookup(self, name):
> + def _format(vg):
> + return {'name': vg['vgname'],
> + 'size': vg['size'],
> + 'free': vg['free'],
> + 'pvs': [pv['pvname'] for pv in disks.pvs(vg['vgname'])],
> + 'lvs': [lv['lvname'] for lv in disks.lvs(vg['vgname'])]}
> +
> + vgs = [_format(vg) for vg in disks.vgs() if vg['vgname'] == name]
> + if not vgs:
> + raise InvalidParameter("KCHLVMS0001E", {'name': name})
> +
> + return vgs[0]
> diff --git a/src/wok/plugins/kimchi/model/libvirtstoragepool.py b/src/wok/plugins/kimchi/model/libvirtstoragepool.py
> index 5da0b3b..0fa8ce0 100644
> --- a/src/wok/plugins/kimchi/model/libvirtstoragepool.py
> +++ b/src/wok/plugins/kimchi/model/libvirtstoragepool.py
> @@ -144,11 +144,12 @@ class LogicalPoolDef(StoragePoolDef):
> pool = E.pool(type='logical')
> pool.append(E.name(self.poolArgs['name']))
>
> - source = E.source()
> - for device_path in self.poolArgs['source']['devices']:
> - source.append(E.device(path=device_path))
> + if not self.poolArgs['source'].get('from_vg', False):
> + source = E.source()
> + for device_path in self.poolArgs['source']['devices']:
> + source.append(E.device(path=device_path))
> + pool.append(source)
>
> - pool.append(source)
> pool.append(E.target(E.path(self.path)))
> return ET.tostring(pool, encoding='unicode', pretty_print=True)
>
> diff --git a/src/wok/plugins/kimchi/model/storagepools.py b/src/wok/plugins/kimchi/model/storagepools.py
> index cc0bc7c..ec866c5 100644
> --- a/src/wok/plugins/kimchi/model/storagepools.py
> +++ b/src/wok/plugins/kimchi/model/storagepools.py
> @@ -142,9 +142,27 @@ class StoragePoolsModel(object):
> raise OperationFailed("KCHPOOL0006E",
> {'err': e.get_error_message()})
>
> + def _check_lvm(self, name, from_vg):
> + vgdisplay_cmd = ['vgdisplay', name.encode('utf-8')]
> + output, error, returncode = run_command(vgdisplay_cmd)
> + # From vgdisplay error codes:
> + # 1 error reading VGDA
> + # 2 volume group doesn't exist
> + # 3 not all physical volumes of volume group online
> + # 4 volume group not found
> + # 5 no volume groups found at all
> + # 6 error reading VGDA from lvmtab
> + if from_vg and returncode in [2, 4, 5]:
> + raise InvalidOperation("KCHPOOL0038E", {'name': name})
> +
> + if not from_vg and returncode not in [2, 4, 5]:
> + raise InvalidOperation("KCHPOOL0036E", {'name': name})
> +
> def create(self, params):
> task_id = None
> conn = self.conn.get()
> + from_vg = params.get('source', {}).get('from_vg', False)
> +
> try:
> name = params['name']
> if name == ISO_POOL_NAME:
> @@ -154,17 +172,7 @@ class StoragePoolsModel(object):
> # used before but a volume group will already exist with this name
> # So check the volume group does not exist to create the pool
> if params['type'] == 'logical':
> - vgdisplay_cmd = ['vgdisplay', name.encode('utf-8')]
> - output, error, returncode = run_command(vgdisplay_cmd)
> - # From vgdisplay error codes:
> - # 1 error reading VGDA
> - # 2 volume group doesn't exist
> - # 3 not all physical volumes of volume group online
> - # 4 volume group not found
> - # 5 no volume groups found at all
> - # 6 error reading VGDA from lvmtab
> - if returncode not in [2, 4, 5]:
> - raise InvalidOperation("KCHPOOL0036E", {'name': name})
> + self._check_lvm(name, from_vg)
>
> if params['type'] == 'kimchi-iso':
> task_id = self._do_deep_scan(params)
> diff --git a/src/wok/plugins/kimchi/tests/test_mock_storagepool.py b/src/wok/plugins/kimchi/tests/test_mock_storagepool.py
> index ea9843b..a2609eb 100644
> --- a/src/wok/plugins/kimchi/tests/test_mock_storagepool.py
> +++ b/src/wok/plugins/kimchi/tests/test_mock_storagepool.py
> @@ -65,6 +65,10 @@ class MockStoragepoolTests(unittest.TestCase):
> )
>
> def test_storagepool(self):
> + # MockModel always returns 2 VGs (hostVG, kimchiVG)
> + vgs = json.loads(self.request('/plugins/kimchi/host/vgs').read())
> + vg_names = [vg['name'] for vg in vgs]
> +
> # MockModel always returns 2 partitions (vdx, vdz)
> partitions = json.loads(
> self.request('/plugins/kimchi/host/partitions').read()
> @@ -89,7 +93,9 @@ class MockStoragepoolTests(unittest.TestCase):
> 'source': {'host': '127.0.0.1',
> 'target': 'iqn.2015-01.localhost.kimchiUnitTest'}},
> {'type': 'logical', 'name': u'kīмсhīUnitTestLogicalPool',
> - 'source': {'devices': [devs[0]]}}]
> + 'source': {'devices': [devs[0]]}},
> + {'type': 'logical', 'name': vg_names[0],
> + 'source': {'from_vg': True}}]
>
> def _do_test(params):
> name = params['name']
More information about the Kimchi-devel
mailing list