mirror of
git://sourceware.org/git/lvm2.git
synced 2024-12-22 17:35:59 +03:00
545 lines
14 KiB
Python
Executable File
545 lines
14 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
# Copyright (C) 2015-2016 Red Hat, Inc. All rights reserved.
|
|
#
|
|
# This copyrighted material is made available to anyone wishing to use,
|
|
# modify, copy, or redistribute it subject to the terms and conditions
|
|
# of the GNU General Public License v.2.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
from collections import OrderedDict
|
|
|
|
import pprint as prettyprint
|
|
import os
|
|
import sys
|
|
|
|
try:
|
|
from . import cmdhandler
|
|
from .utils import log_debug, log_error
|
|
except SystemError:
|
|
import cmdhandler
|
|
from utils import log_debug
|
|
|
|
|
|
class DataStore(object):
|
|
def __init__(self, usejson=None):
|
|
self.pvs = {}
|
|
self.vgs = {}
|
|
self.lvs = {}
|
|
self.pv_lvs = {}
|
|
self.lv_pvs = {}
|
|
self.lvs_hidden = {}
|
|
|
|
self.pv_path_to_uuid = {}
|
|
self.vg_name_to_uuid = {}
|
|
self.lv_full_name_to_uuid = {}
|
|
|
|
self.lvs_in_vgs = {}
|
|
self.pvs_in_vgs = {}
|
|
|
|
# self.refresh()
|
|
self.num_refreshes = 0
|
|
|
|
if usejson is None:
|
|
self.json = cmdhandler.supports_json()
|
|
else:
|
|
self.json = usejson
|
|
|
|
@staticmethod
|
|
def _insert_record(table, key, record, allowed_multiple):
|
|
if key in table:
|
|
existing = table[key]
|
|
|
|
for rec_k, rec_v in record.items():
|
|
if rec_k in allowed_multiple:
|
|
# This column name allows us to store multiple value for
|
|
# each type
|
|
if not isinstance(existing[rec_k], list):
|
|
existing_value = existing[rec_k]
|
|
existing[rec_k] = [existing_value, rec_v]
|
|
else:
|
|
existing[rec_k].append(rec_v)
|
|
else:
|
|
# If something is not expected to have changing values
|
|
# lets ensure that
|
|
if existing[rec_k] != rec_v:
|
|
raise RuntimeError(
|
|
"existing[%s]=%s != %s" %
|
|
(rec_k, str(existing[rec_k]),
|
|
str(rec_v)))
|
|
else:
|
|
table[key] = record
|
|
|
|
@staticmethod
|
|
def _parse_pvs(_pvs):
|
|
pvs = sorted(_pvs, key=lambda pk: pk['pv_name'])
|
|
|
|
c_pvs = OrderedDict()
|
|
c_lookup = {}
|
|
c_pvs_in_vgs = {}
|
|
|
|
for p in pvs:
|
|
DataStore._insert_record(
|
|
c_pvs, p['pv_uuid'], p,
|
|
['pvseg_start', 'pvseg_size', 'segtype'])
|
|
|
|
for p in c_pvs.values():
|
|
# Capture which PVs are associated with which VG
|
|
if p['vg_uuid'] not in c_pvs_in_vgs:
|
|
c_pvs_in_vgs[p['vg_uuid']] = []
|
|
|
|
if p['vg_name']:
|
|
c_pvs_in_vgs[p['vg_uuid']].append(
|
|
(p['pv_name'], p['pv_uuid']))
|
|
|
|
# Lookup for translating between /dev/<name> and pv uuid
|
|
c_lookup[p['pv_name']] = p['pv_uuid']
|
|
|
|
return c_pvs, c_lookup, c_pvs_in_vgs
|
|
|
|
@staticmethod
|
|
def _parse_pvs_json(_all):
|
|
|
|
c_pvs = OrderedDict()
|
|
c_lookup = {}
|
|
c_pvs_in_vgs = {}
|
|
|
|
# Each item item in the report is a collection of information pertaining
|
|
# to the vg
|
|
for r in _all['report']:
|
|
tmp_pv = []
|
|
|
|
# Get the pv data for this VG.
|
|
if 'pv' in r:
|
|
tmp_pv.extend(r['pv'])
|
|
|
|
# Sort them
|
|
sorted_tmp_pv = sorted(tmp_pv, key=lambda pk: pk['pv_name'])
|
|
|
|
# Add them to result set
|
|
for p in sorted_tmp_pv:
|
|
c_pvs[p['pv_uuid']] = p
|
|
|
|
if 'pvseg' in r:
|
|
for s in r['pvseg']:
|
|
r = c_pvs[s['pv_uuid']]
|
|
r.setdefault('pvseg_start', []).append(s['pvseg_start'])
|
|
r.setdefault('pvseg_size', []).append(s['pvseg_size'])
|
|
r.setdefault('segtype', []).append(s['segtype'])
|
|
|
|
# TODO: Remove this bug work around when we have orphan segs.
|
|
for i in c_pvs.values():
|
|
if 'pvseg_start' not in i:
|
|
i['pvseg_start'] = '0'
|
|
i['pvseg_size'] = i['pv_pe_count']
|
|
i['segtype'] = 'free'
|
|
|
|
for p in c_pvs.values():
|
|
# Capture which PVs are associated with which VG
|
|
if p['vg_uuid'] not in c_pvs_in_vgs:
|
|
c_pvs_in_vgs[p['vg_uuid']] = []
|
|
|
|
if p['vg_name']:
|
|
c_pvs_in_vgs[p['vg_uuid']].append(
|
|
(p['pv_name'], p['pv_uuid']))
|
|
|
|
# Lookup for translating between /dev/<name> and pv uuid
|
|
c_lookup[p['pv_name']] = p['pv_uuid']
|
|
|
|
return c_pvs, c_lookup, c_pvs_in_vgs
|
|
|
|
@staticmethod
|
|
def _parse_vgs(_vgs):
|
|
vgs = sorted(_vgs, key=lambda vk: vk['vg_name'])
|
|
|
|
c_vgs = OrderedDict()
|
|
c_lookup = {}
|
|
|
|
for i in vgs:
|
|
c_lookup[i['vg_name']] = i['vg_uuid']
|
|
DataStore._insert_record(c_vgs, i['vg_uuid'], i, [])
|
|
|
|
return c_vgs, c_lookup
|
|
|
|
@staticmethod
|
|
def _parse_vgs_json(_all):
|
|
|
|
tmp_vg = []
|
|
for r in _all['report']:
|
|
# Get the pv data for this VG.
|
|
if 'vg' in r:
|
|
tmp_vg.extend(r['vg'])
|
|
|
|
# Sort for consistent output, however this is optional
|
|
vgs = sorted(tmp_vg, key=lambda vk: vk['vg_name'])
|
|
|
|
c_vgs = OrderedDict()
|
|
c_lookup = {}
|
|
|
|
for i in vgs:
|
|
c_lookup[i['vg_name']] = i['vg_uuid']
|
|
c_vgs[i['vg_uuid']] = i
|
|
|
|
return c_vgs, c_lookup
|
|
|
|
@staticmethod
|
|
def _parse_lvs_common(c_lvs, c_lv_full_lookup):
|
|
|
|
c_lvs_in_vgs = OrderedDict()
|
|
c_lvs_hidden = OrderedDict()
|
|
|
|
for i in c_lvs.values():
|
|
if i['vg_uuid'] not in c_lvs_in_vgs:
|
|
c_lvs_in_vgs[i['vg_uuid']] = []
|
|
|
|
c_lvs_in_vgs[
|
|
i['vg_uuid']].append(
|
|
(i['lv_name'],
|
|
(i['lv_attr'], i['lv_layout'], i['lv_role']),
|
|
i['lv_uuid']))
|
|
|
|
if i['lv_parent']:
|
|
# Lookup what the parent refers too
|
|
parent_name = i['lv_parent']
|
|
full_parent_name = "%s/%s" % (i['vg_name'], parent_name)
|
|
if full_parent_name not in c_lv_full_lookup:
|
|
parent_name = '[%s]' % (parent_name)
|
|
full_parent_name = "%s/%s" % (i['vg_name'], parent_name)
|
|
|
|
parent_uuid = c_lv_full_lookup[full_parent_name]
|
|
|
|
if parent_uuid not in c_lvs_hidden:
|
|
c_lvs_hidden[parent_uuid] = []
|
|
|
|
c_lvs_hidden[parent_uuid].append(
|
|
(i['lv_uuid'], i['lv_name']))
|
|
|
|
return c_lvs, c_lvs_in_vgs, c_lvs_hidden, c_lv_full_lookup
|
|
|
|
@staticmethod
|
|
def _parse_lvs(_lvs):
|
|
lvs = sorted(_lvs, key=lambda vk: vk['lv_name'])
|
|
|
|
c_lvs = OrderedDict()
|
|
c_lv_full_lookup = OrderedDict()
|
|
|
|
for i in lvs:
|
|
full_name = "%s/%s" % (i['vg_name'], i['lv_name'])
|
|
c_lv_full_lookup[full_name] = i['lv_uuid']
|
|
DataStore._insert_record(
|
|
c_lvs, i['lv_uuid'], i,
|
|
['seg_pe_ranges', 'segtype'])
|
|
|
|
return DataStore._parse_lvs_common(c_lvs, c_lv_full_lookup)
|
|
|
|
@staticmethod
|
|
def _parse_lvs_json(_all):
|
|
|
|
c_lvs = OrderedDict()
|
|
c_lv_full_lookup = {}
|
|
|
|
# Each item item in the report is a collection of information pertaining
|
|
# to the vg
|
|
for r in _all['report']:
|
|
# Get the lv data for this VG.
|
|
if 'lv' in r:
|
|
# Add them to result set
|
|
for i in r['lv']:
|
|
full_name = "%s/%s" % (i['vg_name'], i['lv_name'])
|
|
c_lv_full_lookup[full_name] = i['lv_uuid']
|
|
c_lvs[i['lv_uuid']] = i
|
|
|
|
# Add in the segment data
|
|
if 'seg' in r:
|
|
for s in r['seg']:
|
|
r = c_lvs[s['lv_uuid']]
|
|
r.setdefault('seg_pe_ranges', []).append(s['seg_pe_ranges'])
|
|
r.setdefault('segtype', []).append(s['segtype'])
|
|
|
|
return DataStore._parse_lvs_common(c_lvs, c_lv_full_lookup)
|
|
|
|
@staticmethod
|
|
def _make_list(l):
|
|
if not isinstance(l, list):
|
|
l = [l]
|
|
return l
|
|
|
|
@staticmethod
|
|
def _parse_seg_entry(se, segtype):
|
|
if se:
|
|
# print("_parse_seg_entry %s %s" % (str(se), str(segtype)))
|
|
device, segs = se.split(":")
|
|
start, end = segs.split('-')
|
|
return (device, (start, end), segtype)
|
|
else:
|
|
return ("", (), segtype)
|
|
|
|
@staticmethod
|
|
def _build_segments(l, seg_types):
|
|
rc = []
|
|
l = DataStore._make_list(l)
|
|
s = DataStore._make_list(seg_types)
|
|
|
|
assert len(l) == len(s)
|
|
ls = list(zip(l, s))
|
|
|
|
for i in ls:
|
|
if ' ' in i[0]:
|
|
tmp = i[0].split(' ')
|
|
for t in tmp:
|
|
rc.append(DataStore._parse_seg_entry(t, i[1]))
|
|
else:
|
|
rc.append(DataStore._parse_seg_entry(*i))
|
|
return rc
|
|
|
|
@staticmethod
|
|
def _pv_device_lv_entry(table, pv_device, lv_uuid, meta, lv_attr,
|
|
segment_info):
|
|
|
|
if pv_device not in table:
|
|
table[pv_device] = {}
|
|
|
|
if lv_uuid not in table[pv_device]:
|
|
table[pv_device][lv_uuid] = {}
|
|
table[pv_device][lv_uuid]['segs'] = [segment_info]
|
|
table[pv_device][lv_uuid]['name'] = meta
|
|
table[pv_device][lv_uuid]['meta'] = lv_attr
|
|
else:
|
|
table[pv_device][lv_uuid]['segs'].append(segment_info)
|
|
|
|
@staticmethod
|
|
def _pv_device_lv_format(pv_device_lvs):
|
|
rc = {}
|
|
|
|
for pv_device, pd in pv_device_lvs.items():
|
|
lvs = []
|
|
for lv_uuid, ld in sorted(pd.items()):
|
|
lvs.append((lv_uuid, ld['name'], ld['meta'], ld['segs']))
|
|
|
|
rc[pv_device] = lvs
|
|
return rc
|
|
|
|
@staticmethod
|
|
def _lvs_device_pv_entry(table, lv_uuid, pv_device, pv_uuid, segment_info):
|
|
if lv_uuid not in table:
|
|
table[lv_uuid] = {}
|
|
|
|
if pv_device not in table[lv_uuid]:
|
|
table[lv_uuid][pv_device] = {}
|
|
table[lv_uuid][pv_device]['segs'] = [segment_info]
|
|
table[lv_uuid][pv_device]['pv_uuid'] = pv_uuid
|
|
else:
|
|
table[lv_uuid][pv_device]['segs'].append(segment_info)
|
|
|
|
@staticmethod
|
|
def _lvs_device_pv_format(lvs_device_pvs):
|
|
rc = {}
|
|
|
|
for lv_uuid, ld in lvs_device_pvs.items():
|
|
pvs = []
|
|
for pv_device, pd in sorted(ld.items()):
|
|
pvs.append((pd['pv_uuid'], pv_device, pd['segs']))
|
|
|
|
rc[lv_uuid] = pvs
|
|
return rc
|
|
|
|
def _parse_pv_in_lvs(self):
|
|
pv_device_lvs = {} # What LVs are stored on a PV
|
|
lvs_device_pv = {} # Where LV data is stored
|
|
|
|
for i in self.lvs.values():
|
|
segs = self._build_segments(i['seg_pe_ranges'], i['segtype'])
|
|
for s in segs:
|
|
# We are referring to physical device
|
|
if '/dev/' in s[0]:
|
|
device, r, seg_type = s
|
|
|
|
DataStore._pv_device_lv_entry(
|
|
pv_device_lvs, device, i['lv_uuid'], i['lv_name'],
|
|
(i['lv_attr'], i['lv_layout'], i['lv_role']),
|
|
(r[0], r[1], seg_type))
|
|
|
|
# (pv_name, pv_segs, pv_uuid)
|
|
DataStore._lvs_device_pv_entry(
|
|
lvs_device_pv, i['lv_uuid'], device,
|
|
self.pv_path_to_uuid[device], (r[0], r[1], seg_type))
|
|
else:
|
|
# TODO Handle the case where the segments refer to a LV
|
|
# and not a PV
|
|
pass
|
|
# print("Handle this %s %s %s" % (s[0], s[1], s[2]))
|
|
|
|
# Convert form to needed result for consumption
|
|
pv_device_lvs_result = DataStore._pv_device_lv_format(pv_device_lvs)
|
|
lvs_device_pv_result = DataStore._lvs_device_pv_format(lvs_device_pv)
|
|
|
|
return pv_device_lvs_result, lvs_device_pv_result
|
|
|
|
def refresh(self, log=True):
|
|
"""
|
|
Go out and query lvm for the latest data in as few trips as possible
|
|
:param log Add debug log entry/exit messages
|
|
:return: None
|
|
"""
|
|
self.num_refreshes += 1
|
|
if log:
|
|
log_debug("lvmdb - refresh entry")
|
|
|
|
# Grab everything first then parse it
|
|
if self.json:
|
|
# Do a single lvm retrieve for everything in json
|
|
a = cmdhandler.lvm_full_report_json()
|
|
|
|
_pvs, _pvs_lookup, _pvs_in_vgs = self._parse_pvs_json(a)
|
|
_vgs, _vgs_lookup = self._parse_vgs_json(a)
|
|
_lvs, _lvs_in_vgs, _lvs_hidden, _lvs_lookup = self._parse_lvs_json(a)
|
|
|
|
else:
|
|
_raw_pvs = cmdhandler.pv_retrieve_with_segs()
|
|
_raw_vgs = cmdhandler.vg_retrieve(None)
|
|
_raw_lvs = cmdhandler.lv_retrieve_with_segments()
|
|
|
|
_pvs, _pvs_lookup, _pvs_in_vgs = self._parse_pvs(_raw_pvs)
|
|
_vgs, _vgs_lookup = self._parse_vgs(_raw_vgs)
|
|
_lvs, _lvs_in_vgs, _lvs_hidden, _lvs_lookup = self._parse_lvs(_raw_lvs)
|
|
|
|
# Set all
|
|
self.pvs = _pvs
|
|
self.pv_path_to_uuid = _pvs_lookup
|
|
self.vg_name_to_uuid = _vgs_lookup
|
|
self.lv_full_name_to_uuid = _lvs_lookup
|
|
|
|
self.vgs = _vgs
|
|
self.lvs = _lvs
|
|
self.lvs_in_vgs = _lvs_in_vgs
|
|
self.pvs_in_vgs = _pvs_in_vgs
|
|
self.lvs_hidden = _lvs_hidden
|
|
|
|
# Create lookup table for which LV and segments are on each PV
|
|
self.pv_lvs, self.lv_pvs = self._parse_pv_in_lvs()
|
|
|
|
if log:
|
|
log_debug("lvmdb - refresh exit")
|
|
|
|
def fetch_pvs(self, pv_name):
|
|
if not pv_name:
|
|
return self.pvs.values()
|
|
else:
|
|
rc = []
|
|
for s in pv_name:
|
|
# Ths user could be using a symlink instead of the actual
|
|
# block device, make sure we are using actual block device file
|
|
# if the pv name isn't in the lookup
|
|
if s not in self.pv_path_to_uuid:
|
|
s = os.path.realpath(s)
|
|
rc.append(self.pvs[self.pv_path_to_uuid[s]])
|
|
return rc
|
|
|
|
def fetch_vgs(self, vg_name):
|
|
if not vg_name:
|
|
return self.vgs.values()
|
|
else:
|
|
rc = []
|
|
for s in vg_name:
|
|
rc.append(self.vgs[self.vg_name_to_uuid[s]])
|
|
return rc
|
|
|
|
def fetch_lvs(self, lv_names):
|
|
try:
|
|
if not lv_names:
|
|
return self.lvs.values()
|
|
else:
|
|
rc = []
|
|
for s in lv_names:
|
|
rc.append(self.lvs[self.lv_full_name_to_uuid[s]])
|
|
return rc
|
|
except KeyError as ke:
|
|
log_error("Key %s not found!" % (str(lv_names)))
|
|
log_error("lv name to uuid lookup")
|
|
for keys in sorted(self.lv_full_name_to_uuid.keys()):
|
|
log_error("%s" % (keys))
|
|
log_error("lvs entries by uuid")
|
|
for keys in sorted(self.lvs.keys()):
|
|
log_error("%s" % (keys))
|
|
raise ke
|
|
|
|
def pv_pe_segments(self, pv_uuid):
|
|
pv = self.pvs[pv_uuid]
|
|
return list(zip(pv['pvseg_start'], pv['pvseg_size']))
|
|
|
|
def pv_contained_lv(self, pv_device):
|
|
rc = []
|
|
if pv_device in self.pv_lvs:
|
|
rc = self.pv_lvs[pv_device]
|
|
return rc
|
|
|
|
def lv_contained_pv(self, lv_uuid):
|
|
rc = []
|
|
if lv_uuid in self.lv_pvs:
|
|
rc = self.lv_pvs[lv_uuid]
|
|
return rc
|
|
|
|
def lvs_in_vg(self, vg_uuid):
|
|
# Return an array of
|
|
# (lv_name, (lv_attr, lv_layout, lv_role), lv_uuid)
|
|
rc = []
|
|
if vg_uuid in self.lvs_in_vgs:
|
|
rc = self.lvs_in_vgs[vg_uuid]
|
|
return rc
|
|
|
|
def pvs_in_vg(self, vg_uuid):
|
|
# Returns an array of (pv_name, pv_uuid)
|
|
rc = []
|
|
if vg_uuid in self.pvs_in_vgs:
|
|
rc = self.pvs_in_vgs[vg_uuid]
|
|
return rc
|
|
|
|
def hidden_lvs(self, lv_uuid):
|
|
# For a specified LV, return a list of hidden lv_uuid, lv_name
|
|
# for it
|
|
rc = []
|
|
if lv_uuid in self.lvs_hidden:
|
|
rc = self.lvs_hidden[lv_uuid]
|
|
return rc
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pp = prettyprint.PrettyPrinter(indent=4)
|
|
|
|
use_json = False
|
|
|
|
if len(sys.argv) != 1:
|
|
print(len(sys.argv))
|
|
use_json = True
|
|
|
|
ds = DataStore(use_json)
|
|
ds.refresh()
|
|
|
|
print("PVS")
|
|
for v in ds.pvs.values():
|
|
pp.pprint(v)
|
|
|
|
print("VGS")
|
|
for v in ds.vgs.values():
|
|
pp.pprint(v)
|
|
|
|
print("LVS")
|
|
for v in ds.lvs.values():
|
|
pp.pprint(v)
|
|
|
|
print("LVS in VG")
|
|
for k, v in ds.lvs_in_vgs.items():
|
|
print("VG uuid = %s" % (k))
|
|
pp.pprint(v)
|
|
|
|
print("pv_in_lvs")
|
|
for k, v in ds.pv_lvs.items():
|
|
print("PV %s contains LVS:" % (k))
|
|
pp.pprint(v)
|
|
|
|
for k, v in ds.lv_pvs.items():
|
|
print("LV device = %s" % (k))
|
|
pp.pprint(v)
|