[Kimchi-devel] [PATCH 6/9] Keep /host/partitions on Kimchi

Aline Manera alinefm at linux.vnet.ibm.com
Fri Oct 23 23:20:31 UTC 2015


This API is only on Kimchi. More specific to create logical pools.

Gingerbase provides APIs to host basic information, debug reports, repositories
and software update which does not require /host/partitions to work.

Signed-off-by: Aline Manera <alinefm at linux.vnet.ibm.com>
---
 src/wok/plugins/gingerbase/control/host.py         |  33 ----
 src/wok/plugins/gingerbase/disks.py                | 198 ---------------------
 src/wok/plugins/gingerbase/docs/API.md             |  26 ---
 src/wok/plugins/gingerbase/i18n.py                 |   5 -
 src/wok/plugins/gingerbase/mockmodel.py            |  19 --
 src/wok/plugins/gingerbase/model/host.py           |  24 +--
 src/wok/plugins/gingerbase/tests/test_host.py      |  13 --
 .../plugins/gingerbase/ui/js/src/gingerbase.api.js |  11 --
 src/wok/plugins/kimchi/control/host.py             |  44 ++++-
 src/wok/plugins/kimchi/disks.py                    | 196 ++++++++++++++++++++
 src/wok/plugins/kimchi/docs/API.md                 |  28 +++
 src/wok/plugins/kimchi/i18n.py                     |   5 +
 src/wok/plugins/kimchi/mockmodel.py                |  19 ++
 src/wok/plugins/kimchi/model/host.py               |  19 +-
 src/wok/plugins/kimchi/tests/test_host.py          |  13 ++
 15 files changed, 319 insertions(+), 334 deletions(-)
 delete mode 100644 src/wok/plugins/gingerbase/disks.py
 create mode 100644 src/wok/plugins/kimchi/disks.py

diff --git a/src/wok/plugins/gingerbase/control/host.py b/src/wok/plugins/gingerbase/control/host.py
index 0dae80c..b882f9d 100644
--- a/src/wok/plugins/gingerbase/control/host.py
+++ b/src/wok/plugins/gingerbase/control/host.py
@@ -22,7 +22,6 @@
 from wok.control.base import AsyncResource, Collection
 from wok.control.base import Resource
 from wok.control.utils import UrlSubNode
-from wok.exception import NotFoundError
 
 from wok.plugins.gingerbase.control.cpuinfo import CPUInfo
 
@@ -37,8 +36,6 @@ class Host(Resource):
         self.reboot = self.generate_action_handler('reboot')
         self.shutdown = self.generate_action_handler('shutdown')
         self.stats = HostStats(self.model)
-        self.partitions = Partitions(self.model)
-        # self.devices = Devices(self.model)
         self.packagesupdate = PackagesUpdate(self.model)
         self.repositories = Repositories(self.model)
         self.swupdate = self.generate_action_handler_task('swupdate')
@@ -89,36 +86,6 @@ class Capabilities(Resource):
         return self.info
 
 
-class Partitions(Collection):
-    def __init__(self, model):
-        super(Partitions, self).__init__(model)
-        self.role_key = 'storage'
-        self.admin_methods = ['GET']
-        self.resource = Partition
-
-    # Defining get_resources in order to return list of partitions in UI
-    # sorted by their path
-    def _get_resources(self, flag_filter):
-        res_list = super(Partitions, self)._get_resources(flag_filter)
-        res_list = filter(lambda x: x.info['available'], res_list)
-        res_list.sort(key=lambda x: x.info['path'])
-        return res_list
-
-
-class Partition(Resource):
-    def __init__(self, model, id):
-        self.role_key = 'storage'
-        self.admin_methods = ['GET']
-        super(Partition, self).__init__(model, id)
-
-    @property
-    def data(self):
-        if not self.info['available']:
-            raise NotFoundError("GGBPART0001E", {'name': self.info['name']})
-
-        return self.info
-
-
 class PackagesUpdate(Collection):
     def __init__(self, model):
         super(PackagesUpdate, self).__init__(model)
