[PATCH 0/9] Make gingerbase more stable

Hi all, This patch set depends on "[PATCH 00/17] Summary Ginger Base Plug in Code Changes" V7. It covers some issues found in the Ginger Base patches. With that, all tests cases for Wok and Kimchi passes successfully. There is only one test failing for Ginger Base. I will check it on Monday to provide a separated patch. Aline Manera (9): Fix PYTHONPATH to run tests for Ginger Base Get the right internal URI to Ginger Base plugin Fix Ginger Base tests Remove repositories tests from Kimchi Move host authorization tests from Kimchi to Ginger Base Keep /host/partitions on Kimchi Remove Host Resource from Kimchi Add src/wok/plugins directory to the Wok PEP8 backlist Add .gitignore file to gingerbase directory Makefile.am | 2 +- src/wok/plugins/gingerbase/.gitignore | 37 ++++ src/wok/plugins/gingerbase/control/debugreports.py | 2 +- 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/run_tests.sh.in | 4 +- .../plugins/gingerbase/tests/test_authorization.py | 72 ++++++++ src/wok/plugins/gingerbase/tests/test_host.py | 13 -- src/wok/plugins/gingerbase/tests/test_rest.py | 2 +- .../plugins/gingerbase/ui/js/src/gingerbase.api.js | 11 -- src/wok/plugins/kimchi/control/host.py | 46 ++++- 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 | 57 ++---- src/wok/plugins/kimchi/tests/test_authorization.py | 14 -- src/wok/plugins/kimchi/tests/test_host.py | 13 ++ src/wok/plugins/kimchi/tests/test_rest.py | 40 +---- 23 files changed, 436 insertions(+), 430 deletions(-) create mode 100644 src/wok/plugins/gingerbase/.gitignore delete mode 100644 src/wok/plugins/gingerbase/disks.py create mode 100644 src/wok/plugins/gingerbase/tests/test_authorization.py create mode 100644 src/wok/plugins/kimchi/disks.py -- 2.1.0

Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- src/wok/plugins/gingerbase/tests/run_tests.sh.in | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/wok/plugins/gingerbase/tests/run_tests.sh.in b/src/wok/plugins/gingerbase/tests/run_tests.sh.in index 1a34418..3b08fa6 100644 --- a/src/wok/plugins/gingerbase/tests/run_tests.sh.in +++ b/src/wok/plugins/gingerbase/tests/run_tests.sh.in @@ -52,4 +52,6 @@ for ((i=0;i<${#LIST[@]};i++)); do fi done -PYTHONPATH=../plugins:../src:../ $CMD $OPTS ${MODEL_LIST[@]} ${MOCK_LIST[@]} +# ../../../../ refers to wok directory +# ../../../ refers to plugins directory +PYTHONPATH=../../../../:../../../ $CMD $OPTS ${MODEL_LIST[@]} ${MOCK_LIST[@]} -- 2.1.0

Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- src/wok/plugins/gingerbase/control/debugreports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wok/plugins/gingerbase/control/debugreports.py b/src/wok/plugins/gingerbase/control/debugreports.py index bb93f5f..8403209 100644 --- a/src/wok/plugins/gingerbase/control/debugreports.py +++ b/src/wok/plugins/gingerbase/control/debugreports.py @@ -60,5 +60,5 @@ class DebugReportContent(Resource): def get(self): self.lookup() - internal_uri = self.info['uri'].replace('plugins/kimchi', '') + internal_uri = self.info['uri'].replace('plugins/gingerbase', '') raise internal_redirect(internal_uri) -- 2.1.0

Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- src/wok/plugins/gingerbase/tests/test_rest.py | 2 +- src/wok/plugins/kimchi/tests/test_rest.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/wok/plugins/gingerbase/tests/test_rest.py b/src/wok/plugins/gingerbase/tests/test_rest.py index 75729cb..a8138c6 100644 --- a/src/wok/plugins/gingerbase/tests/test_rest.py +++ b/src/wok/plugins/gingerbase/tests/test_rest.py @@ -140,7 +140,7 @@ class RestTests(unittest.TestCase): resp = request(host, ssl_port, '/plugins/gingerbase/debugreports/report1') debugre = json.loads(resp.read()) - resp = request(host, ssl_port, debugre['uri']) + resp = request(host, ssl_port, '/' + debugre['uri']) self.assertEquals(200, resp.status) def test_repositories(self): diff --git a/src/wok/plugins/kimchi/tests/test_rest.py b/src/wok/plugins/kimchi/tests/test_rest.py index 670a6b5..8f04ada 100644 --- a/src/wok/plugins/kimchi/tests/test_rest.py +++ b/src/wok/plugins/kimchi/tests/test_rest.py @@ -1218,8 +1218,7 @@ class RestTests(unittest.TestCase): conf = json.loads(resp) keys = [u'libvirt_stream_protocols', u'qemu_stream', u'qemu_spice', - u'screenshot', u'system_report_tool', u'update_tool', - u'repo_mngt_tool', u'federation', u'kernel_vfio', u'auth', + u'screenshot', u'federation', u'kernel_vfio', u'auth', u'nm_running', u'mem_hotplug_support'] self.assertEquals(sorted(keys), sorted(conf.keys())) -- 2.1.0

Repositories API is now part of Ginger Base Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- src/wok/plugins/kimchi/tests/test_rest.py | 37 ------------------------------- 1 file changed, 37 deletions(-) diff --git a/src/wok/plugins/kimchi/tests/test_rest.py b/src/wok/plugins/kimchi/tests/test_rest.py index 8f04ada..977a255 100644 --- a/src/wok/plugins/kimchi/tests/test_rest.py +++ b/src/wok/plugins/kimchi/tests/test_rest.py @@ -1265,43 +1265,6 @@ class RestTests(unittest.TestCase): # Distro not found error self.assertIn('KCHDISTRO0001E', distro.get('reason')) - def test_repositories(self): - def verify_repo(t, res): - for field in ('repo_id', 'enabled', 'baseurl', 'config'): - if field in t.keys(): - self.assertEquals(t[field], res[field]) - - base_uri = '/plugins/kimchi/host/repositories' - resp = self.request(base_uri) - self.assertEquals(200, resp.status) - # Already have one repo in Kimchi's system - self.assertEquals(1, len(json.loads(resp.read()))) - - # Create a repository - repo = {'repo_id': 'fedora-fake', - 'baseurl': 'http://www.fedora.org'} - req = json.dumps(repo) - resp = self.request(base_uri, req, 'POST') - self.assertEquals(201, resp.status) - - # Verify the repository - res = json.loads(self.request('%s/fedora-fake' % base_uri).read()) - verify_repo(repo, res) - - # Update the repository - params = {} - params['baseurl'] = repo['baseurl'] = 'http://www.fedoraproject.org' - resp = self.request('%s/fedora-fake' % base_uri, json.dumps(params), - 'PUT') - - # Verify the repository - res = json.loads(self.request('%s/fedora-fake' % base_uri).read()) - verify_repo(repo, res) - - # Delete the repository - resp = self.request('%s/fedora-fake' % base_uri, '{}', 'DELETE') - self.assertEquals(204, resp.status) - class HttpsRestTests(RestTests): """ -- 2.1.0

Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- .../plugins/gingerbase/tests/test_authorization.py | 72 ++++++++++++++++++++++ src/wok/plugins/kimchi/tests/test_authorization.py | 14 ----- 2 files changed, 72 insertions(+), 14 deletions(-) create mode 100644 src/wok/plugins/gingerbase/tests/test_authorization.py diff --git a/src/wok/plugins/gingerbase/tests/test_authorization.py b/src/wok/plugins/gingerbase/tests/test_authorization.py new file mode 100644 index 0000000..7460679 --- /dev/null +++ b/src/wok/plugins/gingerbase/tests/test_authorization.py @@ -0,0 +1,72 @@ +# +# Project Ginger Base +# +# Copyright IBM, Corp. 2014-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 +import unittest +from functools import partial + +from wok.plugins.kimchi import mockmodel + +from utils import get_free_port, patch_auth, request, run_server + +test_server = None +model = None +host = None +port = None +ssl_port = None +fake_iso = '/tmp/fake.iso' + + +def setUpModule(): + global test_server, model, host, port, ssl_port + + patch_auth(sudo=False) + model = mockmodel.MockModel('/tmp/obj-store-test') + host = '127.0.0.1' + port = get_free_port('http') + ssl_port = get_free_port('https') + test_server = run_server(host, port, ssl_port, test_mode=True, model=model) + + +def tearDownModule(): + test_server.stop() + os.unlink('/tmp/obj-store-test') + + +class AuthorizationTests(unittest.TestCase): + def setUp(self): + self.request = partial(request, host, ssl_port) + model.reset() + + def test_nonroot_access(self): + # Non-root users can access static host information + resp = self.request('/plugins/gingerbase/host', '{}', 'GET') + self.assertEquals(403, resp.status) + + # Non-root users can access host stats + resp = self.request('/plugins/gingerbase/host/stats', '{}', 'GET') + self.assertEquals(403, resp.status) + + # Non-root users can not reboot/shutdown host system + resp = self.request('/plugins/gingerbase/host/reboot', '{}', 'POST') + self.assertEquals(403, resp.status) + resp = self.request('/plugins/gingerbase/host/shutdown', '{}', 'POST') + self.assertEquals(403, resp.status) diff --git a/src/wok/plugins/kimchi/tests/test_authorization.py b/src/wok/plugins/kimchi/tests/test_authorization.py index fda86a3..cfc1715 100644 --- a/src/wok/plugins/kimchi/tests/test_authorization.py +++ b/src/wok/plugins/kimchi/tests/test_authorization.py @@ -63,20 +63,6 @@ class AuthorizationTests(unittest.TestCase): model.reset() def test_nonroot_access(self): - # Non-root users can access static host information - resp = self.request('/plugins/kimchi/host', '{}', 'GET') - self.assertEquals(403, resp.status) - - # Non-root users can access host stats - resp = self.request('/plugins/kimchi/host/stats', '{}', 'GET') - self.assertEquals(403, resp.status) - - # Non-root users can not reboot/shutdown host system - resp = self.request('/plugins/kimchi/host/reboot', '{}', 'POST') - self.assertEquals(403, resp.status) - resp = self.request('/plugins/kimchi/host/shutdown', '{}', 'POST') - self.assertEquals(403, resp.status) - # Non-root users can not create or delete network (only get) resp = self.request('/plugins/kimchi/networks', '{}', 'GET') self.assertEquals(200, resp.status) -- 2.1.0

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

Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- src/wok/plugins/kimchi/control/host.py | 2 +- src/wok/plugins/kimchi/model/host.py | 38 ---------------------------------- 2 files changed, 1 insertion(+), 39 deletions(-) diff --git a/src/wok/plugins/kimchi/control/host.py b/src/wok/plugins/kimchi/control/host.py index baf17bc..9bc703a 100644 --- a/src/wok/plugins/kimchi/control/host.py +++ b/src/wok/plugins/kimchi/control/host.py @@ -38,7 +38,7 @@ class Host(Resource): @property def data(self): - return self.info + return {} class VMHolders(SimpleCollection): diff --git a/src/wok/plugins/kimchi/model/host.py b/src/wok/plugins/kimchi/model/host.py index a128ead..96f4bea 100644 --- a/src/wok/plugins/kimchi/model/host.py +++ b/src/wok/plugins/kimchi/model/host.py @@ -18,55 +18,17 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA import libvirt -import psutil from lxml import objectify from wok.exception import InvalidParameter 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 - - -class HostModel(object): - def __init__(self, **kargs): - self.conn = kargs['conn'] - self.objstore = kargs['objstore'] - self.task = TaskModel(**kargs) - self.host_info = {} - - def lookup(self, *name): - cpus = 0 - - # psutil is unstable on how to get the number of - # cpus, different versions call it differently - if hasattr(psutil, 'cpu_count'): - cpus = psutil.cpu_count() - - elif hasattr(psutil, 'NUM_CPUS'): - cpus = psutil.NUM_CPUS - - elif hasattr(psutil, '_psplatform'): - for method_name in ['_get_num_cpus', 'get_num_cpus']: - - method = getattr(psutil._psplatform, method_name, None) - if method is not None: - cpus = method() - break - - self.host_info['cpus'] = cpus - if hasattr(psutil, 'phymem_usage'): - self.host_info['memory'] = psutil.phymem_usage().total - elif hasattr(psutil, 'virtual_memory'): - self.host_info['memory'] = psutil.virtual_memory().total - return self.host_info - class DevicesModel(object): def __init__(self, **kargs): -- 2.1.0

Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index 430d6bc..027344f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -38,7 +38,7 @@ EXTRA_DIST = \ $(NULL) -PEP8_BLACKLIST = *src/wok/config.py,*src/wok/i18n.py,*src/wok/plugins/kimchi,*tests/test_config.py +PEP8_BLACKLIST = *src/wok/plugins,*src/wok/config.py,*src/wok/i18n.py,*tests/test_config.py SKIP_PYFLAKES_ERR = "\./src/wok/websocket\.py" -- 2.1.0

Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- src/wok/plugins/gingerbase/.gitignore | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/wok/plugins/gingerbase/.gitignore diff --git a/src/wok/plugins/gingerbase/.gitignore b/src/wok/plugins/gingerbase/.gitignore new file mode 100644 index 0000000..96e8ab4 --- /dev/null +++ b/src/wok/plugins/gingerbase/.gitignore @@ -0,0 +1,37 @@ +*.pyc +*~ +i18n/mo/* +log +data +mo +autom4te.cache +Makefile +Makefile.in +aclocal.m4 +build-aux/compile +build-aux/config.guess +build-aux/config.sub +build-aux/install-sh +build-aux/missing +build-aux/py-compile +configure +config.log +config.py +config.status +contrib/DEBIAN/control +contrib/gingerbase.spec.fedora +contrib/gingerbase.spec.suse +contrib/make-deb.sh +*.min.css +*.min.js +*.gmo +stamp-po +gingerbase-*.tar.gz +tests/run_tests.sh +tests/test_config.py +po/POTFILES +po/gen-pot +*.orig +*.rej +*.pem +ui/pages/help/*/*.html -- 2.1.0

Reviewed-by: Daniel Barboza <dhbarboza82@gmail.com> On 10/23/2015 09:20 PM, Aline Manera wrote:
Hi all,
This patch set depends on "[PATCH 00/17] Summary Ginger Base Plug in Code Changes" V7.
It covers some issues found in the Ginger Base patches.
With that, all tests cases for Wok and Kimchi passes successfully. There is only one test failing for Ginger Base. I will check it on Monday to provide a separated patch.
Aline Manera (9): Fix PYTHONPATH to run tests for Ginger Base Get the right internal URI to Ginger Base plugin Fix Ginger Base tests Remove repositories tests from Kimchi Move host authorization tests from Kimchi to Ginger Base Keep /host/partitions on Kimchi Remove Host Resource from Kimchi Add src/wok/plugins directory to the Wok PEP8 backlist Add .gitignore file to gingerbase directory
Makefile.am | 2 +- src/wok/plugins/gingerbase/.gitignore | 37 ++++ src/wok/plugins/gingerbase/control/debugreports.py | 2 +- 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/run_tests.sh.in | 4 +- .../plugins/gingerbase/tests/test_authorization.py | 72 ++++++++ src/wok/plugins/gingerbase/tests/test_host.py | 13 -- src/wok/plugins/gingerbase/tests/test_rest.py | 2 +- .../plugins/gingerbase/ui/js/src/gingerbase.api.js | 11 -- src/wok/plugins/kimchi/control/host.py | 46 ++++- 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 | 57 ++---- src/wok/plugins/kimchi/tests/test_authorization.py | 14 -- src/wok/plugins/kimchi/tests/test_host.py | 13 ++ src/wok/plugins/kimchi/tests/test_rest.py | 40 +---- 23 files changed, 436 insertions(+), 430 deletions(-) create mode 100644 src/wok/plugins/gingerbase/.gitignore delete mode 100644 src/wok/plugins/gingerbase/disks.py create mode 100644 src/wok/plugins/gingerbase/tests/test_authorization.py create mode 100644 src/wok/plugins/kimchi/disks.py

Reviewed-by: Chandra Shekhar Reddy Potula <chandra@linux.vnet.ibm.com> On 10/24/2015 04:50 AM, Aline Manera wrote:
Hi all,
This patch set depends on "[PATCH 00/17] Summary Ginger Base Plug in Code Changes" V7.
It covers some issues found in the Ginger Base patches.
With that, all tests cases for Wok and Kimchi passes successfully. There is only one test failing for Ginger Base. I will check it on Monday to provide a separated patch.
Aline Manera (9): Fix PYTHONPATH to run tests for Ginger Base Get the right internal URI to Ginger Base plugin Fix Ginger Base tests Remove repositories tests from Kimchi Move host authorization tests from Kimchi to Ginger Base Keep /host/partitions on Kimchi Remove Host Resource from Kimchi Add src/wok/plugins directory to the Wok PEP8 backlist Add .gitignore file to gingerbase directory
Makefile.am | 2 +- src/wok/plugins/gingerbase/.gitignore | 37 ++++ src/wok/plugins/gingerbase/control/debugreports.py | 2 +- 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/run_tests.sh.in | 4 +- .../plugins/gingerbase/tests/test_authorization.py | 72 ++++++++ src/wok/plugins/gingerbase/tests/test_host.py | 13 -- src/wok/plugins/gingerbase/tests/test_rest.py | 2 +- .../plugins/gingerbase/ui/js/src/gingerbase.api.js | 11 -- src/wok/plugins/kimchi/control/host.py | 46 ++++- 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 | 57 ++---- src/wok/plugins/kimchi/tests/test_authorization.py | 14 -- src/wok/plugins/kimchi/tests/test_host.py | 13 ++ src/wok/plugins/kimchi/tests/test_rest.py | 40 +---- 23 files changed, 436 insertions(+), 430 deletions(-) create mode 100644 src/wok/plugins/gingerbase/.gitignore delete mode 100644 src/wok/plugins/gingerbase/disks.py create mode 100644 src/wok/plugins/gingerbase/tests/test_authorization.py create mode 100644 src/wok/plugins/kimchi/disks.py
participants (3)
-
Aline Manera
-
Chandra Shehkhar Reddy Potula
-
Daniel Henrique Barboza