
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@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