blob: 869f7636c5330330795fc0b292842b662749fb1c [file] [log] [blame]
Brad Bishop19323692019-04-05 15:28:33 -04001#! /usr/bin/env python3
2#
Brad Bishopa34c0302019-09-23 22:34:48 -04003# Copyright (C) 2018-2019 Garmin Ltd.
Brad Bishop19323692019-04-05 15:28:33 -04004#
Brad Bishopc342db32019-05-15 21:57:59 -04005# SPDX-License-Identifier: GPL-2.0-only
Brad Bishop19323692019-04-05 15:28:33 -04006#
Brad Bishop19323692019-04-05 15:28:33 -04007
Brad Bishopa34c0302019-09-23 22:34:48 -04008from . import create_server, create_client
Patrick Williamsac13d5f2023-11-24 18:59:46 -06009from .server import DEFAULT_ANON_PERMS, ALL_PERMISSIONS
10from bb.asyncrpc import InvokeError
Brad Bishop19323692019-04-05 15:28:33 -040011import hashlib
Brad Bishopa34c0302019-09-23 22:34:48 -040012import logging
13import multiprocessing
Andrew Geisslerc9f78652020-09-18 14:11:35 -050014import os
Brad Bishopa34c0302019-09-23 22:34:48 -040015import sys
Brad Bishop08902b02019-08-20 09:16:51 -040016import tempfile
Brad Bishopa34c0302019-09-23 22:34:48 -040017import threading
18import unittest
Andrew Geisslerc3d88e42020-10-02 09:45:00 -050019import socket
Patrick Williams213cb262021-08-07 19:21:33 -050020import time
21import signal
Patrick Williamsac13d5f2023-11-24 18:59:46 -060022import subprocess
23import json
24import re
25from pathlib import Path
26
27
28THIS_DIR = Path(__file__).parent
29BIN_DIR = THIS_DIR.parent.parent / "bin"
Brad Bishop19323692019-04-05 15:28:33 -040030
Patrick Williams213cb262021-08-07 19:21:33 -050031def server_prefunc(server, idx):
Andrew Geisslereff27472021-10-29 15:35:00 -050032 logging.basicConfig(level=logging.DEBUG, filename='bbhashserv-%d.log' % idx, filemode='w',
Patrick Williams213cb262021-08-07 19:21:33 -050033 format='%(levelname)s %(filename)s:%(lineno)d %(message)s')
34 server.logger.debug("Running server %d" % idx)
Andrew Geisslereff27472021-10-29 15:35:00 -050035 sys.stdout = open('bbhashserv-stdout-%d.log' % idx, 'w')
Andrew Geissler6ce62a22020-11-30 19:58:47 -060036 sys.stderr = sys.stdout
Andrew Geissler09209ee2020-12-13 08:44:15 -060037
38class HashEquivalenceTestSetup(object):
Brad Bishopa34c0302019-09-23 22:34:48 -040039 METHOD = 'TestMethod'
40
Andrew Geissler6ce62a22020-11-30 19:58:47 -060041 server_index = 0
Patrick Williamsac13d5f2023-11-24 18:59:46 -060042 client_index = 0
Andrew Geissler6ce62a22020-11-30 19:58:47 -060043
Patrick Williamsac13d5f2023-11-24 18:59:46 -060044 def start_server(self, dbpath=None, upstream=None, read_only=False, prefunc=server_prefunc, anon_perms=DEFAULT_ANON_PERMS, admin_username=None, admin_password=None):
Andrew Geissler6ce62a22020-11-30 19:58:47 -060045 self.server_index += 1
46 if dbpath is None:
Patrick Williamsac13d5f2023-11-24 18:59:46 -060047 dbpath = self.make_dbpath()
Andrew Geissler6ce62a22020-11-30 19:58:47 -060048
Patrick Williams213cb262021-08-07 19:21:33 -050049 def cleanup_server(server):
50 if server.process.exitcode is not None:
51 return
52
53 server.process.terminate()
54 server.process.join()
Andrew Geissler6ce62a22020-11-30 19:58:47 -060055
Andrew Geisslerd1e89492021-02-12 15:35:20 -060056 server = create_server(self.get_server_addr(self.server_index),
57 dbpath,
58 upstream=upstream,
Patrick Williamsac13d5f2023-11-24 18:59:46 -060059 read_only=read_only,
60 anon_perms=anon_perms,
61 admin_username=admin_username,
62 admin_password=admin_password)
Andrew Geissler6ce62a22020-11-30 19:58:47 -060063 server.dbpath = dbpath
64
Patrick Williams213cb262021-08-07 19:21:33 -050065 server.serve_as_process(prefunc=prefunc, args=(self.server_index,))
66 self.addCleanup(cleanup_server, server)
Andrew Geissler6ce62a22020-11-30 19:58:47 -060067
Patrick Williamsac13d5f2023-11-24 18:59:46 -060068 return server
69
70 def make_dbpath(self):
71 return os.path.join(self.temp_dir.name, "db%d.sqlite" % self.server_index)
72
73 def start_client(self, server_address, username=None, password=None):
Andrew Geissler6ce62a22020-11-30 19:58:47 -060074 def cleanup_client(client):
75 client.close()
76
Patrick Williamsac13d5f2023-11-24 18:59:46 -060077 client = create_client(server_address, username=username, password=password)
Andrew Geissler6ce62a22020-11-30 19:58:47 -060078 self.addCleanup(cleanup_client, client)
79
Patrick Williamsac13d5f2023-11-24 18:59:46 -060080 return client
81
82 def start_test_server(self):
83 self.server = self.start_server()
84 return self.server.address
85
86 def start_auth_server(self):
87 auth_server = self.start_server(self.server.dbpath, anon_perms=[], admin_username="admin", admin_password="password")
88 self.auth_server_address = auth_server.address
89 self.admin_client = self.start_client(auth_server.address, username="admin", password="password")
90 return self.admin_client
91
92 def auth_client(self, user):
93 return self.start_client(self.auth_server_address, user["username"], user["token"])
Brad Bishopa34c0302019-09-23 22:34:48 -040094
Brad Bishop19323692019-04-05 15:28:33 -040095 def setUp(self):
Brad Bishopa34c0302019-09-23 22:34:48 -040096 if sys.version_info < (3, 5, 0):
97 self.skipTest('Python 3.5 or later required')
98
99 self.temp_dir = tempfile.TemporaryDirectory(prefix='bb-hashserv')
Andrew Geissler6ce62a22020-11-30 19:58:47 -0600100 self.addCleanup(self.temp_dir.cleanup)
Brad Bishopa34c0302019-09-23 22:34:48 -0400101
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600102 self.server_address = self.start_test_server()
103
104 self.client = self.start_client(self.server_address)
Brad Bishop19323692019-04-05 15:28:33 -0400105
Andrew Geissler6ce62a22020-11-30 19:58:47 -0600106 def assertClientGetHash(self, client, taskhash, unihash):
107 result = client.get_unihash(self.METHOD, taskhash)
108 self.assertEqual(result, unihash)
Brad Bishop19323692019-04-05 15:28:33 -0400109
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600110 def assertUserPerms(self, user, permissions):
111 with self.auth_client(user) as client:
112 info = client.get_user()
113 self.assertEqual(info, {
114 "username": user["username"],
115 "permissions": permissions,
116 })
Andrew Geissler09209ee2020-12-13 08:44:15 -0600117
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600118 def assertUserCanAuth(self, user):
119 with self.start_client(self.auth_server_address) as client:
120 client.auth(user["username"], user["token"])
121
122 def assertUserCannotAuth(self, user):
123 with self.start_client(self.auth_server_address) as client, self.assertRaises(InvokeError):
124 client.auth(user["username"], user["token"])
125
126 def create_test_hash(self, client):
Brad Bishop19323692019-04-05 15:28:33 -0400127 # Simple test that hashes can be created
128 taskhash = '35788efcb8dfb0a02659d81cf2bfd695fb30faf9'
129 outhash = '2765d4a5884be49b28601445c2760c5f21e7e5c0ee2b7e3fce98fd7e5970796f'
130 unihash = 'f46d3fbb439bd9b921095da657a4de906510d2cd'
131
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600132 self.assertClientGetHash(client, taskhash, None)
Brad Bishop19323692019-04-05 15:28:33 -0400133
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600134 result = client.report_unihash(taskhash, self.METHOD, outhash, unihash)
Brad Bishopa34c0302019-09-23 22:34:48 -0400135 self.assertEqual(result['unihash'], unihash, 'Server returned bad unihash')
Andrew Geissler20137392023-10-12 04:59:14 -0600136 return taskhash, outhash, unihash
Brad Bishop19323692019-04-05 15:28:33 -0400137
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600138 def run_hashclient(self, args, **kwargs):
139 try:
140 p = subprocess.run(
141 [BIN_DIR / "bitbake-hashclient"] + args,
142 stdout=subprocess.PIPE,
143 stderr=subprocess.STDOUT,
144 encoding="utf-8",
145 **kwargs
146 )
147 except subprocess.CalledProcessError as e:
148 print(e.output)
149 raise e
150
151 print(p.stdout)
152 return p
153
154
155class HashEquivalenceCommonTests(object):
156 def auth_perms(self, *permissions):
157 self.client_index += 1
158 user = self.create_user(f"user-{self.client_index}", permissions)
159 return self.auth_client(user)
160
161 def create_user(self, username, permissions, *, client=None):
162 def remove_user(username):
163 try:
164 self.admin_client.delete_user(username)
165 except bb.asyncrpc.InvokeError:
166 pass
167
168 if client is None:
169 client = self.admin_client
170
171 user = client.new_user(username, permissions)
172 self.addCleanup(remove_user, username)
173
174 return user
175
176 def test_create_hash(self):
177 return self.create_test_hash(self.client)
178
Brad Bishop19323692019-04-05 15:28:33 -0400179 def test_create_equivalent(self):
180 # Tests that a second reported task with the same outhash will be
181 # assigned the same unihash
182 taskhash = '53b8dce672cb6d0c73170be43f540460bfc347b4'
183 outhash = '5a9cb1649625f0bf41fc7791b635cd9c2d7118c7f021ba87dcd03f72b67ce7a8'
184 unihash = 'f37918cc02eb5a520b1aff86faacbc0a38124646'
Brad Bishopa34c0302019-09-23 22:34:48 -0400185
186 result = self.client.report_unihash(taskhash, self.METHOD, outhash, unihash)
187 self.assertEqual(result['unihash'], unihash, 'Server returned bad unihash')
Brad Bishop19323692019-04-05 15:28:33 -0400188
189 # Report a different task with the same outhash. The returned unihash
190 # should match the first task
191 taskhash2 = '3bf6f1e89d26205aec90da04854fbdbf73afe6b4'
192 unihash2 = 'af36b199320e611fbb16f1f277d3ee1d619ca58b'
Brad Bishopa34c0302019-09-23 22:34:48 -0400193 result = self.client.report_unihash(taskhash2, self.METHOD, outhash, unihash2)
194 self.assertEqual(result['unihash'], unihash, 'Server returned bad unihash')
Brad Bishop19323692019-04-05 15:28:33 -0400195
196 def test_duplicate_taskhash(self):
197 # Tests that duplicate reports of the same taskhash with different
198 # outhash & unihash always return the unihash from the first reported
199 # taskhash
200 taskhash = '8aa96fcffb5831b3c2c0cb75f0431e3f8b20554a'
201 outhash = 'afe240a439959ce86f5e322f8c208e1fedefea9e813f2140c81af866cc9edf7e'
202 unihash = '218e57509998197d570e2c98512d0105985dffc9'
Brad Bishopa34c0302019-09-23 22:34:48 -0400203 self.client.report_unihash(taskhash, self.METHOD, outhash, unihash)
Brad Bishop19323692019-04-05 15:28:33 -0400204
Andrew Geissler6ce62a22020-11-30 19:58:47 -0600205 self.assertClientGetHash(self.client, taskhash, unihash)
Brad Bishop19323692019-04-05 15:28:33 -0400206
207 outhash2 = '0904a7fe3dc712d9fd8a74a616ddca2a825a8ee97adf0bd3fc86082c7639914d'
208 unihash2 = 'ae9a7d252735f0dafcdb10e2e02561ca3a47314c'
Brad Bishopa34c0302019-09-23 22:34:48 -0400209 self.client.report_unihash(taskhash, self.METHOD, outhash2, unihash2)
Brad Bishop19323692019-04-05 15:28:33 -0400210
Andrew Geissler6ce62a22020-11-30 19:58:47 -0600211 self.assertClientGetHash(self.client, taskhash, unihash)
Brad Bishop19323692019-04-05 15:28:33 -0400212
213 outhash3 = '77623a549b5b1a31e3732dfa8fe61d7ce5d44b3370f253c5360e136b852967b4'
214 unihash3 = '9217a7d6398518e5dc002ed58f2cbbbc78696603'
Brad Bishopa34c0302019-09-23 22:34:48 -0400215 self.client.report_unihash(taskhash, self.METHOD, outhash3, unihash3)
Brad Bishop19323692019-04-05 15:28:33 -0400216
Andrew Geissler6ce62a22020-11-30 19:58:47 -0600217 self.assertClientGetHash(self.client, taskhash, unihash)
Brad Bishopa34c0302019-09-23 22:34:48 -0400218
Andrew Geissler20137392023-10-12 04:59:14 -0600219 def test_remove_taskhash(self):
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600220 taskhash, outhash, unihash = self.create_test_hash(self.client)
Andrew Geissler20137392023-10-12 04:59:14 -0600221 result = self.client.remove({"taskhash": taskhash})
222 self.assertGreater(result["count"], 0)
223 self.assertClientGetHash(self.client, taskhash, None)
224
225 result_outhash = self.client.get_outhash(self.METHOD, outhash, taskhash)
226 self.assertIsNone(result_outhash)
227
228 def test_remove_unihash(self):
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600229 taskhash, outhash, unihash = self.create_test_hash(self.client)
Andrew Geissler20137392023-10-12 04:59:14 -0600230 result = self.client.remove({"unihash": unihash})
231 self.assertGreater(result["count"], 0)
232 self.assertClientGetHash(self.client, taskhash, None)
233
234 def test_remove_outhash(self):
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600235 taskhash, outhash, unihash = self.create_test_hash(self.client)
Andrew Geissler20137392023-10-12 04:59:14 -0600236 result = self.client.remove({"outhash": outhash})
237 self.assertGreater(result["count"], 0)
238
239 result_outhash = self.client.get_outhash(self.METHOD, outhash, taskhash)
240 self.assertIsNone(result_outhash)
241
242 def test_remove_method(self):
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600243 taskhash, outhash, unihash = self.create_test_hash(self.client)
Andrew Geissler20137392023-10-12 04:59:14 -0600244 result = self.client.remove({"method": self.METHOD})
245 self.assertGreater(result["count"], 0)
246 self.assertClientGetHash(self.client, taskhash, None)
247
248 result_outhash = self.client.get_outhash(self.METHOD, outhash, taskhash)
249 self.assertIsNone(result_outhash)
250
251 def test_clean_unused(self):
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600252 taskhash, outhash, unihash = self.create_test_hash(self.client)
Andrew Geissler20137392023-10-12 04:59:14 -0600253
254 # Clean the database, which should not remove anything because all hashes an in-use
255 result = self.client.clean_unused(0)
256 self.assertEqual(result["count"], 0)
257 self.assertClientGetHash(self.client, taskhash, unihash)
258
259 # Remove the unihash. The row in the outhash table should still be present
260 self.client.remove({"unihash": unihash})
261 result_outhash = self.client.get_outhash(self.METHOD, outhash, taskhash, False)
262 self.assertIsNotNone(result_outhash)
263
264 # Now clean with no minimum age which will remove the outhash
265 result = self.client.clean_unused(0)
266 self.assertEqual(result["count"], 1)
267 result_outhash = self.client.get_outhash(self.METHOD, outhash, taskhash, False)
268 self.assertIsNone(result_outhash)
269
Andrew Geissler475cb722020-07-10 16:00:51 -0500270 def test_huge_message(self):
271 # Simple test that hashes can be created
272 taskhash = 'c665584ee6817aa99edfc77a44dd853828279370'
273 outhash = '3c979c3db45c569f51ab7626a4651074be3a9d11a84b1db076f5b14f7d39db44'
274 unihash = '90e9bc1d1f094c51824adca7f8ea79a048d68824'
275
Andrew Geissler6ce62a22020-11-30 19:58:47 -0600276 self.assertClientGetHash(self.client, taskhash, None)
Andrew Geissler475cb722020-07-10 16:00:51 -0500277
278 siginfo = "0" * (self.client.max_chunk * 4)
279
280 result = self.client.report_unihash(taskhash, self.METHOD, outhash, unihash, {
281 'outhash_siginfo': siginfo
282 })
283 self.assertEqual(result['unihash'], unihash, 'Server returned bad unihash')
284
Andrew Geisslereff27472021-10-29 15:35:00 -0500285 result_unihash = self.client.get_taskhash(self.METHOD, taskhash, True)
286 self.assertEqual(result_unihash['taskhash'], taskhash)
287 self.assertEqual(result_unihash['unihash'], unihash)
288 self.assertEqual(result_unihash['method'], self.METHOD)
289
290 result_outhash = self.client.get_outhash(self.METHOD, outhash, taskhash)
291 self.assertEqual(result_outhash['taskhash'], taskhash)
292 self.assertEqual(result_outhash['method'], self.METHOD)
293 self.assertEqual(result_outhash['unihash'], unihash)
294 self.assertEqual(result_outhash['outhash'], outhash)
295 self.assertEqual(result_outhash['outhash_siginfo'], siginfo)
Andrew Geissler475cb722020-07-10 16:00:51 -0500296
Brad Bishopa34c0302019-09-23 22:34:48 -0400297 def test_stress(self):
298 def query_server(failures):
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600299 client = Client(self.server_address)
Brad Bishopa34c0302019-09-23 22:34:48 -0400300 try:
301 for i in range(1000):
302 taskhash = hashlib.sha256()
303 taskhash.update(str(i).encode('utf-8'))
304 taskhash = taskhash.hexdigest()
305 result = client.get_unihash(self.METHOD, taskhash)
306 if result != taskhash:
307 failures.append("taskhash mismatch: %s != %s" % (result, taskhash))
308 finally:
309 client.close()
310
311 # Report hashes
312 for i in range(1000):
313 taskhash = hashlib.sha256()
314 taskhash.update(str(i).encode('utf-8'))
315 taskhash = taskhash.hexdigest()
316 self.client.report_unihash(taskhash, self.METHOD, taskhash, taskhash)
317
318 failures = []
319 threads = [threading.Thread(target=query_server, args=(failures,)) for t in range(100)]
320
321 for t in threads:
322 t.start()
323
324 for t in threads:
325 t.join()
326
327 self.assertFalse(failures)
Brad Bishop19323692019-04-05 15:28:33 -0400328
Andrew Geissler6ce62a22020-11-30 19:58:47 -0600329 def test_upstream_server(self):
330 # Tests upstream server support. This is done by creating two servers
331 # that share a database file. The downstream server has it upstream
332 # set to the test server, whereas the side server doesn't. This allows
333 # verification that the hash requests are being proxied to the upstream
334 # server by verifying that they appear on the downstream client, but not
335 # the side client. It also verifies that the results are pulled into
336 # the downstream database by checking that the downstream and side servers
337 # match after the downstream is done waiting for all backfill tasks
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600338 down_server = self.start_server(upstream=self.server_address)
339 down_client = self.start_client(down_server.address)
340 side_server = self.start_server(dbpath=down_server.dbpath)
341 side_client = self.start_client(side_server.address)
Andrew Geissler6ce62a22020-11-30 19:58:47 -0600342
343 def check_hash(taskhash, unihash, old_sidehash):
344 nonlocal down_client
345 nonlocal side_client
346
347 # check upstream server
348 self.assertClientGetHash(self.client, taskhash, unihash)
349
350 # Hash should *not* be present on the side server
351 self.assertClientGetHash(side_client, taskhash, old_sidehash)
352
353 # Hash should be present on the downstream server, since it
354 # will defer to the upstream server. This will trigger
355 # the backfill in the downstream server
356 self.assertClientGetHash(down_client, taskhash, unihash)
357
358 # After waiting for the downstream client to finish backfilling the
359 # task from the upstream server, it should appear in the side server
360 # since the database is populated
361 down_client.backfill_wait()
362 self.assertClientGetHash(side_client, taskhash, unihash)
363
364 # Basic report
365 taskhash = '8aa96fcffb5831b3c2c0cb75f0431e3f8b20554a'
366 outhash = 'afe240a439959ce86f5e322f8c208e1fedefea9e813f2140c81af866cc9edf7e'
367 unihash = '218e57509998197d570e2c98512d0105985dffc9'
368 self.client.report_unihash(taskhash, self.METHOD, outhash, unihash)
369
370 check_hash(taskhash, unihash, None)
371
372 # Duplicated taskhash with multiple output hashes and unihashes.
373 # All servers should agree with the originally reported hash
374 outhash2 = '0904a7fe3dc712d9fd8a74a616ddca2a825a8ee97adf0bd3fc86082c7639914d'
375 unihash2 = 'ae9a7d252735f0dafcdb10e2e02561ca3a47314c'
376 self.client.report_unihash(taskhash, self.METHOD, outhash2, unihash2)
377
378 check_hash(taskhash, unihash, unihash)
379
380 # Report an equivalent task. The sideload will originally report
381 # no unihash until backfilled
382 taskhash3 = "044c2ec8aaf480685a00ff6ff49e6162e6ad34e1"
383 unihash3 = "def64766090d28f627e816454ed46894bb3aab36"
384 self.client.report_unihash(taskhash3, self.METHOD, outhash, unihash3)
385
386 check_hash(taskhash3, unihash, None)
387
388 # Test that reporting a unihash in the downstream client isn't
389 # propagating to the upstream server
390 taskhash4 = "e3da00593d6a7fb435c7e2114976c59c5fd6d561"
391 outhash4 = "1cf8713e645f491eb9c959d20b5cae1c47133a292626dda9b10709857cbe688a"
392 unihash4 = "3b5d3d83f07f259e9086fcb422c855286e18a57d"
393 down_client.report_unihash(taskhash4, self.METHOD, outhash4, unihash4)
394 down_client.backfill_wait()
395
396 self.assertClientGetHash(down_client, taskhash4, unihash4)
397 self.assertClientGetHash(side_client, taskhash4, unihash4)
398 self.assertClientGetHash(self.client, taskhash4, None)
399
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600400 # Test that reporting a unihash in the downstream is able to find a
401 # match which was previously reported to the upstream server
402 taskhash5 = '35788efcb8dfb0a02659d81cf2bfd695fb30faf9'
403 outhash5 = '2765d4a5884be49b28601445c2760c5f21e7e5c0ee2b7e3fce98fd7e5970796f'
404 unihash5 = 'f46d3fbb439bd9b921095da657a4de906510d2cd'
405 result = self.client.report_unihash(taskhash5, self.METHOD, outhash5, unihash5)
406
407 taskhash6 = '35788efcb8dfb0a02659d81cf2bfd695fb30fafa'
408 unihash6 = 'f46d3fbb439bd9b921095da657a4de906510d2ce'
409 result = down_client.report_unihash(taskhash6, self.METHOD, outhash5, unihash6)
410 self.assertEqual(result['unihash'], unihash5, 'Server failed to copy unihash from upstream')
411
Andrew Geisslereff27472021-10-29 15:35:00 -0500412 # Tests read through from server with
413 taskhash7 = '9d81d76242cc7cfaf7bf74b94b9cd2e29324ed74'
414 outhash7 = '8470d56547eea6236d7c81a644ce74670ca0bbda998e13c629ef6bb3f0d60b69'
415 unihash7 = '05d2a63c81e32f0a36542ca677e8ad852365c538'
416 self.client.report_unihash(taskhash7, self.METHOD, outhash7, unihash7)
417
418 result = down_client.get_taskhash(self.METHOD, taskhash7, True)
419 self.assertEqual(result['unihash'], unihash7, 'Server failed to copy unihash from upstream')
420 self.assertEqual(result['outhash'], outhash7, 'Server failed to copy unihash from upstream')
421 self.assertEqual(result['taskhash'], taskhash7, 'Server failed to copy unihash from upstream')
422 self.assertEqual(result['method'], self.METHOD)
423
424 taskhash8 = '86978a4c8c71b9b487330b0152aade10c1ee58aa'
425 outhash8 = 'ca8c128e9d9e4a28ef24d0508aa20b5cf880604eacd8f65c0e366f7e0cc5fbcf'
426 unihash8 = 'd8bcf25369d40590ad7d08c84d538982f2023e01'
427 self.client.report_unihash(taskhash8, self.METHOD, outhash8, unihash8)
428
429 result = down_client.get_outhash(self.METHOD, outhash8, taskhash8)
430 self.assertEqual(result['unihash'], unihash8, 'Server failed to copy unihash from upstream')
431 self.assertEqual(result['outhash'], outhash8, 'Server failed to copy unihash from upstream')
432 self.assertEqual(result['taskhash'], taskhash8, 'Server failed to copy unihash from upstream')
433 self.assertEqual(result['method'], self.METHOD)
434
435 taskhash9 = 'ae6339531895ddf5b67e663e6a374ad8ec71d81c'
436 outhash9 = 'afc78172c81880ae10a1fec994b5b4ee33d196a001a1b66212a15ebe573e00b5'
437 unihash9 = '6662e699d6e3d894b24408ff9a4031ef9b038ee8'
438 self.client.report_unihash(taskhash9, self.METHOD, outhash9, unihash9)
439
440 result = down_client.get_taskhash(self.METHOD, taskhash9, False)
441 self.assertEqual(result['unihash'], unihash9, 'Server failed to copy unihash from upstream')
442 self.assertEqual(result['taskhash'], taskhash9, 'Server failed to copy unihash from upstream')
443 self.assertEqual(result['method'], self.METHOD)
444
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600445 def test_ro_server(self):
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600446 rw_server = self.start_server()
447 rw_client = self.start_client(rw_server.address)
448
449 ro_server = self.start_server(dbpath=rw_server.dbpath, read_only=True)
450 ro_client = self.start_client(ro_server.address)
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600451
452 # Report a hash via the read-write server
453 taskhash = '35788efcb8dfb0a02659d81cf2bfd695fb30faf9'
454 outhash = '2765d4a5884be49b28601445c2760c5f21e7e5c0ee2b7e3fce98fd7e5970796f'
455 unihash = 'f46d3fbb439bd9b921095da657a4de906510d2cd'
456
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600457 result = rw_client.report_unihash(taskhash, self.METHOD, outhash, unihash)
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600458 self.assertEqual(result['unihash'], unihash, 'Server returned bad unihash')
459
460 # Check the hash via the read-only server
461 self.assertClientGetHash(ro_client, taskhash, unihash)
462
463 # Ensure that reporting via the read-only server fails
464 taskhash2 = 'c665584ee6817aa99edfc77a44dd853828279370'
465 outhash2 = '3c979c3db45c569f51ab7626a4651074be3a9d11a84b1db076f5b14f7d39db44'
466 unihash2 = '90e9bc1d1f094c51824adca7f8ea79a048d68824'
467
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600468 result = ro_client.report_unihash(taskhash2, self.METHOD, outhash2, unihash2)
469 self.assertEqual(result['unihash'], unihash2)
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600470
471 # Ensure that the database was not modified
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600472 self.assertClientGetHash(rw_client, taskhash2, None)
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600473
Brad Bishop19323692019-04-05 15:28:33 -0400474
Patrick Williams213cb262021-08-07 19:21:33 -0500475 def test_slow_server_start(self):
Andrew Geisslereff27472021-10-29 15:35:00 -0500476 # Ensures that the server will exit correctly even if it gets a SIGTERM
477 # before entering the main loop
Patrick Williams213cb262021-08-07 19:21:33 -0500478
479 event = multiprocessing.Event()
480
481 def prefunc(server, idx):
482 nonlocal event
483 server_prefunc(server, idx)
484 event.wait()
485
486 def do_nothing(signum, frame):
487 pass
488
489 old_signal = signal.signal(signal.SIGTERM, do_nothing)
490 self.addCleanup(signal.signal, signal.SIGTERM, old_signal)
491
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600492 server = self.start_server(prefunc=prefunc)
Patrick Williams213cb262021-08-07 19:21:33 -0500493 server.process.terminate()
494 time.sleep(30)
495 event.set()
496 server.process.join(300)
497 self.assertIsNotNone(server.process.exitcode, "Server did not exit in a timely manner!")
498
Andrew Geisslereff27472021-10-29 15:35:00 -0500499 def test_diverging_report_race(self):
500 # Tests that a reported task will correctly pick up an updated unihash
501
502 # This is a baseline report added to the database to ensure that there
503 # is something to match against as equivalent
504 outhash1 = 'afd11c366050bcd75ad763e898e4430e2a60659b26f83fbb22201a60672019fa'
505 taskhash1 = '3bde230c743fc45ab61a065d7a1815fbfa01c4740e4c895af2eb8dc0f684a4ab'
506 unihash1 = '3bde230c743fc45ab61a065d7a1815fbfa01c4740e4c895af2eb8dc0f684a4ab'
507 result = self.client.report_unihash(taskhash1, self.METHOD, outhash1, unihash1)
508
509 # Add a report that is equivalent to Task 1. It should ignore the
510 # provided unihash and report the unihash from task 1
511 taskhash2 = '6259ae8263bd94d454c086f501c37e64c4e83cae806902ca95b4ab513546b273'
512 unihash2 = taskhash2
513 result = self.client.report_unihash(taskhash2, self.METHOD, outhash1, unihash2)
514 self.assertEqual(result['unihash'], unihash1)
515
516 # Add another report for Task 2, but with a different outhash (e.g. the
517 # task is non-deterministic). It should still be marked with the Task 1
518 # unihash because it has the Task 2 taskhash, which is equivalent to
519 # Task 1
520 outhash3 = 'd2187ee3a8966db10b34fe0e863482288d9a6185cb8ef58a6c1c6ace87a2f24c'
521 result = self.client.report_unihash(taskhash2, self.METHOD, outhash3, unihash2)
522 self.assertEqual(result['unihash'], unihash1)
523
524
525 def test_diverging_report_reverse_race(self):
526 # Same idea as the previous test, but Tasks 2 and 3 are reported in
527 # reverse order the opposite order
528
529 outhash1 = 'afd11c366050bcd75ad763e898e4430e2a60659b26f83fbb22201a60672019fa'
530 taskhash1 = '3bde230c743fc45ab61a065d7a1815fbfa01c4740e4c895af2eb8dc0f684a4ab'
531 unihash1 = '3bde230c743fc45ab61a065d7a1815fbfa01c4740e4c895af2eb8dc0f684a4ab'
532 result = self.client.report_unihash(taskhash1, self.METHOD, outhash1, unihash1)
533
534 taskhash2 = '6259ae8263bd94d454c086f501c37e64c4e83cae806902ca95b4ab513546b273'
535 unihash2 = taskhash2
536
537 # Report Task 3 first. Since there is nothing else in the database it
538 # will use the client provided unihash
539 outhash3 = 'd2187ee3a8966db10b34fe0e863482288d9a6185cb8ef58a6c1c6ace87a2f24c'
540 result = self.client.report_unihash(taskhash2, self.METHOD, outhash3, unihash2)
541 self.assertEqual(result['unihash'], unihash2)
542
543 # Report Task 2. This is equivalent to Task 1 but there is already a mapping for
544 # taskhash2 so it will report unihash2
545 result = self.client.report_unihash(taskhash2, self.METHOD, outhash1, unihash2)
546 self.assertEqual(result['unihash'], unihash2)
547
548 # The originally reported unihash for Task 3 should be unchanged even if it
549 # shares a taskhash with Task 2
550 self.assertClientGetHash(self.client, taskhash2, unihash2)
Patrick Williams213cb262021-08-07 19:21:33 -0500551
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600552 def test_auth_read_perms(self):
553 admin_client = self.start_auth_server()
554
555 # Create hashes with non-authenticated server
556 taskhash, outhash, unihash = self.create_test_hash(self.client)
557
558 # Validate hash can be retrieved using authenticated client
559 with self.auth_perms("@read") as client:
560 self.assertClientGetHash(client, taskhash, unihash)
561
562 with self.auth_perms() as client, self.assertRaises(InvokeError):
563 self.assertClientGetHash(client, taskhash, unihash)
564
565 def test_auth_report_perms(self):
566 admin_client = self.start_auth_server()
567
568 # Without read permission, the user is completely denied
569 with self.auth_perms() as client, self.assertRaises(InvokeError):
570 self.create_test_hash(client)
571
572 # Read permission allows the call to succeed, but it doesn't record
573 # anythin in the database
574 with self.auth_perms("@read") as client:
575 taskhash, outhash, unihash = self.create_test_hash(client)
576 self.assertClientGetHash(client, taskhash, None)
577
578 # Report permission alone is insufficient
579 with self.auth_perms("@report") as client, self.assertRaises(InvokeError):
580 self.create_test_hash(client)
581
582 # Read and report permission actually modify the database
583 with self.auth_perms("@read", "@report") as client:
584 taskhash, outhash, unihash = self.create_test_hash(client)
585 self.assertClientGetHash(client, taskhash, unihash)
586
587 def test_auth_no_token_refresh_from_anon_user(self):
588 self.start_auth_server()
589
590 with self.start_client(self.auth_server_address) as client, self.assertRaises(InvokeError):
591 client.refresh_token()
592
593 def test_auth_self_token_refresh(self):
594 admin_client = self.start_auth_server()
595
596 # Create a new user with no permissions
597 user = self.create_user("test-user", [])
598
599 with self.auth_client(user) as client:
600 new_user = client.refresh_token()
601
602 self.assertEqual(user["username"], new_user["username"])
603 self.assertNotEqual(user["token"], new_user["token"])
604 self.assertUserCanAuth(new_user)
605 self.assertUserCannotAuth(user)
606
607 # Explicitly specifying with your own username is fine also
608 with self.auth_client(new_user) as client:
609 new_user2 = client.refresh_token(user["username"])
610
611 self.assertEqual(user["username"], new_user2["username"])
612 self.assertNotEqual(user["token"], new_user2["token"])
613 self.assertUserCanAuth(new_user2)
614 self.assertUserCannotAuth(new_user)
615 self.assertUserCannotAuth(user)
616
617 def test_auth_token_refresh(self):
618 admin_client = self.start_auth_server()
619
620 user = self.create_user("test-user", [])
621
622 with self.auth_perms() as client, self.assertRaises(InvokeError):
623 client.refresh_token(user["username"])
624
625 with self.auth_perms("@user-admin") as client:
626 new_user = client.refresh_token(user["username"])
627
628 self.assertEqual(user["username"], new_user["username"])
629 self.assertNotEqual(user["token"], new_user["token"])
630 self.assertUserCanAuth(new_user)
631 self.assertUserCannotAuth(user)
632
633 def test_auth_self_get_user(self):
634 admin_client = self.start_auth_server()
635
636 user = self.create_user("test-user", [])
637 user_info = user.copy()
638 del user_info["token"]
639
640 with self.auth_client(user) as client:
641 info = client.get_user()
642 self.assertEqual(info, user_info)
643
644 # Explicitly asking for your own username is fine also
645 info = client.get_user(user["username"])
646 self.assertEqual(info, user_info)
647
648 def test_auth_get_user(self):
649 admin_client = self.start_auth_server()
650
651 user = self.create_user("test-user", [])
652 user_info = user.copy()
653 del user_info["token"]
654
655 with self.auth_perms() as client, self.assertRaises(InvokeError):
656 client.get_user(user["username"])
657
658 with self.auth_perms("@user-admin") as client:
659 info = client.get_user(user["username"])
660 self.assertEqual(info, user_info)
661
662 info = client.get_user("nonexist-user")
663 self.assertIsNone(info)
664
665 def test_auth_reconnect(self):
666 admin_client = self.start_auth_server()
667
668 user = self.create_user("test-user", [])
669 user_info = user.copy()
670 del user_info["token"]
671
672 with self.auth_client(user) as client:
673 info = client.get_user()
674 self.assertEqual(info, user_info)
675
676 client.disconnect()
677
678 info = client.get_user()
679 self.assertEqual(info, user_info)
680
681 def test_auth_delete_user(self):
682 admin_client = self.start_auth_server()
683
684 user = self.create_user("test-user", [])
685
686 # self service
687 with self.auth_client(user) as client:
688 client.delete_user(user["username"])
689
690 self.assertIsNone(admin_client.get_user(user["username"]))
691 user = self.create_user("test-user", [])
692
693 with self.auth_perms() as client, self.assertRaises(InvokeError):
694 client.delete_user(user["username"])
695
696 with self.auth_perms("@user-admin") as client:
697 client.delete_user(user["username"])
698
699 # User doesn't exist, so even though the permission is correct, it's an
700 # error
701 with self.auth_perms("@user-admin") as client, self.assertRaises(InvokeError):
702 client.delete_user(user["username"])
703
704 def test_auth_set_user_perms(self):
705 admin_client = self.start_auth_server()
706
707 user = self.create_user("test-user", [])
708
709 self.assertUserPerms(user, [])
710
711 # No self service to change permissions
712 with self.auth_client(user) as client, self.assertRaises(InvokeError):
713 client.set_user_perms(user["username"], ["@all"])
714 self.assertUserPerms(user, [])
715
716 with self.auth_perms() as client, self.assertRaises(InvokeError):
717 client.set_user_perms(user["username"], ["@all"])
718 self.assertUserPerms(user, [])
719
720 with self.auth_perms("@user-admin") as client:
721 client.set_user_perms(user["username"], ["@all"])
722 self.assertUserPerms(user, sorted(list(ALL_PERMISSIONS)))
723
724 # Bad permissions
725 with self.auth_perms("@user-admin") as client, self.assertRaises(InvokeError):
726 client.set_user_perms(user["username"], ["@this-is-not-a-permission"])
727 self.assertUserPerms(user, sorted(list(ALL_PERMISSIONS)))
728
729 def test_auth_get_all_users(self):
730 admin_client = self.start_auth_server()
731
732 user = self.create_user("test-user", [])
733
734 with self.auth_client(user) as client, self.assertRaises(InvokeError):
735 client.get_all_users()
736
737 # Give the test user the correct permission
738 admin_client.set_user_perms(user["username"], ["@user-admin"])
739
740 with self.auth_client(user) as client:
741 all_users = client.get_all_users()
742
743 # Convert to a dictionary for easier comparison
744 all_users = {u["username"]: u for u in all_users}
745
746 self.assertEqual(all_users,
747 {
748 "admin": {
749 "username": "admin",
750 "permissions": sorted(list(ALL_PERMISSIONS)),
751 },
752 "test-user": {
753 "username": "test-user",
754 "permissions": ["@user-admin"],
755 }
756 }
757 )
758
759 def test_auth_new_user(self):
760 self.start_auth_server()
761
762 permissions = ["@read", "@report", "@db-admin", "@user-admin"]
763 permissions.sort()
764
765 with self.auth_perms() as client, self.assertRaises(InvokeError):
766 self.create_user("test-user", permissions, client=client)
767
768 with self.auth_perms("@user-admin") as client:
769 user = self.create_user("test-user", permissions, client=client)
770 self.assertIn("token", user)
771 self.assertEqual(user["username"], "test-user")
772 self.assertEqual(user["permissions"], permissions)
773
774 def test_auth_become_user(self):
775 admin_client = self.start_auth_server()
776
777 user = self.create_user("test-user", ["@read", "@report"])
778 user_info = user.copy()
779 del user_info["token"]
780
781 with self.auth_perms() as client, self.assertRaises(InvokeError):
782 client.become_user(user["username"])
783
784 with self.auth_perms("@user-admin") as client:
785 become = client.become_user(user["username"])
786 self.assertEqual(become, user_info)
787
788 info = client.get_user()
789 self.assertEqual(info, user_info)
790
791 # Verify become user is preserved across disconnect
792 client.disconnect()
793
794 info = client.get_user()
795 self.assertEqual(info, user_info)
796
797 # test-user doesn't have become_user permissions, so this should
798 # not work
799 with self.assertRaises(InvokeError):
800 client.become_user(user["username"])
801
802 # No self-service of become
803 with self.auth_client(user) as client, self.assertRaises(InvokeError):
804 client.become_user(user["username"])
805
806 # Give test user permissions to become
807 admin_client.set_user_perms(user["username"], ["@user-admin"])
808
809 # It's possible to become yourself (effectively a noop)
810 with self.auth_perms("@user-admin") as client:
811 become = client.become_user(client.username)
812
813 def test_get_db_usage(self):
814 usage = self.client.get_db_usage()
815
816 self.assertTrue(isinstance(usage, dict))
817 for name in usage.keys():
818 self.assertTrue(isinstance(usage[name], dict))
819 self.assertIn("rows", usage[name])
820 self.assertTrue(isinstance(usage[name]["rows"], int))
821
822 def test_get_db_query_columns(self):
823 columns = self.client.get_db_query_columns()
824
825 self.assertTrue(isinstance(columns, list))
826 self.assertTrue(len(columns) > 0)
827
828 for col in columns:
829 self.client.remove({col: ""})
830
831 def test_auth_is_owner(self):
832 admin_client = self.start_auth_server()
833
834 user = self.create_user("test-user", ["@read", "@report"])
835 with self.auth_client(user) as client:
836 taskhash, outhash, unihash = self.create_test_hash(client)
837 data = client.get_taskhash(self.METHOD, taskhash, True)
838 self.assertEqual(data["owner"], user["username"])
839
840
841class TestHashEquivalenceClient(HashEquivalenceTestSetup, unittest.TestCase):
842 def get_server_addr(self, server_idx):
843 return "unix://" + os.path.join(self.temp_dir.name, 'sock%d' % server_idx)
844
Patrick Williamsda295312023-12-05 16:48:56 -0600845 def test_get(self):
846 taskhash, outhash, unihash = self.create_test_hash(self.client)
847
848 p = self.run_hashclient(["--address", self.server_address, "get", self.METHOD, taskhash])
849 data = json.loads(p.stdout)
850 self.assertEqual(data["unihash"], unihash)
851 self.assertEqual(data["outhash"], outhash)
852 self.assertEqual(data["taskhash"], taskhash)
853 self.assertEqual(data["method"], self.METHOD)
854
855 def test_get_outhash(self):
856 taskhash, outhash, unihash = self.create_test_hash(self.client)
857
858 p = self.run_hashclient(["--address", self.server_address, "get-outhash", self.METHOD, outhash, taskhash])
859 data = json.loads(p.stdout)
860 self.assertEqual(data["unihash"], unihash)
861 self.assertEqual(data["outhash"], outhash)
862 self.assertEqual(data["taskhash"], taskhash)
863 self.assertEqual(data["method"], self.METHOD)
864
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600865 def test_stats(self):
866 p = self.run_hashclient(["--address", self.server_address, "stats"], check=True)
867 json.loads(p.stdout)
868
869 def test_stress(self):
870 self.run_hashclient(["--address", self.server_address, "stress"], check=True)
871
872 def test_remove_taskhash(self):
873 taskhash, outhash, unihash = self.create_test_hash(self.client)
874 self.run_hashclient([
875 "--address", self.server_address,
876 "remove",
877 "--where", "taskhash", taskhash,
878 ], check=True)
879 self.assertClientGetHash(self.client, taskhash, None)
880
881 result_outhash = self.client.get_outhash(self.METHOD, outhash, taskhash)
882 self.assertIsNone(result_outhash)
883
884 def test_remove_unihash(self):
885 taskhash, outhash, unihash = self.create_test_hash(self.client)
886 self.run_hashclient([
887 "--address", self.server_address,
888 "remove",
889 "--where", "unihash", unihash,
890 ], check=True)
891 self.assertClientGetHash(self.client, taskhash, None)
892
893 def test_remove_outhash(self):
894 taskhash, outhash, unihash = self.create_test_hash(self.client)
895 self.run_hashclient([
896 "--address", self.server_address,
897 "remove",
898 "--where", "outhash", outhash,
899 ], check=True)
900
901 result_outhash = self.client.get_outhash(self.METHOD, outhash, taskhash)
902 self.assertIsNone(result_outhash)
903
904 def test_remove_method(self):
905 taskhash, outhash, unihash = self.create_test_hash(self.client)
906 self.run_hashclient([
907 "--address", self.server_address,
908 "remove",
909 "--where", "method", self.METHOD,
910 ], check=True)
911 self.assertClientGetHash(self.client, taskhash, None)
912
913 result_outhash = self.client.get_outhash(self.METHOD, outhash, taskhash)
914 self.assertIsNone(result_outhash)
915
916 def test_clean_unused(self):
917 taskhash, outhash, unihash = self.create_test_hash(self.client)
918
919 # Clean the database, which should not remove anything because all hashes an in-use
920 self.run_hashclient([
921 "--address", self.server_address,
922 "clean-unused", "0",
923 ], check=True)
924 self.assertClientGetHash(self.client, taskhash, unihash)
925
926 # Remove the unihash. The row in the outhash table should still be present
927 self.run_hashclient([
928 "--address", self.server_address,
929 "remove",
930 "--where", "unihash", unihash,
931 ], check=True)
932 result_outhash = self.client.get_outhash(self.METHOD, outhash, taskhash, False)
933 self.assertIsNotNone(result_outhash)
934
935 # Now clean with no minimum age which will remove the outhash
936 self.run_hashclient([
937 "--address", self.server_address,
938 "clean-unused", "0",
939 ], check=True)
940 result_outhash = self.client.get_outhash(self.METHOD, outhash, taskhash, False)
941 self.assertIsNone(result_outhash)
942
943 def test_refresh_token(self):
944 admin_client = self.start_auth_server()
945
946 user = admin_client.new_user("test-user", ["@read", "@report"])
947
948 p = self.run_hashclient([
949 "--address", self.auth_server_address,
950 "--login", user["username"],
951 "--password", user["token"],
952 "refresh-token"
953 ], check=True)
954
955 new_token = None
956 for l in p.stdout.splitlines():
957 l = l.rstrip()
958 m = re.match(r'Token: +(.*)$', l)
959 if m is not None:
960 new_token = m.group(1)
961
962 self.assertTrue(new_token)
963
964 print("New token is %r" % new_token)
965
966 self.run_hashclient([
967 "--address", self.auth_server_address,
968 "--login", user["username"],
969 "--password", new_token,
970 "get-user"
971 ], check=True)
972
973 def test_set_user_perms(self):
974 admin_client = self.start_auth_server()
975
976 user = admin_client.new_user("test-user", ["@read"])
977
978 self.run_hashclient([
979 "--address", self.auth_server_address,
980 "--login", admin_client.username,
981 "--password", admin_client.password,
982 "set-user-perms",
983 "-u", user["username"],
984 "@read", "@report",
985 ], check=True)
986
987 new_user = admin_client.get_user(user["username"])
988
989 self.assertEqual(set(new_user["permissions"]), {"@read", "@report"})
990
991 def test_get_user(self):
992 admin_client = self.start_auth_server()
993
994 user = admin_client.new_user("test-user", ["@read"])
995
996 p = self.run_hashclient([
997 "--address", self.auth_server_address,
998 "--login", admin_client.username,
999 "--password", admin_client.password,
1000 "get-user",
1001 "-u", user["username"],
1002 ], check=True)
1003
1004 self.assertIn("Username:", p.stdout)
1005 self.assertIn("Permissions:", p.stdout)
1006
1007 p = self.run_hashclient([
1008 "--address", self.auth_server_address,
1009 "--login", user["username"],
1010 "--password", user["token"],
1011 "get-user",
1012 ], check=True)
1013
1014 self.assertIn("Username:", p.stdout)
1015 self.assertIn("Permissions:", p.stdout)
1016
1017 def test_get_all_users(self):
1018 admin_client = self.start_auth_server()
1019
1020 admin_client.new_user("test-user1", ["@read"])
1021 admin_client.new_user("test-user2", ["@read"])
1022
1023 p = self.run_hashclient([
1024 "--address", self.auth_server_address,
1025 "--login", admin_client.username,
1026 "--password", admin_client.password,
1027 "get-all-users",
1028 ], check=True)
1029
1030 self.assertIn("admin", p.stdout)
1031 self.assertIn("test-user1", p.stdout)
1032 self.assertIn("test-user2", p.stdout)
1033
1034 def test_new_user(self):
1035 admin_client = self.start_auth_server()
1036
1037 p = self.run_hashclient([
1038 "--address", self.auth_server_address,
1039 "--login", admin_client.username,
1040 "--password", admin_client.password,
1041 "new-user",
1042 "-u", "test-user",
1043 "@read", "@report",
1044 ], check=True)
1045
1046 new_token = None
1047 for l in p.stdout.splitlines():
1048 l = l.rstrip()
1049 m = re.match(r'Token: +(.*)$', l)
1050 if m is not None:
1051 new_token = m.group(1)
1052
1053 self.assertTrue(new_token)
1054
1055 user = {
1056 "username": "test-user",
1057 "token": new_token,
1058 }
1059
1060 self.assertUserPerms(user, ["@read", "@report"])
1061
1062 def test_delete_user(self):
1063 admin_client = self.start_auth_server()
1064
1065 user = admin_client.new_user("test-user", ["@read"])
1066
1067 p = self.run_hashclient([
1068 "--address", self.auth_server_address,
1069 "--login", admin_client.username,
1070 "--password", admin_client.password,
1071 "delete-user",
1072 "-u", user["username"],
1073 ], check=True)
1074
1075 self.assertIsNone(admin_client.get_user(user["username"]))
1076
1077 def test_get_db_usage(self):
1078 p = self.run_hashclient([
1079 "--address", self.server_address,
1080 "get-db-usage",
1081 ], check=True)
1082
1083 def test_get_db_query_columns(self):
1084 p = self.run_hashclient([
1085 "--address", self.server_address,
1086 "get-db-query-columns",
1087 ], check=True)
1088
1089
Andrew Geissler09209ee2020-12-13 08:44:15 -06001090class TestHashEquivalenceUnixServer(HashEquivalenceTestSetup, HashEquivalenceCommonTests, unittest.TestCase):
Andrew Geissler6ce62a22020-11-30 19:58:47 -06001091 def get_server_addr(self, server_idx):
1092 return "unix://" + os.path.join(self.temp_dir.name, 'sock%d' % server_idx)
Brad Bishopa34c0302019-09-23 22:34:48 -04001093
1094
Andrew Geissler09209ee2020-12-13 08:44:15 -06001095class TestHashEquivalenceUnixServerLongPath(HashEquivalenceTestSetup, unittest.TestCase):
1096 DEEP_DIRECTORY = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ccccccccccccccccccccccccccccccccccccccccccc"
1097 def get_server_addr(self, server_idx):
1098 os.makedirs(os.path.join(self.temp_dir.name, self.DEEP_DIRECTORY), exist_ok=True)
1099 return "unix://" + os.path.join(self.temp_dir.name, self.DEEP_DIRECTORY, 'sock%d' % server_idx)
1100
1101
1102 def test_long_sock_path(self):
1103 # Simple test that hashes can be created
1104 taskhash = '35788efcb8dfb0a02659d81cf2bfd695fb30faf9'
1105 outhash = '2765d4a5884be49b28601445c2760c5f21e7e5c0ee2b7e3fce98fd7e5970796f'
1106 unihash = 'f46d3fbb439bd9b921095da657a4de906510d2cd'
1107
1108 self.assertClientGetHash(self.client, taskhash, None)
1109
1110 result = self.client.report_unihash(taskhash, self.METHOD, outhash, unihash)
1111 self.assertEqual(result['unihash'], unihash, 'Server returned bad unihash')
1112
1113
1114class TestHashEquivalenceTCPServer(HashEquivalenceTestSetup, HashEquivalenceCommonTests, unittest.TestCase):
Andrew Geissler6ce62a22020-11-30 19:58:47 -06001115 def get_server_addr(self, server_idx):
Andrew Geisslerc3d88e42020-10-02 09:45:00 -05001116 # Some hosts cause asyncio module to misbehave, when IPv6 is not enabled.
1117 # If IPv6 is enabled, it should be safe to use localhost directly, in general
1118 # case it is more reliable to resolve the IP address explicitly.
1119 return socket.gethostbyname("localhost") + ":0"
Patrick Williamsac13d5f2023-11-24 18:59:46 -06001120
1121
1122class TestHashEquivalenceWebsocketServer(HashEquivalenceTestSetup, HashEquivalenceCommonTests, unittest.TestCase):
1123 def setUp(self):
1124 try:
1125 import websockets
1126 except ImportError as e:
1127 self.skipTest(str(e))
1128
1129 super().setUp()
1130
1131 def get_server_addr(self, server_idx):
1132 # Some hosts cause asyncio module to misbehave, when IPv6 is not enabled.
1133 # If IPv6 is enabled, it should be safe to use localhost directly, in general
1134 # case it is more reliable to resolve the IP address explicitly.
1135 host = socket.gethostbyname("localhost")
1136 return "ws://%s:0" % host
1137
1138
1139class TestHashEquivalenceWebsocketsSQLAlchemyServer(TestHashEquivalenceWebsocketServer):
1140 def setUp(self):
1141 try:
1142 import sqlalchemy
1143 import aiosqlite
1144 except ImportError as e:
1145 self.skipTest(str(e))
1146
1147 super().setUp()
1148
1149 def make_dbpath(self):
1150 return "sqlite+aiosqlite:///%s" % os.path.join(self.temp_dir.name, "db%d.sqlite" % self.server_index)
1151
1152
1153class TestHashEquivalenceExternalServer(HashEquivalenceTestSetup, HashEquivalenceCommonTests, unittest.TestCase):
1154 def get_env(self, name):
1155 v = os.environ.get(name)
1156 if not v:
1157 self.skipTest(f'{name} not defined to test an external server')
1158 return v
1159
1160 def start_test_server(self):
1161 return self.get_env('BB_TEST_HASHSERV')
1162
1163 def start_server(self, *args, **kwargs):
1164 self.skipTest('Cannot start local server when testing external servers')
1165
1166 def start_auth_server(self):
1167
1168 self.auth_server_address = self.server_address
1169 self.admin_client = self.start_client(
1170 self.server_address,
1171 username=self.get_env('BB_TEST_HASHSERV_USERNAME'),
1172 password=self.get_env('BB_TEST_HASHSERV_PASSWORD'),
1173 )
1174 return self.admin_client
1175
1176 def setUp(self):
1177 super().setUp()
1178 if "BB_TEST_HASHSERV_USERNAME" in os.environ:
1179 self.client = self.start_client(
1180 self.server_address,
1181 username=os.environ["BB_TEST_HASHSERV_USERNAME"],
1182 password=os.environ["BB_TEST_HASHSERV_PASSWORD"],
1183 )
1184 self.client.remove({"method": self.METHOD})
1185
1186 def tearDown(self):
1187 self.client.remove({"method": self.METHOD})
1188 super().tearDown()
1189
1190
1191 def test_auth_get_all_users(self):
1192 self.skipTest("Cannot test all users with external server")
1193