import logging
import pprint
import collections
import prettytable
import sys
import argparse
import subprocess
import xml.etree.ElementTree as ET
VERSION = '1.0'
COLORS = 1
def setup_logging():
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s - %(module)s - %(funcName)s -\
%(levelname)s - %(message)s',
datefmt='%m/%d/%Y %I:%M:%S %p')
return logging.getLogger(__name__)
def run_command(cmd):
log.debug("Executing command: %s", cmd)
p = subprocess.Popen(cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
shell=True)
stdout, stderr = p.communicate()
if p.returncode:
message("red", "Error running command: " + cmd, 0)
message("red", stderr, 1)
return stdout
def message(color, msg, exitcode=0):
if color == 'blue' : color = '\033[1;34m'
elif color == 'red' : color = '\033[1;31m'
elif color == 'green' : color = '\033[0;32m'
elif color == 'yellow' : color = '\033[0;33m'
elif color == 'p_color' : color = '\033[01;37;44m'
elif color == 'nocolor' : color = '\033[0m'
else:
print("Error: message function: invalid color:" , color)
sys.exit(1)
nocolor ='\033[0m'
if COLORS:
print(color + msg + nocolor)
else:
print(msg)
if exitcode:
sys.exit(exitcode)
def nested_dict():
return collections.defaultdict(nested_dict)
def get_xml_text(elem, tag):
try:
x = elem.find(tag).text
except:
message("red", "Error: XML tag '" + tag + "' not found", 1)
return x
def get_srp_pools(symmid):
log.debug("Finding VMAX pools:")
pools = list()
xml_string = run_command('symcfg -sid '+symmid+' list -pool -output xml_e')
pooltree = ET.fromstring(xml_string)
for pool in pooltree.findall('Symmetrix/DevicePool/pool_name'):
pools.append(pool.text)
log.debug("Pools available: %s", pools)
return pools
def get_tdevs_sg(symmid, tdevs_dict):
log.debug("Getting storage group for tdev")
for lun in tdevs_dict:
xml_string = run_command('symaccess -sid ' +
symmid +
' list -type storage -v -output xml_e -dev '+
lun)
luntree = ET.fromstring(xml_string)
for elem in luntree.iterfind('Symmetrix/Storage_Group'):
sg = get_xml_text(elem, 'Group_Info/group_name')
status = get_xml_text(elem, 'Group_Info/Status')
if status == 'IsParent':
tdevs_dict[lun]['sg_parent'] = sg
elif status == 'IsChild':
tdevs_dict[lun]['sg_child'] = sg
elif status == 'N/A':
tdevs_dict[lun]['sg_parent'] = sg
tdevs_dict[lun]['sg_child'] = 'N/A'
else:
message("red",
"Error: get_tdevs_sg. Luns is not parent, child or N/A "+
lun +
" " +
sg +
" " +
status,
1)
def find_tdev_pool_alloc(symmid, sgname):
log.debug("symmmid: %s and sgname: %s", symmid, sgname)
pools = get_srp_pools(symmid)
xml_string = run_command('symcfg -sid ' +
symmid +
' list -tdev -sg ' +
sgname +
' -tb -detail -output xml_e')
tdevs_dict = nested_dict()
tdevtree = ET.fromstring(xml_string)
for elem in tdevtree.iterfind('Symmetrix/ThinDevs/Device'):
tdev_name = get_xml_text(elem, 'dev_name')
log.debug("LUN: %s", tdev_name)
tdevs_dict[tdev_name]['total_tracks_gb'] = get_xml_text(elem, 'total_tracks_gb')
tdevs_dict[tdev_name]['alloc_tracks_gb'] = get_xml_text(elem, 'alloc_tracks_gb')
try:
tdevs_dict[tdev_name]['alloc_percent' ] = \
int((float(tdevs_dict[tdev_name]['alloc_tracks_gb']) * 100) / \
float(tdevs_dict[tdev_name]['total_tracks_gb']) \
)
except ZeroDivisionError:
tdevs_dict[tdev_name]['alloc_percent' ] = "0.0"
for pool in elem.iterfind('pool'):
pool_name = get_xml_text(pool, 'pool_name')
if pool_name == 'N/A':
continue
tdevs_dict[tdev_name][pool_name]['alloc_tracks_gb'] = \
get_xml_text(pool, 'alloc_tracks_gb')
tdevs_dict[tdev_name][pool_name]['pool_alloc_percent'] = \
get_xml_text(pool, 'pool_alloc_percent')
for pool_name in pools:
if not tdevs_dict[tdev_name][pool_name]:
tdevs_dict[tdev_name][pool_name]['alloc_tracks_gb'] = '0'
tdevs_dict[tdev_name][pool_name]['pool_alloc_percent'] = '00'
get_tdevs_sg(symmid, tdevs_dict)
if not log.disabled:
log.debug("tdevs_dict:")
pprint.pprint(tdevs_dict)
return (pools, tdevs_dict)
def print_tdevs(pools, tdevs):
header = list()
header.append("TDEV")
header.append("Total GB")
header.append("Alloc GB")
header.append("Alloc Pct")
for pool in sorted(pools):
header.append("Alloc GB (" + pool + ")")
header.append("SG Parent")
header.append("SG Child")
output = prettytable.PrettyTable(header)
output.format = True
for tdev in sorted(tdevs):
row = list()
row.append(tdev)
row.append(tdevs[tdev]['total_tracks_gb'])
row.append(tdevs[tdev]['alloc_tracks_gb'])
row.append(tdevs[tdev]['alloc_percent'])
for pool in sorted(pools):
row.append(tdevs[tdev][pool]['alloc_tracks_gb'] +
" (" +
tdevs[tdev][pool]['pool_alloc_percent'] +
"%)")
row.append(tdevs[tdev]['sg_parent'])
row.append(tdevs[tdev]['sg_child'])
output.add_row(row)
output.align["SG Child"] = "l"
output.sortby = "SG Child"
print(output)
def parse_parameters():
epilog = '''
Example of use:
%s -sid 001 STORAGE_GROUP_1
%s -sid 002 STORAGE_GROUP_2
''' % (sys.argv[0],sys.argv[0])
parser = argparse.ArgumentParser(description='Script to show LUNs space \
utilization per pool',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=epilog)
parser.add_argument('--version',
action='version',
version=VERSION)
parser.add_argument('--verbose', '-v',
action='store_true',
help='verbose flag',
dest='verbose')
parser.add_argument('--nocolor',
action='store_false',
help='no color flag',
dest='color')
parser.add_argument('-sid',
type=str,
required=True,
help='Symmetrix ID')
parser.add_argument('SG_NAME',
help='Storage Group Name to show LUNs space utilization')
if len(sys.argv) < 2:
parser.print_help()
sys.exit(0)
return parser.parse_args()
def main():
global COLORS
global log
log = setup_logging()
args = parse_parameters()
if not args.verbose:
log.disabled = True
log.debug('parameters: %s',args)
if not args.color:
log.debug("Disabling colored output")
COLORS = 0
pools, tdevs_dict = find_tdev_pool_alloc(args.sid, args.SG_NAME.upper())
print_tdevs(pools, tdevs_dict)
if __name__ == '__main__':
main()