mirror of
git://git.yoctoproject.org/poky.git
synced 2025-07-19 12:59:02 +02:00
bitbake: hashserv: Support read-only server
The -r/--readonly argument is added to the bitbake-hashserv app. If this argument is given then clients may only perform read operations against the server. The read-only mode is implemented by simply not installing handlers for write operations, this keeps the permission model simple and reduces the risk of accidentally allowing write operations. As a sqlite database can be safely opened by multiple processes in parallel, it's possible to start two hashserv instances against a single database if you wish to export both a read-only port and a read-write port. (Bitbake rev: 492bb02eb0e071c792407ac3113f92492da1a9cc) Signed-off-by: Paul Barker <pbarker@konsulko.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
This commit is contained in:
parent
f5188da2f1
commit
3b559bb16d
|
@ -33,6 +33,7 @@ def main():
|
|||
parser.add_argument('--bind', default=DEFAULT_BIND, help='Bind address (default "%(default)s")')
|
||||
parser.add_argument('--database', default='./hashserv.db', help='Database file (default "%(default)s")')
|
||||
parser.add_argument('--log', default='WARNING', help='Set logging level')
|
||||
parser.add_argument('-r', '--read-only', action='store_true', help='Disallow write operations from clients')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
@ -47,7 +48,7 @@ def main():
|
|||
console.setLevel(level)
|
||||
logger.addHandler(console)
|
||||
|
||||
server = hashserv.create_server(args.bind, args.database)
|
||||
server = hashserv.create_server(args.bind, args.database, read_only=args.read_only)
|
||||
server.serve_forever()
|
||||
return 0
|
||||
|
||||
|
|
|
@ -94,10 +94,10 @@ def chunkify(msg, max_chunk):
|
|||
yield "\n"
|
||||
|
||||
|
||||
def create_server(addr, dbname, *, sync=True, upstream=None):
|
||||
def create_server(addr, dbname, *, sync=True, upstream=None, read_only=False):
|
||||
from . import server
|
||||
db = setup_database(dbname, sync=sync)
|
||||
s = server.Server(db, upstream=upstream)
|
||||
s = server.Server(db, upstream=upstream, read_only=read_only)
|
||||
|
||||
(typ, a) = parse_address(addr)
|
||||
if typ == ADDR_TYPE_UNIX:
|
||||
|
|
|
@ -112,6 +112,9 @@ class Stats(object):
|
|||
class ClientError(Exception):
|
||||
pass
|
||||
|
||||
class ServerError(Exception):
|
||||
pass
|
||||
|
||||
def insert_task(cursor, data, ignore=False):
|
||||
keys = sorted(data.keys())
|
||||
query = '''INSERT%s INTO tasks_v2 (%s) VALUES (%s)''' % (
|
||||
|
@ -138,7 +141,7 @@ class ServerClient(object):
|
|||
FAST_QUERY = 'SELECT taskhash, method, unihash FROM tasks_v2 WHERE method=:method AND taskhash=:taskhash ORDER BY created ASC LIMIT 1'
|
||||
ALL_QUERY = 'SELECT * FROM tasks_v2 WHERE method=:method AND taskhash=:taskhash ORDER BY created ASC LIMIT 1'
|
||||
|
||||
def __init__(self, reader, writer, db, request_stats, backfill_queue, upstream):
|
||||
def __init__(self, reader, writer, db, request_stats, backfill_queue, upstream, read_only):
|
||||
self.reader = reader
|
||||
self.writer = writer
|
||||
self.db = db
|
||||
|
@ -149,15 +152,19 @@ class ServerClient(object):
|
|||
|
||||
self.handlers = {
|
||||
'get': self.handle_get,
|
||||
'report': self.handle_report,
|
||||
'report-equiv': self.handle_equivreport,
|
||||
'get-stream': self.handle_get_stream,
|
||||
'get-stats': self.handle_get_stats,
|
||||
'reset-stats': self.handle_reset_stats,
|
||||
'chunk-stream': self.handle_chunk,
|
||||
'backfill-wait': self.handle_backfill_wait,
|
||||
}
|
||||
|
||||
if not read_only:
|
||||
self.handlers.update({
|
||||
'report': self.handle_report,
|
||||
'report-equiv': self.handle_equivreport,
|
||||
'reset-stats': self.handle_reset_stats,
|
||||
'backfill-wait': self.handle_backfill_wait,
|
||||
})
|
||||
|
||||
async def process_requests(self):
|
||||
if self.upstream is not None:
|
||||
self.upstream_client = await create_async_client(self.upstream)
|
||||
|
@ -455,7 +462,10 @@ class ServerClient(object):
|
|||
|
||||
|
||||
class Server(object):
|
||||
def __init__(self, db, loop=None, upstream=None):
|
||||
def __init__(self, db, loop=None, upstream=None, read_only=False):
|
||||
if upstream and read_only:
|
||||
raise ServerError("Read-only hashserv cannot pull from an upstream server")
|
||||
|
||||
self.request_stats = Stats()
|
||||
self.db = db
|
||||
|
||||
|
@ -467,6 +477,7 @@ class Server(object):
|
|||
self.close_loop = False
|
||||
|
||||
self.upstream = upstream
|
||||
self.read_only = read_only
|
||||
|
||||
self._cleanup_socket = None
|
||||
|
||||
|
@ -510,7 +521,7 @@ class Server(object):
|
|||
async def handle_client(self, reader, writer):
|
||||
# writer.transport.set_write_buffer_limits(0)
|
||||
try:
|
||||
client = ServerClient(reader, writer, self.db, self.request_stats, self.backfill_queue, self.upstream)
|
||||
client = ServerClient(reader, writer, self.db, self.request_stats, self.backfill_queue, self.upstream, self.read_only)
|
||||
await client.process_requests()
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#
|
||||
|
||||
from . import create_server, create_client
|
||||
from .client import HashConnectionError
|
||||
import hashlib
|
||||
import logging
|
||||
import multiprocessing
|
||||
|
@ -29,7 +30,7 @@ class HashEquivalenceTestSetup(object):
|
|||
|
||||
server_index = 0
|
||||
|
||||
def start_server(self, dbpath=None, upstream=None):
|
||||
def start_server(self, dbpath=None, upstream=None, read_only=False):
|
||||
self.server_index += 1
|
||||
if dbpath is None:
|
||||
dbpath = os.path.join(self.temp_dir.name, "db%d.sqlite" % self.server_index)
|
||||
|
@ -38,7 +39,10 @@ class HashEquivalenceTestSetup(object):
|
|||
thread.terminate()
|
||||
thread.join()
|
||||
|
||||
server = create_server(self.get_server_addr(self.server_index), dbpath, upstream=upstream)
|
||||
server = create_server(self.get_server_addr(self.server_index),
|
||||
dbpath,
|
||||
upstream=upstream,
|
||||
read_only=read_only)
|
||||
server.dbpath = dbpath
|
||||
|
||||
server.thread = multiprocessing.Process(target=_run_server, args=(server, self.server_index))
|
||||
|
@ -242,6 +246,31 @@ class HashEquivalenceCommonTests(object):
|
|||
self.assertClientGetHash(side_client, taskhash4, unihash4)
|
||||
self.assertClientGetHash(self.client, taskhash4, None)
|
||||
|
||||
def test_ro_server(self):
|
||||
(ro_client, ro_server) = self.start_server(dbpath=self.server.dbpath, read_only=True)
|
||||
|
||||
# Report a hash via the read-write server
|
||||
taskhash = '35788efcb8dfb0a02659d81cf2bfd695fb30faf9'
|
||||
outhash = '2765d4a5884be49b28601445c2760c5f21e7e5c0ee2b7e3fce98fd7e5970796f'
|
||||
unihash = 'f46d3fbb439bd9b921095da657a4de906510d2cd'
|
||||
|
||||
result = self.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'
|
||||
|
||||
with self.assertRaises(HashConnectionError):
|
||||
ro_client.report_unihash(taskhash2, self.METHOD, outhash2, unihash2)
|
||||
|
||||
# Ensure that the database was not modified
|
||||
self.assertClientGetHash(self.client, taskhash2, None)
|
||||
|
||||
|
||||
class TestHashEquivalenceUnixServer(HashEquivalenceTestSetup, HashEquivalenceCommonTests, unittest.TestCase):
|
||||
def get_server_addr(self, server_idx):
|
||||
|
|
Loading…
Reference in New Issue
Block a user