From 77ce0f4fc7cb8f038d6345f15d41a7e453f902bd Mon Sep 17 00:00:00 2001 From: "A.J. Beamon" Date: Wed, 23 Mar 2022 15:50:06 -0700 Subject: [PATCH] Add a unit test in Python to exercise some of the tenant code. Add some comments to the allocate and delete tenant implementations. --- bindings/python/fdb/impl.py | 16 +++ bindings/python/tests/tenant_tests.py | 123 ++++++++++++++++++ .../python/tests/tenant_tuple_name_tests.py | 47 ------- bindings/python/tests/tester.py | 4 +- 4 files changed, 141 insertions(+), 49 deletions(-) create mode 100755 bindings/python/tests/tenant_tests.py delete mode 100644 bindings/python/tests/tenant_tuple_name_tests.py diff --git a/bindings/python/fdb/impl.py b/bindings/python/fdb/impl.py index 5fcefb73f3..023e85ae95 100644 --- a/bindings/python/fdb/impl.py +++ b/bindings/python/fdb/impl.py @@ -1184,6 +1184,14 @@ class Database(_TransactionCreator): def delete_tenant(self, name): Database.__database_delete_tenant(self, process_tenant_name(name), []) + # Attempt to allocate a tenant in the cluster. If the tenant already exists, + # this function will return a tenant_already_exists error. If the tenant is created + # concurrently, then this function may return success even if another caller creates + # it. + # + # The existence_check_marker is expected to be an empty list. This function will + # modify the list after completing the existence check to avoid checking for existence + # on retries. This allows the operation to be idempotent. @staticmethod @transactional def __database_allocate_tenant(tr, name, existence_check_marker): @@ -1196,6 +1204,14 @@ class Database(_TransactionCreator): raise fdb.FDBError(2132) # tenant_already_exists tr[key] = b'' + # Attempt to remove a tenant in the cluster. If the tenant doesn't exist, this + # function will return a tenant_not_found error. If the tenant is deleted + # concurrently, then this function may return success even if another caller deletes + # it. + # + # The existence_check_marker is expected to be an empty list. This function will + # modify the list after completing the existence check to avoid checking for existence + # on retries. This allows the operation to be idempotent. @staticmethod @transactional def __database_delete_tenant(tr, name, existence_check_marker): diff --git a/bindings/python/tests/tenant_tests.py b/bindings/python/tests/tenant_tests.py new file mode 100755 index 0000000000..9f35620b6a --- /dev/null +++ b/bindings/python/tests/tenant_tests.py @@ -0,0 +1,123 @@ +#!/usr/bin/python +# +# tenant_tests.py +# +# This source file is part of the FoundationDB open source project +# +# Copyright 2013-2022 Apple Inc. and the FoundationDB project authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import fdb +import sys +import json +from fdb.tuple import pack + +if __name__ == '__main__': + fdb.api_version(710) + +def test_tenant_tuple_name(db): + tuplename=(b'test', b'level', b'hierarchy', 3, 1.24, 'str') + db.allocate_tenant(tuplename) + + tenant=db.open_tenant(tuplename) + tenant[b'foo'] = b'bar' + + assert tenant[b'foo'] == b'bar' + + del tenant[b'foo'] + db.delete_tenant(tuplename) + +def cleanup_tenant(db, tenant_name): + try: + tenant = db.open_tenant(tenant_name) + del tenant[:] + db.delete_tenant(tenant_name) + except fdb.FDBError as e: + if e.code == 2131: # tenant not found + pass + else: + raise + +def test_tenant_operations(db): + cleanup_tenant(db, b'tenant1') + cleanup_tenant(db, b'tenant2') + + db.allocate_tenant(b'tenant1') + db.allocate_tenant(b'tenant2') + + tenant1 = db.open_tenant(b'tenant1') + tenant2 = db.open_tenant(b'tenant2') + + db[b'tenant_test_key'] = b'no_tenant' + tenant1[b'tenant_test_key'] = b'tenant1' + tenant2[b'tenant_test_key'] = b'tenant2' + + tenant1_entry = db[b'\xff\xff/management/tenant_map/tenant1'] + tenant1_json = json.loads(tenant1_entry) + prefix1 = tenant1_json['prefix'].encode('utf8') + + tenant2_entry = db[b'\xff\xff/management/tenant_map/tenant2'] + tenant2_json = json.loads(tenant2_entry) + prefix2 = tenant2_json['prefix'].encode('utf8') + + assert tenant1[b'tenant_test_key'] == b'tenant1' + assert db[prefix1 + b'tenant_test_key'] == b'tenant1' + assert tenant2[b'tenant_test_key'] == b'tenant2' + assert db[prefix2 + b'tenant_test_key'] == b'tenant2' + assert db[b'tenant_test_key'] == b'no_tenant' + + tr1 = tenant1.create_transaction() + try: + del tr1[:] + tr1.commit().wait() + except fdb.FDBError as e: + tr.on_error(e).wait() + + assert tenant1[b'tenant_test_key'] == None + assert db[prefix1 + b'tenant_test_key'] == None + assert tenant2[b'tenant_test_key'] == b'tenant2' + assert db[prefix2 + b'tenant_test_key'] == b'tenant2' + assert db[b'tenant_test_key'] == b'no_tenant' + + db.delete_tenant(b'tenant1') + try: + tenant1[b'tenant_test_key'] + assert False + except fdb.FDBError as e: + assert e.code == 2131 # tenant not found + + del tenant2[:] + db.delete_tenant(b'tenant2') + + assert db[prefix1 + b'tenant_test_key'] == None + assert db[prefix2 + b'tenant_test_key'] == None + assert db[b'tenant_test_key'] == b'no_tenant' + + del db[b'tenant_test_key'] + + assert db[b'tenant_test_key'] == None + +def test_tenants(db): + test_tenant_tuple_name(db) + test_tenant_operations(db) + +# Expect a cluster file as input. This test will write to the FDB cluster, so +# be aware of potential side effects. +if __name__ == '__main__': + clusterFile = sys.argv[1] + db = fdb.open(clusterFile) + db.options.set_transaction_timeout(2000) # 2 seconds + db.options.set_transaction_retry_limit(3) + + test_tenants(db) diff --git a/bindings/python/tests/tenant_tuple_name_tests.py b/bindings/python/tests/tenant_tuple_name_tests.py deleted file mode 100644 index 13af513953..0000000000 --- a/bindings/python/tests/tenant_tuple_name_tests.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/python -# -# tenant_tuple_name_tests.py -# -# This source file is part of the FoundationDB open source project -# -# Copyright 2013-2022 Apple Inc. and the FoundationDB project authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -import fdb -import sys -from fdb.tuple import pack - -if __name__ == '__main__': - fdb.api_version(710) - -def test_tenant_tuple_name(db): - tuplename=(b'test', b'level', b'hierarchy', 3, 1.24, 'str') - db.allocate_tenant(tuplename) - - tenant=db.open_tenant(tuplename) - tenant[b'foo'] = b'bar' - - assert tenant[b'foo'] == b'bar' - - del tenant[b'foo'] - db.delete_tenant(tuplename) - -# Expect a cluster file as input. This test will write to the FDB cluster, so -# be aware of potential side effects. -if __name__ == '__main__': - clusterFile = sys.argv[1] - db = fdb.open(clusterFile) - db.options.set_transaction_timeout(2000) # 2 seconds - db.options.set_transaction_retry_limit(3) - test_tenant_tuple_name(db) diff --git a/bindings/python/tests/tester.py b/bindings/python/tests/tester.py index e1a559b396..7f8d794207 100644 --- a/bindings/python/tests/tester.py +++ b/bindings/python/tests/tester.py @@ -49,7 +49,7 @@ from cancellation_timeout_tests import test_db_retry_limits from cancellation_timeout_tests import test_combinations from size_limit_tests import test_size_limit_option, test_get_approximate_size -from tenant_tuple_name_tests import test_tenant_tuple_name +from tenant_tests import test_tenants random.seed(0) @@ -621,7 +621,7 @@ class Tester: test_size_limit_option(db) test_get_approximate_size(db) - test_tenant_tuple_name(db) + test_tenants(db) except fdb.FDBError as e: print("Unit tests failed: %s" % e.description)