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