integ/tools/storage-topology/storage-topology/storage_topology/exec/storage_topology.py

457 lines
16 KiB
Python

#
# Copyright (c) 2016 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
"""
Usage: storage-topology [options]
options:
-d, --diskview view disk data of all server nodes.
-v, --vgview view VG data in all nodes.
-a, --all view both disk and vg view. ( selected by default if any
of the view options is NOT selected)
-e, --extended include additional parameters like uuids in selected
view(s)
-h, --help display this usage
Tool to show a consolidated view of system physical disks and logical volume
groups data.
"""
import os
import sys
import argparse
import datetime
import logging
import textwrap
import keyring
import subprocess
import math
from prettytable import PrettyTable
from cgtsclient.common import utils
from cgtsclient import client as cgts_client
from cgtsclient import exc
"""----------------------------------------------------------------------------
Global definitions
----------------------------------------------------------------------------"""
# logger
logger = logging.getLogger(__name__)
# show options
show = {}
def configure_debuggubg(debug):
if debug:
logging.basicConfig(
format="%(levelname)s (%(module)s:%(lineno)d) %(message)s",
level=logging.DEBUG)
else:
logging.basicConfig(
format="%(levelname)s %(message)s",
level=logging.WARNING)
def parse_arguments(show):
"""
Parse command line arguments.
"""
parser = argparse.ArgumentParser(
prog=os.path.basename(sys.argv[0]),
formatter_class=argparse.RawDescriptionHelpFormatter,
description=textwrap.dedent('''\
Tool to summarize server storage resouce usage.
'''),
epilog=textwrap.dedent('''\
Tables and Field descriptions:
------------------------------
SERVER Physical DISK view:
Host - server host name
Device Node - device node name
Device Type - device node type ( extended view only)
UUID - device node uuid ( extended view only)
Size in GB - disk size in giga bytes
PV Name - name of physical volume
PV State - the physical volume state
PV UUID - the physical volume uuid
VG (name:state:uuid)
name - VG name
state - VG state
uuid - VG uuid (extended view only)
SERVER VOLUME GROUP view:
Host - server host name
Name - volume group name
UUID - volume group uuid
State - VG state
Size - VG size in GB
Current LVs - current Number of logical volumes (lv)
Current PVs - current Number of physical volumes (pv)
PV List (name:state:uuid) - Comma separated list of PVs
name - physical volume name
state - physical volume state
uuid - physical volume uuid (extended view only)
Parameters - list of VG parameters
'''),
)
# Global arguments
parser.add_argument('-d', '--diskview',
default=False, action='store_true',
help="view all physical disks across all nodes"
" including mapped physical volumes and"
"logical volume groups")
parser.add_argument('-v', '--vgview',
default=False, action='store_true',
help="view information pertaining to VGs in all nodes")
parser.add_argument('-a', '--all',
default=False, action='store_true',
help="view both disk and vg views")
parser.add_argument('--debug',
default=bool(utils.env('SYSTEMCLIENT_DEBUG')),
action='store_true',
help=argparse.SUPPRESS)
parser.add_argument('-e', '--extended',
default=False, action="store_true",
help="Print additional disk or vg information")
# Parse arguments
args = parser.parse_args()
show['diskview'] = args.diskview
show['vgview'] = args.vgview
show['all'] = args.all
show['extended'] = args.extended
show['debug'] = args.debug
# Configure logging to appropriate level
configure_debuggubg(show['debug'])
def get_system_creds():
""" Return keystone credentials by sourcing /etc/nova/openrc. """
d = {}
proc = subprocess.Popen(['bash', '-c',
'source /etc/nova/openrc && env'],
stdout=subprocess.PIPE)
for line in proc.stdout:
key, _, value = line.partition("=")
if key == 'OS_USERNAME':
d['os_username'] = value.strip()
elif key == 'OS_PASSWORD':
d['os_password'] = value.strip()
elif key == 'OS_TENANT_NAME':
d['os_tenant_name'] = value.strip()
elif key == 'OS_AUTH_URL':
d['os_auth_url'] = value.strip()
elif key == 'OS_REGION_NAME':
d['os_region_name'] = value.strip()
proc.communicate()
return d
def convert_to_readable_size(size, orig_unit='B'):
""" Converts size to human readable unit """
units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
# convert original size to bytes
try:
i = units.index(orig_unit)
except:
raise RuntimeError('Invalid size unit passed: %s' % (orig_unit))
size = size * pow(1024, i)
unitIndex = int(math.floor(math.log(size, 1024)))
# set size unit to PB max if size is greater than 1024 PB
if unitIndex > 5:
unitIndex = 5
sizer = math.pow(1024, unitIndex)
newsize = round(size/sizer, 2)
return "%s %s" % (newsize, units[unitIndex])
def print_disk_view(rows=None, extended=False):
""" Print all summary Disk views using PrettyTable. """
disk_lables_extended = \
['Host', 'Device Node', 'Device Type', 'UUID', 'Size',
'PV Name', 'PV State', 'PV UUID', 'VG (name:state:uuid)', ]
disk_lables_brief = \
['Host', 'Device Node', 'Size', 'PV Name', 'PV State',
'VG (name:state)']
if len(rows) > 0:
print
print("DISKs: (Physical disk view)")
pt = PrettyTable(disk_lables_extended) if extended else \
PrettyTable(disk_lables_brief)
pt.align = 'l'
pt.align['Size'] = 'r'
for r in rows:
if len(r) == len(pt.field_names):
pt.add_row(r)
else:
print("Disk row has incorrect number of values: %s" % r)
print(pt)
def print_vg_view(rows=None, extended=False):
""" Print all summary VG views using PrettyTable. """
vg_labels_extended = \
['Host', 'VG Name', 'UUID', 'VG State', 'VG Size', 'Current LVs',
'Current PVs', 'PV List (name:state:uuid)', 'VG Parameters']
vg_labels_brief = \
['Host', 'VG Name', 'VG State', 'VG Size', 'Current LVs',
'Current PVs', 'PV List (name:state)', 'VG Parameters']
if len(rows) > 0:
print
print("VOLUME GROUPS: (VG view)")
pt = PrettyTable(vg_labels_extended) if extended else \
PrettyTable(vg_labels_brief)
pt.align = 'l'
for C in ['VG Size', 'Current LVs', 'Current PVs']:
pt.align[C] = 'r'
for r in rows:
if len(r) == len(pt.field_names):
pt.add_row(r)
else:
print("VG row has incorrect number of values: %s" % r)
print(pt)
def get_info_and_display(cc, show=None):
""" Get storage information from server nodes.
Display the following information in table format.
- disk data of all server nodes
- VG data of all servers nodes
"""
# get list of server hosts and for each host retrieve
# the disk, lvg. pv list objects.
host_storage_attr = {}
hosts = cc.ihost.list()
hostnames = []
for h in hosts:
hostname = getattr(h, 'hostname', '')
hostnames.append(hostname)
host_disks = cc.idisk.list(h.uuid)
host_pvs = cc.ipv.list(h.uuid)
host_lvgs = cc.ilvg.list(h.uuid)
host_storage_attr[hostname] = {'host_disks': host_disks,
'host_pvs': host_pvs,
'host_lvgs': host_lvgs}
disk_view = []
vg_view = []
pv_fields_ext = ['lvm_pv_name', 'pv_state', 'lvm_pv_uuid']
pv_fields = ['lvm_pv_name', 'pv_state']
disk_fields_ext = ['device_node', 'device_type', 'uuid', 'size_mib']
disk_fields = ['device_node', 'size_mib']
lvg_fields_ext = ['lvm_vg_name', 'uuid', 'vg_state', 'lvm_vg_size',
'lvm_cur_lv', 'lvm_cur_pv']
lvg_fields = ['lvm_vg_name', 'vg_state', 'lvm_vg_size', 'lvm_cur_lv',
'lvm_cur_pv']
# padding empty values in case not pv in disk.
disk_pd_num_ext = 5
disk_pd_num = 3
pv_pd_num_ext = 4
pv_pd_num = 3
for k, v in host_storage_attr.items():
if show['diskview'] or show['all']:
for disk_o in v['host_disks']:
device_node = getattr(disk_o, 'device_node', '')
drow = [k]
if show['extended']:
drow.extend([(getattr(disk_o, f, ''))
for f in disk_fields_ext])
drow[4] = convert_to_readable_size(
getattr(disk_o, 'size_mib', ''), 'MB')
else:
drow.extend([(getattr(disk_o, f, ''))
for f in disk_fields])
drow[2] = convert_to_readable_size(
getattr(disk_o, 'size_mib', ''), 'MB')
if v['host_pvs']:
first = True
for pv_o in v['host_pvs']:
pv_node = getattr(pv_o, 'idisk_device_node', '')
if pv_node == device_node:
if first:
if show['extended']:
drow.extend([(getattr(pv_o, f, ''))
for f in pv_fields_ext])
else:
drow.extend([(getattr(pv_o, f, ''))
for f in pv_fields])
first = False
else:
disk_view.append(drow)
# new row for next pv
# padd for host, device_node, size
if show['extended']:
drow = [''] * disk_pd_num_ext
drow.extend([(getattr(pv_o, f, ''))
for f in pv_fields_ext])
else:
drow = [''] * disk_pd_num
drow.extend([(getattr(pv_o, f, ''))
for f in pv_fields])
for vg_o in v['host_lvgs']:
vg_str = ""
if getattr(pv_o, 'lvm_vg_name', '') == \
getattr(vg_o, 'lvm_vg_name', ''):
if show['extended']:
vg_str += ":".join(
[str(getattr(pv_o, 'lvm_vg_name',
'')),
str(getattr(vg_o, 'vg_state')),
str(getattr(vg_o, 'uuid'))])
else:
vg_str += ":".join(
[str(getattr(pv_o, 'lvm_vg_name',
'')),
str(getattr(vg_o, 'vg_state'))])
drow.append(vg_str)
else:
if show['extended']:
drow.extend([''] * pv_pd_num_ext)
else:
drow.extend([''] * pv_pd_num)
disk_view.append(drow) # add to disk row disk view rows
if show['vgview'] or show['all']:
for vg_o in v['host_lvgs']:
vgrow = [k]
if show['extended']:
vgrow.extend([(getattr(vg_o, f, ''))
for f in lvg_fields_ext])
vgrow[4] = convert_to_readable_size(
getattr(vg_o, 'lvm_vg_size', ''))
else:
vgrow.extend([(getattr(vg_o, f, '')) for f in lvg_fields])
vgrow[3] = convert_to_readable_size(
getattr(vg_o, 'lvm_vg_size', ''))
# list of current pvs for each VG
count = 0
for pv_o in v['host_pvs']:
pv_str = ""
if getattr(vg_o, 'lvm_vg_name', '') == getattr(
pv_o, 'lvm_vg_name', ''):
count += 1
if count > 1:
pv_str += ', '
if show['extended']:
pv_str += ":".join(
[str(getattr(pv_o, f, ''))
for f in pv_fields_ext])
else:
pv_str += ":".join(
[str(getattr(pv_o, f, '')) for f in pv_fields])
vgrow.append(pv_str)
vgrow.append(getattr(vg_o, 'capabilities', ''))
vg_view.append(vgrow)
print_disk_view(rows=disk_view, extended=show['extended'])
print_vg_view(rows=vg_view, extended=show['extended'])
def main():
try:
# Process command line options and arguments, configure logging,
# configure debug and show options
parse_arguments(show)
if not show['diskview'] and not show['vgview']:
# both disk and vg views are printed
show['all'] = True
api_version = utils.env('SYSTEM_API_VERSION', default='1')
# Print selected options, and timestamp
prog = os.path.basename(sys.argv[0])
ts = datetime.datetime.now()
if show['debug']:
print("%s: %s options: view:%s System api version: %s"
% (prog, ts.isoformat(), show, api_version))
cgts_client_creds = get_system_creds()
if not cgts_client_creds['os_username']:
raise exc.CommandError("You must provide a username via "
"env[OS_USERNAME]")
if not cgts_client_creds['os_password']:
# priviledge check (only allow Keyring retrieval if we are root)
if os.geteuid() == 0:
cgts_client_creds['os_password'] = keyring.get_password(
'CGCS', cgts_client_creds['os_username'])
else:
raise exc.CommandError("You must provide a password via "
"env[OS_PASSWORD]")
if not cgts_client_creds['os_tenant_name']:
raise exc.CommandError("You must provide a tenant_id via "
"env[OS_TENANT_NAME]")
if not cgts_client_creds['os_auth_url']:
raise exc.CommandError("You must provide a auth url via "
"env[OS_AUTH_URL]")
if not cgts_client_creds['os_region_name']:
raise exc.CommandError("You must provide a region_name via "
"env[OS_REGION_NAME]")
cc = cgts_client.get_client(api_version, **cgts_client_creds)
# Get all info and display in table format
get_info_and_display(cc, show)
sys.exit(0)
except KeyboardInterrupt as e:
logger.warning('caught: %r, shutting down', e)
sys.exit(0)
except IOError as e:
logger.warning('caught: %r, shutting down', e)
sys.exit(0)
except Exception as e:
logger.error('exception: %r', e, exc_info=1)
sys.exit(-4)