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