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