1#!/usr/bin/env python3 2 3"""Tool to visualize PLDM PDR's""" 4 5import argparse 6import json 7import hashlib 8import sys 9from datetime import datetime 10import paramiko 11from graphviz import Digraph 12from tabulate import tabulate 13 14 15def connect_to_bmc(hostname, uname, passwd, port): 16 17 """ This function is responsible to connect to the BMC via 18 ssh and returns a client object. 19 20 Parameters: 21 hostname: hostname/IP address of BMC 22 uname: ssh username of BMC 23 passwd: ssh password of BMC 24 port: ssh port of BMC 25 26 """ 27 28 client = paramiko.SSHClient() 29 client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 30 client.connect(hostname, username=uname, password=passwd, port=port) 31 return client 32 33 34def prepare_summary_report(state_sensor_pdr, state_effecter_pdr): 35 36 """ This function is responsible to parse the state sensor pdr 37 and the state effecter pdr dictionaries and creating the 38 summary table. 39 40 Parameters: 41 state_sensor_pdr: list of state sensor pdrs 42 state_effecter_pdr: list of state effecter pdrs 43 44 """ 45 46 summary_table = [] 47 headers = ["sensor_id", "entity_type", "state_set", "states"] 48 summary_table.append(headers) 49 for value in state_sensor_pdr.values(): 50 summary_record = [] 51 sensor_possible_states = '' 52 for sensor_state in value["possibleStates[0]"]: 53 sensor_possible_states += sensor_state+"\n" 54 summary_record.extend([value["sensorID"], value["entityType"], 55 value["stateSetID[0]"], 56 sensor_possible_states]) 57 summary_table.append(summary_record) 58 print("Created at : ", datetime.now().strftime("%Y-%m-%d %H:%M:%S")) 59 print(tabulate(summary_table, tablefmt="fancy_grid", headers="firstrow")) 60 61 summary_table = [] 62 headers = ["effecter_id", "entity_type", "state_set", "states"] 63 summary_table.append(headers) 64 for value in state_effecter_pdr.values(): 65 summary_record = [] 66 effecter_possible_states = '' 67 for state in value["possibleStates[0]"]: 68 effecter_possible_states += state+"\n" 69 summary_record.extend([value["effecterID"], value["entityType"], 70 value["stateSetID[0]"], 71 effecter_possible_states]) 72 summary_table.append(summary_record) 73 print(tabulate(summary_table, tablefmt="fancy_grid", headers="firstrow")) 74 75 76def draw_entity_associations(pdr, counter): 77 78 """ This function is responsible to create a picture that captures 79 the entity association hierarchy based on the entity association 80 PDR's received from the BMC. 81 82 Parameters: 83 pdr: list of entity association PDR's 84 counter: variable to capture the count of PDR's to unflatten 85 the tree 86 87 """ 88 89 dot = Digraph('entity_hierarchy', node_attr={'color': 'lightblue1', 90 'style': 'filled'}) 91 dot.attr(label=r'\n\nEntity Relation Diagram < ' + 92 str(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))+'>\n') 93 dot.attr(fontsize='20') 94 edge_list = [] 95 for value in pdr.values(): 96 parentnode = str(value["containerEntityType"]) + \ 97 str(value["containerEntityInstanceNumber"]) 98 dot.node(hashlib.md5((parentnode + 99 str(value["containerEntityContainerID"])) 100 .encode()).hexdigest(), parentnode) 101 102 for i in range(1, value["containedEntityCount"]+1): 103 childnode = str(value[f"containedEntityType[{i}]"]) + \ 104 str(value[f"containedEntityInstanceNumber[{i}]"]) 105 cid = str(value[f"containedEntityContainerID[{i}]"]) 106 dot.node(hashlib.md5((childnode + cid) 107 .encode()).hexdigest(), childnode) 108 109 if[hashlib.md5((parentnode + 110 str(value["containerEntityContainerID"])) 111 .encode()).hexdigest(), 112 hashlib.md5((childnode + cid) 113 .encode()).hexdigest()] not in edge_list: 114 edge_list.append([hashlib.md5((parentnode + 115 str(value["containerEntityContainerID"])) 116 .encode()).hexdigest(), 117 hashlib.md5((childnode + cid) 118 .encode()).hexdigest()]) 119 dot.edge(hashlib.md5((parentnode + 120 str(value["containerEntityContainerID"])) 121 .encode()).hexdigest(), 122 hashlib.md5((childnode + cid).encode()).hexdigest()) 123 unflattentree = dot.unflatten(stagger=(round(counter/3))) 124 unflattentree.render(filename='entity_association_' + 125 str(datetime.now().strftime("%Y-%m-%d_%H-%M-%S")), 126 view=False, cleanup=True, format='pdf') 127 128 129def fetch_pdrs_from_bmc(client): 130 131 """ This is the core function that would use the existing ssh connection 132 object to connect to BMC and fire the getPDR pldmtool command 133 and it then agreegates the data received from all the calls into 134 the respective dictionaries based on the PDR Type. 135 136 Parameters: 137 client: paramiko ssh client object 138 139 """ 140 141 entity_association_pdr = {} 142 state_sensor_pdr = {} 143 state_effecter_pdr = {} 144 state_effecter_pdr = {} 145 numeric_pdr = {} 146 fru_record_set_pdr = {} 147 tl_pdr = {} 148 handle_number = 0 149 while True: 150 my_str = '' 151 command = 'pldmtool platform getpdr -d ' + str(handle_number) 152 output = client.exec_command(command) 153 for line in output[1]: 154 my_str += line.strip('\n') 155 my_dic = json.loads(my_str) 156 sys.stdout.write("Fetching PDR's from BMC : %8d\r" % (handle_number)) 157 sys.stdout.flush() 158 if my_dic["PDRType"] == "Entity Association PDR": 159 entity_association_pdr[handle_number] = my_dic 160 if my_dic["PDRType"] == "State Sensor PDR": 161 state_sensor_pdr[handle_number] = my_dic 162 if my_dic["PDRType"] == "State Effecter PDR": 163 state_effecter_pdr[handle_number] = my_dic 164 if my_dic["PDRType"] == "FRU Record Set PDR": 165 fru_record_set_pdr[handle_number] = my_dic 166 if my_dic["PDRType"] == "Terminus Locator PDR": 167 tl_pdr[handle_number] = my_dic 168 if my_dic["PDRType"] == "Numeric Effecter PDR": 169 numeric_pdr[handle_number] = my_dic 170 if not my_dic["nextRecordHandle"] == 0: 171 handle_number = my_dic["nextRecordHandle"] 172 else: 173 break 174 client.close() 175 176 total_pdrs = len(entity_association_pdr.keys()) + len(tl_pdr.keys()) + \ 177 len(state_effecter_pdr.keys()) + len(numeric_pdr.keys()) + \ 178 len(state_sensor_pdr.keys()) + len(fru_record_set_pdr.keys()) 179 print("\nSuccessfully fetched " + str(total_pdrs) + " PDR\'s") 180 print("Number of FRU Record PDR's : ", len(fru_record_set_pdr.keys())) 181 print("Number of TerminusLocator PDR's : ", len(tl_pdr.keys())) 182 print("Number of State Sensor PDR's : ", len(state_sensor_pdr.keys())) 183 print("Number of State Effecter PDR's : ", len(state_effecter_pdr.keys())) 184 print("Number of Numeric Effecter PDR's : ", len(numeric_pdr.keys())) 185 print("Number of Entity Association PDR's : ", 186 len(entity_association_pdr.keys())) 187 return (entity_association_pdr, state_sensor_pdr, 188 state_effecter_pdr, len(fru_record_set_pdr.keys())) 189 190 191def main(): 192 193 """ Create a summary table capturing the information of all the PDR's 194 from the BMC & also create a diagram that captures the entity 195 association hierarchy.""" 196 197 parser = argparse.ArgumentParser(prog='pldm_visualise_pdrs.py') 198 parser.add_argument('--bmc', type=str, required=True, 199 help="BMC IPAddress/BMC Hostname") 200 parser.add_argument('--user', type=str, required=True, 201 help="BMC username") 202 parser.add_argument('--password', type=str, required=True, 203 help="BMC Password") 204 parser.add_argument('--port', type=int, help="BMC SSH port", 205 default=22) 206 args = parser.parse_args() 207 if args.bmc and args.password and args.user: 208 client = connect_to_bmc(args.bmc, args.user, args.password, args.port) 209 association_pdr, state_sensor_pdr, state_effecter_pdr, counter = \ 210 fetch_pdrs_from_bmc(client) 211 draw_entity_associations(association_pdr, counter) 212 prepare_summary_report(state_sensor_pdr, state_effecter_pdr) 213 214 215if __name__ == "__main__": 216 main() 217