xref: /openbmc/pldm/tools/visualize-pdr/pldm_visualise_pdrs.py (revision c44715dc10d969ae2dd163901d919751bd5f6bd1)
1b8cc3257SManojkiran Eda#!/usr/bin/env python3
2b8cc3257SManojkiran Eda
3b8cc3257SManojkiran Eda"""Tool to visualize PLDM PDR's"""
4b8cc3257SManojkiran Eda
5b8cc3257SManojkiran Edaimport argparse
6b8cc3257SManojkiran Edaimport hashlib
7*c44715dcSPatrick Williamsimport json
89a8192d9SBrad Bishopimport os
9c8906373SBrad Bishopimport shlex
10c8906373SBrad Bishopimport shutil
11c8906373SBrad Bishopimport subprocess
12*c44715dcSPatrick Williamsimport sys
13*c44715dcSPatrick Williamsfrom datetime import datetime
14*c44715dcSPatrick Williams
15*c44715dcSPatrick Williamsimport paramiko
16*c44715dcSPatrick Williamsfrom graphviz import Digraph
17*c44715dcSPatrick Williamsfrom tabulate import tabulate
18b8cc3257SManojkiran Eda
19b8cc3257SManojkiran Eda
20241e0683SBrad Bishopclass Process:
21241e0683SBrad Bishop    """Interface definition for interacting with a process created by an
22241e0683SBrad Bishop    Executor."""
23241e0683SBrad Bishop
24241e0683SBrad Bishop    def __init__(self, stdout, stderr):
25241e0683SBrad Bishop        """Construct a Process object.  Process object clients can read the
26241e0683SBrad Bishop        process stdout and stderr with os.read(), and can wait for the
27241e0683SBrad Bishop        process to exit.
28241e0683SBrad Bishop
29241e0683SBrad Bishop        Parameters:
30241e0683SBrad Bishop            stdout: os.read()able stream representing stdout
31241e0683SBrad Bishop            stderr: os.read()able stream representing stderr
32241e0683SBrad Bishop        """
33241e0683SBrad Bishop
34241e0683SBrad Bishop        self.stdout = stdout
35241e0683SBrad Bishop        self.stderr = stderr
36241e0683SBrad Bishop
37241e0683SBrad Bishop    def wait(self):
38241e0683SBrad Bishop        """Wait for the process to finish, and return its exit status."""
39241e0683SBrad Bishop
40241e0683SBrad Bishop        raise NotImplementedError
41241e0683SBrad Bishop
42241e0683SBrad Bishop
437dc416f6SBrad Bishopclass Executor:
447dc416f6SBrad Bishop    """Interface definition for interacting with executors.  An executor is an
457dc416f6SBrad Bishop    object that can run a program."""
46b8cc3257SManojkiran Eda
477dc416f6SBrad Bishop    def exec_command(self, cmd):
487dc416f6SBrad Bishop        raise NotImplementedError
497dc416f6SBrad Bishop
507dc416f6SBrad Bishop    def close(self):
517dc416f6SBrad Bishop        pass
527dc416f6SBrad Bishop
537dc416f6SBrad Bishop
54241e0683SBrad Bishopclass ParamikoProcess(Process):
55241e0683SBrad Bishop    """Concrete implementation of the Process interface that adapts Paramiko
56241e0683SBrad Bishop    interfaces to the Process interface requirements."""
57241e0683SBrad Bishop
58241e0683SBrad Bishop    def __init__(self, stdout, stderr):
59241e0683SBrad Bishop        super(ParamikoProcess, self).__init__(stdout, stderr)
60241e0683SBrad Bishop
61241e0683SBrad Bishop    def wait(self):
62241e0683SBrad Bishop        return self.stderr.channel.recv_exit_status()
63241e0683SBrad Bishop
64241e0683SBrad Bishop
657dc416f6SBrad Bishopclass ParamikoExecutor(Executor):
667dc416f6SBrad Bishop    """Concrete implementation of the Executor interface that uses
677dc416f6SBrad Bishop    Paramiko to connect to a remote BMC to run the program."""
687dc416f6SBrad Bishop
697dc416f6SBrad Bishop    def __init__(self, hostname, uname, passwd, port, **kw):
707dc416f6SBrad Bishop        """This function is responsible for connecting to the BMC via
717dc416f6SBrad Bishop        ssh and returning an executor object.
72b8cc3257SManojkiran Eda
73b8cc3257SManojkiran Eda        Parameters:
74b8cc3257SManojkiran Eda            hostname: hostname/IP address of BMC
75b8cc3257SManojkiran Eda            uname: ssh username of BMC
76b8cc3257SManojkiran Eda            passwd: ssh password of BMC
77b8cc3257SManojkiran Eda            port: ssh port of BMC
78b8cc3257SManojkiran Eda        """
79b8cc3257SManojkiran Eda
807dc416f6SBrad Bishop        super(ParamikoExecutor, self).__init__()
817dc416f6SBrad Bishop        self.client = paramiko.SSHClient()
827dc416f6SBrad Bishop        self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
837dc416f6SBrad Bishop        self.client.connect(
84*c44715dcSPatrick Williams            hostname, username=uname, password=passwd, port=port, **kw
85*c44715dcSPatrick Williams        )
867dc416f6SBrad Bishop
877dc416f6SBrad Bishop    def exec_command(self, cmd):
88241e0683SBrad Bishop        _, stdout, stderr = self.client.exec_command(cmd)
89241e0683SBrad Bishop        return ParamikoProcess(stdout, stderr)
907dc416f6SBrad Bishop
917dc416f6SBrad Bishop    def close(self):
927dc416f6SBrad Bishop        self.client.close()
93b8cc3257SManojkiran Eda
94b8cc3257SManojkiran Eda
95c8906373SBrad Bishopclass SubprocessProcess(Process):
96c8906373SBrad Bishop    def __init__(self, popen):
97c8906373SBrad Bishop        self.popen = popen
98c8906373SBrad Bishop        super(SubprocessProcess, self).__init__(popen.stdout, popen.stderr)
99c8906373SBrad Bishop
100c8906373SBrad Bishop    def wait(self):
101c8906373SBrad Bishop        self.popen.wait()
102c8906373SBrad Bishop        return self.popen.returncode
103c8906373SBrad Bishop
104c8906373SBrad Bishop
105c8906373SBrad Bishopclass SubprocessExecutor(Executor):
106c8906373SBrad Bishop    def __init__(self):
107c8906373SBrad Bishop        super(SubprocessExecutor, self).__init__()
108c8906373SBrad Bishop
109c8906373SBrad Bishop    def exec_command(self, cmd):
110c8906373SBrad Bishop        args = shlex.split(cmd)
111c8906373SBrad Bishop        args[0] = shutil.which(args[0])
112c8906373SBrad Bishop        p = subprocess.Popen(
113*c44715dcSPatrick Williams            args, stdout=subprocess.PIPE, stderr=subprocess.PIPE
114*c44715dcSPatrick Williams        )
115c8906373SBrad Bishop        return SubprocessProcess(p)
116c8906373SBrad Bishop
117c8906373SBrad Bishop
118b8cc3257SManojkiran Edadef prepare_summary_report(state_sensor_pdr, state_effecter_pdr):
119b8cc3257SManojkiran Eda    """This function is responsible to parse the state sensor pdr
120b8cc3257SManojkiran Eda    and the state effecter pdr dictionaries and creating the
121b8cc3257SManojkiran Eda    summary table.
122b8cc3257SManojkiran Eda
123b8cc3257SManojkiran Eda    Parameters:
124b8cc3257SManojkiran Eda        state_sensor_pdr: list of state sensor pdrs
125b8cc3257SManojkiran Eda        state_effecter_pdr: list of state effecter pdrs
126b8cc3257SManojkiran Eda
127b8cc3257SManojkiran Eda    """
128b8cc3257SManojkiran Eda
129b8cc3257SManojkiran Eda    summary_table = []
130b8cc3257SManojkiran Eda    headers = ["sensor_id", "entity_type", "state_set", "states"]
131b8cc3257SManojkiran Eda    summary_table.append(headers)
132b8cc3257SManojkiran Eda    for value in state_sensor_pdr.values():
133b8cc3257SManojkiran Eda        summary_record = []
134*c44715dcSPatrick Williams        sensor_possible_states = ""
135b8cc3257SManojkiran Eda        for sensor_state in value["possibleStates[0]"]:
136b8cc3257SManojkiran Eda            sensor_possible_states += sensor_state + "\n"
137*c44715dcSPatrick Williams        summary_record.extend(
138*c44715dcSPatrick Williams            [
139*c44715dcSPatrick Williams                value["sensorID"],
140*c44715dcSPatrick Williams                value["entityType"],
141b8cc3257SManojkiran Eda                value["stateSetID[0]"],
142*c44715dcSPatrick Williams                sensor_possible_states,
143*c44715dcSPatrick Williams            ]
144*c44715dcSPatrick Williams        )
145b8cc3257SManojkiran Eda        summary_table.append(summary_record)
146b8cc3257SManojkiran Eda    print("Created at : ", datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
147b8cc3257SManojkiran Eda    print(tabulate(summary_table, tablefmt="fancy_grid", headers="firstrow"))
148b8cc3257SManojkiran Eda
149b8cc3257SManojkiran Eda    summary_table = []
150b8cc3257SManojkiran Eda    headers = ["effecter_id", "entity_type", "state_set", "states"]
151b8cc3257SManojkiran Eda    summary_table.append(headers)
152b8cc3257SManojkiran Eda    for value in state_effecter_pdr.values():
153b8cc3257SManojkiran Eda        summary_record = []
154*c44715dcSPatrick Williams        effecter_possible_states = ""
155b8cc3257SManojkiran Eda        for state in value["possibleStates[0]"]:
156b8cc3257SManojkiran Eda            effecter_possible_states += state + "\n"
157*c44715dcSPatrick Williams        summary_record.extend(
158*c44715dcSPatrick Williams            [
159*c44715dcSPatrick Williams                value["effecterID"],
160*c44715dcSPatrick Williams                value["entityType"],
161b8cc3257SManojkiran Eda                value["stateSetID[0]"],
162*c44715dcSPatrick Williams                effecter_possible_states,
163*c44715dcSPatrick Williams            ]
164*c44715dcSPatrick Williams        )
165b8cc3257SManojkiran Eda        summary_table.append(summary_record)
166b8cc3257SManojkiran Eda    print(tabulate(summary_table, tablefmt="fancy_grid", headers="firstrow"))
167b8cc3257SManojkiran Eda
168b8cc3257SManojkiran Eda
169b8cc3257SManojkiran Edadef draw_entity_associations(pdr, counter):
170b8cc3257SManojkiran Eda    """This function is responsible to create a picture that captures
171b8cc3257SManojkiran Eda    the entity association hierarchy based on the entity association
172b8cc3257SManojkiran Eda    PDR's received from the BMC.
173b8cc3257SManojkiran Eda
174b8cc3257SManojkiran Eda    Parameters:
175b8cc3257SManojkiran Eda        pdr: list of entity association PDR's
176b8cc3257SManojkiran Eda        counter: variable to capture the count of PDR's to unflatten
177b8cc3257SManojkiran Eda                 the tree
178b8cc3257SManojkiran Eda
179b8cc3257SManojkiran Eda    """
180b8cc3257SManojkiran Eda
181*c44715dcSPatrick Williams    dot = Digraph(
182*c44715dcSPatrick Williams        "entity_hierarchy",
183*c44715dcSPatrick Williams        node_attr={"color": "lightblue1", "style": "filled"},
184*c44715dcSPatrick Williams    )
185*c44715dcSPatrick Williams    dot.attr(
186*c44715dcSPatrick Williams        label=r"\n\nEntity Relation Diagram < "
187*c44715dcSPatrick Williams        + str(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
188*c44715dcSPatrick Williams        + ">\n"
189*c44715dcSPatrick Williams    )
190*c44715dcSPatrick Williams    dot.attr(fontsize="20")
191b8cc3257SManojkiran Eda    edge_list = []
192b8cc3257SManojkiran Eda    for value in pdr.values():
193*c44715dcSPatrick Williams        parentnode = str(value["containerEntityType"]) + str(
194*c44715dcSPatrick Williams            value["containerEntityInstanceNumber"]
195*c44715dcSPatrick Williams        )
196*c44715dcSPatrick Williams        dot.node(
197*c44715dcSPatrick Williams            hashlib.md5(
198*c44715dcSPatrick Williams                (
199*c44715dcSPatrick Williams                    parentnode + str(value["containerEntityContainerID"])
200*c44715dcSPatrick Williams                ).encode()
201*c44715dcSPatrick Williams            ).hexdigest(),
202*c44715dcSPatrick Williams            parentnode,
203*c44715dcSPatrick Williams        )
204b8cc3257SManojkiran Eda
205b8cc3257SManojkiran Eda        for i in range(1, value["containedEntityCount"] + 1):
206*c44715dcSPatrick Williams            childnode = str(value[f"containedEntityType[{i}]"]) + str(
207*c44715dcSPatrick Williams                value[f"containedEntityInstanceNumber[{i}]"]
208*c44715dcSPatrick Williams            )
209b8cc3257SManojkiran Eda            cid = str(value[f"containedEntityContainerID[{i}]"])
210*c44715dcSPatrick Williams            dot.node(
211*c44715dcSPatrick Williams                hashlib.md5((childnode + cid).encode()).hexdigest(), childnode
212*c44715dcSPatrick Williams            )
213b8cc3257SManojkiran Eda
214*c44715dcSPatrick Williams            if [
215*c44715dcSPatrick Williams                hashlib.md5(
216*c44715dcSPatrick Williams                    (
217*c44715dcSPatrick Williams                        parentnode + str(value["containerEntityContainerID"])
218*c44715dcSPatrick Williams                    ).encode()
219*c44715dcSPatrick Williams                ).hexdigest(),
220*c44715dcSPatrick Williams                hashlib.md5((childnode + cid).encode()).hexdigest(),
221*c44715dcSPatrick Williams            ] not in edge_list:
222*c44715dcSPatrick Williams                edge_list.append(
223*c44715dcSPatrick Williams                    [
224*c44715dcSPatrick Williams                        hashlib.md5(
225*c44715dcSPatrick Williams                            (
226*c44715dcSPatrick Williams                                parentnode
227*c44715dcSPatrick Williams                                + str(value["containerEntityContainerID"])
228*c44715dcSPatrick Williams                            ).encode()
229*c44715dcSPatrick Williams                        ).hexdigest(),
230*c44715dcSPatrick Williams                        hashlib.md5((childnode + cid).encode()).hexdigest(),
231*c44715dcSPatrick Williams                    ]
232*c44715dcSPatrick Williams                )
233*c44715dcSPatrick Williams                dot.edge(
234*c44715dcSPatrick Williams                    hashlib.md5(
235*c44715dcSPatrick Williams                        (
236*c44715dcSPatrick Williams                            parentnode
237*c44715dcSPatrick Williams                            + str(value["containerEntityContainerID"])
238*c44715dcSPatrick Williams                        ).encode()
239*c44715dcSPatrick Williams                    ).hexdigest(),
240*c44715dcSPatrick Williams                    hashlib.md5((childnode + cid).encode()).hexdigest(),
241*c44715dcSPatrick Williams                )
242b8cc3257SManojkiran Eda    unflattentree = dot.unflatten(stagger=(round(counter / 3)))
243*c44715dcSPatrick Williams    unflattentree.render(
244*c44715dcSPatrick Williams        filename="entity_association_"
245*c44715dcSPatrick Williams        + str(datetime.now().strftime("%Y-%m-%d_%H-%M-%S")),
246*c44715dcSPatrick Williams        view=False,
247*c44715dcSPatrick Williams        cleanup=True,
248*c44715dcSPatrick Williams        format="pdf",
249*c44715dcSPatrick Williams    )
250b8cc3257SManojkiran Eda
251b8cc3257SManojkiran Eda
252260f75a6SBrad Bishopclass PLDMToolError(Exception):
253260f75a6SBrad Bishop    """Exception class intended to be used to hold pldmtool invocation failure
254260f75a6SBrad Bishop    information such as exit status and stderr.
255260f75a6SBrad Bishop
256260f75a6SBrad Bishop    """
257260f75a6SBrad Bishop
258260f75a6SBrad Bishop    def __init__(self, status, stderr):
259260f75a6SBrad Bishop        msg = "pldmtool failed with exit status {}.\n".format(status)
260260f75a6SBrad Bishop        msg += "stderr: \n\n{}".format(stderr)
261260f75a6SBrad Bishop        super(PLDMToolError, self).__init__(msg)
26298cb3efbSBrad Bishop        self.status = status
26398cb3efbSBrad Bishop
26498cb3efbSBrad Bishop    def get_status(self):
26598cb3efbSBrad Bishop        return self.status
266260f75a6SBrad Bishop
267260f75a6SBrad Bishop
268241e0683SBrad Bishopdef process_pldmtool_output(process):
269260f75a6SBrad Bishop    """Ensure pldmtool runs without error and if it does fail, detect that and
270260f75a6SBrad Bishop    show the pldmtool exit status and it's stderr.
271260f75a6SBrad Bishop
27298cb3efbSBrad Bishop    A simpler implementation would just wait for the pldmtool exit status
27398cb3efbSBrad Bishop    prior to attempting to decode it's stdout.  Instead, optimize for the
27498cb3efbSBrad Bishop    no error case and allow the json decoder to consume pldmtool stdout as
27598cb3efbSBrad Bishop    soon as it is available (in parallel).  This results in the following
27698cb3efbSBrad Bishop    error scenarios:
27798cb3efbSBrad Bishop        - pldmtool fails and the decoder fails
27898cb3efbSBrad Bishop          Ignore the decoder fail and throw PLDMToolError.
27998cb3efbSBrad Bishop        - pldmtool fails and the decoder doesn't fail
28098cb3efbSBrad Bishop          Throw PLDMToolError.
28198cb3efbSBrad Bishop        - pldmtool doesn't fail and the decoder does fail
28298cb3efbSBrad Bishop          This is a pldmtool bug - re-throw the decoder error.
28398cb3efbSBrad Bishop
284260f75a6SBrad Bishop    Parameters:
285241e0683SBrad Bishop        process: A Process object providing process control functions like
286241e0683SBrad Bishop                 wait, and access functions such as reading stdout and
287241e0683SBrad Bishop                 stderr.
288260f75a6SBrad Bishop
289260f75a6SBrad Bishop    """
290260f75a6SBrad Bishop
29198cb3efbSBrad Bishop    status = 0
29298cb3efbSBrad Bishop    try:
293241e0683SBrad Bishop        data = json.load(process.stdout)
29498cb3efbSBrad Bishop        # it's unlikely, but possible, that pldmtool failed but still wrote a
29598cb3efbSBrad Bishop        # valid json document - so check for that.
296241e0683SBrad Bishop        status = process.wait()
297260f75a6SBrad Bishop        if status == 0:
29898cb3efbSBrad Bishop            return data
29998cb3efbSBrad Bishop    except json.decoder.JSONDecodeError:
30098cb3efbSBrad Bishop        # pldmtool wrote an invalid json document.  Check to see if it had
30198cb3efbSBrad Bishop        # non-zero exit status.
302241e0683SBrad Bishop        status = process.wait()
30398cb3efbSBrad Bishop        if status == 0:
30498cb3efbSBrad Bishop            # pldmtool didn't have non zero exit status, so it wrote an invalid
30598cb3efbSBrad Bishop            # json document and the JSONDecodeError is the correct error.
30698cb3efbSBrad Bishop            raise
307260f75a6SBrad Bishop
30898cb3efbSBrad Bishop    # pldmtool had a non-zero exit status, so throw an error for that, possibly
30998cb3efbSBrad Bishop    # discarding a spurious JSONDecodeError exception.
310241e0683SBrad Bishop    raise PLDMToolError(status, "".join(process.stderr))
311260f75a6SBrad Bishop
312260f75a6SBrad Bishop
3137dc416f6SBrad Bishopdef get_pdrs_one_at_a_time(executor):
3147dc416f6SBrad Bishop    """Using pldmtool, generate (record handle, PDR) tuples for each record in
3157dc416f6SBrad Bishop    the PDR repository.
31691523137SBrad Bishop
31791523137SBrad Bishop    Parameters:
3187dc416f6SBrad Bishop        executor: executor object for running pldmtool
31991523137SBrad Bishop
32091523137SBrad Bishop    """
32191523137SBrad Bishop
322*c44715dcSPatrick Williams    command_fmt = "pldmtool platform getpdr -d {}"
32391523137SBrad Bishop    record_handle = 0
32491523137SBrad Bishop    while True:
325241e0683SBrad Bishop        process = executor.exec_command(command_fmt.format(str(record_handle)))
326241e0683SBrad Bishop        pdr = process_pldmtool_output(process)
32791523137SBrad Bishop        yield record_handle, pdr
32891523137SBrad Bishop        record_handle = pdr["nextRecordHandle"]
32991523137SBrad Bishop        if record_handle == 0:
33091523137SBrad Bishop            break
33191523137SBrad Bishop
33291523137SBrad Bishop
3337dc416f6SBrad Bishopdef get_all_pdrs_at_once(executor):
3347dc416f6SBrad Bishop    """Using pldmtool, generate (record handle, PDR) tuples for each record in
3357dc416f6SBrad Bishop    the PDR repository.  Use pldmtool platform getpdr --all.
33698cb3efbSBrad Bishop
33798cb3efbSBrad Bishop    Parameters:
3387dc416f6SBrad Bishop        executor: executor object for running pldmtool
33998cb3efbSBrad Bishop
34098cb3efbSBrad Bishop    """
34198cb3efbSBrad Bishop
342*c44715dcSPatrick Williams    process = executor.exec_command("pldmtool platform getpdr -a")
343241e0683SBrad Bishop    all_pdrs = process_pldmtool_output(process)
34498cb3efbSBrad Bishop
34598cb3efbSBrad Bishop    # Explicitly request record 0 to find out what the real first record is.
346*c44715dcSPatrick Williams    process = executor.exec_command("pldmtool platform getpdr -d 0")
347241e0683SBrad Bishop    pdr_0 = process_pldmtool_output(process)
34898cb3efbSBrad Bishop    record_handle = pdr_0["recordHandle"]
34998cb3efbSBrad Bishop
35098cb3efbSBrad Bishop    while True:
35198cb3efbSBrad Bishop        for pdr in all_pdrs:
35298cb3efbSBrad Bishop            if pdr["recordHandle"] == record_handle:
35398cb3efbSBrad Bishop                yield record_handle, pdr
35498cb3efbSBrad Bishop                record_handle = pdr["nextRecordHandle"]
35598cb3efbSBrad Bishop                if record_handle == 0:
35698cb3efbSBrad Bishop                    return
35798cb3efbSBrad Bishop        raise RuntimeError(
358*c44715dcSPatrick Williams            "Dangling reference to record {}".format(record_handle)
359*c44715dcSPatrick Williams        )
36098cb3efbSBrad Bishop
36198cb3efbSBrad Bishop
3627dc416f6SBrad Bishopdef get_pdrs(executor):
3637dc416f6SBrad Bishop    """Using pldmtool, generate (record handle, PDR) tuples for each record in
3647dc416f6SBrad Bishop    the PDR repository.  Use pldmtool platform getpdr --all or fallback on
3657dc416f6SBrad Bishop    getting them one at a time if pldmtool doesn't support the --all
3667dc416f6SBrad Bishop    option.
36798cb3efbSBrad Bishop
36898cb3efbSBrad Bishop    Parameters:
3697dc416f6SBrad Bishop        executor: executor object for running pldmtool
37098cb3efbSBrad Bishop
37198cb3efbSBrad Bishop    """
37298cb3efbSBrad Bishop    try:
3737dc416f6SBrad Bishop        for record_handle, pdr in get_all_pdrs_at_once(executor):
37498cb3efbSBrad Bishop            yield record_handle, pdr
37598cb3efbSBrad Bishop        return
37698cb3efbSBrad Bishop    except PLDMToolError as e:
37798cb3efbSBrad Bishop        # No support for the -a option
37898cb3efbSBrad Bishop        if e.get_status() != 106:
37998cb3efbSBrad Bishop            raise
38098cb3efbSBrad Bishop    except json.decoder.JSONDecodeError as e:
38198cb3efbSBrad Bishop        # Some versions of pldmtool don't print valid json documents with -a
38298cb3efbSBrad Bishop        if e.msg != "Extra data":
38398cb3efbSBrad Bishop            raise
38498cb3efbSBrad Bishop
3857dc416f6SBrad Bishop    for record_handle, pdr in get_pdrs_one_at_a_time(executor):
38698cb3efbSBrad Bishop        yield record_handle, pdr
38798cb3efbSBrad Bishop
38898cb3efbSBrad Bishop
3897dc416f6SBrad Bishopdef fetch_pdrs_from_bmc(executor):
3907dc416f6SBrad Bishop    """This is the core function that would fire the getPDR pldmtool command
3917dc416f6SBrad Bishop    and it then agreegates the data received from all the calls into the
3927dc416f6SBrad Bishop    respective dictionaries based on the PDR Type.
393b8cc3257SManojkiran Eda
394b8cc3257SManojkiran Eda    Parameters:
3957dc416f6SBrad Bishop        executor: executor object for running pldmtool
396b8cc3257SManojkiran Eda
397b8cc3257SManojkiran Eda    """
398b8cc3257SManojkiran Eda
399b8cc3257SManojkiran Eda    entity_association_pdr = {}
400b8cc3257SManojkiran Eda    state_sensor_pdr = {}
401b8cc3257SManojkiran Eda    state_effecter_pdr = {}
402b8cc3257SManojkiran Eda    state_effecter_pdr = {}
403b8cc3257SManojkiran Eda    numeric_pdr = {}
404b8cc3257SManojkiran Eda    fru_record_set_pdr = {}
405b8cc3257SManojkiran Eda    tl_pdr = {}
4067dc416f6SBrad Bishop    for handle_number, my_dic in get_pdrs(executor):
40781c04518SBrad Bishop        if sys.stdout.isatty():
40881c04518SBrad Bishop            sys.stdout.write(
409*c44715dcSPatrick Williams                "Fetching PDR's from BMC : %8d\r" % (handle_number)
410*c44715dcSPatrick Williams            )
411b8cc3257SManojkiran Eda            sys.stdout.flush()
412b8cc3257SManojkiran Eda        if my_dic["PDRType"] == "Entity Association PDR":
413b8cc3257SManojkiran Eda            entity_association_pdr[handle_number] = my_dic
414b8cc3257SManojkiran Eda        if my_dic["PDRType"] == "State Sensor PDR":
415b8cc3257SManojkiran Eda            state_sensor_pdr[handle_number] = my_dic
416b8cc3257SManojkiran Eda        if my_dic["PDRType"] == "State Effecter PDR":
417b8cc3257SManojkiran Eda            state_effecter_pdr[handle_number] = my_dic
418b8cc3257SManojkiran Eda        if my_dic["PDRType"] == "FRU Record Set PDR":
419b8cc3257SManojkiran Eda            fru_record_set_pdr[handle_number] = my_dic
420b8cc3257SManojkiran Eda        if my_dic["PDRType"] == "Terminus Locator PDR":
421b8cc3257SManojkiran Eda            tl_pdr[handle_number] = my_dic
422b8cc3257SManojkiran Eda        if my_dic["PDRType"] == "Numeric Effecter PDR":
423b8cc3257SManojkiran Eda            numeric_pdr[handle_number] = my_dic
4247dc416f6SBrad Bishop    executor.close()
425b8cc3257SManojkiran Eda
426*c44715dcSPatrick Williams    total_pdrs = (
427*c44715dcSPatrick Williams        len(entity_association_pdr.keys())
428*c44715dcSPatrick Williams        + len(tl_pdr.keys())
429*c44715dcSPatrick Williams        + len(state_effecter_pdr.keys())
430*c44715dcSPatrick Williams        + len(numeric_pdr.keys())
431*c44715dcSPatrick Williams        + len(state_sensor_pdr.keys())
432*c44715dcSPatrick Williams        + len(fru_record_set_pdr.keys())
433*c44715dcSPatrick Williams    )
434*c44715dcSPatrick Williams    print("\nSuccessfully fetched " + str(total_pdrs) + " PDR's")
435b8cc3257SManojkiran Eda    print("Number of FRU Record PDR's : ", len(fru_record_set_pdr.keys()))
436b8cc3257SManojkiran Eda    print("Number of TerminusLocator PDR's : ", len(tl_pdr.keys()))
437b8cc3257SManojkiran Eda    print("Number of State Sensor PDR's : ", len(state_sensor_pdr.keys()))
438b8cc3257SManojkiran Eda    print("Number of State Effecter PDR's : ", len(state_effecter_pdr.keys()))
439b8cc3257SManojkiran Eda    print("Number of Numeric Effecter PDR's : ", len(numeric_pdr.keys()))
440*c44715dcSPatrick Williams    print(
441*c44715dcSPatrick Williams        "Number of Entity Association PDR's : ",
442*c44715dcSPatrick Williams        len(entity_association_pdr.keys()),
443*c44715dcSPatrick Williams    )
444*c44715dcSPatrick Williams    return (
445*c44715dcSPatrick Williams        entity_association_pdr,
446*c44715dcSPatrick Williams        state_sensor_pdr,
447*c44715dcSPatrick Williams        state_effecter_pdr,
448*c44715dcSPatrick Williams        len(fru_record_set_pdr.keys()),
449*c44715dcSPatrick Williams    )
450b8cc3257SManojkiran Eda
451b8cc3257SManojkiran Eda
452b8cc3257SManojkiran Edadef main():
453b8cc3257SManojkiran Eda    """Create a summary table capturing the information of all the PDR's
454b8cc3257SManojkiran Eda    from the BMC & also create a diagram that captures the entity
455b8cc3257SManojkiran Eda    association hierarchy."""
456b8cc3257SManojkiran Eda
457*c44715dcSPatrick Williams    parser = argparse.ArgumentParser(prog="pldm_visualise_pdrs.py")
458*c44715dcSPatrick Williams    parser.add_argument("--bmc", type=str, help="BMC IPAddress/BMC Hostname")
459*c44715dcSPatrick Williams    parser.add_argument("--user", type=str, help="BMC username")
460*c44715dcSPatrick Williams    parser.add_argument("--password", type=str, help="BMC Password")
461*c44715dcSPatrick Williams    parser.add_argument("--port", type=int, help="BMC SSH port", default=22)
462b8cc3257SManojkiran Eda    args = parser.parse_args()
4639a8192d9SBrad Bishop
4649e0265b5SBrad Bishop    extra_cfg = {}
465c8906373SBrad Bishop    if args.bmc:
4669a8192d9SBrad Bishop        try:
4679a8192d9SBrad Bishop            with open(os.path.expanduser("~/.ssh/config")) as f:
4689a8192d9SBrad Bishop                ssh_config = paramiko.SSHConfig()
4699a8192d9SBrad Bishop                ssh_config.parse(f)
4709a8192d9SBrad Bishop                host_config = ssh_config.lookup(args.bmc)
4719a8192d9SBrad Bishop                if host_config:
472*c44715dcSPatrick Williams                    if "hostname" in host_config:
473*c44715dcSPatrick Williams                        args.bmc = host_config["hostname"]
474*c44715dcSPatrick Williams                    if "user" in host_config and args.user is None:
475*c44715dcSPatrick Williams                        args.user = host_config["user"]
476*c44715dcSPatrick Williams                    if "proxycommand" in host_config:
477*c44715dcSPatrick Williams                        extra_cfg["sock"] = paramiko.ProxyCommand(
478*c44715dcSPatrick Williams                            host_config["proxycommand"]
479*c44715dcSPatrick Williams                        )
4809a8192d9SBrad Bishop        except FileNotFoundError:
4819a8192d9SBrad Bishop            pass
4829a8192d9SBrad Bishop
4837dc416f6SBrad Bishop        executor = ParamikoExecutor(
484*c44715dcSPatrick Williams            args.bmc, args.user, args.password, args.port, **extra_cfg
485*c44715dcSPatrick Williams        )
486*c44715dcSPatrick Williams    elif shutil.which("pldmtool"):
487c8906373SBrad Bishop        executor = SubprocessExecutor()
488c8906373SBrad Bishop    else:
489*c44715dcSPatrick Williams        sys.exit(
490*c44715dcSPatrick Williams            "Can't find any PDRs: specify remote BMC with --bmc or "
491*c44715dcSPatrick Williams            "install pldmtool."
492*c44715dcSPatrick Williams        )
493c8906373SBrad Bishop
494*c44715dcSPatrick Williams    (
495*c44715dcSPatrick Williams        association_pdr,
496*c44715dcSPatrick Williams        state_sensor_pdr,
497*c44715dcSPatrick Williams        state_effecter_pdr,
498*c44715dcSPatrick Williams        counter,
499*c44715dcSPatrick Williams    ) = fetch_pdrs_from_bmc(executor)
500b8cc3257SManojkiran Eda    draw_entity_associations(association_pdr, counter)
501b8cc3257SManojkiran Eda    prepare_summary_report(state_sensor_pdr, state_effecter_pdr)
502b8cc3257SManojkiran Eda
503b8cc3257SManojkiran Eda
504b8cc3257SManojkiran Edaif __name__ == "__main__":
505b8cc3257SManojkiran Eda    main()
506