blob: 2cb63386662518d6021aad5369dccd5648813a72 [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():
Patrick Williamsda295312023-12-05 16:48:56 -060055 def handle_get(args, client):
56 result = client.get_taskhash(args.method, args.taskhash, all_properties=True)
57 if not result:
58 return 0
59
60 print(json.dumps(result, sort_keys=True, indent=4))
61 return 0
62
63 def handle_get_outhash(args, client):
64 result = client.get_outhash(args.method, args.outhash, args.taskhash)
65 if not result:
66 return 0
67
68 print(json.dumps(result, sort_keys=True, indent=4))
69 return 0
70
Brad Bishopa34c0302019-09-23 22:34:48 -040071 def handle_stats(args, client):
72 if args.reset:
73 s = client.reset_stats()
74 else:
75 s = client.get_stats()
Patrick Williamsac13d5f2023-11-24 18:59:46 -060076 print(json.dumps(s, sort_keys=True, indent=4))
Brad Bishopa34c0302019-09-23 22:34:48 -040077 return 0
78
79 def handle_stress(args, client):
80 def thread_main(pbar, lock):
81 nonlocal found_hashes
82 nonlocal missed_hashes
83 nonlocal max_time
84
Patrick Williamsac13d5f2023-11-24 18:59:46 -060085 with hashserv.create_client(args.address) as client:
86 for i in range(args.requests):
87 taskhash = hashlib.sha256()
88 taskhash.update(args.taskhash_seed.encode('utf-8'))
89 taskhash.update(str(i).encode('utf-8'))
Brad Bishopa34c0302019-09-23 22:34:48 -040090
Patrick Williamsac13d5f2023-11-24 18:59:46 -060091 start_time = time.perf_counter()
92 l = client.get_unihash(METHOD, taskhash.hexdigest())
93 elapsed = time.perf_counter() - start_time
Brad Bishopa34c0302019-09-23 22:34:48 -040094
Patrick Williamsac13d5f2023-11-24 18:59:46 -060095 with lock:
96 if l:
97 found_hashes += 1
98 else:
99 missed_hashes += 1
Brad Bishopa34c0302019-09-23 22:34:48 -0400100
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600101 max_time = max(elapsed, max_time)
102 pbar.update()
Brad Bishopa34c0302019-09-23 22:34:48 -0400103
104 max_time = 0
105 found_hashes = 0
106 missed_hashes = 0
107 lock = threading.Lock()
108 total_requests = args.clients * args.requests
109 start_time = time.perf_counter()
110 with ProgressBar(total=total_requests) as pbar:
111 threads = [threading.Thread(target=thread_main, args=(pbar, lock), daemon=False) for _ in range(args.clients)]
112 for t in threads:
113 t.start()
114
115 for t in threads:
116 t.join()
117
118 elapsed = time.perf_counter() - start_time
119 with lock:
120 print("%d requests in %.1fs. %.1f requests per second" % (total_requests, elapsed, total_requests / elapsed))
121 print("Average request time %.8fs" % (elapsed / total_requests))
122 print("Max request time was %.8fs" % max_time)
123 print("Found %d hashes, missed %d" % (found_hashes, missed_hashes))
124
125 if args.report:
126 with ProgressBar(total=args.requests) as pbar:
127 for i in range(args.requests):
128 taskhash = hashlib.sha256()
129 taskhash.update(args.taskhash_seed.encode('utf-8'))
130 taskhash.update(str(i).encode('utf-8'))
131
132 outhash = hashlib.sha256()
133 outhash.update(args.outhash_seed.encode('utf-8'))
134 outhash.update(str(i).encode('utf-8'))
135
136 client.report_unihash(taskhash.hexdigest(), METHOD, outhash.hexdigest(), taskhash.hexdigest())
137
138 with lock:
139 pbar.update()
140
Andrew Geissler20137392023-10-12 04:59:14 -0600141 def handle_remove(args, client):
142 where = {k: v for k, v in args.where}
143 if where:
144 result = client.remove(where)
145 print("Removed %d row(s)" % (result["count"]))
146 else:
147 print("No query specified")
148
149 def handle_clean_unused(args, client):
150 result = client.clean_unused(args.max_age)
151 print("Removed %d rows" % (result["count"]))
152 return 0
153
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600154 def handle_refresh_token(args, client):
155 r = client.refresh_token(args.username)
156 print_user(r)
157
158 def handle_set_user_permissions(args, client):
159 r = client.set_user_perms(args.username, args.permissions)
160 print_user(r)
161
162 def handle_get_user(args, client):
163 r = client.get_user(args.username)
164 print_user(r)
165
166 def handle_get_all_users(args, client):
167 users = client.get_all_users()
168 print("{username:20}| {permissions}".format(username="Username", permissions="Permissions"))
169 print(("-" * 20) + "+" + ("-" * 20))
170 for u in users:
171 print("{username:20}| {permissions}".format(username=u["username"], permissions=" ".join(u["permissions"])))
172
173 def handle_new_user(args, client):
174 r = client.new_user(args.username, args.permissions)
175 print_user(r)
176
177 def handle_delete_user(args, client):
178 r = client.delete_user(args.username)
179 print_user(r)
180
181 def handle_get_db_usage(args, client):
182 usage = client.get_db_usage()
183 print(usage)
184 tables = sorted(usage.keys())
185 print("{name:20}| {rows:20}".format(name="Table name", rows="Rows"))
186 print(("-" * 20) + "+" + ("-" * 20))
187 for t in tables:
188 print("{name:20}| {rows:<20}".format(name=t, rows=usage[t]["rows"]))
189 print()
190
191 total_rows = sum(t["rows"] for t in usage.values())
192 print(f"Total rows: {total_rows}")
193
194 def handle_get_db_query_columns(args, client):
195 columns = client.get_db_query_columns()
196 print("\n".join(sorted(columns)))
197
Brad Bishopa34c0302019-09-23 22:34:48 -0400198 parser = argparse.ArgumentParser(description='Hash Equivalence Client')
199 parser.add_argument('--address', default=DEFAULT_ADDRESS, help='Server address (default "%(default)s")')
200 parser.add_argument('--log', default='WARNING', help='Set logging level')
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600201 parser.add_argument('--login', '-l', metavar="USERNAME", help="Authenticate as USERNAME")
202 parser.add_argument('--password', '-p', metavar="TOKEN", help="Authenticate using token TOKEN")
203 parser.add_argument('--become', '-b', metavar="USERNAME", help="Impersonate user USERNAME (if allowed) when performing actions")
204 parser.add_argument('--no-netrc', '-n', action="store_false", dest="netrc", help="Do not use .netrc")
Brad Bishopa34c0302019-09-23 22:34:48 -0400205
206 subparsers = parser.add_subparsers()
207
Patrick Williamsda295312023-12-05 16:48:56 -0600208 get_parser = subparsers.add_parser('get', help="Get the unihash for a taskhash")
209 get_parser.add_argument("method", help="Method to query")
210 get_parser.add_argument("taskhash", help="Task hash to query")
211 get_parser.set_defaults(func=handle_get)
212
213 get_outhash_parser = subparsers.add_parser('get-outhash', help="Get output hash information")
214 get_outhash_parser.add_argument("method", help="Method to query")
215 get_outhash_parser.add_argument("outhash", help="Output hash to query")
216 get_outhash_parser.add_argument("taskhash", help="Task hash to query")
217 get_outhash_parser.set_defaults(func=handle_get_outhash)
218
Brad Bishopa34c0302019-09-23 22:34:48 -0400219 stats_parser = subparsers.add_parser('stats', help='Show server stats')
220 stats_parser.add_argument('--reset', action='store_true',
221 help='Reset server stats')
222 stats_parser.set_defaults(func=handle_stats)
223
224 stress_parser = subparsers.add_parser('stress', help='Run stress test')
225 stress_parser.add_argument('--clients', type=int, default=10,
226 help='Number of simultaneous clients')
227 stress_parser.add_argument('--requests', type=int, default=1000,
228 help='Number of requests each client will perform')
229 stress_parser.add_argument('--report', action='store_true',
230 help='Report new hashes')
231 stress_parser.add_argument('--taskhash-seed', default='',
232 help='Include string in taskhash')
233 stress_parser.add_argument('--outhash-seed', default='',
234 help='Include string in outhash')
235 stress_parser.set_defaults(func=handle_stress)
236
Andrew Geissler20137392023-10-12 04:59:14 -0600237 remove_parser = subparsers.add_parser('remove', help="Remove hash entries")
238 remove_parser.add_argument("--where", "-w", metavar="KEY VALUE", nargs=2, action="append", default=[],
239 help="Remove entries from table where KEY == VALUE")
240 remove_parser.set_defaults(func=handle_remove)
241
242 clean_unused_parser = subparsers.add_parser('clean-unused', help="Remove unused database entries")
243 clean_unused_parser.add_argument("max_age", metavar="SECONDS", type=int, help="Remove unused entries older than SECONDS old")
244 clean_unused_parser.set_defaults(func=handle_clean_unused)
245
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600246 refresh_token_parser = subparsers.add_parser('refresh-token', help="Refresh auth token")
247 refresh_token_parser.add_argument("--username", "-u", help="Refresh the token for another user (if authorized)")
248 refresh_token_parser.set_defaults(func=handle_refresh_token)
249
250 set_user_perms_parser = subparsers.add_parser('set-user-perms', help="Set new permissions for user")
251 set_user_perms_parser.add_argument("--username", "-u", help="Username", required=True)
252 set_user_perms_parser.add_argument("permissions", metavar="PERM", nargs="*", default=[], help="New permissions")
253 set_user_perms_parser.set_defaults(func=handle_set_user_permissions)
254
255 get_user_parser = subparsers.add_parser('get-user', help="Get user")
256 get_user_parser.add_argument("--username", "-u", help="Username")
257 get_user_parser.set_defaults(func=handle_get_user)
258
259 get_all_users_parser = subparsers.add_parser('get-all-users', help="List all users")
260 get_all_users_parser.set_defaults(func=handle_get_all_users)
261
262 new_user_parser = subparsers.add_parser('new-user', help="Create new user")
263 new_user_parser.add_argument("--username", "-u", help="Username", required=True)
264 new_user_parser.add_argument("permissions", metavar="PERM", nargs="*", default=[], help="New permissions")
265 new_user_parser.set_defaults(func=handle_new_user)
266
267 delete_user_parser = subparsers.add_parser('delete-user', help="Delete user")
268 delete_user_parser.add_argument("--username", "-u", help="Username", required=True)
269 delete_user_parser.set_defaults(func=handle_delete_user)
270
271 db_usage_parser = subparsers.add_parser('get-db-usage', help="Database Usage")
272 db_usage_parser.set_defaults(func=handle_get_db_usage)
273
274 db_query_columns_parser = subparsers.add_parser('get-db-query-columns', help="Show columns that can be used in database queries")
275 db_query_columns_parser.set_defaults(func=handle_get_db_query_columns)
276
Brad Bishopa34c0302019-09-23 22:34:48 -0400277 args = parser.parse_args()
278
279 logger = logging.getLogger('hashserv')
280
281 level = getattr(logging, args.log.upper(), None)
282 if not isinstance(level, int):
283 raise ValueError('Invalid log level: %s' % args.log)
284
285 logger.setLevel(level)
286 console = logging.StreamHandler()
287 console.setLevel(level)
288 logger.addHandler(console)
289
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600290 login = args.login
291 password = args.password
292
293 if login is None and args.netrc:
294 try:
295 n = netrc.netrc()
296 auth = n.authenticators(args.address)
297 if auth is not None:
298 login, _, password = auth
299 except FileNotFoundError:
300 pass
301
Brad Bishopa34c0302019-09-23 22:34:48 -0400302 func = getattr(args, 'func', None)
303 if func:
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600304 try:
305 with hashserv.create_client(args.address, login, password) as client:
306 if args.become:
307 client.become_user(args.become)
308 return func(args, client)
309 except bb.asyncrpc.InvokeError as e:
310 print(f"ERROR: {e}")
311 return 1
Brad Bishopa34c0302019-09-23 22:34:48 -0400312
313 return 0
314
315
316if __name__ == '__main__':
317 try:
318 ret = main()
319 except Exception:
320 ret = 1
321 import traceback
322 traceback.print_exc()
323 sys.exit(ret)