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(a)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