1#!/usr/bin/env python3
2
3r"""
4See help text for details.
5"""
6
7import json
8import ssl
9import sys
10
11import requests
12import websocket
13from gen_arg import *
14from gen_print import *
15from gen_valid import *
16from retrying import retry
17
18save_path_0 = sys.path[0]
19del sys.path[0]
20
21# Restore sys.path[0].
22sys.path.insert(0, save_path_0)
23
24# Set exit_on_error for gen_valid functions.
25set_exit_on_error(True)
26
27
28parser = argparse.ArgumentParser(
29    usage="%(prog)s [OPTIONS]",
30    description="%(prog)s will open a websocket session on a remote OpenBMC. "
31    + "When an eSEL is created on that BMC, the monitor will receive "
32    + "notice over websocket that the eSEL was created "
33    + "and it will print a message.",
34    formatter_class=argparse.ArgumentDefaultsHelpFormatter,
35    prefix_chars="-+",
36)
37parser.add_argument(
38    "openbmc_host", default="", help="The BMC host name or IP address."
39)
40parser.add_argument(
41    "--openbmc_username",
42    default="root",
43    help="The userid for the open BMC system.",
44)
45parser.add_argument(
46    "--openbmc_password",
47    default="",
48    help="The password for the open BMC system.",
49)
50parser.add_argument(
51    "--monitor_type",
52    choices=["logging", "dump"],
53    default="logging",
54    help="The type of notifications from websocket to monitor.",
55)
56
57
58stock_list = [("test_mode", 0), ("quiet", 0), ("debug", 0)]
59
60
61def exit_function(signal_number=0, frame=None):
62    r"""
63    Execute whenever the program ends normally or with the signals that we
64    catch (i.e. TERM, INT).
65    """
66
67    qprint_dashes(width=160)
68    qprint_executing()
69    qprint_pgm_footer()
70
71
72def signal_handler(signal_number, frame):
73    r"""
74    Handle signals.  Without a function to catch a SIGTERM or SIGINT, the
75    program would terminate immediately with return code 143 and without
76    calling the exit_function.
77    """
78
79    # Our convention is to set up exit_function with atexit.register() so
80    # there is no need to explicitly call exit_function from here.
81
82    dprint_executing()
83
84    # Calling exit prevents returning to the code that was running
85    # when the signal was received.
86    exit(0)
87
88
89def validate_parms():
90    r"""
91    Validate program parameters, etc.
92    """
93
94    register_passwords(openbmc_password)
95    valid_value(openbmc_host)
96    valid_value(openbmc_username)
97    valid_value(openbmc_password)
98    global monitoring_uri
99    monitoring_uri = "/xyz/openbmc_project/" + monitor_type
100    gen_post_validation(exit_function, signal_handler)
101
102
103@retry(stop_max_attempt_number=3, wait_fixed=1000)
104def login(openbmc_host, openbmc_username, openbmc_password):
105    r"""
106    Log into the BMC and return the session object.
107
108    Description of argument(s):
109    openbmc_host          The BMC host name or IP address.
110    openbmc_username      The userid for the open BMC system.
111    openbmc_password      The password for the open BMC system.
112    """
113
114    qprint_executing()
115
116    http_header = {"Content-Type": "application/json"}
117    session = requests.session()
118    response = session.post(
119        "https://" + openbmc_host + "/login",
120        headers=http_header,
121        json={"data": [openbmc_username, openbmc_password]},
122        verify=False,
123        timeout=30,
124    )
125    valid_value(response.status_code, valid_values=[200])
126    login_response = json.loads(response.text)
127    qprint_var(login_response)
128    valid_value(login_response["status"], valid_values=["ok"])
129
130    return session
131
132
133def on_message(websocket_obj, message):
134    """
135    Websocket message handler.  Close the websocket if the
136    message is an eSEL message.
137
138    Description of argument(s):
139    websocket_obj  The websocket established during opne_socket().
140    message        The message sent from the websocket interface.
141    """
142
143    qprint_dashes(width=160)
144    qprint_executing()
145
146    # A typical message:
147    # /xyz/openbmc_project/logging/entry/24","properties":{"Id":24}}
148    # or
149    # /xyz/openbmc_project/dump/entry/1","properties":{"Size":186180}}').
150
151    if monitoring_uri + "/entry" in message:
152        if "Id" in message:
153            qprint_timen("eSEL received over websocket interface.")
154            websocket_obj.close()
155        elif "Size" in message:
156            qprint_timen(
157                "Dump notification received over websocket interface."
158            )
159            websocket_obj.close()
160
161
162def on_error(websocket_obj, wserror):
163    """
164    Websocket error handler.  This routine is called whenever the
165    websocket interfaces wishes to report an issue.
166
167    Description of argument(s):
168    websocket_obj  The websocket established during opne_socket().
169    wserror        The error message sent from the websocket interface.
170    """
171
172    # It is normal to receive this message when websocked closes:
173    # 'NoneType' object has no attribute 'connected'.
174
175    qprint_dashes(width=160)
176    qprint_executing()
177
178
179def on_close(websocket_obj):
180    """
181    Websocket close event handler.
182
183    Description of argument(s):
184    websocket_obj  The websocket established during opne_socket().
185    """
186
187    qprint_dashes(width=160)
188    qprint_executing()
189
190
191def on_open(websocket_obj):
192    """
193    Send the filters needed to listen to the logging interface.
194
195    Description of argument(s):
196    websocket_obj  The websocket established during opne_socket().
197    """
198
199    qprint_dashes(width=160)
200    qprint_executing()
201    data = {"paths": [monitoring_uri]}
202    websocket_obj.send(json.dumps(data))
203    qprint_timen("Registered for websocket monitoring: " + monitoring_uri)
204
205
206def open_socket(openbmc_host, openbmc_username, openbmc_password):
207    """
208    Open a long-running websocket to the BMC.
209    Description of argument(s):
210    openbmc_host      The BMC host name or IP address.
211    openbmc_username  The userid for the open BMC system.
212    openbmc_password  The Password for the open BMC system.
213    """
214    websocket.enableTrace(False)
215    qprint_dashes(width=160)
216    qprint_executing()
217    session = login(openbmc_host, openbmc_username, openbmc_password)
218    qprint_timen("Registering websocket handlers.")
219    cookies = session.cookies.get_dict()
220    cookies = sprint_var(
221        cookies,
222        fmt=no_header() | strip_brackets(),
223        col1_width=0,
224        trailing_char="",
225        delim="=",
226    ).replace("\n", ";")
227    # Register the event handlers. When an ESEL is created by the system
228    # under test, the on_message() handler will be called.
229    websocket_obj = websocket.WebSocketApp(
230        "wss://" + openbmc_host + "/subscribe",
231        on_message=on_message,
232        on_error=on_error,
233        on_close=on_close,
234        on_open=on_open,
235        cookie=cookies,
236    )
237    qprint_timen("Completed registering of websocket handlers.")
238    websocket_obj.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
239
240
241def main():
242    gen_get_options(parser, stock_list)
243    validate_parms()
244    qprint_pgm_header()
245    qprint_var(monitoring_uri)
246    open_socket(openbmc_host, openbmc_username, openbmc_password)
247
248
249main()
250