blob: 3ff7b76378b1fc7bedb02fa6fb51dd52b902916a [file] [log] [blame]
Brad Bishopa34c0302019-09-23 22:34:48 -04001#! /usr/bin/env python3
2#
3# Copyright (C) 2019 Garmin Ltd.
4#
5# SPDX-License-Identifier: GPL-2.0-only
6#
7
8import argparse
9import hashlib
10import logging
11import os
12import pprint
13import sys
14import threading
15import time
Andrew Geissler5199d832021-09-24 16:47:35 -050016import warnings
Patrick Williamsac13d5f2023-11-24 18:59:46 -060017import netrc
18import json
Andrew Geissler5199d832021-09-24 16:47:35 -050019warnings.simplefilter("default")
Brad Bishopa34c0302019-09-23 22:34:48 -040020
21try:
22 import tqdm
23 ProgressBar = tqdm.tqdm
24except ImportError:
25 class ProgressBar(object):
26 def __init__(self, *args, **kwargs):
27 pass
28
29 def __enter__(self):
30 return self
31
32 def __exit__(self, *args, **kwargs):
33 pass
34
35 def update(self):
36 pass
37
38sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib'))
39
40import hashserv
Patrick Williamsac13d5f2023-11-24 18:59:46 -060041import bb.asyncrpc
Brad Bishopa34c0302019-09-23 22:34:48 -040042
43DEFAULT_ADDRESS = 'unix://./hashserve.sock'
44METHOD = 'stress.test.method'
45
Patrick Williamsac13d5f2023-11-24 18:59:46 -060046def print_user(u):
47 print(f"Username: {u['username']}")
48 if "permissions" in u:
49 print("Permissions: " + " ".join(u["permissions"]))
50 if "token" in u:
51 print(f"Token: {u['token']}")
52
Brad Bishopa34c0302019-09-23 22:34:48 -040053
54def main():
55 def handle_stats(args, client):
56 if args.reset:
57 s = client.reset_stats()
58 else:
59 s = client.get_stats()
Patrick Williamsac13d5f2023-11-24 18:59:46 -060060 print(json.dumps(s, sort_keys=True, indent=4))
Brad Bishopa34c0302019-09-23 22:34:48 -040061 return 0
62
63 def handle_stress(args, client):
64 def thread_main(pbar, lock):
65 nonlocal found_hashes
66 nonlocal missed_hashes
67 nonlocal max_time
68
Patrick Williamsac13d5f2023-11-24 18:59:46 -060069 with hashserv.create_client(args.address) as client:
70 for i in range(args.requests):
71 taskhash = hashlib.sha256()
72 taskhash.update(args.taskhash_seed.encode('utf-8'))
73 taskhash.update(str(i).encode('utf-8'))
Brad Bishopa34c0302019-09-23 22:34:48 -040074
Patrick Williamsac13d5f2023-11-24 18:59:46 -060075 start_time = time.perf_counter()
76 l = client.get_unihash(METHOD, taskhash.hexdigest())
77 elapsed = time.perf_counter() - start_time
Brad Bishopa34c0302019-09-23 22:34:48 -040078
Patrick Williamsac13d5f2023-11-24 18:59:46 -060079 with lock:
80 if l:
81 found_hashes += 1
82 else:
83 missed_hashes += 1
Brad Bishopa34c0302019-09-23 22:34:48 -040084
Patrick Williamsac13d5f2023-11-24 18:59:46 -060085 max_time = max(elapsed, max_time)
86 pbar.update()
Brad Bishopa34c0302019-09-23 22:34:48 -040087
88 max_time = 0
89 found_hashes = 0
90 missed_hashes = 0
91 lock = threading.Lock()
92 total_requests = args.clients * args.requests
93 start_time = time.perf_counter()
94 with ProgressBar(total=total_requests) as pbar:
95 threads = [threading.Thread(target=thread_main, args=(pbar, lock), daemon=False) for _ in range(args.clients)]
96 for t in threads:
97 t.start()
98
99 for t in threads:
100 t.join()
101
102 elapsed = time.perf_counter() - start_time
103 with lock:
104 print("%d requests in %.1fs. %.1f requests per second" % (total_requests, elapsed, total_requests / elapsed))
105 print("Average request time %.8fs" % (elapsed / total_requests))
106 print("Max request time was %.8fs" % max_time)
107 print("Found %d hashes, missed %d" % (found_hashes, missed_hashes))
108
109 if args.report:
110 with ProgressBar(total=args.requests) as pbar:
111 for i in range(args.requests):
112 taskhash = hashlib.sha256()
113 taskhash.update(args.taskhash_seed.encode('utf-8'))
114 taskhash.update(str(i).encode('utf-8'))
115
116 outhash = hashlib.sha256()
117 outhash.update(args.outhash_seed.encode('utf-8'))
118 outhash.update(str(i).encode('utf-8'))
119
120 client.report_unihash(taskhash.hexdigest(), METHOD, outhash.hexdigest(), taskhash.hexdigest())
121
122 with lock:
123 pbar.update()
124
Andrew Geissler20137392023-10-12 04:59:14 -0600125 def handle_remove(args, client):
126 where = {k: v for k, v in args.where}
127 if where:
128 result = client.remove(where)
129 print("Removed %d row(s)" % (result["count"]))
130 else:
131 print("No query specified")
132
133 def handle_clean_unused(args, client):
134 result = client.clean_unused(args.max_age)
135 print("Removed %d rows" % (result["count"]))
136 return 0
137
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600138 def handle_refresh_token(args, client):
139 r = client.refresh_token(args.username)
140 print_user(r)
141
142 def handle_set_user_permissions(args, client):
143 r = client.set_user_perms(args.username, args.permissions)
144 print_user(r)
145
146 def handle_get_user(args, client):
147 r = client.get_user(args.username)
148 print_user(r)
149
150 def handle_get_all_users(args, client):
151 users = client.get_all_users()
152 print("{username:20}| {permissions}".format(username="Username", permissions="Permissions"))
153 print(("-" * 20) + "+" + ("-" * 20))
154 for u in users:
155 print("{username:20}| {permissions}".format(username=u["username"], permissions=" ".join(u["permissions"])))
156
157 def handle_new_user(args, client):
158 r = client.new_user(args.username, args.permissions)
159 print_user(r)
160
161 def handle_delete_user(args, client):
162 r = client.delete_user(args.username)
163 print_user(r)
164
165 def handle_get_db_usage(args, client):
166 usage = client.get_db_usage()
167 print(usage)
168 tables = sorted(usage.keys())
169 print("{name:20}| {rows:20}".format(name="Table name", rows="Rows"))
170 print(("-" * 20) + "+" + ("-" * 20))
171 for t in tables:
172 print("{name:20}| {rows:<20}".format(name=t, rows=usage[t]["rows"]))
173 print()
174
175 total_rows = sum(t["rows"] for t in usage.values())
176 print(f"Total rows: {total_rows}")
177
178 def handle_get_db_query_columns(args, client):
179 columns = client.get_db_query_columns()
180 print("\n".join(sorted(columns)))
181
Brad Bishopa34c0302019-09-23 22:34:48 -0400182 parser = argparse.ArgumentParser(description='Hash Equivalence Client')
183 parser.add_argument('--address', default=DEFAULT_ADDRESS, help='Server address (default "%(default)s")')
184 parser.add_argument('--log', default='WARNING', help='Set logging level')
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600185 parser.add_argument('--login', '-l', metavar="USERNAME", help="Authenticate as USERNAME")
186 parser.add_argument('--password', '-p', metavar="TOKEN", help="Authenticate using token TOKEN")
187 parser.add_argument('--become', '-b', metavar="USERNAME", help="Impersonate user USERNAME (if allowed) when performing actions")
188 parser.add_argument('--no-netrc', '-n', action="store_false", dest="netrc", help="Do not use .netrc")
Brad Bishopa34c0302019-09-23 22:34:48 -0400189
190 subparsers = parser.add_subparsers()
191
192 stats_parser = subparsers.add_parser('stats', help='Show server stats')
193 stats_parser.add_argument('--reset', action='store_true',
194 help='Reset server stats')
195 stats_parser.set_defaults(func=handle_stats)
196
197 stress_parser = subparsers.add_parser('stress', help='Run stress test')
198 stress_parser.add_argument('--clients', type=int, default=10,
199 help='Number of simultaneous clients')
200 stress_parser.add_argument('--requests', type=int, default=1000,
201 help='Number of requests each client will perform')
202 stress_parser.add_argument('--report', action='store_true',
203 help='Report new hashes')
204 stress_parser.add_argument('--taskhash-seed', default='',
205 help='Include string in taskhash')
206 stress_parser.add_argument('--outhash-seed', default='',
207 help='Include string in outhash')
208 stress_parser.set_defaults(func=handle_stress)
209
Andrew Geissler20137392023-10-12 04:59:14 -0600210 remove_parser = subparsers.add_parser('remove', help="Remove hash entries")
211 remove_parser.add_argument("--where", "-w", metavar="KEY VALUE", nargs=2, action="append", default=[],
212 help="Remove entries from table where KEY == VALUE")
213 remove_parser.set_defaults(func=handle_remove)
214
215 clean_unused_parser = subparsers.add_parser('clean-unused', help="Remove unused database entries")
216 clean_unused_parser.add_argument("max_age", metavar="SECONDS", type=int, help="Remove unused entries older than SECONDS old")
217 clean_unused_parser.set_defaults(func=handle_clean_unused)
218
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600219 refresh_token_parser = subparsers.add_parser('refresh-token', help="Refresh auth token")
220 refresh_token_parser.add_argument("--username", "-u", help="Refresh the token for another user (if authorized)")
221 refresh_token_parser.set_defaults(func=handle_refresh_token)
222
223 set_user_perms_parser = subparsers.add_parser('set-user-perms', help="Set new permissions for user")
224 set_user_perms_parser.add_argument("--username", "-u", help="Username", required=True)
225 set_user_perms_parser.add_argument("permissions", metavar="PERM", nargs="*", default=[], help="New permissions")
226 set_user_perms_parser.set_defaults(func=handle_set_user_permissions)
227
228 get_user_parser = subparsers.add_parser('get-user', help="Get user")
229 get_user_parser.add_argument("--username", "-u", help="Username")
230 get_user_parser.set_defaults(func=handle_get_user)
231
232 get_all_users_parser = subparsers.add_parser('get-all-users', help="List all users")
233 get_all_users_parser.set_defaults(func=handle_get_all_users)
234
235 new_user_parser = subparsers.add_parser('new-user', help="Create new user")
236 new_user_parser.add_argument("--username", "-u", help="Username", required=True)
237 new_user_parser.add_argument("permissions", metavar="PERM", nargs="*", default=[], help="New permissions")
238 new_user_parser.set_defaults(func=handle_new_user)
239
240 delete_user_parser = subparsers.add_parser('delete-user', help="Delete user")
241 delete_user_parser.add_argument("--username", "-u", help="Username", required=True)
242 delete_user_parser.set_defaults(func=handle_delete_user)
243
244 db_usage_parser = subparsers.add_parser('get-db-usage', help="Database Usage")
245 db_usage_parser.set_defaults(func=handle_get_db_usage)
246
247 db_query_columns_parser = subparsers.add_parser('get-db-query-columns', help="Show columns that can be used in database queries")
248 db_query_columns_parser.set_defaults(func=handle_get_db_query_columns)
249
Brad Bishopa34c0302019-09-23 22:34:48 -0400250 args = parser.parse_args()
251
252 logger = logging.getLogger('hashserv')
253
254 level = getattr(logging, args.log.upper(), None)
255 if not isinstance(level, int):
256 raise ValueError('Invalid log level: %s' % args.log)
257
258 logger.setLevel(level)
259 console = logging.StreamHandler()
260 console.setLevel(level)
261 logger.addHandler(console)
262
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600263 login = args.login
264 password = args.password
265
266 if login is None and args.netrc:
267 try:
268 n = netrc.netrc()
269 auth = n.authenticators(args.address)
270 if auth is not None:
271 login, _, password = auth
272 except FileNotFoundError:
273 pass
274
Brad Bishopa34c0302019-09-23 22:34:48 -0400275 func = getattr(args, 'func', None)
276 if func:
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600277 try:
278 with hashserv.create_client(args.address, login, password) as client:
279 if args.become:
280 client.become_user(args.become)
281 return func(args, client)
282 except bb.asyncrpc.InvokeError as e:
283 print(f"ERROR: {e}")
284 return 1
Brad Bishopa34c0302019-09-23 22:34:48 -0400285
286 return 0
287
288
289if __name__ == '__main__':
290 try:
291 ret = main()
292 except Exception:
293 ret = 1
294 import traceback
295 traceback.print_exc()
296 sys.exit(ret)