From f320f02923e251b123d164befc371b23bf2b75b6 Mon Sep 17 00:00:00 2001 From: Sebastian Wagner Date: Wed, 16 Oct 2019 13:07:31 +0200 Subject: [PATCH] mgr/orchestrator: move InventoryDevice to python-common Adapted: * `mgr/dashboard` * `mgr/ansible` * `mgr/rook` * `mgr/ssh` * `mgr/orchestrator_cli` Signed-off-by: Sebastian Wagner Co-authored--by: Kiefer Chang --- doc/mgr/orchestrator_modules.rst | 12 +- qa/tasks/mgr/dashboard/test_orchestrator.py | 14 +- src/pybind/mgr/ansible/output_wizards.py | 12 +- src/pybind/mgr/ansible/requirements.txt | 1 + .../mgr/ansible/tests/test_output_wizards.py | 4 +- .../cluster/inventory/inventory.component.ts | 12 +- .../ceph/cluster/inventory/inventory.model.ts | 20 ++- src/pybind/mgr/dashboard/requirements.txt | 1 + src/pybind/mgr/dashboard/tests/test_host.py | 2 +- .../mgr/dashboard/tests/test_orchestrator.py | 2 + src/pybind/mgr/deepsea/module.py | 3 +- src/pybind/mgr/orchestrator.py | 122 ++----------- src/pybind/mgr/orchestrator_cli/module.py | 40 +++-- .../mgr/orchestrator_cli/requirements.txt | 5 +- .../mgr/orchestrator_cli/test_orchestrator.py | 33 ++-- src/pybind/mgr/orchestrator_cli/tox.ini | 5 +- src/pybind/mgr/rook/module.py | 32 ++-- src/pybind/mgr/ssh/module.py | 5 +- src/pybind/mgr/test_orchestrator/module.py | 8 +- .../ceph/tests/test_inventory.py | 163 ++++++++++++++++++ 20 files changed, 288 insertions(+), 208 deletions(-) create mode 100644 src/python-common/ceph/tests/test_inventory.py diff --git a/doc/mgr/orchestrator_modules.rst b/doc/mgr/orchestrator_modules.rst index c68b6dd8ed357..1d52a253dc422 100644 --- a/doc/mgr/orchestrator_modules.rst +++ b/doc/mgr/orchestrator_modules.rst @@ -230,11 +230,19 @@ Inventory and status .. automethod:: Orchestrator.get_inventory .. autoclass:: InventoryFilter -.. autoclass:: InventoryNode -.. autoclass:: InventoryDevice +.. py:currentmodule:: ceph.deployment.inventory + +.. autoclass:: Devices + :members: + +.. autoclass:: Device :members: +.. py:currentmodule:: orchestrator + + + .. automethod:: Orchestrator.describe_service .. autoclass:: ServiceDescription diff --git a/qa/tasks/mgr/dashboard/test_orchestrator.py b/qa/tasks/mgr/dashboard/test_orchestrator.py index be541b8d1790a..cfa4f9cba75ca 100644 --- a/qa/tasks/mgr/dashboard/test_orchestrator.py +++ b/qa/tasks/mgr/dashboard/test_orchestrator.py @@ -12,10 +12,7 @@ test_data = { 'name': 'test-host0', 'devices': [ { - 'type': 'hdd', - 'id': '/dev/sda', - 'size': 1024**4 * 4, - 'rotates': True + 'path': '/dev/sda', } ] }, @@ -23,10 +20,7 @@ test_data = { 'name': 'test-host1', 'devices': [ { - 'type': 'hdd', - 'id': '/dev/sda', - 'size': 1024**4 * 4, - 'rotates': True + 'path': '/dev/sdb', } ] } @@ -94,8 +88,8 @@ class OrchestratorControllerTest(DashboardTestCase): if not data['devices']: return - test_devices = sorted(data['devices'], key=lambda d: d['id']) - resp_devices = sorted(resp_data['devices'], key=lambda d: d['id']) + test_devices = sorted(data['devices'], key=lambda d: d['path']) + resp_devices = sorted(resp_data['devices'], key=lambda d: d['path']) for test, resp in zip(test_devices, resp_devices): self._validate_device(test, resp) diff --git a/src/pybind/mgr/ansible/output_wizards.py b/src/pybind/mgr/ansible/output_wizards.py index 6c157a0d17fe8..a49b70d478a09 100644 --- a/src/pybind/mgr/ansible/output_wizards.py +++ b/src/pybind/mgr/ansible/output_wizards.py @@ -7,8 +7,8 @@ completion objects import json - -from orchestrator import InventoryDevice, InventoryNode +from ceph.deployment import inventory +from orchestrator import InventoryNode from .ansible_runner_svc import EVENT_DATA_URL @@ -75,11 +75,7 @@ class ProcessInventory(OutputWizard): host = event_data["host"] devices = json.loads(event_data["res"]["stdout"]) - devs = [] - for storage_device in devices: - dev = InventoryDevice.from_ceph_volume_inventory(storage_device) - devs.append(dev) - + devs = inventory.Devices.from_json(devices) inventory_nodes.append(InventoryNode(host, devs)) @@ -140,7 +136,7 @@ class ProcessHostsList(OutputWizard): json_resp = json.loads(host_ls_json) for host in json_resp["data"]["hosts"]: - inventory_nodes.append(InventoryNode(host, [])) + inventory_nodes.append(InventoryNode(host, inventory.Devices([]))) except ValueError: self.log.exception("Malformed json response") diff --git a/src/pybind/mgr/ansible/requirements.txt b/src/pybind/mgr/ansible/requirements.txt index 11914371262ef..c6ddb76f9f1e7 100644 --- a/src/pybind/mgr/ansible/requirements.txt +++ b/src/pybind/mgr/ansible/requirements.txt @@ -2,3 +2,4 @@ tox==2.9.1 pytest mock requests-mock +-e ../../../python-common diff --git a/src/pybind/mgr/ansible/tests/test_output_wizards.py b/src/pybind/mgr/ansible/tests/test_output_wizards.py index 2a3a9017d3dab..3c3437659d4fa 100644 --- a/src/pybind/mgr/ansible/tests/test_output_wizards.py +++ b/src/pybind/mgr/ansible/tests/test_output_wizards.py @@ -199,9 +199,9 @@ class OutputWizardProcessInventory(unittest.TestCase): self.assertEqual(nodes_list[0].name, "192.168.121.144") # Devices - self.assertTrue(len(nodes_list[0].devices), 4) + self.assertTrue(len(nodes_list[0].devices.devices), 4) expected_device_ids = ["/dev/sdc", "/dev/sda", "/dev/sdb", "/dev/vda"] - device_ids = [dev.id for dev in nodes_list[0].devices] + device_ids = [dev.path for dev in nodes_list[0].devices.devices] self.assertEqual(expected_device_ids, device_ids) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory.component.ts index 6a4809d151fae..9bbcac8328c9a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory.component.ts @@ -44,23 +44,23 @@ export class InventoryComponent implements OnChanges, OnInit { this.columns = [ { name: this.i18n('Device path'), - prop: 'id', + prop: 'path', flexGrow: 1 }, { name: this.i18n('Type'), - prop: 'type', + prop: 'human_readable_type', flexGrow: 1 }, { name: this.i18n('Size'), - prop: 'size', + prop: 'sys_api.size', flexGrow: 1, pipe: this.dimlessBinary }, { name: this.i18n('Rotates'), - prop: 'rotates', + prop: 'sys_api.rotational', flexGrow: 1 }, { @@ -70,7 +70,7 @@ export class InventoryComponent implements OnChanges, OnInit { }, { name: this.i18n('Model'), - prop: 'model', + prop: 'sys_api.model', flexGrow: 1 }, { @@ -128,7 +128,7 @@ export class InventoryComponent implements OnChanges, OnInit { data.forEach((node: InventoryNode) => { node.devices.forEach((device: Device) => { device.hostname = node.name; - device.uid = `${node.name}-${device.id}`; + device.uid = `${node.name}-${device.device_id}`; devices.push(device); }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory.model.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory.model.ts index 2757703775f1e..b7cd2ea9a0590 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory.model.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory.model.ts @@ -1,16 +1,22 @@ +export class SysAPI { + vendor: string; + model: string; + size: number; + rotational: string; + human_readable_size: string; +} + export class Device { hostname: string; uid: string; osd_ids: number[]; - blank: boolean; - type: string; - id: string; - size: number; - rotates: boolean; + path: string; + sys_api: SysAPI; available: boolean; - dev_id: string; - extended: any; + rejected_reasons: string[]; + device_id: string; + human_readable_type: string; } export class InventoryNode { diff --git a/src/pybind/mgr/dashboard/requirements.txt b/src/pybind/mgr/dashboard/requirements.txt index 8661c59998f66..eca83046077e6 100644 --- a/src/pybind/mgr/dashboard/requirements.txt +++ b/src/pybind/mgr/dashboard/requirements.txt @@ -8,3 +8,4 @@ python3-saml requests Routes six +../../../python-common diff --git a/src/pybind/mgr/dashboard/tests/test_host.py b/src/pybind/mgr/dashboard/tests/test_host.py index 23bd75390a60a..2e6bbc624a8c8 100644 --- a/src/pybind/mgr/dashboard/tests/test_host.py +++ b/src/pybind/mgr/dashboard/tests/test_host.py @@ -80,7 +80,7 @@ class TestHosts(unittest.TestCase): fake_client = mock.Mock() fake_client.available.return_value = True fake_client.hosts.list.return_value = [ - InventoryNode('node1', []), InventoryNode('node2', [])] + InventoryNode('node1'), InventoryNode('node2')] instance.return_value = fake_client hosts = get_hosts() diff --git a/src/pybind/mgr/dashboard/tests/test_orchestrator.py b/src/pybind/mgr/dashboard/tests/test_orchestrator.py index 332f4b71e7264..8fa191a51dbca 100644 --- a/src/pybind/mgr/dashboard/tests/test_orchestrator.py +++ b/src/pybind/mgr/dashboard/tests/test_orchestrator.py @@ -4,6 +4,8 @@ try: except ImportError: from unittest import mock +from ceph.deployment.inventory import Devices + from orchestrator import InventoryNode, ServiceDescription from . import ControllerTestCase diff --git a/src/pybind/mgr/deepsea/module.py b/src/pybind/mgr/deepsea/module.py index 734a457d8587e..114f6aefff94e 100644 --- a/src/pybind/mgr/deepsea/module.py +++ b/src/pybind/mgr/deepsea/module.py @@ -14,6 +14,7 @@ import requests from threading import Event, Thread, Lock +from ceph.deployment import inventory from mgr_module import MgrModule import orchestrator @@ -154,7 +155,7 @@ class DeepSeaOrchestrator(MgrModule, orchestrator.Orchestrator): # nodes, the cache will never be populated, and you'll always have # the full round trip to DeepSea. self.inventory_cache[node_name] = orchestrator.OutdatableData(node_devs) - devs = orchestrator.InventoryDevice.from_ceph_volume_inventory_list(node_devs) + devs = inventory.Devices.from_json(node_devs) result.append(orchestrator.InventoryNode(node_name, devs)) else: self.log.error(event_data['return']) diff --git a/src/pybind/mgr/orchestrator.py b/src/pybind/mgr/orchestrator.py index dc9e57f2fe9ed..fe2dc23026532 100644 --- a/src/pybind/mgr/orchestrator.py +++ b/src/pybind/mgr/orchestrator.py @@ -14,6 +14,8 @@ import random import datetime import copy +from ceph.deployment import inventory + from mgr_module import MgrModule, PersistentStoreDict from mgr_util import format_bytes @@ -836,107 +838,6 @@ class InventoryFilter(object): self.nodes = nodes # Optional: get info about certain named nodes only -class InventoryDevice(object): - """ - When fetching inventory, block devices are reported in this format. - - Note on device identifiers: the format of this is up to the orchestrator, - but the same identifier must also work when passed into StatefulServiceSpec. - The identifier should be something meaningful like a device WWID or - stable device node path -- not something made up by the orchestrator. - - "Extended" is for reporting any special configuration that may have - already been done out of band on the block device. For example, if - the device has already been configured for encryption, report that - here so that it can be indicated to the user. The set of - extended properties may differ between orchestrators. An orchestrator - is permitted to support no extended properties (only normal block - devices) - """ - def __init__(self, blank=False, type=None, id=None, size=None, - rotates=False, available=False, dev_id=None, extended=None, - metadata_space_free=None): - # type: (bool, str, str, int, bool, bool, str, dict, bool) -> None - - self.blank = blank - - #: 'ssd', 'hdd', 'nvme' - self.type = type - - #: unique within a node (or globally if you like). - self.id = id - - #: byte integer. - self.size = size - - #: indicates if it is a spinning disk - self.rotates = rotates - - #: can be used to create a new OSD? - self.available = available - - #: vendor/model - self.dev_id = dev_id - - #: arbitrary JSON-serializable object - self.extended = extended if extended is not None else extended - - # If this drive is not empty, but is suitable for appending - # additional journals, wals, or bluestore dbs, then report - # how much space is available. - self.metadata_space_free = metadata_space_free - - def to_json(self): - return dict(type=self.type, blank=self.blank, id=self.id, - size=self.size, rotates=self.rotates, - available=self.available, dev_id=self.dev_id, - extended=self.extended) - - @classmethod - @handle_type_error - def from_json(cls, data): - return cls(**data) - - @classmethod - def from_ceph_volume_inventory(cls, data): - # TODO: change InventoryDevice itself to mirror c-v inventory closely! - - dev = InventoryDevice() - dev.id = data["path"] - dev.type = 'hdd' if data["sys_api"]["rotational"] == "1" else 'ssd/nvme' - dev.size = data["sys_api"]["size"] - dev.rotates = data["sys_api"]["rotational"] == "1" - dev.available = data["available"] - dev.dev_id = "%s/%s" % (data["sys_api"]["vendor"], - data["sys_api"]["model"]) - dev.extended = data - return dev - - @classmethod - def from_ceph_volume_inventory_list(cls, datas): - return [cls.from_ceph_volume_inventory(d) for d in datas] - - def pretty_print(self, only_header=False): - """Print a human friendly line with the information of the device - - :param only_header: Print only the name of the device attributes - - Ex:: - - Device Path Type Size Rotates Available Model - /dev/sdc hdd 50.00 GB True True ATA/QEMU - - """ - row_format = " {0:<15} {1:>10} {2:>10} {3:>10} {4:>10} {5:<15}\n" - if only_header: - return row_format.format("Device Path", "Type", "Size", "Rotates", - "Available", "Model") - else: - return row_format.format(str(self.id), self.type if self.type is not None else "", - format_bytes(self.size if self.size is not None else 0, 5, - colored=False), - str(self.rotates), str(self.available), - self.dev_id if self.dev_id is not None else "") class InventoryNode(object): @@ -944,22 +845,24 @@ class InventoryNode(object): When fetching inventory, all Devices are groups inside of an InventoryNode. """ - def __init__(self, name, devices): - # type: (str, List[InventoryDevice]) -> None - assert isinstance(devices, list) + def __init__(self, name, devices=None): + # type: (str, inventory.Devices) -> None + if devices is None: + devices = inventory.Devices([]) + assert isinstance(devices, inventory.Devices) + self.name = name # unique within cluster. For example a hostname. self.devices = devices def to_json(self): - return {'name': self.name, 'devices': [d.to_json() for d in self.devices]} + return {'name': self.name, 'devices': self.devices.to_json()} @classmethod def from_json(cls, data): try: _data = copy.deepcopy(data) name = _data.pop('name') - devices = [InventoryDevice.from_json(device) - for device in _data.pop('devices')] + devices = inventory.Devices.from_json(_data.pop('devices')) if _data: error_msg = 'Unknown key(s) in Inventory: {}'.format(','.join(_data.keys())) raise OrchestratorValidationError(error_msg) @@ -967,10 +870,13 @@ class InventoryNode(object): except KeyError as e: error_msg = '{} is required for {}'.format(e, cls.__name__) raise OrchestratorValidationError(error_msg) + except TypeError as e: + raise OrchestratorValidationError('Failed to read inventory: {}'.format(e)) + @classmethod def from_nested_items(cls, hosts): - devs = InventoryDevice.from_ceph_volume_inventory_list + devs = inventory.Devices.from_json return [cls(item[0], devs(item[1].data)) for item in hosts] diff --git a/src/pybind/mgr/orchestrator_cli/module.py b/src/pybind/mgr/orchestrator_cli/module.py index 6f9f3c30008b7..174e23028839e 100644 --- a/src/pybind/mgr/orchestrator_cli/module.py +++ b/src/pybind/mgr/orchestrator_cli/module.py @@ -2,8 +2,11 @@ import errno import json from functools import wraps +from ceph.deployment.inventory import Device from prettytable import PrettyTable +from mgr_util import format_bytes + try: from typing import List, Set, Optional except ImportError: @@ -215,22 +218,27 @@ class OrchestratorCli(orchestrator.OrchestratorClientMixin, MgrModule): data = [n.to_json() for n in completion.result] return HandleCommandResult(stdout=json.dumps(data)) else: - # Return a human readable version - result = "" - - for inventory_node in completion.result: - result += "Host {0}:\n".format(inventory_node.name) - - if inventory_node.devices: - result += inventory_node.devices[0].pretty_print(only_header=True) - else: - result += "No storage devices found" - - for d in inventory_node.devices: - result += d.pretty_print() - result += "\n" - - return HandleCommandResult(stdout=result) + out = [] + + for host in completion.result: # type: orchestrator.InventoryNode + out.append('Host {}:'.format(host.name)) + table = PrettyTable( + ['Path', 'Type', 'Size', 'Available', 'Ceph Device ID', 'Reject Reasons'], + border=False) + table._align['Path'] = 'l' + for d in host.devices.devices: # type: Device + table.add_row( + ( + d.path, + d.human_readable_type, + format_bytes(d.sys_api.get('size', 0), 5, colored=False), + d.available, + d.device_id, + ', '.join(d.rejected_reasons) + ) + ) + out.append(table.get_string()) + return HandleCommandResult(stdout='\n'.join(out)) @_read_cli('orchestrator service ls', "name=host,type=CephString,req=false " diff --git a/src/pybind/mgr/orchestrator_cli/requirements.txt b/src/pybind/mgr/orchestrator_cli/requirements.txt index 62843e4929ea1..2585d05fc1b88 100644 --- a/src/pybind/mgr/orchestrator_cli/requirements.txt +++ b/src/pybind/mgr/orchestrator_cli/requirements.txt @@ -1,2 +1,5 @@ tox==2.9.1 --e ../../../python-common \ No newline at end of file +../../../python-common +pytest +mock +requests-mock diff --git a/src/pybind/mgr/orchestrator_cli/test_orchestrator.py b/src/pybind/mgr/orchestrator_cli/test_orchestrator.py index adc911b45732c..de50acb0722eb 100644 --- a/src/pybind/mgr/orchestrator_cli/test_orchestrator.py +++ b/src/pybind/mgr/orchestrator_cli/test_orchestrator.py @@ -3,25 +3,22 @@ import json import pytest +from ceph.deployment import inventory from orchestrator import ReadCompletion, raise_if_exception, RGWSpec -from orchestrator import InventoryNode, InventoryDevice, ServiceDescription +from orchestrator import InventoryNode, ServiceDescription from orchestrator import OrchestratorValidationError -def _test_resource(data, resource_class, extra): - # create the instance with normal way - rsc = resource_class(**data) - if hasattr(rsc, 'pretty_print'): - assert rsc.pretty_print() - +def _test_resource(data, resource_class, extra=None): # ensure we can deserialize and serialize rsc = resource_class.from_json(data) rsc.to_json() - # if there is an unexpected data provided - data.update(extra) - with pytest.raises(OrchestratorValidationError): - resource_class.from_json(data) + if extra: + # if there is an unexpected data provided + data.update(extra) + with pytest.raises(OrchestratorValidationError): + resource_class.from_json(data) def test_inventory(): @@ -29,16 +26,20 @@ def test_inventory(): 'name': 'host0', 'devices': [ { - 'type': 'hdd', - 'id': '/dev/sda', - 'size': 1024, - 'rotates': True + 'sys_api': { + 'rotational': '1', + 'size': 1024, + }, + 'path': '/dev/sda', + 'available': False, + 'rejected_reasons': [], + 'lvs': [] } ] } _test_resource(json_data, InventoryNode, {'abc': False}) for devices in json_data['devices']: - _test_resource(devices, InventoryDevice, {'abc': False}) + _test_resource(devices, inventory.Device) json_data = [{}, {'name': 'host0'}, {'devices': []}] for data in json_data: diff --git a/src/pybind/mgr/orchestrator_cli/tox.ini b/src/pybind/mgr/orchestrator_cli/tox.ini index fb3c90e712a08..cbec129837500 100644 --- a/src/pybind/mgr/orchestrator_cli/tox.ini +++ b/src/pybind/mgr/orchestrator_cli/tox.ini @@ -5,10 +5,7 @@ toxworkdir = {env:CEPH_BUILD_DIR}/orchestrator_cli minversion = 2.5 [testenv] -deps = - pytest - mock - requests-mock +deps = -rrequirements.txt setenv= UNITTEST = true py27: PYTHONPATH = {toxinidir}/../../../../build/lib/cython_modules/lib.2 diff --git a/src/pybind/mgr/rook/module.py b/src/pybind/mgr/rook/module.py index 2d196ecac2255..63bb25cffdb6a 100644 --- a/src/pybind/mgr/rook/module.py +++ b/src/pybind/mgr/rook/module.py @@ -3,6 +3,8 @@ import functools import os import uuid +from ceph.deployment import inventory + try: from typing import List, Dict from ceph.deployment.drive_group import DriveGroupSpec @@ -312,32 +314,24 @@ class RookOrchestrator(MgrModule, orchestrator.Orchestrator): for node_name, node_devs in devs.items(): devs = [] for d in node_devs: - dev = orchestrator.InventoryDevice() - - # XXX CAUTION! https://github.com/rook/rook/issues/1716 - # Passing this through for the sake of completeness but it - # is not trustworthy! - dev.blank = d['empty'] - dev.type = 'hdd' if d['rotational'] else 'ssd' - dev.id = d['name'] - dev.size = d['size'] - - if d['filesystem'] == "" and not d['rotational']: - # Empty or partitioned SSD - partitioned_space = sum( - [p['size'] for p in d['Partitions']]) - dev.metadata_space_free = max(0, d[ - 'size'] - partitioned_space) - + dev = inventory.Device( + path=d['name'], + sys_api=dict( + rotational='1' if d['rotational'] else '0', + size=d['size'] + ), + available=d['empty'], + rejected_reasons=[] if d['empty'] else ['not empty'], + ) devs.append(dev) - result.append(orchestrator.InventoryNode(node_name, devs)) + result.append(orchestrator.InventoryNode(node_name, inventory.Devices(devs))) return result @deferred_read def get_hosts(self): - return [orchestrator.InventoryNode(n, []) for n in self.rook_cluster.get_node_names()] + return [orchestrator.InventoryNode(n, inventory.Devices([])) for n in self.rook_cluster.get_node_names()] @deferred_read def describe_service(self, service_type=None, service_id=None, node_name=None, refresh=False): diff --git a/src/pybind/mgr/ssh/module.py b/src/pybind/mgr/ssh/module.py index 9601580462c54..bc4e890003471 100644 --- a/src/pybind/mgr/ssh/module.py +++ b/src/pybind/mgr/ssh/module.py @@ -10,6 +10,7 @@ import random import tempfile import multiprocessing.pool +from ceph.deployment import inventory from mgr_module import MgrModule import orchestrator @@ -464,7 +465,7 @@ class SSHOrchestrator(MgrModule, orchestrator.OrchestratorClientMixin): TODO: - InventoryNode probably needs to be able to report labels """ - nodes = [orchestrator.InventoryNode(host_name, []) for host_name in self.inventory_cache] + nodes = [orchestrator.InventoryNode(host_name, inventory.Devices([])) for host_name in self.inventory_cache] return orchestrator.TrivialReadCompletion(nodes) def _refresh_host_services(self, host): @@ -639,7 +640,7 @@ class SSHOrchestrator(MgrModule, orchestrator.OrchestratorClientMixin): else: self.log.debug("reading cached inventory for '{}'".format(host)) - devices = orchestrator.InventoryDevice.from_ceph_volume_inventory_list(host_info.data) + devices = inventory.Devices.from_json(host_info.data) return orchestrator.InventoryNode(host, devices) results = [] diff --git a/src/pybind/mgr/test_orchestrator/module.py b/src/pybind/mgr/test_orchestrator/module.py index cb097c5f2816a..9ad65968dff9e 100644 --- a/src/pybind/mgr/test_orchestrator/module.py +++ b/src/pybind/mgr/test_orchestrator/module.py @@ -9,6 +9,7 @@ from subprocess import check_output, CalledProcessError import six +from ceph.deployment import inventory from mgr_module import CLICommand, HandleCommandResult from mgr_module import MgrModule, PersistentStoreDict @@ -201,10 +202,7 @@ class TestOrchestrator(MgrModule, orchestrator.Orchestrator): for out in c_v_out.splitlines(): self.log.error(out) - devs = [] - for device in json.loads(out): - dev = orchestrator.InventoryDevice.from_ceph_volume_inventory(device) - devs.append(dev) + devs = inventory.Devices.from_json(json.loads(out)) return [orchestrator.InventoryNode('localhost', devs)] self.log.error('c-v failed: ' + str(c_v_out)) raise Exception('c-v failed') @@ -289,7 +287,7 @@ class TestOrchestrator(MgrModule, orchestrator.Orchestrator): def get_hosts(self): if self._inventory: return self._inventory - return [orchestrator.InventoryNode('localhost', [])] + return [orchestrator.InventoryNode('localhost', inventory.Devices([]))] @deferred_write("add_host") def add_host(self, host): diff --git a/src/python-common/ceph/tests/test_inventory.py b/src/python-common/ceph/tests/test_inventory.py new file mode 100644 index 0000000000000..647564b2dd2df --- /dev/null +++ b/src/python-common/ceph/tests/test_inventory.py @@ -0,0 +1,163 @@ +import json + +from ceph.deployment.inventory import Devices + + +def test_from_json(): + data = json.loads(""" + [ + { + "available": false, + "rejected_reasons": [ + "locked" + ], + "sys_api": { + "scheduler_mode": "", + "rotational": "0", + "vendor": "", + "human_readable_size": "50.00 GB", + "sectors": 0, + "sas_device_handle": "", + "partitions": {}, + "rev": "", + "sas_address": "", + "locked": 1, + "sectorsize": "512", + "removable": "0", + "path": "/dev/dm-0", + "support_discard": "", + "model": "", + "ro": "0", + "nr_requests": "128", + "size": 53687091200 + }, + "lvs": [], + "path": "/dev/dm-0" + }, + { + "available": false, + "rejected_reasons": [ + "locked" + ], + "sys_api": { + "scheduler_mode": "", + "rotational": "0", + "vendor": "", + "human_readable_size": "31.47 GB", + "sectors": 0, + "sas_device_handle": "", + "partitions": {}, + "rev": "", + "sas_address": "", + "locked": 1, + "sectorsize": "512", + "removable": "0", + "path": "/dev/dm-1", + "support_discard": "", + "model": "", + "ro": "0", + "nr_requests": "128", + "size": 33789313024 + }, + "lvs": [], + "path": "/dev/dm-1" + }, + { + "available": false, + "rejected_reasons": [ + "locked" + ], + "sys_api": { + "scheduler_mode": "", + "rotational": "0", + "vendor": "", + "human_readable_size": "394.27 GB", + "sectors": 0, + "sas_device_handle": "", + "partitions": {}, + "rev": "", + "sas_address": "", + "locked": 1, + "sectorsize": "512", + "removable": "0", + "path": "/dev/dm-2", + "support_discard": "", + "model": "", + "ro": "0", + "nr_requests": "128", + "size": 423347879936 + }, + "lvs": [], + "path": "/dev/dm-2" + }, + { + "available": false, + "rejected_reasons": [ + "locked" + ], + "sys_api": { + "scheduler_mode": "cfq", + "rotational": "0", + "vendor": "ATA", + "human_readable_size": "476.94 GB", + "sectors": 0, + "sas_device_handle": "", + "partitions": { + "sda2": { + "start": "411648", + "holders": [], + "sectorsize": 512, + "sectors": "2097152", + "size": "1024.00 MB" + }, + "sda3": { + "start": "2508800", + "holders": [ + "dm-1", + "dm-2", + "dm-0" + ], + "sectorsize": 512, + "sectors": "997705728", + "size": "475.74 GB" + }, + "sda1": { + "start": "2048", + "holders": [], + "sectorsize": 512, + "sectors": "409600", + "size": "200.00 MB" + } + }, + "rev": "0000", + "sas_address": "", + "locked": 1, + "sectorsize": "512", + "removable": "0", + "path": "/dev/sda", + "support_discard": "", + "model": "SanDisk SD8SN8U5", + "ro": "0", + "nr_requests": "128", + "size": 512110190592 + }, + "lvs": [ + { + "comment": "not used by ceph", + "name": "swap" + }, + { + "comment": "not used by ceph", + "name": "home" + }, + { + "comment": "not used by ceph", + "name": "root" + } + ], + "path": "/dev/sda" + } +]""".strip()) + ds = Devices.from_json(data) + assert len(ds.devices) == 4 + assert Devices.from_json(ds.to_json()) == ds -- 2.39.5