blob: 967236162f2e3999cfcd5ddf2b953f1b3822f0d4 [file] [log] [blame]
Manojkiran Edab8cc3252021-05-24 11:29:36 +05301#!/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()