blob: 5ec8d88617fc5c4f5fbb140e4fea4560608babc3 [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
Brad Bishop91523132021-10-14 19:25:37 -0400129def get_pdrs(client):
130 """ Using pldmtool over SSH, generate (record handle, PDR) tuples for each
131 record in the PDR repository.
132
133 Parameters:
134 client: paramiko ssh client object
135
136 """
137
138 command_fmt = 'pldmtool platform getpdr -d {}'
139 record_handle = 0
140 while True:
141 output = client.exec_command(command_fmt.format(str(record_handle)))
142 _, stdout, stderr = output
143 pdr = json.load(stdout)
144 yield record_handle, pdr
145 record_handle = pdr["nextRecordHandle"]
146 if record_handle == 0:
147 break
148
149
Manojkiran Edab8cc3252021-05-24 11:29:36 +0530150def fetch_pdrs_from_bmc(client):
151
152 """ This is the core function that would use the existing ssh connection
153 object to connect to BMC and fire the getPDR pldmtool command
154 and it then agreegates the data received from all the calls into
155 the respective dictionaries based on the PDR Type.
156
157 Parameters:
158 client: paramiko ssh client object
159
160 """
161
162 entity_association_pdr = {}
163 state_sensor_pdr = {}
164 state_effecter_pdr = {}
165 state_effecter_pdr = {}
166 numeric_pdr = {}
167 fru_record_set_pdr = {}
168 tl_pdr = {}
Brad Bishop91523132021-10-14 19:25:37 -0400169 for handle_number, my_dic in get_pdrs(client):
Manojkiran Edab8cc3252021-05-24 11:29:36 +0530170 sys.stdout.write("Fetching PDR's from BMC : %8d\r" % (handle_number))
171 sys.stdout.flush()
172 if my_dic["PDRType"] == "Entity Association PDR":
173 entity_association_pdr[handle_number] = my_dic
174 if my_dic["PDRType"] == "State Sensor PDR":
175 state_sensor_pdr[handle_number] = my_dic
176 if my_dic["PDRType"] == "State Effecter PDR":
177 state_effecter_pdr[handle_number] = my_dic
178 if my_dic["PDRType"] == "FRU Record Set PDR":
179 fru_record_set_pdr[handle_number] = my_dic
180 if my_dic["PDRType"] == "Terminus Locator PDR":
181 tl_pdr[handle_number] = my_dic
182 if my_dic["PDRType"] == "Numeric Effecter PDR":
183 numeric_pdr[handle_number] = my_dic
Manojkiran Edab8cc3252021-05-24 11:29:36 +0530184 client.close()
185
186 total_pdrs = len(entity_association_pdr.keys()) + len(tl_pdr.keys()) + \
187 len(state_effecter_pdr.keys()) + len(numeric_pdr.keys()) + \
188 len(state_sensor_pdr.keys()) + len(fru_record_set_pdr.keys())
189 print("\nSuccessfully fetched " + str(total_pdrs) + " PDR\'s")
190 print("Number of FRU Record PDR's : ", len(fru_record_set_pdr.keys()))
191 print("Number of TerminusLocator PDR's : ", len(tl_pdr.keys()))
192 print("Number of State Sensor PDR's : ", len(state_sensor_pdr.keys()))
193 print("Number of State Effecter PDR's : ", len(state_effecter_pdr.keys()))
194 print("Number of Numeric Effecter PDR's : ", len(numeric_pdr.keys()))
195 print("Number of Entity Association PDR's : ",
196 len(entity_association_pdr.keys()))
197 return (entity_association_pdr, state_sensor_pdr,
198 state_effecter_pdr, len(fru_record_set_pdr.keys()))
199
200
201def main():
202
203 """ Create a summary table capturing the information of all the PDR's
204 from the BMC & also create a diagram that captures the entity
205 association hierarchy."""
206
207 parser = argparse.ArgumentParser(prog='pldm_visualise_pdrs.py')
208 parser.add_argument('--bmc', type=str, required=True,
209 help="BMC IPAddress/BMC Hostname")
Brad Bishope0c55c82021-10-12 17:14:07 -0400210 parser.add_argument('--user', type=str, help="BMC username")
Manojkiran Edab8cc3252021-05-24 11:29:36 +0530211 parser.add_argument('--password', type=str, required=True,
212 help="BMC Password")
213 parser.add_argument('--port', type=int, help="BMC SSH port",
214 default=22)
215 args = parser.parse_args()
Brad Bishop70ac60b2021-10-04 18:48:43 -0400216 client = connect_to_bmc(args.bmc, args.user, args.password, args.port)
217 association_pdr, state_sensor_pdr, state_effecter_pdr, counter = \
218 fetch_pdrs_from_bmc(client)
219 draw_entity_associations(association_pdr, counter)
220 prepare_summary_report(state_sensor_pdr, state_effecter_pdr)
Manojkiran Edab8cc3252021-05-24 11:29:36 +0530221
222
223if __name__ == "__main__":
224 main()