diff --git a/src/wok/plugins/gingerbase/disks.py b/src/wok/plugins/gingerbase/disks.py
deleted file mode 100644
index 234e385..0000000
--- a/src/wok/plugins/gingerbase/disks.py
+++ /dev/null
@@ -1,198 +0,0 @@
-#
-# Project Ginger Base
-#
-# Copyright IBM, Corp. 2013-2015
-#
-# Code derived from Project Kimchi
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
-
-import os.path
-import re
-import subprocess
-from parted import Device as PDevice
-from parted import Disk as PDisk
-
-from wok.exception import OperationFailed
-from wok.utils import wok_log
-
-
-def _get_dev_node_path(maj_min):
-    """ Returns device node path given the device number 'major:min' """
-
-    dm_name = "/sys/dev/block/%s/dm/name" % maj_min
-    if os.path.exists(dm_name):
-        with open(dm_name) as dm_f:
-            content = dm_f.read().rstrip('\n')
-        return "/dev/mapper/" + content
-
-    uevent = "/sys/dev/block/%s/uevent" % maj_min
-    with open(uevent) as ueventf:
-        content = ueventf.read()
-
-    data = dict(re.findall(r'(\S+)=(".*?"|\S+)', content.replace("\n", " ")))
-
-    return "/dev/%s" % data["DEVNAME"]
-
-
-def _get_lsblk_devs(keys, devs=[]):
-    lsblk = subprocess.Popen(
-        ["lsblk", "-Pbo"] + [','.join(keys)] + devs,
-        stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-    out, err = lsblk.communicate()
-    if lsblk.returncode != 0:
-        raise OperationFailed("GGBDISKS0001E", {'err': err})
-
-    return _parse_lsblk_output(out, keys)
-
-
-def _get_dev_major_min(name):
-    maj_min = None
-
-    keys = ["NAME", "MAJ:MIN"]
-    dev_list = _get_lsblk_devs(keys)
-
-    for dev in dev_list:
-        if dev['name'].split()[0] == name:
-            maj_min = dev['maj:min']
-            break
-    else:
-        raise OperationFailed("GGBDISKS0002E", {'device': name})
-
-    return maj_min
-
-
-def _is_dev_leaf(devNodePath):
-    try:
-        # By default, lsblk prints a device information followed by children
-        # device information
-        childrenCount = len(
-            _get_lsblk_devs(["NAME"], [devNodePath])) - 1
-    except OperationFailed as e:
-        # lsblk is known to fail on multipath devices
-        # Assume these devices contain children
-        wok_log.error(
-            "Error getting device info for %s: %s", devNodePath, e)
-        return False
-
-    return childrenCount == 0
-
-
-def _is_dev_extended_partition(devType, devNodePath):
-    if devType != 'part':
-        return False
-    diskPath = devNodePath.rstrip('0123456789')
-    device = PDevice(diskPath)
-    try:
-        extended_part = PDisk(device).getExtendedPartition()
-    except NotImplementedError as e:
-        wok_log.warning(
-            "Error getting extended partition info for dev %s type %s: %s",
-            devNodePath, devType, e.message)
-        # Treate disk with unsupported partiton table as if it does not
-        # contain extended partitions.
-        return False
-    if extended_part and extended_part.path == devNodePath:
-        return True
-    return False
-
-
-def _parse_lsblk_output(output, keys):
-    # output is on format key="value",
-    # where key can be NAME, TYPE, FSTYPE, SIZE, MOUNTPOINT, etc
-    lines = output.rstrip("\n").split("\n")
-    r = []
-    for line in lines:
-        d = {}
-        for key in keys:
-            expression = r"%s=\".*?\"" % key
-            match = re.search(expression, line)
-            field = match.group()
-            k, v = field.split('=', 1)
-            d[k.lower()] = v[1:-1]
-        r.append(d)
-    return r
-
-
-def _get_vgname(devNodePath):
-    """ Return volume group name of a physical volume. If the device node path
-    is not a physical volume, return empty string. """
-    pvs = subprocess.Popen(
-        ["pvs", "--unbuffered", "--nameprefixes", "--noheadings",
-         "-o", "vg_name", devNodePath],
-        stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-    out, err = pvs.communicate()
-    if pvs.returncode != 0:
-        return ""
-
-    return re.findall(r"LVM2_VG_NAME='([^\']*)'", out)[0]
-
-
-def _is_available(name, devtype, fstype, mountpoint, majmin):
-    devNodePath = _get_dev_node_path(majmin)
-    # Only list unmounted and unformated and leaf and (partition or disk)
-    # leaf means a partition, a disk has no partition, or a disk not held
-    # by any multipath device. Physical volume belongs to no volume group
-    # is also listed. Extended partitions should not be listed.
-    if (devtype in ['part', 'disk', 'mpath'] and
-            fstype in ['', 'LVM2_member'] and
-            mountpoint == "" and
-            _get_vgname(devNodePath) == "" and
-            _is_dev_leaf(devNodePath) and
-            not _is_dev_extended_partition(devtype, devNodePath)):
-        return True
-    return False
-
-
-def get_partitions_names(check=False):
-    names = set()
-    keys = ["NAME", "TYPE", "FSTYPE", "MOUNTPOINT", "MAJ:MIN"]
-    # output is on format key="value",
-    # where key can be NAME, TYPE, FSTYPE, MOUNTPOINT
-    for dev in _get_lsblk_devs(keys):
-        # split()[0] to avoid the second part of the name, after the
-        # whiteline
-        name = dev['name'].split()[0]
-        if check and not _is_available(name, dev['type'], dev['fstype'],
-                                       dev['mountpoint'], dev['maj:min']):
-            continue
-        names.add(name)
-
-    return list(names)
-
-
-def get_partition_details(name):
-    majmin = _get_dev_major_min(name)
-    dev_path = _get_dev_node_path(majmin)
-
-    keys = ["TYPE", "FSTYPE", "SIZE", "MOUNTPOINT"]
-    try:
-        dev = _get_lsblk_devs(keys, [dev_path])[0]
-    except OperationFailed as e:
-        wok_log.error(
-            "Error getting partition info for %s: %s", name, e)
-        return {}
-
-    dev['available'] = _is_available(name, dev['type'], dev['fstype'],
-                                     dev['mountpoint'], majmin)
-    if dev['mountpoint']:
-        # Sometimes the mountpoint comes with [SWAP] or other
-        # info which is not an actual mount point. Filtering it
-        regexp = re.compile(r"\[.*\]")
-        if regexp.search(dev['mountpoint']) is not None:
-            dev['mountpoint'] = ''
-    dev['path'] = dev_path
-    dev['name'] = name
-    return dev
diff --git a/src/wok/plugins/gingerbase/docs/API.md b/src/wok/plugins/gingerbase/docs/API.md
index e34c86c..8f37ddc 100644
--- a/src/wok/plugins/gingerbase/docs/API.md
+++ b/src/wok/plugins/gingerbase/docs/API.md
@@ -197,32 +197,6 @@ stats history
 
 *No actions defined*
 
-### Collection: Partitions
-
-**URI:** /plugins/gingerbase/host/partitions
-
-**Methods:**
-
-* **GET**: Retrieves a detailed list of all partitions of the host.
-
-### Resource: Partition
-
-**URI:** /plugins/gingerbase/host/partitions/*:name*
-
-**Methods:**
-
-* **GET**: Retrieve the description of a single Partition:
-    * name: The name of the partition. Used to identify it in this API
-    * path: The device path of this partition.
-    * type: The type of the partition:
-        * part: a standard partition
-        * lvm: a partition that belongs to a lvm
-    * fstype: The file system type of the partition
-    * size: The total size of the partition, in bytes
-    * mountpoint: If the partition is mounted, represents the mountpoint.
-      Otherwise blank.
-    * available: false, if the partition is in use by system; true, otherwise.
-
 ### Collection: Host Packages Update
 
 **URI:** /plugins/gingerbase/host/packagesupdate
diff --git a/src/wok/plugins/gingerbase/i18n.py b/src/wok/plugins/gingerbase/i18n.py
index fbc2516..8596f17 100644
--- a/src/wok/plugins/gingerbase/i18n.py
+++ b/src/wok/plugins/gingerbase/i18n.py
@@ -27,9 +27,6 @@ _ = gettext.gettext
 messages = {
     "GGBAPI0001E": _("Unknown parameter %(value)s"),
 
-    "GGBDISKS0001E": _("Error while getting block devices. Details: %(err)s"),
-    "GGBDISKS0002E": _("Error while getting block device information for %(device)s."),
-
     "GGBDR0001E": _("Debug report %(name)s does not exist"),
     "GGBDR0002E": _("Debug report tool not found in system"),
     "GGBDR0003E": _("Unable to create debug report %(name)s. Details: %(err)s."),
@@ -40,8 +37,6 @@ messages = {
                     "hyphen ('-') are allowed."),
     "GGBDR0008E": _("The debug report with specified name \"%(name)s\" already exists. Please use another one."),
 
-    "GGBPART0001E": _("Partition %(name)s does not exist in the host"),
-
     "GGBHOST0001E": _("Unable to shutdown host machine as there are running virtual machines"),
     "GGBHOST0002E": _("Unable to reboot host machine as there are running virtual machines"),
     "GGBHOST0005E": _("When specifying CPU topology, each element must be an integer greater than zero."),
diff --git a/src/wok/plugins/gingerbase/mockmodel.py b/src/wok/plugins/gingerbase/mockmodel.py
index 0b5ae22..f821509 100644
--- a/src/wok/plugins/gingerbase/mockmodel.py
+++ b/src/wok/plugins/gingerbase/mockmodel.py
@@ -42,7 +42,6 @@ class MockModel(Model):
         # Override osinfo.defaults to ajust the values according to
         # test:///default driver
 
-        self._mock_partitions = MockPartitions()
         self._mock_swupdate = MockSoftwareUpdate()
         self._mock_repositories = MockRepositories()
 
@@ -105,12 +104,6 @@ class MockModel(Model):
     def _mock_host_reboot(self, *name):
         wok_log.info("The host system will be rebooted")
 
-    def _mock_partitions_get_list(self):
-        return self._mock_partitions.partitions.keys()
-
-    def _mock_partition_lookup(self, name):
-        return self._mock_partitions.partitions[name]
-
     def _mock_packagesupdate_get_list(self):
         return self._mock_swupdate.pkgs.keys()
 
@@ -163,18 +156,6 @@ class MockModel(Model):
         return repo_id
 
 
-class MockPartitions(object):
-    def __init__(self):
-        self.partitions = {"vdx": {"available": True, "name": "vdx",
-                                   "fstype": "", "path": "/dev/vdx",
-                                   "mountpoint": "", "type": "disk",
-                                   "size": "2147483648"},
-                           "vdz": {"available": True, "name": "vdz",
-                                   "fstype": "", "path": "/dev/vdz",
-                                   "mountpoint": "", "type": "disk",
-                                   "size": "2147483648"}}
-
-
 class MockSoftwareUpdate(object):
     def __init__(self):
         self.pkgs = {
diff --git a/src/wok/plugins/gingerbase/model/host.py b/src/wok/plugins/gingerbase/model/host.py
index 062cf24..670fec5 100644
--- a/src/wok/plugins/gingerbase/model/host.py
+++ b/src/wok/plugins/gingerbase/model/host.py
@@ -28,18 +28,15 @@ from collections import defaultdict
 import glob
 
 from wok.basemodel import Singleton
+from wok.config import config as kconfig
 from wok.exception import InvalidOperation
 from wok.exception import OperationFailed
 from wok.utils import add_task, wok_log
 from wok.model.tasks import TaskModel
-from wok.config import config as kconfig
-
 
-from wok.plugins.gingerbase import disks
+from wok.plugins.gingerbase.model.debugreports import DebugReportsModel
 from wok.plugins.gingerbase.repositories import Repositories
 from wok.plugins.gingerbase.swupdate import SoftwareUpdate
-from wok.plugins.gingerbase.model.debugreports import DebugReportsModel
-
 
 HOST_STATS_INTERVAL = 1
 
@@ -347,23 +344,6 @@ class CapabilitiesModel(object):
                 }
 
 
-class PartitionsModel(object):
-    def __init__(self, **kargs):
-        pass
-
-    def get_list(self):
-        result = disks.get_partitions_names()
-        return result
-
-
-class PartitionModel(object):
-    def __init__(self, **kargs):
-        pass
-
-    def lookup(self, name):
-        return disks.get_partition_details(name)
-
-
 class PackagesUpdateModel(object):
     def __init__(self, **kargs):
         try:
diff --git a/src/wok/plugins/gingerbase/tests/test_host.py b/src/wok/plugins/gingerbase/tests/test_host.py
index 8c2e39d..e553a89 100644
--- a/src/wok/plugins/gingerbase/tests/test_host.py
+++ b/src/wok/plugins/gingerbase/tests/test_host.py
@@ -139,16 +139,3 @@ class HostTests(unittest.TestCase):
         self.assertIn(u'All packages updated', task_info['message'])
         pkgs = model.packagesupdate_get_list()
         self.assertEquals(0, len(pkgs))
-
-    def test_host_partitions(self):
-        resp = self.request('/plugins/gingerbase/host/partitions')
-        self.assertEquals(200, resp.status)
-        partitions = json.loads(resp.read())
-
-        keys = ['name', 'path', 'type', 'fstype', 'size', 'mountpoint',
-                'available']
-        for item in partitions:
-            resp = self.request('/plugins/gingerbase/host/partitions/%s' %
-                                item['name'])
-            info = json.loads(resp.read())
-            self.assertEquals(sorted(info.keys()), sorted(keys))
\ No newline at end of file
diff --git a/src/wok/plugins/gingerbase/ui/js/src/gingerbase.api.js b/src/wok/plugins/gingerbase/ui/js/src/gingerbase.api.js
index 60ec35a..db6543f 100644
--- a/src/wok/plugins/gingerbase/ui/js/src/gingerbase.api.js
+++ b/src/wok/plugins/gingerbase/ui/js/src/gingerbase.api.js
@@ -221,17 +221,6 @@ var kimchi = {
         });
     },
 
-    listHostPartitions : function(suc, err) {
-        wok.requestJSON({
-            url : 'plugins/gingerbase/host/partitions',
-            type : 'GET',
-            contentType : 'application/json',
-            dataType : 'json',
-            success : suc,
-            error : err
-        });
-    },
-
     listSoftwareUpdates : function(suc, err) {
         wok.requestJSON({
             url : 'plugins/gingerbase/host/packagesupdate',
diff --git a/src/wok/plugins/kimchi/control/host.py b/src/wok/plugins/kimchi/control/host.py
index e6bc662..baf17bc 100644
--- a/src/wok/plugins/kimchi/control/host.py
+++ b/src/wok/plugins/kimchi/control/host.py
@@ -20,6 +20,7 @@
 from wok.control.base import Collection
 from wok.control.base import Resource, SimpleCollection
 from wok.control.utils import UrlSubNode
+from wok.exception import NotFoundError
 
 from wok.plugins.kimchi.control.cpuinfo import CPUInfo
 
@@ -33,24 +34,25 @@ class Host(Resource):
         self.uri_fmt = '/host/%s'
         self.devices = Devices(self.model)
         self.cpuinfo = CPUInfo(self.model)
+        self.partitions = Partitions(self.model)
 
     @property
     def data(self):
         return self.info
 
 
-class Devices(Collection):
-    def __init__(self, model):
-        super(Devices, self).__init__(model)
-        self.resource = Device
-
-
 class VMHolders(SimpleCollection):
     def __init__(self, model, device_id):
         super(VMHolders, self).__init__(model)
         self.model_args = (device_id, )
 
 
+class Devices(Collection):
+    def __init__(self, model):
+        super(Devices, self).__init__(model)
+        self.resource = Device
+
+
 class Device(Resource):
     def __init__(self, model, id):
         super(Device, self).__init__(model, id)
@@ -59,3 +61,33 @@ class Device(Resource):
     @property
     def data(self):
         return self.info
+
+
+class Partitions(Collection):
+    def __init__(self, model):
+        super(Partitions, self).__init__(model)
+        self.role_key = 'storage'
+        self.admin_methods = ['GET']
+        self.resource = Partition
+
+    # Defining get_resources in order to return list of partitions in UI
+    # sorted by their path
+    def _get_resources(self, flag_filter):
+        res_list = super(Partitions, self)._get_resources(flag_filter)
+        res_list = filter(lambda x: x.info['available'], res_list)
+        res_list.sort(key=lambda x: x.info['path'])
+        return res_list
+
+
+class Partition(Resource):
+    def __init__(self, model, id):
+        self.role_key = 'storage'
+        self.admin_methods = ['GET']
+        super(Partition, self).__init__(model, id)
+
+    @property
+    def data(self):
+        if not self.info['available']:
+            raise NotFoundError("KCHPART0001E", {'name': self.info['name']})
+
+        return self.info
diff --git a/src/wok/plugins/kimchi/disks.py b/src/wok/plugins/kimchi/disks.py
new file mode 100644
index 0000000..eb40e3a
--- /dev/null
+++ b/src/wok/plugins/kimchi/disks.py
@@ -0,0 +1,196 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013-2015
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+
+import os.path
+import re
+import subprocess
+from parted import Device as PDevice
+from parted import Disk as PDisk
+
+from wok.exception import OperationFailed
+from wok.utils import wok_log
+
+
+def _get_dev_node_path(maj_min):
+    """ Returns device node path given the device number 'major:min' """
+
+    dm_name = "/sys/dev/block/%s/dm/name" % maj_min
+    if os.path.exists(dm_name):
+        with open(dm_name) as dm_f:
+            content = dm_f.read().rstrip('\n')
+        return "/dev/mapper/" + content
+
+    uevent = "/sys/dev/block/%s/uevent" % maj_min
+    with open(uevent) as ueventf:
+        content = ueventf.read()
+
+    data = dict(re.findall(r'(\S+)=(".*?"|\S+)', content.replace("\n", " ")))
+
+    return "/dev/%s" % data["DEVNAME"]
+
+
+def _get_lsblk_devs(keys, devs=[]):
+    lsblk = subprocess.Popen(
+        ["lsblk", "-Pbo"] + [','.join(keys)] + devs,
+        stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    out, err = lsblk.communicate()
+    if lsblk.returncode != 0:
+        raise OperationFailed("KCHDISKS0001E", {'err': err})
+
+    return _parse_lsblk_output(out, keys)
+
+
+def _get_dev_major_min(name):
+    maj_min = None
+
+    keys = ["NAME", "MAJ:MIN"]
+    dev_list = _get_lsblk_devs(keys)
+
+    for dev in dev_list:
+        if dev['name'].split()[0] == name:
+            maj_min = dev['maj:min']
+            break
+    else:
+        raise OperationFailed("KCHDISKS0002E", {'device': name})
+
+    return maj_min
+
+
+def _is_dev_leaf(devNodePath):
+    try:
+        # By default, lsblk prints a device information followed by children
+        # device information
+        childrenCount = len(
+            _get_lsblk_devs(["NAME"], [devNodePath])) - 1
+    except OperationFailed as e:
+        # lsblk is known to fail on multipath devices
+        # Assume these devices contain children
+        wok_log.error(
+            "Error getting device info for %s: %s", devNodePath, e)
+        return False
+
+    return childrenCount == 0
+
+
+def _is_dev_extended_partition(devType, devNodePath):
+    if devType != 'part':
+        return False
+    diskPath = devNodePath.rstrip('0123456789')
+    device = PDevice(diskPath)
+    try:
+        extended_part = PDisk(device).getExtendedPartition()
+    except NotImplementedError as e:
+        wok_log.warning(
+            "Error getting extended partition info for dev %s type %s: %s",
+            devNodePath, devType, e.message)
+        # Treate disk with unsupported partiton table as if it does not
+        # contain extended partitions.
+        return False
+    if extended_part and extended_part.path == devNodePath:
+        return True
+    return False
+
+
+def _parse_lsblk_output(output, keys):
+    # output is on format key="value",
+    # where key can be NAME, TYPE, FSTYPE, SIZE, MOUNTPOINT, etc
+    lines = output.rstrip("\n").split("\n")
+    r = []
+    for line in lines:
+        d = {}
+        for key in keys:
+            expression = r"%s=\".*?\"" % key
+            match = re.search(expression, line)
+            field = match.group()
+            k, v = field.split('=', 1)
+            d[k.lower()] = v[1:-1]
+        r.append(d)
+    return r
+
+
+def _get_vgname(devNodePath):
+    """ Return volume group name of a physical volume. If the device node path
+    is not a physical volume, return empty string. """
+    pvs = subprocess.Popen(
+        ["pvs", "--unbuffered", "--nameprefixes", "--noheadings",
+         "-o", "vg_name", devNodePath],
+        stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    out, err = pvs.communicate()
+    if pvs.returncode != 0:
+        return ""
+
+    return re.findall(r"LVM2_VG_NAME='([^\']*)'", out)[0]
+
+
+def _is_available(name, devtype, fstype, mountpoint, majmin):
+    devNodePath = _get_dev_node_path(majmin)
+    # Only list unmounted and unformated and leaf and (partition or disk)
+    # leaf means a partition, a disk has no partition, or a disk not held
+    # by any multipath device. Physical volume belongs to no volume group
+    # is also listed. Extended partitions should not be listed.
+    if (devtype in ['part', 'disk', 'mpath'] and
+            fstype in ['', 'LVM2_member'] and
+            mountpoint == "" and
+            _get_vgname(devNodePath) == "" and
+            _is_dev_leaf(devNodePath) and
+            not _is_dev_extended_partition(devtype, devNodePath)):
+        return True
+    return False
+
+
+def get_partitions_names(check=False):
+    names = set()
+    keys = ["NAME", "TYPE", "FSTYPE", "MOUNTPOINT", "MAJ:MIN"]
+    # output is on format key="value",
+    # where key can be NAME, TYPE, FSTYPE, MOUNTPOINT
+    for dev in _get_lsblk_devs(keys):
+        # split()[0] to avoid the second part of the name, after the
+        # whiteline
+        name = dev['name'].split()[0]
+        if check and not _is_available(name, dev['type'], dev['fstype'],
+                                       dev['mountpoint'], dev['maj:min']):
+            continue
+        names.add(name)
+
+    return list(names)
+
+
+def get_partition_details(name):
+    majmin = _get_dev_major_min(name)
+    dev_path = _get_dev_node_path(majmin)
+
+    keys = ["TYPE", "FSTYPE", "SIZE", "MOUNTPOINT"]
+    try:
+        dev = _get_lsblk_devs(keys, [dev_path])[0]
+    except OperationFailed as e:
+        wok_log.error(
+            "Error getting partition info for %s: %s", name, e)
+        return {}
+
+    dev['available'] = _is_available(name, dev['type'], dev['fstype'],
+                                     dev['mountpoint'], majmin)
+    if dev['mountpoint']:
+        # Sometimes the mountpoint comes with [SWAP] or other
+        # info which is not an actual mount point. Filtering it
+        regexp = re.compile(r"\[.*\]")
+        if regexp.search(dev['mountpoint']) is not None:
+            dev['mountpoint'] = ''
+    dev['path'] = dev_path
+    dev['name'] = name
+    return dev
diff --git a/src/wok/plugins/kimchi/docs/API.md b/src/wok/plugins/kimchi/docs/API.md
index 280c8b2..a9333b5 100644
--- a/src/wok/plugins/kimchi/docs/API.md
+++ b/src/wok/plugins/kimchi/docs/API.md
@@ -863,6 +863,34 @@ List of available groups.
     * name: The name of the VM.
     * state: The power state of the VM. Could be "running" and "shutdown".
 
+
+### Collection: Partitions
+
+**URI:** /plugins/kimchi/host/partitions
+
+**Methods:**
+
+* **GET**: Retrieves a detailed list of all partitions of the host.
+
+### Resource: Partition
+
+**URI:** /plugins/kimchi/host/partitions/*:name*
+
+**Methods:**
+
+* **GET**: Retrieve the description of a single Partition:
+    * name: The name of the partition. Used to identify it in this API
+    * path: The device path of this partition.
+    * type: The type of the partition:
+        * part: a standard partition
+        * lvm: a partition that belongs to a lvm
+    * fstype: The file system type of the partition
+    * size: The total size of the partition, in bytes
+    * mountpoint: If the partition is mounted, represents the mountpoint.
+      Otherwise blank.
+    * available: false, if the partition is in use by system; true, otherwise.
+
+
 ### Collection: Peers
 
 **URI:** /plugins/kimchi/peers
diff --git a/src/wok/plugins/kimchi/i18n.py b/src/wok/plugins/kimchi/i18n.py
index 8002dd1..f9a2ca1 100644
--- a/src/wok/plugins/kimchi/i18n.py
+++ b/src/wok/plugins/kimchi/i18n.py
@@ -27,6 +27,11 @@ messages = {
 
     "KCHAUTH0004E": _("User %(user_id)s not found with given LDAP settings."),
 
+    "KCHDISKS0001E": _("Error while getting block devices. Details: %(err)s"),
+    "KCHDISKS0002E": _("Error while getting block device information for %(device)s."),
+
+    "KCHPART0001E": _("Partition %(name)s does not exist in the host"),
+
     "KCHDEVS0001E": _('Unknown "_cap" specified'),
     "KCHDEVS0002E": _('"_passthrough" should be "true" or "false"'),
     "KCHDEVS0003E": _('"_passthrough_affected_by" should be a device name string'),
diff --git a/src/wok/plugins/kimchi/mockmodel.py b/src/wok/plugins/kimchi/mockmodel.py
index 1be477e..895401e 100644
--- a/src/wok/plugins/kimchi/mockmodel.py
+++ b/src/wok/plugins/kimchi/mockmodel.py
@@ -73,6 +73,7 @@ class MockModel(Model):
         defaults.update(mockmodel_defaults)
         osinfo.defaults = dict(defaults)
 
+        self._mock_partitions = MockPartitions()
         self._mock_devices = MockDevices()
         self._mock_storagevolumes = MockStorageVolumes()
 
@@ -327,6 +328,12 @@ class MockModel(Model):
     def _mock_device_lookup(self, dev_name):
         return self._mock_devices.devices[dev_name]
 
+    def _mock_partitions_get_list(self):
+        return self._mock_partitions.partitions.keys()
+
+    def _mock_partition_lookup(self, name):
+        return self._mock_partitions.partitions[name]
+
     def _mock_vm_clone(self, name):
         new_name = get_next_clone_name(self.vms_get_list(), name)
         snapshots = MockModel._mock_snapshots.get(name, [])
@@ -411,6 +418,18 @@ class MockStorageVolumes(object):
                                             'isvalid': True}}
 
 
+class MockPartitions(object):
+    def __init__(self):
+        self.partitions = {"vdx": {"available": True, "name": "vdx",
+                                   "fstype": "", "path": "/dev/vdx",
+                                   "mountpoint": "", "type": "disk",
+                                   "size": "2147483648"},
+                           "vdz": {"available": True, "name": "vdz",
+                                   "fstype": "", "path": "/dev/vdz",
+                                   "mountpoint": "", "type": "disk",
+                                   "size": "2147483648"}}
+
+
 class MockDevices(object):
     def __init__(self):
         self.devices = {
diff --git a/src/wok/plugins/kimchi/model/host.py b/src/wok/plugins/kimchi/model/host.py
index 54a0e71..a128ead 100644
--- a/src/wok/plugins/kimchi/model/host.py
+++ b/src/wok/plugins/kimchi/model/host.py
@@ -26,11 +26,11 @@ from wok.exception import NotFoundError
 from wok.xmlutils.utils import xpath_get_text
 from wok.model.tasks import TaskModel
 
+from wok.plugins.kimchi import disks
 from wok.plugins.kimchi.model import hostdev
 from wok.plugins.kimchi.model.config import CapabilitiesModel
 from wok.plugins.kimchi.model.vms import VMModel, VMsModel
 
-
 HOST_STATS_INTERVAL = 1
 
 
@@ -262,3 +262,20 @@ class DeviceModel(object):
             if ebus == ibus and edevice == idevice:
                 return usb_info['name']
         return unknown_dev
+
+
+class PartitionsModel(object):
+    def __init__(self, **kargs):
+        pass
+
+    def get_list(self):
+        result = disks.get_partitions_names()
+        return result
+
+
+class PartitionModel(object):
+    def __init__(self, **kargs):
+        pass
+
+    def lookup(self, name):
+        return disks.get_partition_details(name)
diff --git a/src/wok/plugins/kimchi/tests/test_host.py b/src/wok/plugins/kimchi/tests/test_host.py
index 86cf5d0..4416bed 100644
--- a/src/wok/plugins/kimchi/tests/test_host.py
+++ b/src/wok/plugins/kimchi/tests/test_host.py
@@ -119,3 +119,16 @@ class HostTests(unittest.TestCase):
         available_devs = [dev['name'] for dev in json.loads(resp.read())]
 
         self.assertLessEqual(len(available_devs), len(all_devs))
+
+    def test_host_partitions(self):
+        resp = self.request('/plugins/kimchi/host/partitions')
+        self.assertEquals(200, resp.status)
+        partitions = json.loads(resp.read())
+
+        keys = ['name', 'path', 'type', 'fstype', 'size', 'mountpoint',
+                'available']
+        for item in partitions:
+            resp = self.request('/plugins/kimchi/host/partitions/%s' %
+                                item['name'])
+            info = json.loads(resp.read())
+            self.assertEquals(sorted(info.keys()), sorted(keys))
-- 
2.1.0




More information about the Kimchi-devel mailing list