1b8cc3257SManojkiran Eda#!/usr/bin/env python3 2b8cc3257SManojkiran Eda 3b8cc3257SManojkiran Eda"""Tool to visualize PLDM PDR's""" 4b8cc3257SManojkiran Eda 5b8cc3257SManojkiran Edaimport argparse 6b8cc3257SManojkiran Edaimport json 7b8cc3257SManojkiran Edaimport hashlib 8b8cc3257SManojkiran Edaimport sys 9b8cc3257SManojkiran Edafrom datetime import datetime 10b8cc3257SManojkiran Edaimport paramiko 11b8cc3257SManojkiran Edafrom graphviz import Digraph 12b8cc3257SManojkiran Edafrom tabulate import tabulate 139a8192d9SBrad Bishopimport os 14b8cc3257SManojkiran Eda 15b8cc3257SManojkiran Eda 16*9e0265b5SBrad Bishopdef connect_to_bmc(hostname, uname, passwd, port, **kw): 17b8cc3257SManojkiran Eda 18b8cc3257SManojkiran Eda """ This function is responsible to connect to the BMC via 19b8cc3257SManojkiran Eda ssh and returns a client object. 20b8cc3257SManojkiran Eda 21b8cc3257SManojkiran Eda Parameters: 22b8cc3257SManojkiran Eda hostname: hostname/IP address of BMC 23b8cc3257SManojkiran Eda uname: ssh username of BMC 24b8cc3257SManojkiran Eda passwd: ssh password of BMC 25b8cc3257SManojkiran Eda port: ssh port of BMC 26b8cc3257SManojkiran Eda 27b8cc3257SManojkiran Eda """ 28b8cc3257SManojkiran Eda 29b8cc3257SManojkiran Eda client = paramiko.SSHClient() 30b8cc3257SManojkiran Eda client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 31*9e0265b5SBrad Bishop client.connect(hostname, username=uname, password=passwd, port=port, **kw) 32b8cc3257SManojkiran Eda return client 33b8cc3257SManojkiran Eda 34b8cc3257SManojkiran Eda 35b8cc3257SManojkiran Edadef prepare_summary_report(state_sensor_pdr, state_effecter_pdr): 36b8cc3257SManojkiran Eda 37b8cc3257SManojkiran Eda """ This function is responsible to parse the state sensor pdr 38b8cc3257SManojkiran Eda and the state effecter pdr dictionaries and creating the 39b8cc3257SManojkiran Eda summary table. 40b8cc3257SManojkiran Eda 41b8cc3257SManojkiran Eda Parameters: 42b8cc3257SManojkiran Eda state_sensor_pdr: list of state sensor pdrs 43b8cc3257SManojkiran Eda state_effecter_pdr: list of state effecter pdrs 44b8cc3257SManojkiran Eda 45b8cc3257SManojkiran Eda """ 46b8cc3257SManojkiran Eda 47b8cc3257SManojkiran Eda summary_table = [] 48b8cc3257SManojkiran Eda headers = ["sensor_id", "entity_type", "state_set", "states"] 49b8cc3257SManojkiran Eda summary_table.append(headers) 50b8cc3257SManojkiran Eda for value in state_sensor_pdr.values(): 51b8cc3257SManojkiran Eda summary_record = [] 52b8cc3257SManojkiran Eda sensor_possible_states = '' 53b8cc3257SManojkiran Eda for sensor_state in value["possibleStates[0]"]: 54b8cc3257SManojkiran Eda sensor_possible_states += sensor_state+"\n" 55b8cc3257SManojkiran Eda summary_record.extend([value["sensorID"], value["entityType"], 56b8cc3257SManojkiran Eda value["stateSetID[0]"], 57b8cc3257SManojkiran Eda sensor_possible_states]) 58b8cc3257SManojkiran Eda summary_table.append(summary_record) 59b8cc3257SManojkiran Eda print("Created at : ", datetime.now().strftime("%Y-%m-%d %H:%M:%S")) 60b8cc3257SManojkiran Eda print(tabulate(summary_table, tablefmt="fancy_grid", headers="firstrow")) 61b8cc3257SManojkiran Eda 62b8cc3257SManojkiran Eda summary_table = [] 63b8cc3257SManojkiran Eda headers = ["effecter_id", "entity_type", "state_set", "states"] 64b8cc3257SManojkiran Eda summary_table.append(headers) 65b8cc3257SManojkiran Eda for value in state_effecter_pdr.values(): 66b8cc3257SManojkiran Eda summary_record = [] 67b8cc3257SManojkiran Eda effecter_possible_states = '' 68b8cc3257SManojkiran Eda for state in value["possibleStates[0]"]: 69b8cc3257SManojkiran Eda effecter_possible_states += state+"\n" 70b8cc3257SManojkiran Eda summary_record.extend([value["effecterID"], value["entityType"], 71b8cc3257SManojkiran Eda value["stateSetID[0]"], 72b8cc3257SManojkiran Eda effecter_possible_states]) 73b8cc3257SManojkiran Eda summary_table.append(summary_record) 74b8cc3257SManojkiran Eda print(tabulate(summary_table, tablefmt="fancy_grid", headers="firstrow")) 75b8cc3257SManojkiran Eda 76b8cc3257SManojkiran Eda 77b8cc3257SManojkiran Edadef draw_entity_associations(pdr, counter): 78b8cc3257SManojkiran Eda 79b8cc3257SManojkiran Eda """ This function is responsible to create a picture that captures 80b8cc3257SManojkiran Eda the entity association hierarchy based on the entity association 81b8cc3257SManojkiran Eda PDR's received from the BMC. 82b8cc3257SManojkiran Eda 83b8cc3257SManojkiran Eda Parameters: 84b8cc3257SManojkiran Eda pdr: list of entity association PDR's 85b8cc3257SManojkiran Eda counter: variable to capture the count of PDR's to unflatten 86b8cc3257SManojkiran Eda the tree 87b8cc3257SManojkiran Eda 88b8cc3257SManojkiran Eda """ 89b8cc3257SManojkiran Eda 90b8cc3257SManojkiran Eda dot = Digraph('entity_hierarchy', node_attr={'color': 'lightblue1', 91b8cc3257SManojkiran Eda 'style': 'filled'}) 92b8cc3257SManojkiran Eda dot.attr(label=r'\n\nEntity Relation Diagram < ' + 93b8cc3257SManojkiran Eda str(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))+'>\n') 94b8cc3257SManojkiran Eda dot.attr(fontsize='20') 95b8cc3257SManojkiran Eda edge_list = [] 96b8cc3257SManojkiran Eda for value in pdr.values(): 97b8cc3257SManojkiran Eda parentnode = str(value["containerEntityType"]) + \ 98b8cc3257SManojkiran Eda str(value["containerEntityInstanceNumber"]) 99b8cc3257SManojkiran Eda dot.node(hashlib.md5((parentnode + 100b8cc3257SManojkiran Eda str(value["containerEntityContainerID"])) 101b8cc3257SManojkiran Eda .encode()).hexdigest(), parentnode) 102b8cc3257SManojkiran Eda 103b8cc3257SManojkiran Eda for i in range(1, value["containedEntityCount"]+1): 104b8cc3257SManojkiran Eda childnode = str(value[f"containedEntityType[{i}]"]) + \ 105b8cc3257SManojkiran Eda str(value[f"containedEntityInstanceNumber[{i}]"]) 106b8cc3257SManojkiran Eda cid = str(value[f"containedEntityContainerID[{i}]"]) 107b8cc3257SManojkiran Eda dot.node(hashlib.md5((childnode + cid) 108b8cc3257SManojkiran Eda .encode()).hexdigest(), childnode) 109b8cc3257SManojkiran Eda 110b8cc3257SManojkiran Eda if[hashlib.md5((parentnode + 111b8cc3257SManojkiran Eda str(value["containerEntityContainerID"])) 112b8cc3257SManojkiran Eda .encode()).hexdigest(), 113b8cc3257SManojkiran Eda hashlib.md5((childnode + cid) 114b8cc3257SManojkiran Eda .encode()).hexdigest()] not in edge_list: 115b8cc3257SManojkiran Eda edge_list.append([hashlib.md5((parentnode + 116b8cc3257SManojkiran Eda str(value["containerEntityContainerID"])) 117b8cc3257SManojkiran Eda .encode()).hexdigest(), 118b8cc3257SManojkiran Eda hashlib.md5((childnode + cid) 119b8cc3257SManojkiran Eda .encode()).hexdigest()]) 120b8cc3257SManojkiran Eda dot.edge(hashlib.md5((parentnode + 121b8cc3257SManojkiran Eda str(value["containerEntityContainerID"])) 122b8cc3257SManojkiran Eda .encode()).hexdigest(), 123b8cc3257SManojkiran Eda hashlib.md5((childnode + cid).encode()).hexdigest()) 124b8cc3257SManojkiran Eda unflattentree = dot.unflatten(stagger=(round(counter/3))) 125b8cc3257SManojkiran Eda unflattentree.render(filename='entity_association_' + 126b8cc3257SManojkiran Eda str(datetime.now().strftime("%Y-%m-%d_%H-%M-%S")), 127b8cc3257SManojkiran Eda view=False, cleanup=True, format='pdf') 128b8cc3257SManojkiran Eda 129b8cc3257SManojkiran Eda 130260f75a6SBrad Bishopclass PLDMToolError(Exception): 131260f75a6SBrad Bishop """ Exception class intended to be used to hold pldmtool invocation failure 132260f75a6SBrad Bishop information such as exit status and stderr. 133260f75a6SBrad Bishop 134260f75a6SBrad Bishop """ 135260f75a6SBrad Bishop 136260f75a6SBrad Bishop def __init__(self, status, stderr): 137260f75a6SBrad Bishop msg = "pldmtool failed with exit status {}.\n".format(status) 138260f75a6SBrad Bishop msg += "stderr: \n\n{}".format(stderr) 139260f75a6SBrad Bishop super(PLDMToolError, self).__init__(msg) 14098cb3efbSBrad Bishop self.status = status 14198cb3efbSBrad Bishop 14298cb3efbSBrad Bishop def get_status(self): 14398cb3efbSBrad Bishop return self.status 144260f75a6SBrad Bishop 145260f75a6SBrad Bishop 146260f75a6SBrad Bishopdef process_pldmtool_output(stdout_channel, stderr_channel): 147260f75a6SBrad Bishop """ Ensure pldmtool runs without error and if it does fail, detect that and 148260f75a6SBrad Bishop show the pldmtool exit status and it's stderr. 149260f75a6SBrad Bishop 15098cb3efbSBrad Bishop A simpler implementation would just wait for the pldmtool exit status 15198cb3efbSBrad Bishop prior to attempting to decode it's stdout. Instead, optimize for the 15298cb3efbSBrad Bishop no error case and allow the json decoder to consume pldmtool stdout as 15398cb3efbSBrad Bishop soon as it is available (in parallel). This results in the following 15498cb3efbSBrad Bishop error scenarios: 15598cb3efbSBrad Bishop - pldmtool fails and the decoder fails 15698cb3efbSBrad Bishop Ignore the decoder fail and throw PLDMToolError. 15798cb3efbSBrad Bishop - pldmtool fails and the decoder doesn't fail 15898cb3efbSBrad Bishop Throw PLDMToolError. 15998cb3efbSBrad Bishop - pldmtool doesn't fail and the decoder does fail 16098cb3efbSBrad Bishop This is a pldmtool bug - re-throw the decoder error. 16198cb3efbSBrad Bishop 162260f75a6SBrad Bishop Parameters: 163260f75a6SBrad Bishop stdout_channel: file-like stdout channel 164260f75a6SBrad Bishop stderr_channel: file-like stderr channel 165260f75a6SBrad Bishop 166260f75a6SBrad Bishop """ 167260f75a6SBrad Bishop 16898cb3efbSBrad Bishop status = 0 16998cb3efbSBrad Bishop try: 17098cb3efbSBrad Bishop data = json.load(stdout_channel) 17198cb3efbSBrad Bishop # it's unlikely, but possible, that pldmtool failed but still wrote a 17298cb3efbSBrad Bishop # valid json document - so check for that. 173260f75a6SBrad Bishop status = stderr_channel.channel.recv_exit_status() 174260f75a6SBrad Bishop if status == 0: 17598cb3efbSBrad Bishop return data 17698cb3efbSBrad Bishop except json.decoder.JSONDecodeError: 17798cb3efbSBrad Bishop # pldmtool wrote an invalid json document. Check to see if it had 17898cb3efbSBrad Bishop # non-zero exit status. 17998cb3efbSBrad Bishop status = stderr_channel.channel.recv_exit_status() 18098cb3efbSBrad Bishop if status == 0: 18198cb3efbSBrad Bishop # pldmtool didn't have non zero exit status, so it wrote an invalid 18298cb3efbSBrad Bishop # json document and the JSONDecodeError is the correct error. 18398cb3efbSBrad Bishop raise 184260f75a6SBrad Bishop 18598cb3efbSBrad Bishop # pldmtool had a non-zero exit status, so throw an error for that, possibly 18698cb3efbSBrad Bishop # discarding a spurious JSONDecodeError exception. 187260f75a6SBrad Bishop raise PLDMToolError(status, "".join(stderr_channel)) 188260f75a6SBrad Bishop 189260f75a6SBrad Bishop 19098cb3efbSBrad Bishopdef get_pdrs_one_at_a_time(client): 19191523137SBrad Bishop """ Using pldmtool over SSH, generate (record handle, PDR) tuples for each 19291523137SBrad Bishop record in the PDR repository. 19391523137SBrad Bishop 19491523137SBrad Bishop Parameters: 19591523137SBrad Bishop client: paramiko ssh client object 19691523137SBrad Bishop 19791523137SBrad Bishop """ 19891523137SBrad Bishop 19991523137SBrad Bishop command_fmt = 'pldmtool platform getpdr -d {}' 20091523137SBrad Bishop record_handle = 0 20191523137SBrad Bishop while True: 20291523137SBrad Bishop output = client.exec_command(command_fmt.format(str(record_handle))) 20391523137SBrad Bishop _, stdout, stderr = output 204260f75a6SBrad Bishop pdr = process_pldmtool_output(stdout, stderr) 20591523137SBrad Bishop yield record_handle, pdr 20691523137SBrad Bishop record_handle = pdr["nextRecordHandle"] 20791523137SBrad Bishop if record_handle == 0: 20891523137SBrad Bishop break 20991523137SBrad Bishop 21091523137SBrad Bishop 21198cb3efbSBrad Bishopdef get_all_pdrs_at_once(client): 21298cb3efbSBrad Bishop """ Using pldmtool over SSH, generate (record handle, PDR) tuples for each 21398cb3efbSBrad Bishop record in the PDR repository. Use pldmtool platform getpdr --all. 21498cb3efbSBrad Bishop 21598cb3efbSBrad Bishop Parameters: 21698cb3efbSBrad Bishop client: paramiko ssh client object 21798cb3efbSBrad Bishop 21898cb3efbSBrad Bishop """ 21998cb3efbSBrad Bishop 22098cb3efbSBrad Bishop _, stdout, stderr = client.exec_command('pldmtool platform getpdr -a') 22198cb3efbSBrad Bishop all_pdrs = process_pldmtool_output(stdout, stderr) 22298cb3efbSBrad Bishop 22398cb3efbSBrad Bishop # Explicitly request record 0 to find out what the real first record is. 22498cb3efbSBrad Bishop _, stdout, stderr = client.exec_command('pldmtool platform getpdr -d 0') 22598cb3efbSBrad Bishop pdr_0 = process_pldmtool_output(stdout, stderr) 22698cb3efbSBrad Bishop record_handle = pdr_0["recordHandle"] 22798cb3efbSBrad Bishop 22898cb3efbSBrad Bishop while True: 22998cb3efbSBrad Bishop for pdr in all_pdrs: 23098cb3efbSBrad Bishop if pdr["recordHandle"] == record_handle: 23198cb3efbSBrad Bishop yield record_handle, pdr 23298cb3efbSBrad Bishop record_handle = pdr["nextRecordHandle"] 23398cb3efbSBrad Bishop if record_handle == 0: 23498cb3efbSBrad Bishop return 23598cb3efbSBrad Bishop raise RuntimeError( 23698cb3efbSBrad Bishop "Dangling reference to record {}".format(record_handle)) 23798cb3efbSBrad Bishop 23898cb3efbSBrad Bishop 23998cb3efbSBrad Bishopdef get_pdrs(client): 24098cb3efbSBrad Bishop """ Using pldmtool over SSH, generate (record handle, PDR) tuples for each 24198cb3efbSBrad Bishop record in the PDR repository. Use pldmtool platform getpdr --all or 24298cb3efbSBrad Bishop fallback on getting them one at a time if pldmtool doesn't support the 24398cb3efbSBrad Bishop --all option. 24498cb3efbSBrad Bishop 24598cb3efbSBrad Bishop Parameters: 24698cb3efbSBrad Bishop client: paramiko ssh client object 24798cb3efbSBrad Bishop 24898cb3efbSBrad Bishop """ 24998cb3efbSBrad Bishop try: 25098cb3efbSBrad Bishop for record_handle, pdr in get_all_pdrs_at_once(client): 25198cb3efbSBrad Bishop yield record_handle, pdr 25298cb3efbSBrad Bishop return 25398cb3efbSBrad Bishop except PLDMToolError as e: 25498cb3efbSBrad Bishop # No support for the -a option 25598cb3efbSBrad Bishop if e.get_status() != 106: 25698cb3efbSBrad Bishop raise 25798cb3efbSBrad Bishop except json.decoder.JSONDecodeError as e: 25898cb3efbSBrad Bishop # Some versions of pldmtool don't print valid json documents with -a 25998cb3efbSBrad Bishop if e.msg != "Extra data": 26098cb3efbSBrad Bishop raise 26198cb3efbSBrad Bishop 26298cb3efbSBrad Bishop for record_handle, pdr in get_pdrs_one_at_a_time(client): 26398cb3efbSBrad Bishop yield record_handle, pdr 26498cb3efbSBrad Bishop 26598cb3efbSBrad Bishop 266b8cc3257SManojkiran Edadef fetch_pdrs_from_bmc(client): 267b8cc3257SManojkiran Eda 268b8cc3257SManojkiran Eda """ This is the core function that would use the existing ssh connection 269b8cc3257SManojkiran Eda object to connect to BMC and fire the getPDR pldmtool command 270b8cc3257SManojkiran Eda and it then agreegates the data received from all the calls into 271b8cc3257SManojkiran Eda the respective dictionaries based on the PDR Type. 272b8cc3257SManojkiran Eda 273b8cc3257SManojkiran Eda Parameters: 274b8cc3257SManojkiran Eda client: paramiko ssh client object 275b8cc3257SManojkiran Eda 276b8cc3257SManojkiran Eda """ 277b8cc3257SManojkiran Eda 278b8cc3257SManojkiran Eda entity_association_pdr = {} 279b8cc3257SManojkiran Eda state_sensor_pdr = {} 280b8cc3257SManojkiran Eda state_effecter_pdr = {} 281b8cc3257SManojkiran Eda state_effecter_pdr = {} 282b8cc3257SManojkiran Eda numeric_pdr = {} 283b8cc3257SManojkiran Eda fru_record_set_pdr = {} 284b8cc3257SManojkiran Eda tl_pdr = {} 28591523137SBrad Bishop for handle_number, my_dic in get_pdrs(client): 286b8cc3257SManojkiran Eda sys.stdout.write("Fetching PDR's from BMC : %8d\r" % (handle_number)) 287b8cc3257SManojkiran Eda sys.stdout.flush() 288b8cc3257SManojkiran Eda if my_dic["PDRType"] == "Entity Association PDR": 289b8cc3257SManojkiran Eda entity_association_pdr[handle_number] = my_dic 290b8cc3257SManojkiran Eda if my_dic["PDRType"] == "State Sensor PDR": 291b8cc3257SManojkiran Eda state_sensor_pdr[handle_number] = my_dic 292b8cc3257SManojkiran Eda if my_dic["PDRType"] == "State Effecter PDR": 293b8cc3257SManojkiran Eda state_effecter_pdr[handle_number] = my_dic 294b8cc3257SManojkiran Eda if my_dic["PDRType"] == "FRU Record Set PDR": 295b8cc3257SManojkiran Eda fru_record_set_pdr[handle_number] = my_dic 296b8cc3257SManojkiran Eda if my_dic["PDRType"] == "Terminus Locator PDR": 297b8cc3257SManojkiran Eda tl_pdr[handle_number] = my_dic 298b8cc3257SManojkiran Eda if my_dic["PDRType"] == "Numeric Effecter PDR": 299b8cc3257SManojkiran Eda numeric_pdr[handle_number] = my_dic 300b8cc3257SManojkiran Eda client.close() 301b8cc3257SManojkiran Eda 302b8cc3257SManojkiran Eda total_pdrs = len(entity_association_pdr.keys()) + len(tl_pdr.keys()) + \ 303b8cc3257SManojkiran Eda len(state_effecter_pdr.keys()) + len(numeric_pdr.keys()) + \ 304b8cc3257SManojkiran Eda len(state_sensor_pdr.keys()) + len(fru_record_set_pdr.keys()) 305b8cc3257SManojkiran Eda print("\nSuccessfully fetched " + str(total_pdrs) + " PDR\'s") 306b8cc3257SManojkiran Eda print("Number of FRU Record PDR's : ", len(fru_record_set_pdr.keys())) 307b8cc3257SManojkiran Eda print("Number of TerminusLocator PDR's : ", len(tl_pdr.keys())) 308b8cc3257SManojkiran Eda print("Number of State Sensor PDR's : ", len(state_sensor_pdr.keys())) 309b8cc3257SManojkiran Eda print("Number of State Effecter PDR's : ", len(state_effecter_pdr.keys())) 310b8cc3257SManojkiran Eda print("Number of Numeric Effecter PDR's : ", len(numeric_pdr.keys())) 311b8cc3257SManojkiran Eda print("Number of Entity Association PDR's : ", 312b8cc3257SManojkiran Eda len(entity_association_pdr.keys())) 313b8cc3257SManojkiran Eda return (entity_association_pdr, state_sensor_pdr, 314b8cc3257SManojkiran Eda state_effecter_pdr, len(fru_record_set_pdr.keys())) 315b8cc3257SManojkiran Eda 316b8cc3257SManojkiran Eda 317b8cc3257SManojkiran Edadef main(): 318b8cc3257SManojkiran Eda 319b8cc3257SManojkiran Eda """ Create a summary table capturing the information of all the PDR's 320b8cc3257SManojkiran Eda from the BMC & also create a diagram that captures the entity 321b8cc3257SManojkiran Eda association hierarchy.""" 322b8cc3257SManojkiran Eda 323b8cc3257SManojkiran Eda parser = argparse.ArgumentParser(prog='pldm_visualise_pdrs.py') 324b8cc3257SManojkiran Eda parser.add_argument('--bmc', type=str, required=True, 325b8cc3257SManojkiran Eda help="BMC IPAddress/BMC Hostname") 326e0c55c8aSBrad Bishop parser.add_argument('--user', type=str, help="BMC username") 327b8cc3257SManojkiran Eda parser.add_argument('--password', type=str, required=True, 328b8cc3257SManojkiran Eda help="BMC Password") 329b8cc3257SManojkiran Eda parser.add_argument('--port', type=int, help="BMC SSH port", 330b8cc3257SManojkiran Eda default=22) 331b8cc3257SManojkiran Eda args = parser.parse_args() 3329a8192d9SBrad Bishop 333*9e0265b5SBrad Bishop extra_cfg = {} 3349a8192d9SBrad Bishop try: 3359a8192d9SBrad Bishop with open(os.path.expanduser("~/.ssh/config")) as f: 3369a8192d9SBrad Bishop ssh_config = paramiko.SSHConfig() 3379a8192d9SBrad Bishop ssh_config.parse(f) 3389a8192d9SBrad Bishop host_config = ssh_config.lookup(args.bmc) 3399a8192d9SBrad Bishop if host_config: 3409a8192d9SBrad Bishop if 'hostname' in host_config: 3419a8192d9SBrad Bishop args.bmc = host_config['hostname'] 3429a8192d9SBrad Bishop if 'user' in host_config and args.user is None: 3439a8192d9SBrad Bishop args.user = host_config['user'] 344*9e0265b5SBrad Bishop if 'proxycommand' in host_config: 345*9e0265b5SBrad Bishop extra_cfg['sock'] = paramiko.ProxyCommand( 346*9e0265b5SBrad Bishop host_config['proxycommand']) 3479a8192d9SBrad Bishop except FileNotFoundError: 3489a8192d9SBrad Bishop pass 3499a8192d9SBrad Bishop 350*9e0265b5SBrad Bishop client = connect_to_bmc( 351*9e0265b5SBrad Bishop args.bmc, args.user, args.password, args.port, **extra_cfg) 352b8cc3257SManojkiran Eda association_pdr, state_sensor_pdr, state_effecter_pdr, counter = \ 353b8cc3257SManojkiran Eda fetch_pdrs_from_bmc(client) 354b8cc3257SManojkiran Eda draw_entity_associations(association_pdr, counter) 355b8cc3257SManojkiran Eda prepare_summary_report(state_sensor_pdr, state_effecter_pdr) 356b8cc3257SManojkiran Eda 357b8cc3257SManojkiran Eda 358b8cc3257SManojkiran Edaif __name__ == "__main__": 359b8cc3257SManojkiran Eda main() 360