mirror of
git://sourceware.org/git/lvm2.git
synced 2025-01-03 05:18:29 +03:00
f43b7bb461
Lvm can at times have duplicate names. When this happens the daemon will internally use vg_name:vg_uuid as the name for lookups, but display just the vg_name externally. If an API user uses the Manager.LookUpByLvmId and queries the vg name they will only get returned one result as the API can only accommodate returning 1. The one returned is the first instance found when sorting the volume groups by UUID. Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1583510
563 lines
14 KiB
Python
563 lines
14 KiB
Python
#!@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
|
|
|
|
from lvmdbusd import cmdhandler
|
|
from lvmdbusd.utils import log_debug, log_error
|
|
|
|
|
|
class DataStore(object):
|
|
def __init__(self, usejson=True):
|
|
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:
|
|
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 _pvs_parse_common(c_pvs, c_pvs_in_vgs, c_lookup):
|
|
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']
|
|
|
|
@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'])
|
|
|
|
DataStore._pvs_parse_common(c_pvs, c_pvs_in_vgs, c_lookup)
|
|
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'
|
|
|
|
DataStore._pvs_parse_common(c_pvs, c_pvs_in_vgs, c_lookup)
|
|
|
|
return c_pvs, c_lookup, c_pvs_in_vgs
|
|
|
|
@staticmethod
|
|
def _parse_vgs(_vgs):
|
|
vgs = sorted(_vgs, key=lambda vk: vk['vg_uuid'])
|
|
|
|
c_vgs = OrderedDict()
|
|
c_lookup = {}
|
|
|
|
for i in vgs:
|
|
vg_name = i['vg_name']
|
|
|
|
# Lvm allows duplicate vg names. When this occurs, each subsequent
|
|
# matching VG name will be called vg_name:vg_uuid. Note: ':' is an
|
|
# invalid character for lvm VG names
|
|
if vg_name in c_lookup:
|
|
vg_name = "%s:%s" % (vg_name, i['vg_uuid'])
|
|
i['vg_name'] = vg_name
|
|
|
|
c_lookup[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_uuid'])
|
|
|
|
c_vgs = OrderedDict()
|
|
c_lookup = {}
|
|
|
|
for i in vgs:
|
|
vg_name = i['vg_name']
|
|
|
|
# Lvm allows duplicate vg names. When this occurs, each subsequent
|
|
# matching VG name will be called vg_name:vg_uuid. Note: ':' is an
|
|
# invalid character for lvm VG names
|
|
if vg_name in c_lookup:
|
|
vg_name = "%s:%s" % (vg_name, i['vg_uuid'])
|
|
i['vg_name'] = vg_name
|
|
|
|
c_lookup[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 pv_missing(self, pv_uuid):
|
|
if pv_uuid in self.pvs:
|
|
if self.pvs[pv_uuid]['pv_missing'] == '':
|
|
return False
|
|
return True
|
|
|
|
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('PV missing is %s' % ds.pv_missing(v['pv_uuid']))
|
|
|
|
print("VGS")
|
|
for v in ds.vgs.values():
|
|
pp.pprint(v)
|
|
|
|
print("VG name to UUID")
|
|
for k, v in ds.vg_name_to_uuid.items():
|
|
print("%s: %s" % (k, 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)
|