This is an automated email from the ASF dual-hosted git repository.

dlmarion pushed a commit to branch 2.1
in repository https://gitbox.apache.org/repos/asf/accumulo.git


The following commit(s) were added to refs/heads/2.1 by this push:
     new f02d6a880e Fix src and dest namespace handling in clone table 
operation (#5334)
f02d6a880e is described below

commit f02d6a880ed822820881f2fdf7d0827fcbd8db52
Author: Dave Marion <dlmar...@apache.org>
AuthorDate: Thu Apr 3 07:33:38 2025 -0400

    Fix src and dest namespace handling in clone table operation (#5334)
    
    Updates clone table fate operation to use both the source and dest
    namespaces when checking permissions.
    
    Closes #5324
---
 .../accumulo/manager/FateServiceHandler.java       |  19 ++-
 .../accumulo/manager/tableOps/clone/CloneInfo.java |  79 ++++++++++--
 .../manager/tableOps/clone/CloneMetadata.java      |  11 +-
 .../manager/tableOps/clone/ClonePermissions.java   |   8 +-
 .../manager/tableOps/clone/CloneTable.java         |  28 ++---
 .../manager/tableOps/clone/CloneZookeeper.java     |  41 +++----
 .../manager/tableOps/clone/FinishCloneTable.java   |  25 ++--
 .../accumulo/test/functional/CloneTestIT.java      | 132 +++++++++++++++++++++
 8 files changed, 268 insertions(+), 75 deletions(-)

diff --git 
a/server/manager/src/main/java/org/apache/accumulo/manager/FateServiceHandler.java
 
b/server/manager/src/main/java/org/apache/accumulo/manager/FateServiceHandler.java
index 0444099675..c9ecd1d4d8 100644
--- 
a/server/manager/src/main/java/org/apache/accumulo/manager/FateServiceHandler.java
+++ 
b/server/manager/src/main/java/org/apache/accumulo/manager/FateServiceHandler.java
@@ -286,12 +286,21 @@ class FateServiceHandler implements FateService.Iface {
           keepOffline = 
Boolean.parseBoolean(ByteBufferUtil.toString(arguments.get(2)));
         }
 
+        NamespaceId srcNamespaceId;
+        try {
+          srcNamespaceId = manager.getContext().getNamespaceId(srcTableId);
+        } catch (TableNotFoundException e) {
+          // could happen if the table was deleted while processing this 
request
+          throw new ThriftTableOperationException(srcTableId.canonical(), 
null, tableOp,
+              TableOperationExceptionType.NOTFOUND, "");
+        }
+
         NamespaceId namespaceId;
         try {
           namespaceId = Namespaces.getNamespaceId(manager.getContext(),
               TableNameUtil.qualify(tableName).getFirst());
         } catch (NamespaceNotFoundException e) {
-          // shouldn't happen, but possible once cloning between namespaces is 
supported
+          // dest namespace does not exist yet, needs to be created
           throw new ThriftTableOperationException(null, tableName, tableOp,
               TableOperationExceptionType.NAMESPACE_NOTFOUND, "");
         }
@@ -299,7 +308,7 @@ class FateServiceHandler implements FateService.Iface {
         final boolean canCloneTable;
         try {
           canCloneTable =
-              manager.security.canCloneTable(c, srcTableId, tableName, 
namespaceId, namespaceId);
+              manager.security.canCloneTable(c, srcTableId, tableName, 
namespaceId, srcNamespaceId);
         } catch (ThriftSecurityException e) {
           throwIfTableMissingSecurityException(e, srcTableId, null, 
TableOperation.CLONE);
           throw e;
@@ -336,9 +345,9 @@ class FateServiceHandler implements FateService.Iface {
           goalMessage += " and keep offline.";
         }
 
-        manager.fate().seedTransaction(
-            op.toString(), opid, new TraceRepo<>(new 
CloneTable(c.getPrincipal(), namespaceId,
-                srcTableId, tableName, propertiesToSet, propertiesToExclude, 
keepOffline)),
+        manager.fate().seedTransaction(op.toString(), opid,
+            new TraceRepo<>(new CloneTable(c.getPrincipal(), srcNamespaceId, 
srcTableId,
+                namespaceId, tableName, propertiesToSet, propertiesToExclude, 
keepOffline)),
             autoCleanup, goalMessage);
 
         break;
diff --git 
a/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/clone/CloneInfo.java
 
b/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/clone/CloneInfo.java
index 676e4d88e7..2a697827ad 100644
--- 
a/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/clone/CloneInfo.java
+++ 
b/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/clone/CloneInfo.java
@@ -29,14 +29,73 @@ class CloneInfo implements Serializable {
 
   private static final long serialVersionUID = 1L;
 
-  TableId srcTableId;
-  String tableName;
-  TableId tableId;
-  NamespaceId namespaceId;
-  NamespaceId srcNamespaceId;
-  Map<String,String> propertiesToSet;
-  Set<String> propertiesToExclude;
-  boolean keepOffline;
-
-  public String user;
+  private final TableId srcTableId;
+  private final String tableName;
+  private TableId tableId;
+  // TODO: Make final in 3.1
+  private NamespaceId namespaceId;
+  private final NamespaceId srcNamespaceId;
+  private final Map<String,String> propertiesToSet;
+  private final Set<String> propertiesToExclude;
+  private final boolean keepOffline;
+  private final String user;
+
+  public CloneInfo(NamespaceId srcNamespaceId, TableId srcTableId, NamespaceId 
dstNamespaceId,
+      String dstTableName, Map<String,String> propertiesToSet, Set<String> 
propertiesToExclude,
+      boolean keepOffline, String user) {
+    super();
+    this.srcNamespaceId = srcNamespaceId;
+    this.srcTableId = srcTableId;
+    this.tableName = dstTableName;
+    this.namespaceId = dstNamespaceId;
+    this.propertiesToSet = propertiesToSet;
+    this.propertiesToExclude = propertiesToExclude;
+    this.keepOffline = keepOffline;
+    this.user = user;
+  }
+
+  public TableId getSrcTableId() {
+    return srcTableId;
+  }
+
+  public String getTableName() {
+    return tableName;
+  }
+
+  public void setTableId(TableId dstTableId) {
+    this.tableId = dstTableId;
+  }
+
+  public TableId getTableId() {
+    return tableId;
+  }
+
+  public NamespaceId getNamespaceId() {
+    return namespaceId;
+  }
+
+  public void setNamespaceId(NamespaceId nid) {
+    this.namespaceId = nid;
+  }
+
+  public NamespaceId getSrcNamespaceId() {
+    return srcNamespaceId;
+  }
+
+  public Map<String,String> getPropertiesToSet() {
+    return propertiesToSet;
+  }
+
+  public Set<String> getPropertiesToExclude() {
+    return propertiesToExclude;
+  }
+
+  public boolean isKeepOffline() {
+    return keepOffline;
+  }
+
+  public String getUser() {
+    return user;
+  }
+
 }
diff --git 
a/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/clone/CloneMetadata.java
 
b/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/clone/CloneMetadata.java
index 0616edb1eb..82f73d0383 100644
--- 
a/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/clone/CloneMetadata.java
+++ 
b/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/clone/CloneMetadata.java
@@ -41,19 +41,20 @@ class CloneMetadata extends ManagerRepo {
   @Override
   public Repo<Manager> call(long tid, Manager environment) throws Exception {
     LoggerFactory.getLogger(CloneMetadata.class)
-        .info(String.format("Cloning %s with tableId %s from srcTableId %s", 
cloneInfo.tableName,
-            cloneInfo.tableId, cloneInfo.srcTableId));
+        .info(String.format("Cloning %s with tableId %s from srcTableId %s",
+            cloneInfo.getTableName(), cloneInfo.getTableId(), 
cloneInfo.getSrcTableId()));
     // need to clear out any metadata entries for tableId just in case this
     // died before and is executing again
-    MetadataTableUtil.deleteTable(cloneInfo.tableId, false, 
environment.getContext(),
+    MetadataTableUtil.deleteTable(cloneInfo.getTableId(), false, 
environment.getContext(),
         environment.getManagerLock());
-    MetadataTableUtil.cloneTable(environment.getContext(), 
cloneInfo.srcTableId, cloneInfo.tableId);
+    MetadataTableUtil.cloneTable(environment.getContext(), 
cloneInfo.getSrcTableId(),
+        cloneInfo.getTableId());
     return new FinishCloneTable(cloneInfo);
   }
 
   @Override
   public void undo(long tid, Manager environment) throws Exception {
-    MetadataTableUtil.deleteTable(cloneInfo.tableId, false, 
environment.getContext(),
+    MetadataTableUtil.deleteTable(cloneInfo.getTableId(), false, 
environment.getContext(),
         environment.getManagerLock());
   }
 
diff --git 
a/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/clone/ClonePermissions.java
 
b/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/clone/ClonePermissions.java
index e448e289ef..ac1432c3d2 100644
--- 
a/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/clone/ClonePermissions.java
+++ 
b/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/clone/ClonePermissions.java
@@ -50,8 +50,8 @@ class ClonePermissions extends ManagerRepo {
     for (TablePermission permission : TablePermission.values()) {
       try {
         environment.getContext().getSecurityOperation().grantTablePermission(
-            environment.getContext().rpcCreds(), cloneInfo.user, 
cloneInfo.tableId,
-            cloneInfo.tableName, permission, cloneInfo.namespaceId);
+            environment.getContext().rpcCreds(), cloneInfo.getUser(), 
cloneInfo.getTableId(),
+            cloneInfo.getTableName(), permission, cloneInfo.getNamespaceId());
       } catch (ThriftSecurityException e) {
         LoggerFactory.getLogger(ClonePermissions.class).error("{}", 
e.getMessage(), e);
         throw e;
@@ -64,7 +64,7 @@ class ClonePermissions extends ManagerRepo {
     try {
       return new CloneZookeeper(cloneInfo, environment.getContext());
     } catch (NamespaceNotFoundException e) {
-      throw new AcceptableThriftTableOperationException(null, 
cloneInfo.tableName,
+      throw new AcceptableThriftTableOperationException(null, 
cloneInfo.getTableName(),
           TableOperation.CLONE, TableOperationExceptionType.NAMESPACE_NOTFOUND,
           "Namespace for target table not found");
     }
@@ -73,6 +73,6 @@ class ClonePermissions extends ManagerRepo {
   @Override
   public void undo(long tid, Manager environment) throws Exception {
     
environment.getContext().getSecurityOperation().deleteTable(environment.getContext().rpcCreds(),
-        cloneInfo.tableId, cloneInfo.namespaceId);
+        cloneInfo.getTableId(), cloneInfo.getNamespaceId());
   }
 }
diff --git 
a/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/clone/CloneTable.java
 
b/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/clone/CloneTable.java
index 0a03526c38..c5587ee692 100644
--- 
a/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/clone/CloneTable.java
+++ 
b/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/clone/CloneTable.java
@@ -34,23 +34,18 @@ public class CloneTable extends ManagerRepo {
   private static final long serialVersionUID = 1L;
   private final CloneInfo cloneInfo;
 
-  public CloneTable(String user, NamespaceId namespaceId, TableId srcTableId, 
String tableName,
-      Map<String,String> propertiesToSet, Set<String> propertiesToExclude, 
boolean keepOffline) {
-    cloneInfo = new CloneInfo();
-    cloneInfo.user = user;
-    cloneInfo.srcTableId = srcTableId;
-    cloneInfo.tableName = tableName;
-    cloneInfo.propertiesToExclude = propertiesToExclude;
-    cloneInfo.propertiesToSet = propertiesToSet;
-    cloneInfo.srcNamespaceId = namespaceId;
-    cloneInfo.keepOffline = keepOffline;
+  public CloneTable(String user, NamespaceId srcNamespaceId, TableId 
srcTableId,
+      NamespaceId namespaceId, String tableName, Map<String,String> 
propertiesToSet,
+      Set<String> propertiesToExclude, boolean keepOffline) {
+    cloneInfo = new CloneInfo(srcNamespaceId, srcTableId, namespaceId, 
tableName, propertiesToSet,
+        propertiesToExclude, keepOffline, user);
   }
 
   @Override
   public long isReady(long tid, Manager environment) throws Exception {
-    long val = Utils.reserveNamespace(environment, cloneInfo.srcNamespaceId, 
tid, false, true,
+    long val = Utils.reserveNamespace(environment, cloneInfo.getNamespaceId(), 
tid, false, true,
         TableOperation.CLONE);
-    val += Utils.reserveTable(environment, cloneInfo.srcTableId, tid, false, 
true,
+    val += Utils.reserveTable(environment, cloneInfo.getSrcTableId(), tid, 
false, true,
         TableOperation.CLONE);
     return val;
   }
@@ -60,9 +55,8 @@ public class CloneTable extends ManagerRepo {
 
     Utils.getIdLock().lock();
     try {
-      cloneInfo.tableId =
-          Utils.getNextId(cloneInfo.tableName, environment.getContext(), 
TableId::of);
-
+      cloneInfo.setTableId(
+          Utils.getNextId(cloneInfo.getTableName(), environment.getContext(), 
TableId::of));
       return new ClonePermissions(cloneInfo);
     } finally {
       Utils.getIdLock().unlock();
@@ -71,8 +65,8 @@ public class CloneTable extends ManagerRepo {
 
   @Override
   public void undo(long tid, Manager environment) {
-    Utils.unreserveNamespace(environment, cloneInfo.srcNamespaceId, tid, 
false);
-    Utils.unreserveTable(environment, cloneInfo.srcTableId, tid, false);
+    Utils.unreserveNamespace(environment, cloneInfo.getNamespaceId(), tid, 
false);
+    Utils.unreserveTable(environment, cloneInfo.getSrcTableId(), tid, false);
   }
 
 }
diff --git 
a/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/clone/CloneZookeeper.java
 
b/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/clone/CloneZookeeper.java
index 77ef41df12..fe9c9fcd13 100644
--- 
a/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/clone/CloneZookeeper.java
+++ 
b/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/clone/CloneZookeeper.java
@@ -37,20 +37,24 @@ class CloneZookeeper extends ManagerRepo {
   public CloneZookeeper(CloneInfo cloneInfo, ClientContext context)
       throws NamespaceNotFoundException {
     this.cloneInfo = cloneInfo;
-    this.cloneInfo.namespaceId = Namespaces.getNamespaceId(context,
-        TableNameUtil.qualify(this.cloneInfo.tableName).getFirst());
+    if (this.cloneInfo.getNamespaceId() == null) {
+      // Prior to 2.1.4 the namespaceId was calculated in this
+      // step and set on the cloneInfo object here. If for some
+      // reason we are processing a pre-2.1.3 CloneTable operation,
+      // then we need to continue to set this here as it will be
+      // null in the deserialized CloneInfo object.
+      //
+      // TODO: Remove this check in 3.1 as Fate operations
+      // need to be cleaned up before a major upgrade.
+      this.cloneInfo.setNamespaceId(Namespaces.getNamespaceId(context,
+          TableNameUtil.qualify(this.cloneInfo.getTableName()).getFirst()));
+    }
   }
 
   @Override
   public long isReady(long tid, Manager environment) throws Exception {
-    long val = 0;
-    if (!cloneInfo.srcNamespaceId.equals(cloneInfo.namespaceId)) {
-      val += Utils.reserveNamespace(environment, cloneInfo.namespaceId, tid, 
false, true,
-          TableOperation.CLONE);
-    }
-    val +=
-        Utils.reserveTable(environment, cloneInfo.tableId, tid, true, false, 
TableOperation.CLONE);
-    return val;
+    return Utils.reserveTable(environment, cloneInfo.getTableId(), tid, true, 
false,
+        TableOperation.CLONE);
   }
 
   @Override
@@ -59,12 +63,12 @@ class CloneZookeeper extends ManagerRepo {
     try {
       // write tableName & tableId to zookeeper
 
-      Utils.checkTableNameDoesNotExist(environment.getContext(), 
cloneInfo.tableName,
-          cloneInfo.namespaceId, cloneInfo.tableId, TableOperation.CLONE);
+      Utils.checkTableNameDoesNotExist(environment.getContext(), 
cloneInfo.getTableName(),
+          cloneInfo.getNamespaceId(), cloneInfo.getTableId(), 
TableOperation.CLONE);
 
-      environment.getTableManager().cloneTable(cloneInfo.srcTableId, 
cloneInfo.tableId,
-          cloneInfo.tableName, cloneInfo.namespaceId, 
cloneInfo.propertiesToSet,
-          cloneInfo.propertiesToExclude);
+      environment.getTableManager().cloneTable(cloneInfo.getSrcTableId(), 
cloneInfo.getTableId(),
+          cloneInfo.getTableName(), cloneInfo.getNamespaceId(), 
cloneInfo.getPropertiesToSet(),
+          cloneInfo.getPropertiesToExclude());
       environment.getContext().clearTableListCache();
 
       return new CloneMetadata(cloneInfo);
@@ -75,11 +79,8 @@ class CloneZookeeper extends ManagerRepo {
 
   @Override
   public void undo(long tid, Manager environment) throws Exception {
-    environment.getTableManager().removeTable(cloneInfo.tableId);
-    if (!cloneInfo.srcNamespaceId.equals(cloneInfo.namespaceId)) {
-      Utils.unreserveNamespace(environment, cloneInfo.namespaceId, tid, false);
-    }
-    Utils.unreserveTable(environment, cloneInfo.tableId, tid, true);
+    environment.getTableManager().removeTable(cloneInfo.getTableId());
+    Utils.unreserveTable(environment, cloneInfo.getTableId(), tid, true);
     environment.getContext().clearTableListCache();
   }
 
diff --git 
a/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/clone/FinishCloneTable.java
 
b/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/clone/FinishCloneTable.java
index 8eed682359..5fedbcfed7 100644
--- 
a/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/clone/FinishCloneTable.java
+++ 
b/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/clone/FinishCloneTable.java
@@ -50,26 +50,23 @@ class FinishCloneTable extends ManagerRepo {
     // that are not used... tablet will create directories as needed
 
     final EnumSet<TableState> expectedCurrStates = EnumSet.of(TableState.NEW);
-    if (cloneInfo.keepOffline) {
-      environment.getTableManager().transitionTableState(cloneInfo.tableId, 
TableState.OFFLINE,
+    if (cloneInfo.isKeepOffline()) {
+      
environment.getTableManager().transitionTableState(cloneInfo.getTableId(), 
TableState.OFFLINE,
           expectedCurrStates);
     } else {
-      environment.getTableManager().transitionTableState(cloneInfo.tableId, 
TableState.ONLINE,
+      
environment.getTableManager().transitionTableState(cloneInfo.getTableId(), 
TableState.ONLINE,
           expectedCurrStates);
     }
+    Utils.unreserveTable(environment, cloneInfo.getTableId(), tid, true);
+    Utils.unreserveNamespace(environment, cloneInfo.getNamespaceId(), tid, 
false);
+    Utils.unreserveTable(environment, cloneInfo.getSrcTableId(), tid, false);
 
-    Utils.unreserveNamespace(environment, cloneInfo.srcNamespaceId, tid, 
false);
-    if (!cloneInfo.srcNamespaceId.equals(cloneInfo.namespaceId)) {
-      Utils.unreserveNamespace(environment, cloneInfo.namespaceId, tid, false);
-    }
-    Utils.unreserveTable(environment, cloneInfo.srcTableId, tid, false);
-    Utils.unreserveTable(environment, cloneInfo.tableId, tid, true);
-
-    environment.getEventCoordinator().event("Cloned table %s from %s", 
cloneInfo.tableName,
-        cloneInfo.srcTableId);
+    environment.getEventCoordinator().event("Cloned table %s from %s", 
cloneInfo.getTableName(),
+        cloneInfo.getSrcTableId());
 
-    LoggerFactory.getLogger(FinishCloneTable.class).debug("Cloned table " + 
cloneInfo.srcTableId
-        + " " + cloneInfo.tableId + " " + cloneInfo.tableName);
+    LoggerFactory.getLogger(FinishCloneTable.class)
+        .debug("Cloned table " + cloneInfo.getSrcTableId() + " " + 
cloneInfo.getTableId() + " "
+            + cloneInfo.getTableName());
 
     return null;
   }
diff --git 
a/test/src/main/java/org/apache/accumulo/test/functional/CloneTestIT.java 
b/test/src/main/java/org/apache/accumulo/test/functional/CloneTestIT.java
index 6390a2ca1c..7963043049 100644
--- a/test/src/main/java/org/apache/accumulo/test/functional/CloneTestIT.java
+++ b/test/src/main/java/org/apache/accumulo/test/functional/CloneTestIT.java
@@ -42,12 +42,15 @@ import org.apache.accumulo.cluster.AccumuloCluster;
 import org.apache.accumulo.core.client.Accumulo;
 import org.apache.accumulo.core.client.AccumuloClient;
 import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
 import org.apache.accumulo.core.client.BatchWriter;
 import org.apache.accumulo.core.client.Scanner;
+import org.apache.accumulo.core.client.TableExistsException;
 import org.apache.accumulo.core.client.TableNotFoundException;
 import org.apache.accumulo.core.client.admin.CloneConfiguration;
 import org.apache.accumulo.core.client.admin.DiskUsage;
 import org.apache.accumulo.core.client.admin.NewTableConfiguration;
+import org.apache.accumulo.core.client.security.tokens.PasswordToken;
 import org.apache.accumulo.core.clientImpl.ClientContext;
 import org.apache.accumulo.core.conf.Property;
 import org.apache.accumulo.core.data.Key;
@@ -61,6 +64,8 @@ import org.apache.accumulo.core.metadata.RootTable;
 import 
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.DataFileColumnFamily;
 import 
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ServerColumnFamily;
 import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.security.NamespacePermission;
+import org.apache.accumulo.core.security.TablePermission;
 import org.apache.accumulo.harness.AccumuloClusterHarness;
 import org.apache.accumulo.miniclusterImpl.MiniAccumuloClusterImpl;
 import org.apache.hadoop.fs.FileStatus;
@@ -350,4 +355,131 @@ public class CloneTestIT extends AccumuloClusterHarness {
           "mc1", CloneConfiguration.empty()));
     }
   }
+
+  private void baseCloneNamespace(AccumuloClient client, String src, String 
dest) throws Exception {
+    writeData(src, client).close();
+    // Don't force a flush on the table, let's make sure the
+    // clone operation does this
+    client.tableOperations().clone(src, dest, CloneConfiguration.empty());
+    checkData(dest, client);
+  }
+
+  @Test
+  public void testCloneSameNamespace() throws Exception {
+    try (AccumuloClient client = 
Accumulo.newClient().from(getClientProps()).build()) {
+      String tableName = getUniqueNames(1)[0];
+      client.namespaceOperations().create("old");
+      client.tableOperations().create("old." + tableName);
+      assertThrows(TableExistsException.class,
+          () -> baseCloneNamespace(client, "old." + tableName, "old." + 
tableName));
+    }
+  }
+
+  @Test
+  public void testCloneIntoDiffNamespace() throws Exception {
+    try (AccumuloClient client = 
Accumulo.newClient().from(getClientProps()).build()) {
+      String tableName = getUniqueNames(1)[0];
+      client.namespaceOperations().create("old");
+      client.tableOperations().create("old." + tableName);
+      client.namespaceOperations().create("new");
+      baseCloneNamespace(client, "old." + tableName, "new." + tableName);
+    }
+  }
+
+  @Test
+  public void testCloneIntoDiffNamespaceTableExists() throws Exception {
+    try (AccumuloClient client = 
Accumulo.newClient().from(getClientProps()).build()) {
+      String tableName = getUniqueNames(1)[0];
+      client.namespaceOperations().create("old");
+      client.tableOperations().create("old." + tableName);
+      client.namespaceOperations().create("new");
+      client.tableOperations().create("new." + tableName);
+      assertThrows(TableExistsException.class,
+          () -> baseCloneNamespace(client, "old." + tableName, "new." + 
tableName));
+    }
+  }
+
+  @Test
+  public void testCloneIntoDiffNamespaceDoesntExist() throws Exception {
+    try (AccumuloClient client = 
Accumulo.newClient().from(getClientProps()).build()) {
+      String tableName = getUniqueNames(1)[0];
+      client.namespaceOperations().create("old");
+      client.tableOperations().create("old." + tableName);
+      assertThrows(AccumuloException.class,
+          () -> baseCloneNamespace(client, "old." + tableName, "missing." + 
tableName));
+    }
+  }
+
+  @Test
+  public void testCloneIntoAccumuloNamespace() throws Exception {
+    try (AccumuloClient client = 
Accumulo.newClient().from(getClientProps()).build()) {
+      String tableName = getUniqueNames(1)[0];
+      client.namespaceOperations().create("old");
+      client.tableOperations().create("old." + tableName);
+      assertThrows(AccumuloException.class,
+          () -> baseCloneNamespace(client, "old." + tableName, "accumulo." + 
tableName));
+    }
+  }
+
+  @Test
+  public void testCloneNamespaceIncorrectPermissions() throws Exception {
+    final String tableName = getUniqueNames(1)[0];
+    final String newUserName = "NEW_USER";
+    final String srcNs = "src";
+    final String srcTableName = srcNs + "." + tableName;
+    final String destNs = "dst";
+    final String dstTableName = destNs + "." + tableName;
+
+    try (AccumuloClient client = 
Accumulo.newClient().from(getClientProps()).build()) {
+      client.namespaceOperations().create(srcNs);
+      client.tableOperations().create(srcTableName);
+      client.namespaceOperations().create(destNs);
+      client.securityOperations().createLocalUser(newUserName, new 
PasswordToken(newUserName));
+      // User needs WRITE or ALTER_TABLE on the src table to flush it as part 
of the clone operation
+      client.securityOperations().grantTablePermission(newUserName, 
srcTableName,
+          TablePermission.ALTER_TABLE);
+      client.securityOperations().grantNamespacePermission(newUserName, destNs,
+          NamespacePermission.READ);
+      client.securityOperations().grantNamespacePermission(newUserName, destNs,
+          NamespacePermission.CREATE_TABLE);
+    }
+
+    try (AccumuloClient client =
+        Accumulo.newClient().to(getCluster().getInstanceName(), 
getCluster().getZooKeepers())
+            .as(newUserName, newUserName).build()) {
+      // READ permission is needed on the src table, not the dst namespace
+      assertThrows(AccumuloSecurityException.class, () -> 
client.tableOperations()
+          .clone(srcTableName, dstTableName, CloneConfiguration.empty()));
+    }
+  }
+
+  @Test
+  public void testCloneNamespacePermissions() throws Exception {
+    final String tableName = getUniqueNames(1)[0];
+    final String newUserName = "NEW_USER";
+    final String srcNs = "src";
+    final String srcTableName = srcNs + "." + tableName;
+    final String destNs = "dst";
+    final String dstTableName = destNs + "." + tableName;
+
+    try (AccumuloClient client = 
Accumulo.newClient().from(getClientProps()).build()) {
+      client.namespaceOperations().create(srcNs);
+      client.tableOperations().create(srcTableName);
+      client.namespaceOperations().create(destNs);
+      client.securityOperations().createLocalUser(newUserName, new 
PasswordToken(newUserName));
+      // User needs WRITE or ALTER_TABLE on the src table to flush it as part 
of the clone operation
+      client.securityOperations().grantTablePermission(newUserName, 
srcTableName,
+          TablePermission.ALTER_TABLE);
+      client.securityOperations().grantTablePermission(newUserName, 
srcTableName,
+          TablePermission.READ);
+      client.securityOperations().grantNamespacePermission(newUserName, destNs,
+          NamespacePermission.CREATE_TABLE);
+    }
+
+    try (AccumuloClient client =
+        Accumulo.newClient().to(getCluster().getInstanceName(), 
getCluster().getZooKeepers())
+            .as(newUserName, newUserName).build()) {
+      client.tableOperations().clone(srcTableName, dstTableName, 
CloneConfiguration.empty());
+    }
+  }
 }

Reply via email to