blob: 0809453cf87fc215b1c22dcd40e0d60269e239be [file] [log] [blame]
#! /usr/bin/env python3
#
# Copyright (C) 2018-2019 Garmin Ltd.
#
# SPDX-License-Identifier: GPL-2.0-only
#
from . import create_server, create_client
from .server import DEFAULT_ANON_PERMS, ALL_PERMISSIONS
from bb.asyncrpc import InvokeError
from .client import ClientPool
import hashlib
import logging
import multiprocessing
import os
import sys
import tempfile
import threading
import unittest
import socket
import time
import signal
import subprocess
import json
import re
from pathlib import Path
THIS_DIR = Path(__file__).parent
BIN_DIR = THIS_DIR.parent.parent / "bin"
def server_prefunc(server, idx):
logging.basicConfig(level=logging.DEBUG, filename='bbhashserv-%d.log' % idx, filemode='w',
format='%(levelname)s %(filename)s:%(lineno)d %(message)s')
server.logger.debug("Running server %d" % idx)
sys.stdout = open('bbhashserv-stdout-%d.log' % idx, 'w')
sys.stderr = sys.stdout
class HashEquivalenceTestSetup(object):
METHOD = 'TestMethod'
server_index = 0
client_index = 0
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):
self.server_index += 1
if dbpath is None:
dbpath = self.make_dbpath()
def cleanup_server(server):
if server.process.exitcode is not None:
return
server.process.terminate()
server.process.join()
server = create_server(self.get_server_addr(self.server_index),
dbpath,
upstream=upstream,
read_only=read_only,
anon_perms=anon_perms,
admin_username=admin_username,
admin_password=admin_password)
server.dbpath = dbpath
server.serve_as_process(prefunc=prefunc, args=(self.server_index,))
self.addCleanup(cleanup_server, server)
return server
def make_dbpath(self):
return os.path.join(self.temp_dir.name, "db%d.sqlite" % self.server_index)
def start_client(self, server_address, username=None, password=None):
def cleanup_client(client):
client.close()
client = create_client(server_address, username=username, password=password)
self.addCleanup(cleanup_client, client)
return client
def start_test_server(self):
self.server = self.start_server()
return self.server.address
def start_auth_server(self):
auth_server = self.start_server(self.server.dbpath, anon_perms=[], admin_username="admin", admin_password="password")
self.auth_server_address = auth_server.address
self.admin_client = self.start_client(auth_server.address, username="admin", password="password")
return self.admin_client
def auth_client(self, user):
return self.start_client(self.auth_server_address, user["username"], user["token"])
def setUp(self):
if sys.version_info < (3, 5, 0):
self.skipTest('Python 3.5 or later required')
self.temp_dir = tempfile.TemporaryDirectory(prefix='bb-hashserv')
self.addCleanup(self.temp_dir.cleanup)
self.server_address = self.start_test_server()
self.client = self.start_client(self.server_address)
def assertClientGetHash(self, client, taskhash, unihash):
result = client.get_unihash(self.METHOD, taskhash)
self.assertEqual(result, unihash)
def assertUserPerms(self, user, permissions):
with self.auth_client(user) as client:
info = client.get_user()
self.assertEqual(info, {
"username": user["username"],
"permissions": permissions,
})
def assertUserCanAuth(self, user):
with self.start_client(self.auth_server_address) as client:
client.auth(user["username"], user["token"])
def assertUserCannotAuth(self, user):
with self.start_client(self.auth_server_address) as client, self.assertRaises(InvokeError):
client.auth(user["username"], user["token"])
def create_test_hash(self, client):
# Simple test that hashes can be created
taskhash = '35788efcb8dfb0a02659d81cf2bfd695fb30faf9'
outhash = '2765d4a5884be49b28601445c2760c5f21e7e5c0ee2b7e3fce98fd7e5970796f'
unihash = 'f46d3fbb439bd9b921095da657a4de906510d2cd'
self.assertClientGetHash(client, taskhash, None)
result = client.report_unihash(taskhash, self.METHOD, outhash, unihash)
self.assertEqual(result['unihash'], unihash, 'Server returned bad unihash')
return taskhash, outhash, unihash
def run_hashclient(self, args, **kwargs):
try:
p = subprocess.run(
[BIN_DIR / "bitbake-hashclient"] + args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
encoding="utf-8",
**kwargs
)
except subprocess.CalledProcessError as e:
print(e.output)
raise e
print(p.stdout)
return p
class HashEquivalenceCommonTests(object):
def auth_perms(self, *permissions):
self.client_index += 1
user = self.create_user(f"user-{self.client_index}", permissions)
return self.auth_client(user)
def create_user(self, username, permissions, *, client=None):
def remove_user(username):
try:
self.admin_client.delete_user(username)
except bb.asyncrpc.InvokeError:
pass
if client is None:
client = self.admin_client
user = client.new_user(username, permissions)
self.addCleanup(remove_user, username)
return user
def test_create_hash(self):
return self.create_test_hash(self.client)
def test_create_equivalent(self):
# Tests that a second reported task with the same outhash will be
# assigned the same unihash
taskhash = '53b8dce672cb6d0c73170be43f540460bfc347b4'
outhash = '5a9cb1649625f0bf41fc7791b635cd9c2d7118c7f021ba87dcd03f72b67ce7a8'
unihash = 'f37918cc02eb5a520b1aff86faacbc0a38124646'
result = self.client.report_unihash(taskhash, self.METHOD, outhash, unihash)
self.assertEqual(result['unihash'], unihash, 'Server returned bad unihash')
# Report a different task with the same outhash. The returned unihash
# should match the first task
taskhash2 = '3bf6f1e89d26205aec90da04854fbdbf73afe6b4'
unihash2 = 'af36b199320e611fbb16f1f277d3ee1d619ca58b'
result = self.client.report_unihash(taskhash2, self.METHOD, outhash, unihash2)
self.assertEqual(result['unihash'], unihash, 'Server returned bad unihash')
def test_duplicate_taskhash(self):
# Tests that duplicate reports of the same taskhash with different
# outhash & unihash always return the unihash from the first reported
# taskhash
taskhash = '8aa96fcffb5831b3c2c0cb75f0431e3f8b20554a'
outhash = 'afe240a439959ce86f5e322f8c208e1fedefea9e813f2140c81af866cc9edf7e'
unihash = '218e57509998197d570e2c98512d0105985dffc9'
self.client.report_unihash(taskhash, self.METHOD, outhash, unihash)
self.assertClientGetHash(self.client, taskhash, unihash)
outhash2 = '0904a7fe3dc712d9fd8a74a616ddca2a825a8ee97adf0bd3fc86082c7639914d'
unihash2 = 'ae9a7d252735f0dafcdb10e2e02561ca3a47314c'
self.client.report_unihash(taskhash, self.METHOD, outhash2, unihash2)
self.assertClientGetHash(self.client, taskhash, unihash)
outhash3 = '77623a549b5b1a31e3732dfa8fe61d7ce5d44b3370f253c5360e136b852967b4'
unihash3 = '9217a7d6398518e5dc002ed58f2cbbbc78696603'
self.client.report_unihash(taskhash, self.METHOD, outhash3, unihash3)
self.assertClientGetHash(self.client, taskhash, unihash)
def test_remove_taskhash(self):
taskhash, outhash, unihash = self.create_test_hash(self.client)
result = self.client.remove({"taskhash": taskhash})
self.assertGreater(result["count"], 0)
self.assertClientGetHash(self.client, taskhash, None)
result_outhash = self.client.get_outhash(self.METHOD, outhash, taskhash)
self.assertIsNone(result_outhash)
def test_remove_unihash(self):
taskhash, outhash, unihash = self.create_test_hash(self.client)
result = self.client.remove({"unihash": unihash})
self.assertGreater(result["count"], 0)
self.assertClientGetHash(self.client, taskhash, None)
def test_remove_outhash(self):
taskhash, outhash, unihash = self.create_test_hash(self.client)
result = self.client.remove({"outhash": outhash})
self.assertGreater(result["count"], 0)
result_outhash = self.client.get_outhash(self.METHOD, outhash, taskhash)
self.assertIsNone(result_outhash)
def test_remove_method(self):
taskhash, outhash, unihash = self.create_test_hash(self.client)
result = self.client.remove({"method": self.METHOD})
self.assertGreater(result["count"], 0)
self.assertClientGetHash(self.client, taskhash, None)
result_outhash = self.client.get_outhash(self.METHOD, outhash, taskhash)
self.assertIsNone(result_outhash)
def test_clean_unused(self):
taskhash, outhash, unihash = self.create_test_hash(self.client)
# Clean the database, which should not remove anything because all hashes an in-use
result = self.client.clean_unused(0)
self.assertEqual(result["count"], 0)
self.assertClientGetHash(self.client, taskhash, unihash)
# Remove the unihash. The row in the outhash table should still be present
self.client.remove({"unihash": unihash})
result_outhash = self.client.get_outhash(self.METHOD, outhash, taskhash, False)
self.assertIsNotNone(result_outhash)
# Now clean with no minimum age which will remove the outhash
result = self.client.clean_unused(0)
self.assertEqual(result["count"], 1)
result_outhash = self.client.get_outhash(self.METHOD, outhash, taskhash, False)
self.assertIsNone(result_outhash)
def test_huge_message(self):
# Simple test that hashes can be created
taskhash = 'c665584ee6817aa99edfc77a44dd853828279370'
outhash = '3c979c3db45c569f51ab7626a4651074be3a9d11a84b1db076f5b14f7d39db44'
unihash = '90e9bc1d1f094c51824adca7f8ea79a048d68824'
self.assertClientGetHash(self.client, taskhash, None)
siginfo = "0" * (self.client.max_chunk * 4)
result = self.client.report_unihash(taskhash, self.METHOD, outhash, unihash, {
'outhash_siginfo': siginfo
})
self.assertEqual(result['unihash'], unihash, 'Server returned bad unihash')
result_unihash = self.client.get_taskhash(self.METHOD, taskhash, True)
self.assertEqual(result_unihash['taskhash'], taskhash)
self.assertEqual(result_unihash['unihash'], unihash)
self.assertEqual(result_unihash['method'], self.METHOD)
result_outhash = self.client.get_outhash(self.METHOD, outhash, taskhash)
self.assertEqual(result_outhash['taskhash'], taskhash)
self.assertEqual(result_outhash['method'], self.METHOD)
self.assertEqual(result_outhash['unihash'], unihash)
self.assertEqual(result_outhash['outhash'], outhash)
self.assertEqual(result_outhash['outhash_siginfo'], siginfo)
def test_stress(self):
def query_server(failures):
client = Client(self.server_address)
try:
for i in range(1000):
taskhash = hashlib.sha256()
taskhash.update(str(i).encode('utf-8'))
taskhash = taskhash.hexdigest()
result = client.get_unihash(self.METHOD, taskhash)
if result != taskhash:
failures.append("taskhash mismatch: %s != %s" % (result, taskhash))
finally:
client.close()
# Report hashes
for i in range(1000):
taskhash = hashlib.sha256()
taskhash.update(str(i).encode('utf-8'))
taskhash = taskhash.hexdigest()
self.client.report_unihash(taskhash, self.METHOD, taskhash, taskhash)
failures = []
threads = [threading.Thread(target=query_server, args=(failures,)) for t in range(100)]
for t in threads:
t.start()
for t in threads:
t.join()
self.assertFalse(failures)
def test_upstream_server(self):
# Tests upstream server support. This is done by creating two servers
# that share a database file. The downstream server has it upstream
# set to the test server, whereas the side server doesn't. This allows
# verification that the hash requests are being proxied to the upstream
# server by verifying that they appear on the downstream client, but not
# the side client. It also verifies that the results are pulled into
# the downstream database by checking that the downstream and side servers
# match after the downstream is done waiting for all backfill tasks
down_server = self.start_server(upstream=self.server_address)
down_client = self.start_client(down_server.address)
side_server = self.start_server(dbpath=down_server.dbpath)
side_client = self.start_client(side_server.address)
def check_hash(taskhash, unihash, old_sidehash):
nonlocal down_client
nonlocal side_client
# check upstream server
self.assertClientGetHash(self.client, taskhash, unihash)
# Hash should *not* be present on the side server
self.assertClientGetHash(side_client, taskhash, old_sidehash)
# Hash should be present on the downstream server, since it
# will defer to the upstream server. This will trigger
# the backfill in the downstream server
self.assertClientGetHash(down_client, taskhash, unihash)
# After waiting for the downstream client to finish backfilling the
# task from the upstream server, it should appear in the side server
# since the database is populated
down_client.backfill_wait()
self.assertClientGetHash(side_client, taskhash, unihash)
# Basic report
taskhash = '8aa96fcffb5831b3c2c0cb75f0431e3f8b20554a'
outhash = 'afe240a439959ce86f5e322f8c208e1fedefea9e813f2140c81af866cc9edf7e'
unihash = '218e57509998197d570e2c98512d0105985dffc9'
self.client.report_unihash(taskhash, self.METHOD, outhash, unihash)
check_hash(taskhash, unihash, None)
# Duplicated taskhash with multiple output hashes and unihashes.
# All servers should agree with the originally reported hash
outhash2 = '0904a7fe3dc712d9fd8a74a616ddca2a825a8ee97adf0bd3fc86082c7639914d'
unihash2 = 'ae9a7d252735f0dafcdb10e2e02561ca3a47314c'
self.client.report_unihash(taskhash, self.METHOD, outhash2, unihash2)
check_hash(taskhash, unihash, unihash)
# Report an equivalent task. The sideload will originally report
# no unihash until backfilled
taskhash3 = "044c2ec8aaf480685a00ff6ff49e6162e6ad34e1"
unihash3 = "def64766090d28f627e816454ed46894bb3aab36"
self.client.report_unihash(taskhash3, self.METHOD, outhash, unihash3)
check_hash(taskhash3, unihash, None)
# Test that reporting a unihash in the downstream client isn't
# propagating to the upstream server
taskhash4 = "e3da00593d6a7fb435c7e2114976c59c5fd6d561"
outhash4 = "1cf8713e645f491eb9c959d20b5cae1c47133a292626dda9b10709857cbe688a"
unihash4 = "3b5d3d83f07f259e9086fcb422c855286e18a57d"
down_client.report_unihash(taskhash4, self.METHOD, outhash4, unihash4)
down_client.backfill_wait()
self.assertClientGetHash(down_client, taskhash4, unihash4)
self.assertClientGetHash(side_client, taskhash4, unihash4)
self.assertClientGetHash(self.client, taskhash4, None)
# Test that reporting a unihash in the downstream is able to find a
# match which was previously reported to the upstream server
taskhash5 = '35788efcb8dfb0a02659d81cf2bfd695fb30faf9'
outhash5 = '2765d4a5884be49b28601445c2760c5f21e7e5c0ee2b7e3fce98fd7e5970796f'
unihash5 = 'f46d3fbb439bd9b921095da657a4de906510d2cd'
result = self.client.report_unihash(taskhash5, self.METHOD, outhash5, unihash5)
taskhash6 = '35788efcb8dfb0a02659d81cf2bfd695fb30fafa'
unihash6 = 'f46d3fbb439bd9b921095da657a4de906510d2ce'
result = down_client.report_unihash(taskhash6, self.METHOD, outhash5, unihash6)
self.assertEqual(result['unihash'], unihash5, 'Server failed to copy unihash from upstream')
# Tests read through from server with
taskhash7 = '9d81d76242cc7cfaf7bf74b94b9cd2e29324ed74'
outhash7 = '8470d56547eea6236d7c81a644ce74670ca0bbda998e13c629ef6bb3f0d60b69'
unihash7 = '05d2a63c81e32f0a36542ca677e8ad852365c538'
self.client.report_unihash(taskhash7, self.METHOD, outhash7, unihash7)
result = down_client.get_taskhash(self.METHOD, taskhash7, True)
self.assertEqual(result['unihash'], unihash7, 'Server failed to copy unihash from upstream')
self.assertEqual(result['outhash'], outhash7, 'Server failed to copy unihash from upstream')
self.assertEqual(result['taskhash'], taskhash7, 'Server failed to copy unihash from upstream')
self.assertEqual(result['method'], self.METHOD)
taskhash8 = '86978a4c8c71b9b487330b0152aade10c1ee58aa'
outhash8 = 'ca8c128e9d9e4a28ef24d0508aa20b5cf880604eacd8f65c0e366f7e0cc5fbcf'
unihash8 = 'd8bcf25369d40590ad7d08c84d538982f2023e01'
self.client.report_unihash(taskhash8, self.METHOD, outhash8, unihash8)
result = down_client.get_outhash(self.METHOD, outhash8, taskhash8)
self.assertEqual(result['unihash'], unihash8, 'Server failed to copy unihash from upstream')
self.assertEqual(result['outhash'], outhash8, 'Server failed to copy unihash from upstream')
self.assertEqual(result['taskhash'], taskhash8, 'Server failed to copy unihash from upstream')
self.assertEqual(result['method'], self.METHOD)
taskhash9 = 'ae6339531895ddf5b67e663e6a374ad8ec71d81c'
outhash9 = 'afc78172c81880ae10a1fec994b5b4ee33d196a001a1b66212a15ebe573e00b5'
unihash9 = '6662e699d6e3d894b24408ff9a4031ef9b038ee8'
self.client.report_unihash(taskhash9, self.METHOD, outhash9, unihash9)
result = down_client.get_taskhash(self.METHOD, taskhash9, False)
self.assertEqual(result['unihash'], unihash9, 'Server failed to copy unihash from upstream')
self.assertEqual(result['taskhash'], taskhash9, 'Server failed to copy unihash from upstream')
self.assertEqual(result['method'], self.METHOD)
def test_unihash_exsits(self):
taskhash, outhash, unihash = self.create_test_hash(self.client)
self.assertTrue(self.client.unihash_exists(unihash))
self.assertFalse(self.client.unihash_exists('6662e699d6e3d894b24408ff9a4031ef9b038ee8'))
def test_ro_server(self):
rw_server = self.start_server()
rw_client = self.start_client(rw_server.address)
ro_server = self.start_server(dbpath=rw_server.dbpath, read_only=True)
ro_client = self.start_client(ro_server.address)
# Report a hash via the read-write server
taskhash = '35788efcb8dfb0a02659d81cf2bfd695fb30faf9'
outhash = '2765d4a5884be49b28601445c2760c5f21e7e5c0ee2b7e3fce98fd7e5970796f'
unihash = 'f46d3fbb439bd9b921095da657a4de906510d2cd'
result = rw_client.report_unihash(taskhash, self.METHOD, outhash, unihash)
self.assertEqual(result['unihash'], unihash, 'Server returned bad unihash')
# Check the hash via the read-only server
self.assertClientGetHash(ro_client, taskhash, unihash)
# Ensure that reporting via the read-only server fails
taskhash2 = 'c665584ee6817aa99edfc77a44dd853828279370'
outhash2 = '3c979c3db45c569f51ab7626a4651074be3a9d11a84b1db076f5b14f7d39db44'
unihash2 = '90e9bc1d1f094c51824adca7f8ea79a048d68824'
result = ro_client.report_unihash(taskhash2, self.METHOD, outhash2, unihash2)
self.assertEqual(result['unihash'], unihash2)
# Ensure that the database was not modified
self.assertClientGetHash(rw_client, taskhash2, None)
def test_slow_server_start(self):
# Ensures that the server will exit correctly even if it gets a SIGTERM
# before entering the main loop
event = multiprocessing.Event()
def prefunc(server, idx):
nonlocal event
server_prefunc(server, idx)
event.wait()
def do_nothing(signum, frame):
pass
old_signal = signal.signal(signal.SIGTERM, do_nothing)
self.addCleanup(signal.signal, signal.SIGTERM, old_signal)
server = self.start_server(prefunc=prefunc)
server.process.terminate()
time.sleep(30)
event.set()
server.process.join(300)
self.assertIsNotNone(server.process.exitcode, "Server did not exit in a timely manner!")
def test_diverging_report_race(self):
# Tests that a reported task will correctly pick up an updated unihash
# This is a baseline report added to the database to ensure that there
# is something to match against as equivalent
outhash1 = 'afd11c366050bcd75ad763e898e4430e2a60659b26f83fbb22201a60672019fa'
taskhash1 = '3bde230c743fc45ab61a065d7a1815fbfa01c4740e4c895af2eb8dc0f684a4ab'
unihash1 = '3bde230c743fc45ab61a065d7a1815fbfa01c4740e4c895af2eb8dc0f684a4ab'
result = self.client.report_unihash(taskhash1, self.METHOD, outhash1, unihash1)
# Add a report that is equivalent to Task 1. It should ignore the
# provided unihash and report the unihash from task 1
taskhash2 = '6259ae8263bd94d454c086f501c37e64c4e83cae806902ca95b4ab513546b273'
unihash2 = taskhash2
result = self.client.report_unihash(taskhash2, self.METHOD, outhash1, unihash2)
self.assertEqual(result['unihash'], unihash1)
# Add another report for Task 2, but with a different outhash (e.g. the
# task is non-deterministic). It should still be marked with the Task 1
# unihash because it has the Task 2 taskhash, which is equivalent to
# Task 1
outhash3 = 'd2187ee3a8966db10b34fe0e863482288d9a6185cb8ef58a6c1c6ace87a2f24c'
result = self.client.report_unihash(taskhash2, self.METHOD, outhash3, unihash2)
self.assertEqual(result['unihash'], unihash1)
def test_diverging_report_reverse_race(self):
# Same idea as the previous test, but Tasks 2 and 3 are reported in
# reverse order the opposite order
outhash1 = 'afd11c366050bcd75ad763e898e4430e2a60659b26f83fbb22201a60672019fa'
taskhash1 = '3bde230c743fc45ab61a065d7a1815fbfa01c4740e4c895af2eb8dc0f684a4ab'
unihash1 = '3bde230c743fc45ab61a065d7a1815fbfa01c4740e4c895af2eb8dc0f684a4ab'
result = self.client.report_unihash(taskhash1, self.METHOD, outhash1, unihash1)
taskhash2 = '6259ae8263bd94d454c086f501c37e64c4e83cae806902ca95b4ab513546b273'
unihash2 = taskhash2
# Report Task 3 first. Since there is nothing else in the database it
# will use the client provided unihash
outhash3 = 'd2187ee3a8966db10b34fe0e863482288d9a6185cb8ef58a6c1c6ace87a2f24c'
result = self.client.report_unihash(taskhash2, self.METHOD, outhash3, unihash2)
self.assertEqual(result['unihash'], unihash2)
# Report Task 2. This is equivalent to Task 1 but there is already a mapping for
# taskhash2 so it will report unihash2
result = self.client.report_unihash(taskhash2, self.METHOD, outhash1, unihash2)
self.assertEqual(result['unihash'], unihash2)
# The originally reported unihash for Task 3 should be unchanged even if it
# shares a taskhash with Task 2
self.assertClientGetHash(self.client, taskhash2, unihash2)
def test_client_pool_get_unihashes(self):
TEST_INPUT = (
# taskhash outhash unihash
('8aa96fcffb5831b3c2c0cb75f0431e3f8b20554a', 'afe240a439959ce86f5e322f8c208e1fedefea9e813f2140c81af866cc9edf7e','218e57509998197d570e2c98512d0105985dffc9'),
# Duplicated taskhash with multiple output hashes and unihashes.
('8aa96fcffb5831b3c2c0cb75f0431e3f8b20554a', '0904a7fe3dc712d9fd8a74a616ddca2a825a8ee97adf0bd3fc86082c7639914d', 'ae9a7d252735f0dafcdb10e2e02561ca3a47314c'),
# Equivalent hash
("044c2ec8aaf480685a00ff6ff49e6162e6ad34e1", '0904a7fe3dc712d9fd8a74a616ddca2a825a8ee97adf0bd3fc86082c7639914d', "def64766090d28f627e816454ed46894bb3aab36"),
("e3da00593d6a7fb435c7e2114976c59c5fd6d561", "1cf8713e645f491eb9c959d20b5cae1c47133a292626dda9b10709857cbe688a", "3b5d3d83f07f259e9086fcb422c855286e18a57d"),
('35788efcb8dfb0a02659d81cf2bfd695fb30faf9', '2765d4a5884be49b28601445c2760c5f21e7e5c0ee2b7e3fce98fd7e5970796f', 'f46d3fbb439bd9b921095da657a4de906510d2cd'),
('35788efcb8dfb0a02659d81cf2bfd695fb30fafa', '2765d4a5884be49b28601445c2760c5f21e7e5c0ee2b7e3fce98fd7e5970796f', 'f46d3fbb439bd9b921095da657a4de906510d2ce'),
('9d81d76242cc7cfaf7bf74b94b9cd2e29324ed74', '8470d56547eea6236d7c81a644ce74670ca0bbda998e13c629ef6bb3f0d60b69', '05d2a63c81e32f0a36542ca677e8ad852365c538'),
)
EXTRA_QUERIES = (
"6b6be7a84ab179b4240c4302518dc3f6",
)
with ClientPool(self.server_address, 10) as client_pool:
for taskhash, outhash, unihash in TEST_INPUT:
self.client.report_unihash(taskhash, self.METHOD, outhash, unihash)
query = {idx: (self.METHOD, data[0]) for idx, data in enumerate(TEST_INPUT)}
for idx, taskhash in enumerate(EXTRA_QUERIES):
query[idx + len(TEST_INPUT)] = (self.METHOD, taskhash)
result = client_pool.get_unihashes(query)
self.assertDictEqual(result, {
0: "218e57509998197d570e2c98512d0105985dffc9",
1: "218e57509998197d570e2c98512d0105985dffc9",
2: "218e57509998197d570e2c98512d0105985dffc9",
3: "3b5d3d83f07f259e9086fcb422c855286e18a57d",
4: "f46d3fbb439bd9b921095da657a4de906510d2cd",
5: "f46d3fbb439bd9b921095da657a4de906510d2cd",
6: "05d2a63c81e32f0a36542ca677e8ad852365c538",
7: None,
})
def test_client_pool_unihash_exists(self):
TEST_INPUT = (
# taskhash outhash unihash
('8aa96fcffb5831b3c2c0cb75f0431e3f8b20554a', 'afe240a439959ce86f5e322f8c208e1fedefea9e813f2140c81af866cc9edf7e','218e57509998197d570e2c98512d0105985dffc9'),
# Duplicated taskhash with multiple output hashes and unihashes.
('8aa96fcffb5831b3c2c0cb75f0431e3f8b20554a', '0904a7fe3dc712d9fd8a74a616ddca2a825a8ee97adf0bd3fc86082c7639914d', 'ae9a7d252735f0dafcdb10e2e02561ca3a47314c'),
# Equivalent hash
("044c2ec8aaf480685a00ff6ff49e6162e6ad34e1", '0904a7fe3dc712d9fd8a74a616ddca2a825a8ee97adf0bd3fc86082c7639914d', "def64766090d28f627e816454ed46894bb3aab36"),
("e3da00593d6a7fb435c7e2114976c59c5fd6d561", "1cf8713e645f491eb9c959d20b5cae1c47133a292626dda9b10709857cbe688a", "3b5d3d83f07f259e9086fcb422c855286e18a57d"),
('35788efcb8dfb0a02659d81cf2bfd695fb30faf9', '2765d4a5884be49b28601445c2760c5f21e7e5c0ee2b7e3fce98fd7e5970796f', 'f46d3fbb439bd9b921095da657a4de906510d2cd'),
('35788efcb8dfb0a02659d81cf2bfd695fb30fafa', '2765d4a5884be49b28601445c2760c5f21e7e5c0ee2b7e3fce98fd7e5970796f', 'f46d3fbb439bd9b921095da657a4de906510d2ce'),
('9d81d76242cc7cfaf7bf74b94b9cd2e29324ed74', '8470d56547eea6236d7c81a644ce74670ca0bbda998e13c629ef6bb3f0d60b69', '05d2a63c81e32f0a36542ca677e8ad852365c538'),
)
EXTRA_QUERIES = (
"6b6be7a84ab179b4240c4302518dc3f6",
)
result_unihashes = set()
with ClientPool(self.server_address, 10) as client_pool:
for taskhash, outhash, unihash in TEST_INPUT:
result = self.client.report_unihash(taskhash, self.METHOD, outhash, unihash)
result_unihashes.add(result["unihash"])
query = {}
expected = {}
for _, _, unihash in TEST_INPUT:
idx = len(query)
query[idx] = unihash
expected[idx] = unihash in result_unihashes
for unihash in EXTRA_QUERIES:
idx = len(query)
query[idx] = unihash
expected[idx] = False
result = client_pool.unihashes_exist(query)
self.assertDictEqual(result, expected)
def test_auth_read_perms(self):
admin_client = self.start_auth_server()
# Create hashes with non-authenticated server
taskhash, outhash, unihash = self.create_test_hash(self.client)
# Validate hash can be retrieved using authenticated client
with self.auth_perms("@read") as client:
self.assertClientGetHash(client, taskhash, unihash)
with self.auth_perms() as client, self.assertRaises(InvokeError):
self.assertClientGetHash(client, taskhash, unihash)
def test_auth_report_perms(self):
admin_client = self.start_auth_server()
# Without read permission, the user is completely denied
with self.auth_perms() as client, self.assertRaises(InvokeError):
self.create_test_hash(client)
# Read permission allows the call to succeed, but it doesn't record
# anythin in the database
with self.auth_perms("@read") as client:
taskhash, outhash, unihash = self.create_test_hash(client)
self.assertClientGetHash(client, taskhash, None)
# Report permission alone is insufficient
with self.auth_perms("@report") as client, self.assertRaises(InvokeError):
self.create_test_hash(client)
# Read and report permission actually modify the database
with self.auth_perms("@read", "@report") as client:
taskhash, outhash, unihash = self.create_test_hash(client)
self.assertClientGetHash(client, taskhash, unihash)
def test_auth_no_token_refresh_from_anon_user(self):
self.start_auth_server()
with self.start_client(self.auth_server_address) as client, self.assertRaises(InvokeError):
client.refresh_token()
def test_auth_self_token_refresh(self):
admin_client = self.start_auth_server()
# Create a new user with no permissions
user = self.create_user("test-user", [])
with self.auth_client(user) as client:
new_user = client.refresh_token()
self.assertEqual(user["username"], new_user["username"])
self.assertNotEqual(user["token"], new_user["token"])
self.assertUserCanAuth(new_user)
self.assertUserCannotAuth(user)
# Explicitly specifying with your own username is fine also
with self.auth_client(new_user) as client:
new_user2 = client.refresh_token(user["username"])
self.assertEqual(user["username"], new_user2["username"])
self.assertNotEqual(user["token"], new_user2["token"])
self.assertUserCanAuth(new_user2)
self.assertUserCannotAuth(new_user)
self.assertUserCannotAuth(user)
def test_auth_token_refresh(self):
admin_client = self.start_auth_server()
user = self.create_user("test-user", [])
with self.auth_perms() as client, self.assertRaises(InvokeError):
client.refresh_token(user["username"])
with self.auth_perms("@user-admin") as client:
new_user = client.refresh_token(user["username"])
self.assertEqual(user["username"], new_user["username"])
self.assertNotEqual(user["token"], new_user["token"])
self.assertUserCanAuth(new_user)
self.assertUserCannotAuth(user)
def test_auth_self_get_user(self):
admin_client = self.start_auth_server()
user = self.create_user("test-user", [])
user_info = user.copy()
del user_info["token"]
with self.auth_client(user) as client:
info = client.get_user()
self.assertEqual(info, user_info)
# Explicitly asking for your own username is fine also
info = client.get_user(user["username"])
self.assertEqual(info, user_info)
def test_auth_get_user(self):
admin_client = self.start_auth_server()
user = self.create_user("test-user", [])
user_info = user.copy()
del user_info["token"]
with self.auth_perms() as client, self.assertRaises(InvokeError):
client.get_user(user["username"])
with self.auth_perms("@user-admin") as client:
info = client.get_user(user["username"])
self.assertEqual(info, user_info)
info = client.get_user("nonexist-user")
self.assertIsNone(info)
def test_auth_reconnect(self):
admin_client = self.start_auth_server()
user = self.create_user("test-user", [])
user_info = user.copy()
del user_info["token"]
with self.auth_client(user) as client:
info = client.get_user()
self.assertEqual(info, user_info)
client.disconnect()
info = client.get_user()
self.assertEqual(info, user_info)
def test_auth_delete_user(self):
admin_client = self.start_auth_server()
user = self.create_user("test-user", [])
# self service
with self.auth_client(user) as client:
client.delete_user(user["username"])
self.assertIsNone(admin_client.get_user(user["username"]))
user = self.create_user("test-user", [])
with self.auth_perms() as client, self.assertRaises(InvokeError):
client.delete_user(user["username"])
with self.auth_perms("@user-admin") as client:
client.delete_user(user["username"])
# User doesn't exist, so even though the permission is correct, it's an
# error
with self.auth_perms("@user-admin") as client, self.assertRaises(InvokeError):
client.delete_user(user["username"])
def test_auth_set_user_perms(self):
admin_client = self.start_auth_server()
user = self.create_user("test-user", [])
self.assertUserPerms(user, [])
# No self service to change permissions
with self.auth_client(user) as client, self.assertRaises(InvokeError):
client.set_user_perms(user["username"], ["@all"])
self.assertUserPerms(user, [])
with self.auth_perms() as client, self.assertRaises(InvokeError):
client.set_user_perms(user["username"], ["@all"])
self.assertUserPerms(user, [])
with self.auth_perms("@user-admin") as client:
client.set_user_perms(user["username"], ["@all"])
self.assertUserPerms(user, sorted(list(ALL_PERMISSIONS)))
# Bad permissions
with self.auth_perms("@user-admin") as client, self.assertRaises(InvokeError):
client.set_user_perms(user["username"], ["@this-is-not-a-permission"])
self.assertUserPerms(user, sorted(list(ALL_PERMISSIONS)))
def test_auth_get_all_users(self):
admin_client = self.start_auth_server()
user = self.create_user("test-user", [])
with self.auth_client(user) as client, self.assertRaises(InvokeError):
client.get_all_users()
# Give the test user the correct permission
admin_client.set_user_perms(user["username"], ["@user-admin"])
with self.auth_client(user) as client:
all_users = client.get_all_users()
# Convert to a dictionary for easier comparison
all_users = {u["username"]: u for u in all_users}
self.assertEqual(all_users,
{
"admin": {
"username": "admin",
"permissions": sorted(list(ALL_PERMISSIONS)),
},
"test-user": {
"username": "test-user",
"permissions": ["@user-admin"],
}
}
)
def test_auth_new_user(self):
self.start_auth_server()
permissions = ["@read", "@report", "@db-admin", "@user-admin"]
permissions.sort()
with self.auth_perms() as client, self.assertRaises(InvokeError):
self.create_user("test-user", permissions, client=client)
with self.auth_perms("@user-admin") as client:
user = self.create_user("test-user", permissions, client=client)
self.assertIn("token", user)
self.assertEqual(user["username"], "test-user")
self.assertEqual(user["permissions"], permissions)
def test_auth_become_user(self):
admin_client = self.start_auth_server()
user = self.create_user("test-user", ["@read", "@report"])
user_info = user.copy()
del user_info["token"]
with self.auth_perms() as client, self.assertRaises(InvokeError):
client.become_user(user["username"])
with self.auth_perms("@user-admin") as client:
become = client.become_user(user["username"])
self.assertEqual(become, user_info)
info = client.get_user()
self.assertEqual(info, user_info)
# Verify become user is preserved across disconnect
client.disconnect()
info = client.get_user()
self.assertEqual(info, user_info)
# test-user doesn't have become_user permissions, so this should
# not work
with self.assertRaises(InvokeError):
client.become_user(user["username"])
# No self-service of become
with self.auth_client(user) as client, self.assertRaises(InvokeError):
client.become_user(user["username"])
# Give test user permissions to become
admin_client.set_user_perms(user["username"], ["@user-admin"])
# It's possible to become yourself (effectively a noop)
with self.auth_perms("@user-admin") as client:
become = client.become_user(client.username)
def test_auth_gc(self):
admin_client = self.start_auth_server()
with self.auth_perms() as client, self.assertRaises(InvokeError):
client.gc_mark("ABC", {"unihash": "123"})
with self.auth_perms() as client, self.assertRaises(InvokeError):
client.gc_status()
with self.auth_perms() as client, self.assertRaises(InvokeError):
client.gc_sweep("ABC")
with self.auth_perms("@db-admin") as client:
client.gc_mark("ABC", {"unihash": "123"})
with self.auth_perms("@db-admin") as client:
client.gc_status()
with self.auth_perms("@db-admin") as client:
client.gc_sweep("ABC")
def test_get_db_usage(self):
usage = self.client.get_db_usage()
self.assertTrue(isinstance(usage, dict))
for name in usage.keys():
self.assertTrue(isinstance(usage[name], dict))
self.assertIn("rows", usage[name])
self.assertTrue(isinstance(usage[name]["rows"], int))
def test_get_db_query_columns(self):
columns = self.client.get_db_query_columns()
self.assertTrue(isinstance(columns, list))
self.assertTrue(len(columns) > 0)
for col in columns:
self.client.remove({col: ""})
def test_auth_is_owner(self):
admin_client = self.start_auth_server()
user = self.create_user("test-user", ["@read", "@report"])
with self.auth_client(user) as client:
taskhash, outhash, unihash = self.create_test_hash(client)
data = client.get_taskhash(self.METHOD, taskhash, True)
self.assertEqual(data["owner"], user["username"])
def test_gc(self):
taskhash = '53b8dce672cb6d0c73170be43f540460bfc347b4'
outhash = '5a9cb1649625f0bf41fc7791b635cd9c2d7118c7f021ba87dcd03f72b67ce7a8'
unihash = 'f37918cc02eb5a520b1aff86faacbc0a38124646'
result = self.client.report_unihash(taskhash, self.METHOD, outhash, unihash)
self.assertEqual(result['unihash'], unihash, 'Server returned bad unihash')
taskhash2 = '3bf6f1e89d26205aec90da04854fbdbf73afe6b4'
outhash2 = '77623a549b5b1a31e3732dfa8fe61d7ce5d44b3370f253c5360e136b852967b4'
unihash2 = 'af36b199320e611fbb16f1f277d3ee1d619ca58b'
result = self.client.report_unihash(taskhash2, self.METHOD, outhash2, unihash2)
self.assertClientGetHash(self.client, taskhash2, unihash2)
# Mark the first unihash to be kept
ret = self.client.gc_mark("ABC", {"unihash": unihash, "method": self.METHOD})
self.assertEqual(ret, {"count": 1})
ret = self.client.gc_status()
self.assertEqual(ret, {"mark": "ABC", "keep": 1, "remove": 1})
# Second hash is still there; mark doesn't delete hashes
self.assertClientGetHash(self.client, taskhash2, unihash2)
ret = self.client.gc_sweep("ABC")
self.assertEqual(ret, {"count": 1})
# Hash is gone. Taskhash is returned for second hash
self.assertClientGetHash(self.client, taskhash2, None)
# First hash is still present
self.assertClientGetHash(self.client, taskhash, unihash)
def test_gc_switch_mark(self):
taskhash = '53b8dce672cb6d0c73170be43f540460bfc347b4'
outhash = '5a9cb1649625f0bf41fc7791b635cd9c2d7118c7f021ba87dcd03f72b67ce7a8'
unihash = 'f37918cc02eb5a520b1aff86faacbc0a38124646'
result = self.client.report_unihash(taskhash, self.METHOD, outhash, unihash)
self.assertEqual(result['unihash'], unihash, 'Server returned bad unihash')
taskhash2 = '3bf6f1e89d26205aec90da04854fbdbf73afe6b4'
outhash2 = '77623a549b5b1a31e3732dfa8fe61d7ce5d44b3370f253c5360e136b852967b4'
unihash2 = 'af36b199320e611fbb16f1f277d3ee1d619ca58b'
result = self.client.report_unihash(taskhash2, self.METHOD, outhash2, unihash2)
self.assertClientGetHash(self.client, taskhash2, unihash2)
# Mark the first unihash to be kept
ret = self.client.gc_mark("ABC", {"unihash": unihash, "method": self.METHOD})
self.assertEqual(ret, {"count": 1})
ret = self.client.gc_status()
self.assertEqual(ret, {"mark": "ABC", "keep": 1, "remove": 1})
# Second hash is still there; mark doesn't delete hashes
self.assertClientGetHash(self.client, taskhash2, unihash2)
# Switch to a different mark and mark the second hash. This will start
# a new collection cycle
ret = self.client.gc_mark("DEF", {"unihash": unihash2, "method": self.METHOD})
self.assertEqual(ret, {"count": 1})
ret = self.client.gc_status()
self.assertEqual(ret, {"mark": "DEF", "keep": 1, "remove": 1})
# Both hashes are still present
self.assertClientGetHash(self.client, taskhash2, unihash2)
self.assertClientGetHash(self.client, taskhash, unihash)
# Sweep with the new mark
ret = self.client.gc_sweep("DEF")
self.assertEqual(ret, {"count": 1})
# First hash is gone, second is kept
self.assertClientGetHash(self.client, taskhash2, unihash2)
self.assertClientGetHash(self.client, taskhash, None)
def test_gc_switch_sweep_mark(self):
taskhash = '53b8dce672cb6d0c73170be43f540460bfc347b4'
outhash = '5a9cb1649625f0bf41fc7791b635cd9c2d7118c7f021ba87dcd03f72b67ce7a8'
unihash = 'f37918cc02eb5a520b1aff86faacbc0a38124646'
result = self.client.report_unihash(taskhash, self.METHOD, outhash, unihash)
self.assertEqual(result['unihash'], unihash, 'Server returned bad unihash')
taskhash2 = '3bf6f1e89d26205aec90da04854fbdbf73afe6b4'
outhash2 = '77623a549b5b1a31e3732dfa8fe61d7ce5d44b3370f253c5360e136b852967b4'
unihash2 = 'af36b199320e611fbb16f1f277d3ee1d619ca58b'
result = self.client.report_unihash(taskhash2, self.METHOD, outhash2, unihash2)
self.assertClientGetHash(self.client, taskhash2, unihash2)
# Mark the first unihash to be kept
ret = self.client.gc_mark("ABC", {"unihash": unihash, "method": self.METHOD})
self.assertEqual(ret, {"count": 1})
ret = self.client.gc_status()
self.assertEqual(ret, {"mark": "ABC", "keep": 1, "remove": 1})
# Sweeping with a different mark raises an error
with self.assertRaises(InvokeError):
self.client.gc_sweep("DEF")
# Both hashes are present
self.assertClientGetHash(self.client, taskhash2, unihash2)
self.assertClientGetHash(self.client, taskhash, unihash)
def test_gc_new_hashes(self):
taskhash = '53b8dce672cb6d0c73170be43f540460bfc347b4'
outhash = '5a9cb1649625f0bf41fc7791b635cd9c2d7118c7f021ba87dcd03f72b67ce7a8'
unihash = 'f37918cc02eb5a520b1aff86faacbc0a38124646'
result = self.client.report_unihash(taskhash, self.METHOD, outhash, unihash)
self.assertEqual(result['unihash'], unihash, 'Server returned bad unihash')
# Start a new garbage collection
ret = self.client.gc_mark("ABC", {"unihash": unihash, "method": self.METHOD})
self.assertEqual(ret, {"count": 1})
ret = self.client.gc_status()
self.assertEqual(ret, {"mark": "ABC", "keep": 1, "remove": 0})
# Add second hash. It should inherit the mark from the current garbage
# collection operation
taskhash2 = '3bf6f1e89d26205aec90da04854fbdbf73afe6b4'
outhash2 = '77623a549b5b1a31e3732dfa8fe61d7ce5d44b3370f253c5360e136b852967b4'
unihash2 = 'af36b199320e611fbb16f1f277d3ee1d619ca58b'
result = self.client.report_unihash(taskhash2, self.METHOD, outhash2, unihash2)
self.assertClientGetHash(self.client, taskhash2, unihash2)
# Sweep should remove nothing
ret = self.client.gc_sweep("ABC")
self.assertEqual(ret, {"count": 0})
# Both hashes are present
self.assertClientGetHash(self.client, taskhash2, unihash2)
self.assertClientGetHash(self.client, taskhash, unihash)
class TestHashEquivalenceClient(HashEquivalenceTestSetup, unittest.TestCase):
def get_server_addr(self, server_idx):
return "unix://" + os.path.join(self.temp_dir.name, 'sock%d' % server_idx)
def test_get(self):
taskhash, outhash, unihash = self.create_test_hash(self.client)
p = self.run_hashclient(["--address", self.server_address, "get", self.METHOD, taskhash])
data = json.loads(p.stdout)
self.assertEqual(data["unihash"], unihash)
self.assertEqual(data["outhash"], outhash)
self.assertEqual(data["taskhash"], taskhash)
self.assertEqual(data["method"], self.METHOD)
def test_get_outhash(self):
taskhash, outhash, unihash = self.create_test_hash(self.client)
p = self.run_hashclient(["--address", self.server_address, "get-outhash", self.METHOD, outhash, taskhash])
data = json.loads(p.stdout)
self.assertEqual(data["unihash"], unihash)
self.assertEqual(data["outhash"], outhash)
self.assertEqual(data["taskhash"], taskhash)
self.assertEqual(data["method"], self.METHOD)
def test_stats(self):
p = self.run_hashclient(["--address", self.server_address, "stats"], check=True)
json.loads(p.stdout)
def test_stress(self):
self.run_hashclient(["--address", self.server_address, "stress"], check=True)
def test_unihash_exsits(self):
taskhash, outhash, unihash = self.create_test_hash(self.client)
p = self.run_hashclient([
"--address", self.server_address,
"unihash-exists", unihash,
], check=True)
self.assertEqual(p.stdout.strip(), "true")
p = self.run_hashclient([
"--address", self.server_address,
"unihash-exists", '6662e699d6e3d894b24408ff9a4031ef9b038ee8',
], check=True)
self.assertEqual(p.stdout.strip(), "false")
def test_unihash_exsits_quiet(self):
taskhash, outhash, unihash = self.create_test_hash(self.client)
p = self.run_hashclient([
"--address", self.server_address,
"unihash-exists", unihash,
"--quiet",
])
self.assertEqual(p.returncode, 0)
self.assertEqual(p.stdout.strip(), "")
p = self.run_hashclient([
"--address", self.server_address,
"unihash-exists", '6662e699d6e3d894b24408ff9a4031ef9b038ee8',
"--quiet",
])
self.assertEqual(p.returncode, 1)
self.assertEqual(p.stdout.strip(), "")
def test_remove_taskhash(self):
taskhash, outhash, unihash = self.create_test_hash(self.client)
self.run_hashclient([
"--address", self.server_address,
"remove",
"--where", "taskhash", taskhash,
], check=True)
self.assertClientGetHash(self.client, taskhash, None)
result_outhash = self.client.get_outhash(self.METHOD, outhash, taskhash)
self.assertIsNone(result_outhash)
def test_remove_unihash(self):
taskhash, outhash, unihash = self.create_test_hash(self.client)
self.run_hashclient([
"--address", self.server_address,
"remove",
"--where", "unihash", unihash,
], check=True)
self.assertClientGetHash(self.client, taskhash, None)
def test_remove_outhash(self):
taskhash, outhash, unihash = self.create_test_hash(self.client)
self.run_hashclient([
"--address", self.server_address,
"remove",
"--where", "outhash", outhash,
], check=True)
result_outhash = self.client.get_outhash(self.METHOD, outhash, taskhash)
self.assertIsNone(result_outhash)
def test_remove_method(self):
taskhash, outhash, unihash = self.create_test_hash(self.client)
self.run_hashclient([
"--address", self.server_address,
"remove",
"--where", "method", self.METHOD,
], check=True)
self.assertClientGetHash(self.client, taskhash, None)
result_outhash = self.client.get_outhash(self.METHOD, outhash, taskhash)
self.assertIsNone(result_outhash)
def test_clean_unused(self):
taskhash, outhash, unihash = self.create_test_hash(self.client)
# Clean the database, which should not remove anything because all hashes an in-use
self.run_hashclient([
"--address", self.server_address,
"clean-unused", "0",
], check=True)
self.assertClientGetHash(self.client, taskhash, unihash)
# Remove the unihash. The row in the outhash table should still be present
self.run_hashclient([
"--address", self.server_address,
"remove",
"--where", "unihash", unihash,
], check=True)
result_outhash = self.client.get_outhash(self.METHOD, outhash, taskhash, False)
self.assertIsNotNone(result_outhash)
# Now clean with no minimum age which will remove the outhash
self.run_hashclient([
"--address", self.server_address,
"clean-unused", "0",
], check=True)
result_outhash = self.client.get_outhash(self.METHOD, outhash, taskhash, False)
self.assertIsNone(result_outhash)
def test_refresh_token(self):
admin_client = self.start_auth_server()
user = admin_client.new_user("test-user", ["@read", "@report"])
p = self.run_hashclient([
"--address", self.auth_server_address,
"--login", user["username"],
"--password", user["token"],
"refresh-token"
], check=True)
new_token = None
for l in p.stdout.splitlines():
l = l.rstrip()
m = re.match(r'Token: +(.*)$', l)
if m is not None:
new_token = m.group(1)
self.assertTrue(new_token)
print("New token is %r" % new_token)
self.run_hashclient([
"--address", self.auth_server_address,
"--login", user["username"],
"--password", new_token,
"get-user"
], check=True)
def test_set_user_perms(self):
admin_client = self.start_auth_server()
user = admin_client.new_user("test-user", ["@read"])
self.run_hashclient([
"--address", self.auth_server_address,
"--login", admin_client.username,
"--password", admin_client.password,
"set-user-perms",
"-u", user["username"],
"@read", "@report",
], check=True)
new_user = admin_client.get_user(user["username"])
self.assertEqual(set(new_user["permissions"]), {"@read", "@report"})
def test_get_user(self):
admin_client = self.start_auth_server()
user = admin_client.new_user("test-user", ["@read"])
p = self.run_hashclient([
"--address", self.auth_server_address,
"--login", admin_client.username,
"--password", admin_client.password,
"get-user",
"-u", user["username"],
], check=True)
self.assertIn("Username:", p.stdout)
self.assertIn("Permissions:", p.stdout)
p = self.run_hashclient([
"--address", self.auth_server_address,
"--login", user["username"],
"--password", user["token"],
"get-user",
], check=True)
self.assertIn("Username:", p.stdout)
self.assertIn("Permissions:", p.stdout)
def test_get_all_users(self):
admin_client = self.start_auth_server()
admin_client.new_user("test-user1", ["@read"])
admin_client.new_user("test-user2", ["@read"])
p = self.run_hashclient([
"--address", self.auth_server_address,
"--login", admin_client.username,
"--password", admin_client.password,
"get-all-users",
], check=True)
self.assertIn("admin", p.stdout)
self.assertIn("test-user1", p.stdout)
self.assertIn("test-user2", p.stdout)
def test_new_user(self):
admin_client = self.start_auth_server()
p = self.run_hashclient([
"--address", self.auth_server_address,
"--login", admin_client.username,
"--password", admin_client.password,
"new-user",
"-u", "test-user",
"@read", "@report",
], check=True)
new_token = None
for l in p.stdout.splitlines():
l = l.rstrip()
m = re.match(r'Token: +(.*)$', l)
if m is not None:
new_token = m.group(1)
self.assertTrue(new_token)
user = {
"username": "test-user",
"token": new_token,
}
self.assertUserPerms(user, ["@read", "@report"])
def test_delete_user(self):
admin_client = self.start_auth_server()
user = admin_client.new_user("test-user", ["@read"])
p = self.run_hashclient([
"--address", self.auth_server_address,
"--login", admin_client.username,
"--password", admin_client.password,
"delete-user",
"-u", user["username"],
], check=True)
self.assertIsNone(admin_client.get_user(user["username"]))
def test_get_db_usage(self):
p = self.run_hashclient([
"--address", self.server_address,
"get-db-usage",
], check=True)
def test_get_db_query_columns(self):
p = self.run_hashclient([
"--address", self.server_address,
"get-db-query-columns",
], check=True)
def test_gc(self):
taskhash = '53b8dce672cb6d0c73170be43f540460bfc347b4'
outhash = '5a9cb1649625f0bf41fc7791b635cd9c2d7118c7f021ba87dcd03f72b67ce7a8'
unihash = 'f37918cc02eb5a520b1aff86faacbc0a38124646'
result = self.client.report_unihash(taskhash, self.METHOD, outhash, unihash)
self.assertEqual(result['unihash'], unihash, 'Server returned bad unihash')
taskhash2 = '3bf6f1e89d26205aec90da04854fbdbf73afe6b4'
outhash2 = '77623a549b5b1a31e3732dfa8fe61d7ce5d44b3370f253c5360e136b852967b4'
unihash2 = 'af36b199320e611fbb16f1f277d3ee1d619ca58b'
result = self.client.report_unihash(taskhash2, self.METHOD, outhash2, unihash2)
self.assertClientGetHash(self.client, taskhash2, unihash2)
# Mark the first unihash to be kept
self.run_hashclient([
"--address", self.server_address,
"gc-mark", "ABC",
"--where", "unihash", unihash,
"--where", "method", self.METHOD
], check=True)
# Second hash is still there; mark doesn't delete hashes
self.assertClientGetHash(self.client, taskhash2, unihash2)
self.run_hashclient([
"--address", self.server_address,
"gc-sweep", "ABC",
], check=True)
# Hash is gone. Taskhash is returned for second hash
self.assertClientGetHash(self.client, taskhash2, None)
# First hash is still present
self.assertClientGetHash(self.client, taskhash, unihash)
class TestHashEquivalenceUnixServer(HashEquivalenceTestSetup, HashEquivalenceCommonTests, unittest.TestCase):
def get_server_addr(self, server_idx):
return "unix://" + os.path.join(self.temp_dir.name, 'sock%d' % server_idx)
class TestHashEquivalenceUnixServerLongPath(HashEquivalenceTestSetup, unittest.TestCase):
DEEP_DIRECTORY = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ccccccccccccccccccccccccccccccccccccccccccc"
def get_server_addr(self, server_idx):
os.makedirs(os.path.join(self.temp_dir.name, self.DEEP_DIRECTORY), exist_ok=True)
return "unix://" + os.path.join(self.temp_dir.name, self.DEEP_DIRECTORY, 'sock%d' % server_idx)
def test_long_sock_path(self):
# Simple test that hashes can be created
taskhash = '35788efcb8dfb0a02659d81cf2bfd695fb30faf9'
outhash = '2765d4a5884be49b28601445c2760c5f21e7e5c0ee2b7e3fce98fd7e5970796f'
unihash = 'f46d3fbb439bd9b921095da657a4de906510d2cd'
self.assertClientGetHash(self.client, taskhash, None)
result = self.client.report_unihash(taskhash, self.METHOD, outhash, unihash)
self.assertEqual(result['unihash'], unihash, 'Server returned bad unihash')
class TestHashEquivalenceTCPServer(HashEquivalenceTestSetup, HashEquivalenceCommonTests, unittest.TestCase):
def get_server_addr(self, server_idx):
# Some hosts cause asyncio module to misbehave, when IPv6 is not enabled.
# If IPv6 is enabled, it should be safe to use localhost directly, in general
# case it is more reliable to resolve the IP address explicitly.
return socket.gethostbyname("localhost") + ":0"
class TestHashEquivalenceWebsocketServer(HashEquivalenceTestSetup, HashEquivalenceCommonTests, unittest.TestCase):
def setUp(self):
try:
import websockets
except ImportError as e:
self.skipTest(str(e))
super().setUp()
def get_server_addr(self, server_idx):
# Some hosts cause asyncio module to misbehave, when IPv6 is not enabled.
# If IPv6 is enabled, it should be safe to use localhost directly, in general
# case it is more reliable to resolve the IP address explicitly.
host = socket.gethostbyname("localhost")
return "ws://%s:0" % host
class TestHashEquivalenceWebsocketsSQLAlchemyServer(TestHashEquivalenceWebsocketServer):
def setUp(self):
try:
import sqlalchemy
import aiosqlite
except ImportError as e:
self.skipTest(str(e))
super().setUp()
def make_dbpath(self):
return "sqlite+aiosqlite:///%s" % os.path.join(self.temp_dir.name, "db%d.sqlite" % self.server_index)
class TestHashEquivalenceExternalServer(HashEquivalenceTestSetup, HashEquivalenceCommonTests, unittest.TestCase):
def get_env(self, name):
v = os.environ.get(name)
if not v:
self.skipTest(f'{name} not defined to test an external server')
return v
def start_test_server(self):
return self.get_env('BB_TEST_HASHSERV')
def start_server(self, *args, **kwargs):
self.skipTest('Cannot start local server when testing external servers')
def start_auth_server(self):
self.auth_server_address = self.server_address
self.admin_client = self.start_client(
self.server_address,
username=self.get_env('BB_TEST_HASHSERV_USERNAME'),
password=self.get_env('BB_TEST_HASHSERV_PASSWORD'),
)
return self.admin_client
def setUp(self):
super().setUp()
if "BB_TEST_HASHSERV_USERNAME" in os.environ:
self.client = self.start_client(
self.server_address,
username=os.environ["BB_TEST_HASHSERV_USERNAME"],
password=os.environ["BB_TEST_HASHSERV_PASSWORD"],
)
self.client.remove({"method": self.METHOD})
def tearDown(self):
self.client.remove({"method": self.METHOD})
super().tearDown()
def test_auth_get_all_users(self):
self.skipTest("Cannot test all users with external server")