Skip to content

Commit

Permalink
ceph-volume: add inventory command
Browse files Browse the repository at this point in the history
The inventory command provides information about a nodes disk inventory.
Existing logical volumes on a disk or one of its partitions are scanned
and reported.
The output can be formatted as plain text or json.

Signed-off-by: Jan Fajerski <jfajerski@suse.com>
  • Loading branch information
Jan Fajerski committed Nov 9, 2018
1 parent 22d78ad commit 57adfc6
Show file tree
Hide file tree
Showing 7 changed files with 336 additions and 32 deletions.
21 changes: 21 additions & 0 deletions src/ceph-volume/ceph_volume/api/lvm.py
Expand Up @@ -1081,6 +1081,7 @@ def __init__(self, **kw):
self.name = kw['lv_name']
self.tags = parse_tags(kw['lv_tags'])
self.encrypted = self.tags.get('ceph.encrypted', '0') == '1'
self.used_by_ceph = 'ceph.osd_id' in self.tags

def __str__(self):
return '<%s>' % self.lv_api['lv_path']
Expand All @@ -1097,6 +1098,26 @@ def as_dict(self):
obj['path'] = self.lv_path
return obj

def report(self):
if not self.used_by_ceph:
return {
'name': self.lv_name,
'comment': 'not used by ceph'
}
else:
type_ = self.tags['ceph.type']
report = {
'name': self.lv_name,
'osd_id': self.tags['ceph.osd_id'],
'cluster_name': self.tags['ceph.cluster_name'],
'type': type_,
'osd_fsid': self.tags['ceph.osd_fsid'],
'cluster_fsid': self.tags['ceph.cluster_fsid'],
}
type_uuid = '{}_uuid'.format(type_)
report[type_uuid] = self.tags['ceph.{}'.format(type_uuid)]
return report

def clear_tags(self):
"""
Removes all tags from the Logical Volume.
Expand Down
1 change: 1 addition & 0 deletions src/ceph-volume/ceph_volume/inventory/__init__.py
@@ -0,0 +1 @@
from .main import Inventory # noqa
46 changes: 46 additions & 0 deletions src/ceph-volume/ceph_volume/inventory/main.py
@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-

import argparse
import pprint

from ceph_volume.util.device import Devices, Device


class Inventory(object):

help = "Get this nodes available disk inventory"

def __init__(self, argv):
self.argv = argv

def main(self):
parser = argparse.ArgumentParser(
prog='ceph-volume inventory',
formatter_class=argparse.RawDescriptionHelpFormatter,
description=self.help,
)
parser.add_argument(
'path',
nargs='?',
default=None,
help=('Report on specific disk'),
)
parser.add_argument(
'--format',
choices=['plain', 'json', 'json-pretty'],
default='plain',
help='Output format',
)
self.args = parser.parse_args(self.argv)
if self.args.path:
self.format_report(Device(self.args.path))
else:
self.format_report(Devices())

def format_report(self, inventory):
if self.args.format == 'json':
print(inventory.json_report())
elif self.args.format == 'json-pretty':
pprint.pprint(inventory.json_report())
else:
print(inventory.pretty_report())
3 changes: 2 additions & 1 deletion src/ceph-volume/ceph_volume/main.py
Expand Up @@ -6,7 +6,7 @@
import logging

from ceph_volume.decorators import catches
from ceph_volume import log, devices, configuration, conf, exceptions, terminal
from ceph_volume import log, devices, configuration, conf, exceptions, terminal, inventory


class Volume(object):
Expand All @@ -27,6 +27,7 @@ def __init__(self, argv=None, parse=True):
self.mapper = {
'lvm': devices.lvm.LVM,
'simple': devices.simple.Simple,
'inventory': inventory.Inventory,
}
self.plugin_help = "No plugins found/loaded"
if argv is None:
Expand Down
88 changes: 88 additions & 0 deletions src/ceph-volume/ceph_volume/tests/test_inventory.py
@@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-

import pytest
from ceph_volume.util.device import Devices
from ceph_volume import sys_info

@pytest.fixture
def device_report_keys():
report = Devices().json_report()[0]
return list(report.keys())

@pytest.fixture
def device_sys_api_keys():
report = Devices().json_report()[0]
return list(report['sys_api'].keys())


class TestInventory(object):

# populate sys_info with something; creating a Device instance will use
# this data
sys_info.devices = {
# example output of disk.get_devices()
'/dev/sdb': {'human_readable_size': '1.82 TB',
'locked': 0,
'model': 'PERC H700',
'nr_requests': '128',
'partitions': {},
'path': '/dev/sdb',
'removable': '0',
'rev': '2.10',
'ro': '0',
'rotational': '1',
'sas_address': '',
'sas_device_handle': '',
'scheduler_mode': 'cfq',
'sectors': 0,
'sectorsize': '512',
'size': 1999844147200.0,
'support_discard': '',
'vendor': 'DELL'}
}

expected_keys = [
'path',
'rejected_reasons',
'sys_api',
'valid',
'lvs',
]

expected_sys_api_keys = [
'human_readable_size',
'locked',
'model',
'nr_requests',
'partitions',
'path',
'removable',
'rev',
'ro',
'rotational',
'sas_address',
'sas_device_handle',
'scheduler_mode',
'sectors',
'sectorsize',
'size',
'support_discard',
'vendor',
]

def test_json_inventory_keys_unexpected(self, device_report_keys):
for k in device_report_keys:
assert k in self.expected_keys, "unexpected key {} in report".format(k)

def test_json_inventory_keys_missing(self, device_report_keys):
for k in self.expected_keys:
assert k in device_report_keys, "expected key {} in report".format(k)

def test_sys_api_keys_unexpected(self, device_sys_api_keys):
for k in device_sys_api_keys:
assert k in self.expected_sys_api_keys, "unexpected key {} in sys_api field".format(k)

def test_sys_api_keys_missing(self, device_sys_api_keys):
for k in self.expected_sys_api_keys:
assert k in device_sys_api_keys, "expected key {} in sys_api field".format(k)

15 changes: 15 additions & 0 deletions src/ceph-volume/ceph_volume/tests/util/test_device.py
Expand Up @@ -117,6 +117,21 @@ def test_not_used_by_ceph(self, device_info, pvolumes, monkeypatch):
disk = device.Device("/dev/sda")
assert not disk.used_by_ceph

disk1 = device.Device("/dev/sda")
disk2 = device.Device("/dev/sdb")
disk2._valid = False
disk3 = device.Device("/dev/sdc")
disk4 = device.Device("/dev/sdd")
disk4._valid = False

@pytest.mark.parametrize("diska, diskb", [
pytest.param(disk1, disk2, id="(_, valid) < (_, invalid)"),
pytest.param(disk1, disk3, id="(sda, valid) < (sdc, valid)"),
pytest.param(disk3, disk2, id="(sdc, valid) < (sdb, invalid)"),
pytest.param(disk2, disk4, id="(sdb, invalid) < (sdd, invalid)"),
])
def test_ordering(self, diska, diskb):
assert diska < diskb and diskb > diska

ceph_partlabels = [
'ceph data', 'ceph journal', 'ceph block',
Expand Down

0 comments on commit 57adfc6

Please sign in to comment.