Move tenant creation/deletion into a TenantManagement class

This commit is contained in:
A.J. Beamon 2022-03-28 13:54:43 -07:00
parent 6570ae44c6
commit be7315473a
7 changed files with 223 additions and 109 deletions

View File

@ -66,6 +66,7 @@ set(JAVA_BINDING_SRCS
src/main/com/apple/foundationdb/subspace/package-info.java
src/main/com/apple/foundationdb/subspace/Subspace.java
src/main/com/apple/foundationdb/Tenant.java
src/main/com/apple/foundationdb/TenantManagement.java
src/main/com/apple/foundationdb/Transaction.java
src/main/com/apple/foundationdb/TransactionContext.java
src/main/com/apple/foundationdb/EventKeeper.java

View File

@ -41,50 +41,6 @@ import com.apple.foundationdb.tuple.Tuple;
* in use in order to free any associated resources.
*/
public interface Database extends AutoCloseable, TransactionContext {
/**
* Creates a new tenant in the cluster.
*
* @param tenantName The name of the tenant. Can be any byte string that does not begin a 0xFF byte.
* @return a {@code CompletableFuture} that when set without error will indicate that the tenant has
* been created.
*/
CompletableFuture<Void> allocateTenant(byte[] tenantName);
/**
* Creates a new tenant in the cluster. This is a convenience method that generates the tenant name by packing a
* {@code Tuple}.
*
* @param tenantName The name of the tenant, as a Tuple.
* @return a {@code CompletableFuture} that when set without error will indicate that the tenant has
* been created.
*/
CompletableFuture<Void> allocateTenant(Tuple tenantName);
/**
* Deletes a tenant from the cluster.<br>
* <br>
* <b>Note:</b> A tenant cannot be deleted if it has any data in it. To delete a non-empty tenant, you must
* first use a clear operation to delete all of its keys.
*
* @param tenantName The name of the tenant being deleted.
* @return a {@code CompletableFuture} that when set without error will indicate that the tenant has
* been deleted.
*/
CompletableFuture<Void> deleteTenant(byte[] tenantName);
/**
* Deletes a tenant from the cluster. This is a convenience method that generates the tenant name by packing a
* {@code Tuple}.<br>
* <br>
* <b>Note:</b> A tenant cannot be deleted if it has any data in it. To delete a non-empty
* tenant, you must first use a clear operation to delete all of its keys.
*
* @param tenantName The name of the tenant being deleted, as a Tuple.
* @return a {@code CompletableFuture} that when set without error will indicate that the tenant has
* been deleted.
*/
CompletableFuture<Void> deleteTenant(Tuple tenantName);
/**
* Opens an existing tenant to be used for running transactions.
*

View File

@ -23,7 +23,6 @@ package com.apple.foundationdb;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
@ -119,60 +118,6 @@ class FDBDatabase extends NativeObjectWrapper implements Database, OptionConsume
}
}
@Override
public CompletableFuture<Void> allocateTenant(byte[] tenantName) {
final AtomicBoolean checkedExistence = new AtomicBoolean(false);
final byte[] key = ByteArrayUtil.join(FDBTenant.TENANT_MAP_PREFIX, tenantName);
return runAsync(tr -> {
tr.options().setSpecialKeySpaceEnableWrites();
if(checkedExistence.get()) {
tr.set(key, new byte[0]);
return CompletableFuture.completedFuture(null);
}
else {
return tr.get(key).thenAcceptAsync(result -> {
checkedExistence.set(true);
if(result != null) {
throw new FDBException("A tenant with the given name already exists", 2132);
}
tr.set(key, new byte[0]);
});
}
});
}
@Override
public CompletableFuture<Void> allocateTenant(Tuple tenantName) {
return allocateTenant(tenantName.pack());
}
@Override
public CompletableFuture<Void> deleteTenant(byte[] tenantName) {
final AtomicBoolean checkedExistence = new AtomicBoolean(false);
final byte[] key = ByteArrayUtil.join(FDBTenant.TENANT_MAP_PREFIX, tenantName);
return runAsync(tr -> {
tr.options().setSpecialKeySpaceEnableWrites();
if(checkedExistence.get()) {
tr.clear(key);
return CompletableFuture.completedFuture(null);
}
else {
return tr.get(key).thenAcceptAsync(result -> {
checkedExistence.set(true);
if (result == null) {
throw new FDBException("Tenant does not exist", 2131);
}
tr.clear(key);
});
}
});
}
@Override
public CompletableFuture<Void> deleteTenant(Tuple tenantName) {
return deleteTenant(tenantName.pack());
}
@Override
public Tenant openTenant(byte[] tenantName, Executor e) {
return openTenant(tenantName, e, eventKeeper);
@ -265,8 +210,6 @@ class FDBDatabase extends NativeObjectWrapper implements Database, OptionConsume
Database_dispose(cPtr);
}
private native long Database_allocateTenant(long cPtr, byte[] tenantName);
private native long Database_deleteTenant(long cPtr, byte[] tenantName);
private native long Database_openTenant(long cPtr, byte[] tenantName);
private native long Database_createTransaction(long cPtr);
private native void Database_dispose(long cPtr);

View File

@ -35,8 +35,6 @@ class FDBTenant extends NativeObjectWrapper implements Tenant {
private final Executor executor;
private final EventKeeper eventKeeper;
static final byte[] TENANT_MAP_PREFIX = ByteArrayUtil.join(new byte[] { (byte)255, (byte)255 }, "/management/tenant_map/".getBytes());
protected FDBTenant(long cPtr, Database database, byte[] name, Executor executor) {
this(cPtr, database, name, executor, null);
}

View File

@ -0,0 +1,214 @@
/*
* TenantManagement.java
*
* 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.
*/
package com.apple.foundationdb;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiFunction;
import com.apple.foundationdb.async.AsyncIterable;
import com.apple.foundationdb.async.AsyncIterator;
import com.apple.foundationdb.async.AsyncUtil;
import com.apple.foundationdb.async.CloseableAsyncIterator;
import com.apple.foundationdb.tuple.ByteArrayUtil;
import com.apple.foundationdb.tuple.Tuple;
/**
* The FoundationDB API includes function to manage the set of tenants in a cluster.
*/
public class TenantManagement {
static final byte[] TENANT_MAP_PREFIX = ByteArrayUtil.join(new byte[] { (byte)255, (byte)255 },
"/management/tenant_map/".getBytes());
/**
* Creates a new tenant in the cluster. If the tenant already exists, this operation will complete
* successfully without changing anything. The transaction must be committed for the creation to take
* effect or to observe any errors.
*
* @param tr The transaction used to create the tenant.
* @param tenantName The name of the tenant. Can be any byte string that does not begin a 0xFF byte.
*/
public static void createTenant(Transaction tr, byte[] tenantName) {
tr.options().setSpecialKeySpaceEnableWrites();
tr.set(ByteArrayUtil.join(TENANT_MAP_PREFIX, tenantName), new byte[0]);
}
/**
* Creates a new tenant in the cluster. If the tenant already exists, this operation will complete
* successfully without changing anything. The transaction must be committed for the creation to take
* effect or to observe any errors.<br>
* <br>
* This is a convenience method that generates the tenant name by packing a {@code Tuple}.
*
* @param tr The transaction used to create the tenant.
* @param tenantName The name of the tenant, as a Tuple.
*/
public static void createTenant(Transaction tr, Tuple tenantName) {
createTenant(tr, tenantName.pack());
}
/**
* Creates a new tenant in the cluster using a transaction created on the specified {@code Database}.
* This operation will first check whether the tenant exists, and if it does it will set the
* {@code CompletableFuture} to a tenant_already_exists error. Otherwise, it will attempt to create
* the tenant in a retry loop. If the tenant is created concurrently by another transaction, this
* function may still return successfully.
*
* @param db The database used to create a transaction for creating the tenant.
* @param tenantName The name of the tenant. Can be any byte string that does not begin a 0xFF byte.
* @return a {@code CompletableFuture} that when set without error will indicate that the tenant has
* been created.
*/
public static CompletableFuture<Void> createTenant(Database db, byte[] tenantName) {
final AtomicBoolean checkedExistence = new AtomicBoolean(false);
final byte[] key = ByteArrayUtil.join(TENANT_MAP_PREFIX, tenantName);
return db.runAsync(tr -> {
tr.options().setSpecialKeySpaceEnableWrites();
if(checkedExistence.get()) {
tr.set(key, new byte[0]);
return CompletableFuture.completedFuture(null);
}
else {
return tr.get(key).thenAcceptAsync(result -> {
checkedExistence.set(true);
if(result != null) {
throw new FDBException("A tenant with the given name already exists", 2132);
}
tr.set(key, new byte[0]);
});
}
});
}
/**
* Creates a new tenant in the cluster using a transaction created on the specified {@code Database}.
* This operation will first check whether the tenant exists, and if it does it will set the
* {@code CompletableFuture} to a tenant_already_exists error. Otherwise, it will attempt to create
* the tenant in a retry loop. If the tenant is created concurrently by another transaction, this
* function may still return successfully.<br>
* <br>
* This is a convenience method that generates the tenant name by packing a {@code Tuple}.
*
* @param db The database used to create a transaction for creating the tenant.
* @param tenantName The name of the tenant, as a Tuple.
* @return a {@code CompletableFuture} that when set without error will indicate that the tenant has
* been created.
*/
public static CompletableFuture<Void> createTenant(Database db, Tuple tenantName) {
return createTenant(db, tenantName.pack());
}
/**
* Deletes a tenant from the cluster. If the tenant does not exists, this operation will complete
* successfully without changing anything. The transaction must be committed for the deletion to take
* effect or to observe any errors.<br>
* <br>
* <b>Note:</b> A tenant cannot be deleted if it has any data in it. To delete a non-empty tenant, you must
* first use a clear operation to delete all of its keys.
*
* @param tr The transaction used to delete the tenant.
* @param tenantName The name of the tenant being deleted.
*/
public static void deleteTenant(Transaction tr, byte[] tenantName) {
tr.options().setSpecialKeySpaceEnableWrites();
tr.clear(ByteArrayUtil.join(TENANT_MAP_PREFIX, tenantName));
}
/**
* Deletes a tenant from the cluster. If the tenant does not exists, this operation will complete
* successfully without changing anything. The transaction must be committed for the deletion to take
* effect or to observe any errors.<br>
* <br>
* <b>Note:</b> A tenant cannot be deleted if it has any data in it. To delete a non-empty tenant, you must
* first use a clear operation to delete all of its keys.<br>
* <br>
* This is a convenience method that generates the tenant name by packing a {@code Tuple}.
*
* @param tr The transaction used to delete the tenant.
* @param tenantName The name of the tenant being deleted, as a Tuple.
*/
public static void deleteTenant(Transaction tr, Tuple tenantName) {
deleteTenant(tr, tenantName.pack());
}
/**
* Deletes a tenant from the cluster using a transaction created on the specified {@code Database}. This
* operation will first check whether the tenant exists, and if it does not it will set the
* {@code CompletableFuture} to a tenant_not_found error. Otherwise, it will attempt to delete the
* tenant in a retry loop. If the tenant is deleted concurrently by another transaction, this function may
* still return successfully.<br>
* <br>
* <b>Note:</b> A tenant cannot be deleted if it has any data in it. To delete a non-empty tenant, you must
* first use a clear operation to delete all of its keys.
*
* @param db The database used to create a transaction for deleting the tenant.
* @param tenantName The name of the tenant being deleted.
* @return a {@code CompletableFuture} that when set without error will indicate that the tenant has
* been deleted.
*/
public static CompletableFuture<Void> deleteTenant(Database db, byte[] tenantName) {
final AtomicBoolean checkedExistence = new AtomicBoolean(false);
final byte[] key = ByteArrayUtil.join(TENANT_MAP_PREFIX, tenantName);
return db.runAsync(tr -> {
tr.options().setSpecialKeySpaceEnableWrites();
if(checkedExistence.get()) {
tr.clear(key);
return CompletableFuture.completedFuture(null);
}
else {
return tr.get(key).thenAcceptAsync(result -> {
checkedExistence.set(true);
if(result == null) {
throw new FDBException("Tenant does not exist", 2131);
}
tr.clear(key);
});
}
});
}
/**
* Deletes a tenant from the cluster using a transaction created on the specified {@code Database}. This
* operation will first check whether the tenant exists, and if it does not it will set the
* {@code CompletableFuture} to a tenant_not_found error. Otherwise, it will attempt to delete the
* tenant in a retry loop. If the tenant is deleted concurrently by another transaction, this function may
* still return successfully.<br>
* <br>
* <b>Note:</b> A tenant cannot be deleted if it has any data in it. To delete a non-empty tenant, you must
* first use a clear operation to delete all of its keys.<br>
* <br>
* This is a convenience method that generates the tenant name by packing a {@code Tuple}.
*
* @param db The database used to create a transaction for deleting the tenant.
* @param tenantName The name of the tenant being deleted.
* @return a {@code CompletableFuture} that when set without error will indicate that the tenant has
* been deleted.
*/
public static CompletableFuture<Void> deleteTenant(Database db, Tuple tenantName) {
return deleteTenant(db, tenantName.pack());
}
private TenantManagement() {}
}

View File

@ -43,6 +43,7 @@ import com.apple.foundationdb.KeyArrayResult;
import com.apple.foundationdb.MutationType;
import com.apple.foundationdb.Range;
import com.apple.foundationdb.StreamingMode;
import com.apple.foundationdb.TenantManagement;
import com.apple.foundationdb.Transaction;
import com.apple.foundationdb.async.AsyncUtil;
import com.apple.foundationdb.tuple.ByteArrayUtil;
@ -473,13 +474,13 @@ public class AsyncStackTester {
else if (op == StackOperation.TENANT_CREATE) {
return inst.popParam().thenAcceptAsync(param -> {
byte[] tenantName = (byte[])param;
inst.push(inst.context.db.allocateTenant(tenantName));
inst.push(TenantManagement.createTenant(inst.context.db, tenantName));
}, FDB.DEFAULT_EXECUTOR);
}
else if (op == StackOperation.TENANT_DELETE) {
return inst.popParam().thenAcceptAsync(param -> {
byte[] tenantName = (byte[])param;
inst.push(inst.context.db.deleteTenant(tenantName));
inst.push(TenantManagement.deleteTenant(inst.context.db, tenantName));
}, FDB.DEFAULT_EXECUTOR);
}
else if (op == StackOperation.TENANT_SET_ACTIVE) {

View File

@ -45,6 +45,7 @@ import com.apple.foundationdb.LocalityUtil;
import com.apple.foundationdb.MutationType;
import com.apple.foundationdb.Range;
import com.apple.foundationdb.StreamingMode;
import com.apple.foundationdb.TenantManagement;
import com.apple.foundationdb.Transaction;
import com.apple.foundationdb.async.AsyncIterable;
import com.apple.foundationdb.async.AsyncUtil;
@ -422,11 +423,11 @@ public class StackTester {
}
else if (op == StackOperation.TENANT_CREATE) {
byte[] tenantName = (byte[])inst.popParam().join();
inst.push(inst.context.db.allocateTenant(tenantName));
inst.push(TenantManagement.createTenant(inst.context.db, tenantName));
}
else if (op == StackOperation.TENANT_DELETE) {
byte[] tenantName = (byte[])inst.popParam().join();
inst.push(inst.context.db.deleteTenant(tenantName));
inst.push(TenantManagement.deleteTenant(inst.context.db, tenantName));
}
else if (op == StackOperation.TENANT_SET_ACTIVE) {
byte[] tenantName = (byte[])inst.popParam().join();
@ -761,7 +762,7 @@ public class StackTester {
private static void testTenantTupleNames(Database db) {
try {
db.allocateTenant(Tuple.from("tenant")).join();
TenantManagement.createTenant(db, Tuple.from("tenant")).join();
Tenant tenant = db.openTenant(Tuple.from("tenant"));
tenant.run(tr -> {
@ -781,7 +782,7 @@ public class StackTester {
return null;
});
db.deleteTenant(Tuple.from("tenant")).join();
TenantManagement.deleteTenant(db, Tuple.from("tenant")).join();
}
catch(Exception e) {
e.printStackTrace();