Return-Path: X-Original-To: apmail-accumulo-commits-archive@www.apache.org Delivered-To: apmail-accumulo-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id CB88911BD3 for ; Wed, 24 Sep 2014 21:23:04 +0000 (UTC) Received: (qmail 96827 invoked by uid 500); 24 Sep 2014 21:23:04 -0000 Delivered-To: apmail-accumulo-commits-archive@accumulo.apache.org Received: (qmail 96722 invoked by uid 500); 24 Sep 2014 21:23:04 -0000 Mailing-List: contact commits-help@accumulo.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@accumulo.apache.org Delivered-To: mailing list commits@accumulo.apache.org Received: (qmail 96631 invoked by uid 99); 24 Sep 2014 21:23:04 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 24 Sep 2014 21:23:04 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 6B4589A3DED; Wed, 24 Sep 2014 21:23:04 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: elserj@apache.org To: commits@accumulo.apache.org Date: Wed, 24 Sep 2014 21:23:08 -0000 Message-Id: <89d303af569a4fc5bf8b009a5e7203ed@git.apache.org> In-Reply-To: <876ef8bc77c948f8ac7a2ee043b4662b@git.apache.org> References: <876ef8bc77c948f8ac7a2ee043b4662b@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [5/6] git commit: Merge branch '1.5.3-SNAPSHOT' into 1.6.2-SNAPSHOT Merge branch '1.5.3-SNAPSHOT' into 1.6.2-SNAPSHOT Conflicts: server/src/main/java/org/apache/accumulo/server/master/Master.java Project: http://git-wip-us.apache.org/repos/asf/accumulo/repo Commit: http://git-wip-us.apache.org/repos/asf/accumulo/commit/443888b4 Tree: http://git-wip-us.apache.org/repos/asf/accumulo/tree/443888b4 Diff: http://git-wip-us.apache.org/repos/asf/accumulo/diff/443888b4 Branch: refs/heads/1.6.2-SNAPSHOT Commit: 443888b4db7674ec4e1502cb66ddb67f6956bc44 Parents: 53705e7 2579d51 Author: Josh Elser Authored: Wed Sep 24 15:07:29 2014 -0400 Committer: Josh Elser Committed: Wed Sep 24 15:07:29 2014 -0400 ---------------------------------------------------------------------- .../accumulo/master/FateServiceHandler.java | 158 +++++++++++++++++-- 1 file changed, 141 insertions(+), 17 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/accumulo/blob/443888b4/server/master/src/main/java/org/apache/accumulo/master/FateServiceHandler.java ---------------------------------------------------------------------- diff --cc server/master/src/main/java/org/apache/accumulo/master/FateServiceHandler.java index cd522f5,0000000..a3ea117 mode 100644,000000..100644 --- a/server/master/src/main/java/org/apache/accumulo/master/FateServiceHandler.java +++ b/server/master/src/main/java/org/apache/accumulo/master/FateServiceHandler.java @@@ -1,426 -1,0 +1,550 @@@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.accumulo.master; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + ++import org.apache.accumulo.core.client.AccumuloSecurityException; +import org.apache.accumulo.core.client.IteratorSetting; +import org.apache.accumulo.core.client.NamespaceNotFoundException; - import org.apache.accumulo.core.client.impl.TableOperationsImpl; ++import org.apache.accumulo.core.client.TableNotFoundException; +import org.apache.accumulo.core.client.admin.TimeType; +import org.apache.accumulo.core.client.impl.Namespaces; ++import org.apache.accumulo.core.client.impl.TableOperationsImpl; +import org.apache.accumulo.core.client.impl.Tables; +import org.apache.accumulo.core.client.impl.thrift.SecurityErrorCode; +import org.apache.accumulo.core.client.impl.thrift.TableOperation; +import org.apache.accumulo.core.client.impl.thrift.TableOperationExceptionType; +import org.apache.accumulo.core.client.impl.thrift.ThriftSecurityException; +import org.apache.accumulo.core.client.impl.thrift.ThriftTableOperationException; +import org.apache.accumulo.core.iterators.IteratorUtil; +import org.apache.accumulo.core.master.thrift.FateOperation; +import org.apache.accumulo.core.master.thrift.FateService; +import org.apache.accumulo.core.security.thrift.TCredentials; +import org.apache.accumulo.core.util.ArgumentChecker.Validator; +import org.apache.accumulo.core.util.ByteBufferUtil; +import org.apache.accumulo.fate.ReadOnlyTStore.TStatus; +import org.apache.accumulo.master.tableOps.BulkImport; +import org.apache.accumulo.master.tableOps.CancelCompactions; +import org.apache.accumulo.master.tableOps.ChangeTableState; +import org.apache.accumulo.master.tableOps.CloneTable; +import org.apache.accumulo.master.tableOps.CompactRange; +import org.apache.accumulo.master.tableOps.CreateNamespace; +import org.apache.accumulo.master.tableOps.CreateTable; +import org.apache.accumulo.master.tableOps.DeleteNamespace; +import org.apache.accumulo.master.tableOps.DeleteTable; +import org.apache.accumulo.master.tableOps.ExportTable; +import org.apache.accumulo.master.tableOps.ImportTable; +import org.apache.accumulo.master.tableOps.RenameNamespace; +import org.apache.accumulo.master.tableOps.RenameTable; +import org.apache.accumulo.master.tableOps.TableRangeOp; +import org.apache.accumulo.master.tableOps.TraceRepo; +import org.apache.accumulo.server.client.ClientServiceHandler; +import org.apache.accumulo.server.master.state.MergeInfo; +import org.apache.accumulo.server.util.TablePropUtil; +import org.apache.accumulo.trace.thrift.TInfo; +import org.apache.hadoop.io.Text; +import org.apache.log4j.Logger; + +/** - * ++ * + */ +class FateServiceHandler implements FateService.Iface { + + protected final Master master; + protected static final Logger log = Master.log; + + public FateServiceHandler(Master master) { + this.master = master; + } + + @Override + public long beginFateOperation(TInfo tinfo, TCredentials credentials) throws ThriftSecurityException { + authenticate(credentials); + return master.fate.startTransaction(); + } + + @Override + public void executeFateOperation(TInfo tinfo, TCredentials c, long opid, FateOperation op, List arguments, Map options, + boolean autoCleanup) throws ThriftSecurityException, ThriftTableOperationException { + authenticate(c); + + switch (op) { + case NAMESPACE_CREATE: { + TableOperation tableOp = TableOperation.CREATE; + String namespace = validateNamespaceArgument(arguments.get(0), tableOp, null); + + if (!master.security.canCreateNamespace(c, namespace)) + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + + master.fate.seedTransaction(opid, new TraceRepo(new CreateNamespace(c.getPrincipal(), namespace, options)), autoCleanup); + break; + } + case NAMESPACE_RENAME: { + TableOperation tableOp = TableOperation.RENAME; + String oldName = validateNamespaceArgument(arguments.get(0), tableOp, Namespaces.NOT_DEFAULT.and(Namespaces.NOT_ACCUMULO)); + String newName = validateNamespaceArgument(arguments.get(1), tableOp, null); + + String namespaceId = ClientServiceHandler.checkNamespaceId(master.getInstance(), oldName, tableOp); + if (!master.security.canRenameNamespace(c, namespaceId, oldName, newName)) + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + + master.fate.seedTransaction(opid, new TraceRepo(new RenameNamespace(namespaceId, oldName, newName)), autoCleanup); + break; + } + case NAMESPACE_DELETE: { + TableOperation tableOp = TableOperation.DELETE; + String namespace = validateNamespaceArgument(arguments.get(0), tableOp, Namespaces.NOT_DEFAULT.and(Namespaces.NOT_ACCUMULO)); + + String namespaceId = ClientServiceHandler.checkNamespaceId(master.getInstance(), namespace, tableOp); + if (!master.security.canDeleteNamespace(c, namespaceId)) + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + + master.fate.seedTransaction(opid, new TraceRepo(new DeleteNamespace(namespaceId)), autoCleanup); + break; + } + case TABLE_CREATE: { + TableOperation tableOp = TableOperation.CREATE; + String tableName = validateTableNameArgument(arguments.get(0), tableOp, Tables.NOT_SYSTEM); + TimeType timeType = TimeType.valueOf(ByteBufferUtil.toString(arguments.get(1))); + + String namespaceId; + + try { + namespaceId = Namespaces.getNamespaceId(master.getInstance(), Tables.qualify(tableName).getFirst()); + } catch (NamespaceNotFoundException e) { + throw new ThriftTableOperationException(null, tableName, tableOp, TableOperationExceptionType.NAMESPACE_NOTFOUND, ""); + } + + if (!master.security.canCreateTable(c, tableName, namespaceId)) + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + + master.fate.seedTransaction(opid, new TraceRepo(new CreateTable(c.getPrincipal(), tableName, timeType, options, namespaceId)), autoCleanup); + + break; + } + case TABLE_RENAME: { + TableOperation tableOp = TableOperation.RENAME; + final String oldTableName = validateTableNameArgument(arguments.get(0), tableOp, Tables.NOT_SYSTEM); + String newTableName = validateTableNameArgument(arguments.get(1), tableOp, new Validator() { + + @Override + public boolean isValid(String argument) { + // verify they are in the same namespace + String oldNamespace = Tables.qualify(oldTableName).getFirst(); + return oldNamespace.equals(Tables.qualify(argument).getFirst()); + } + + @Override + public String invalidMessage(String argument) { + return "Cannot move tables to a new namespace by renaming. The namespace for " + oldTableName + " does not match " + argument; + } + + }); + + String tableId = ClientServiceHandler.checkTableId(master.getInstance(), oldTableName, tableOp); + String namespaceId = Tables.getNamespaceId(master.getInstance(), tableId); + - if (!master.security.canRenameTable(c, tableId, oldTableName, newTableName, namespaceId)) ++ final boolean canRename; ++ try { ++ canRename = master.security.canRenameTable(c, tableId, oldTableName, newTableName, namespaceId); ++ } catch (ThriftSecurityException e) { ++ throwIfTableMissingSecurityException(e, tableId, oldTableName, TableOperation.RENAME); ++ throw e; ++ } ++ ++ if (!canRename) + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + + try { + master.fate.seedTransaction(opid, new TraceRepo(new RenameTable(tableId, oldTableName, newTableName)), autoCleanup); + } catch (NamespaceNotFoundException e) { + throw new ThriftTableOperationException(null, oldTableName, tableOp, TableOperationExceptionType.NAMESPACE_NOTFOUND, ""); + } + + break; + } + case TABLE_CLONE: { + TableOperation tableOp = TableOperation.CLONE; + String srcTableId = validateTableIdArgument(arguments.get(0), tableOp, Tables.NOT_ROOT_ID); + String tableName = validateTableNameArgument(arguments.get(1), tableOp, Tables.NOT_SYSTEM); + String namespaceId; + try { + namespaceId = Namespaces.getNamespaceId(master.getInstance(), Tables.qualify(tableName).getFirst()); + } catch (NamespaceNotFoundException e) { + // shouldn't happen, but possible once cloning between namespaces is supported + throw new ThriftTableOperationException(null, tableName, tableOp, TableOperationExceptionType.NAMESPACE_NOTFOUND, ""); + } - - if (!master.security.canCloneTable(c, srcTableId, tableName, namespaceId, namespaceId)) ++ ++ final boolean canCloneTable; ++ try { ++ canCloneTable = master.security.canCloneTable(c, srcTableId, tableName, namespaceId, namespaceId); ++ } catch (ThriftSecurityException e) { ++ throwIfTableMissingSecurityException(e, srcTableId, null, TableOperation.CLONE); ++ throw e; ++ } ++ ++ if (!canCloneTable) + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + + Map propertiesToSet = new HashMap(); + Set propertiesToExclude = new HashSet(); + + for (Entry entry : options.entrySet()) { + if (entry.getKey().startsWith(TableOperationsImpl.CLONE_EXCLUDE_PREFIX)) { + propertiesToExclude.add(entry.getKey().substring(TableOperationsImpl.CLONE_EXCLUDE_PREFIX.length())); + continue; + } + + if (!TablePropUtil.isPropertyValid(entry.getKey(), entry.getValue())) { + throw new ThriftTableOperationException(null, tableName, tableOp, TableOperationExceptionType.OTHER, "Property or value not valid " + + entry.getKey() + "=" + entry.getValue()); + } + + propertiesToSet.put(entry.getKey(), entry.getValue()); + } + + master.fate.seedTransaction(opid, new TraceRepo(new CloneTable(c.getPrincipal(), srcTableId, tableName, propertiesToSet, propertiesToExclude)), + autoCleanup); + + break; + } + case TABLE_DELETE: { + TableOperation tableOp = TableOperation.DELETE; + String tableName = validateTableNameArgument(arguments.get(0), tableOp, Tables.NOT_SYSTEM); + + final String tableId = ClientServiceHandler.checkTableId(master.getInstance(), tableName, tableOp); + String namespaceId = Tables.getNamespaceId(master.getInstance(), tableId); + - if (!master.security.canDeleteTable(c, tableId, namespaceId)) ++ final boolean canDeleteTable; ++ try { ++ canDeleteTable = master.security.canDeleteTable(c, tableId, namespaceId); ++ } catch (ThriftSecurityException e) { ++ throwIfTableMissingSecurityException(e, tableId, tableName, TableOperation.DELETE); ++ throw e; ++ } ++ ++ if (!canDeleteTable) + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + master.fate.seedTransaction(opid, new TraceRepo(new DeleteTable(tableId)), autoCleanup); + break; + } + case TABLE_ONLINE: { + TableOperation tableOp = TableOperation.ONLINE; + final String tableId = validateTableIdArgument(arguments.get(0), tableOp, Tables.NOT_ROOT_ID); + String namespaceId = Tables.getNamespaceId(master.getInstance(), tableId); + - if (!master.security.canOnlineOfflineTable(c, tableId, op, namespaceId)) ++ final boolean canOnlineOfflineTable; ++ try { ++ canOnlineOfflineTable = master.security.canOnlineOfflineTable(c, tableId, op, namespaceId); ++ } catch (ThriftSecurityException e) { ++ throwIfTableMissingSecurityException(e, tableId, null, TableOperation.ONLINE); ++ throw e; ++ } ++ ++ if (!canOnlineOfflineTable) + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + + master.fate.seedTransaction(opid, new TraceRepo(new ChangeTableState(tableId, tableOp)), autoCleanup); + break; + } + case TABLE_OFFLINE: { + TableOperation tableOp = TableOperation.OFFLINE; + final String tableId = validateTableIdArgument(arguments.get(0), tableOp, Tables.NOT_ROOT_ID); + String namespaceId = Tables.getNamespaceId(master.getInstance(), tableId); + - if (!master.security.canOnlineOfflineTable(c, tableId, op, namespaceId)) ++ final boolean canOnlineOfflineTable; ++ try { ++ canOnlineOfflineTable = master.security.canOnlineOfflineTable(c, tableId, op, namespaceId); ++ } catch (ThriftSecurityException e) { ++ throwIfTableMissingSecurityException(e, tableId, null, TableOperation.OFFLINE); ++ throw e; ++ } ++ ++ if (!canOnlineOfflineTable) + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + + master.fate.seedTransaction(opid, new TraceRepo(new ChangeTableState(tableId, tableOp)), autoCleanup); + break; + } + case TABLE_MERGE: { + TableOperation tableOp = TableOperation.MERGE; + String tableName = validateTableNameArgument(arguments.get(0), tableOp, null); + Text startRow = ByteBufferUtil.toText(arguments.get(1)); + Text endRow = ByteBufferUtil.toText(arguments.get(2)); + + final String tableId = ClientServiceHandler.checkTableId(master.getInstance(), tableName, tableOp); + String namespaceId = Tables.getNamespaceId(master.getInstance(), tableId); + - if (!master.security.canMerge(c, tableId, namespaceId)) ++ final boolean canMerge; ++ try { ++ canMerge = master.security.canMerge(c, tableId, namespaceId); ++ } catch (ThriftSecurityException e) { ++ throwIfTableMissingSecurityException(e, tableId, tableName, TableOperation.MERGE); ++ throw e; ++ } ++ ++ if (!canMerge) + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + + Master.log.debug("Creating merge op: " + tableId + " " + startRow + " " + endRow); + master.fate.seedTransaction(opid, new TraceRepo(new TableRangeOp(MergeInfo.Operation.MERGE, tableId, startRow, endRow)), autoCleanup); + break; + } + case TABLE_DELETE_RANGE: { + TableOperation tableOp = TableOperation.DELETE_RANGE; + String tableName = validateTableNameArgument(arguments.get(0), tableOp, Tables.NOT_SYSTEM); + Text startRow = ByteBufferUtil.toText(arguments.get(1)); + Text endRow = ByteBufferUtil.toText(arguments.get(2)); + + final String tableId = ClientServiceHandler.checkTableId(master.getInstance(), tableName, tableOp); + String namespaceId = Tables.getNamespaceId(master.getInstance(), tableId); + - if (!master.security.canDeleteRange(c, tableId, tableName, startRow, endRow, namespaceId)) ++ final boolean canDeleteRange; ++ try { ++ canDeleteRange = master.security.canDeleteRange(c, tableId, tableName, startRow, endRow, namespaceId); ++ } catch (ThriftSecurityException e) { ++ throwIfTableMissingSecurityException(e, tableId, tableName, TableOperation.DELETE_RANGE); ++ throw e; ++ } ++ ++ if (!canDeleteRange) + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + + master.fate.seedTransaction(opid, new TraceRepo(new TableRangeOp(MergeInfo.Operation.DELETE, tableId, startRow, endRow)), autoCleanup); + break; + } + case TABLE_BULK_IMPORT: { + TableOperation tableOp = TableOperation.BULK_IMPORT; + String tableName = validateTableNameArgument(arguments.get(0), tableOp, Tables.NOT_SYSTEM); + String dir = ByteBufferUtil.toString(arguments.get(1)); + String failDir = ByteBufferUtil.toString(arguments.get(2)); + boolean setTime = Boolean.parseBoolean(ByteBufferUtil.toString(arguments.get(3))); + + final String tableId = ClientServiceHandler.checkTableId(master.getInstance(), tableName, tableOp); + String namespaceId = Tables.getNamespaceId(master.getInstance(), tableId); - - if (!master.security.canBulkImport(c, tableId, tableName, dir, failDir, namespaceId)) ++ ++ final boolean canBulkImport; ++ try { ++ canBulkImport = master.security.canBulkImport(c, tableId, tableName, dir, failDir, namespaceId); ++ } catch (ThriftSecurityException e) { ++ throwIfTableMissingSecurityException(e, tableId, tableName, TableOperation.BULK_IMPORT); ++ throw e; ++ } ++ ++ if (!canBulkImport) + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + + master.fate.seedTransaction(opid, new TraceRepo(new BulkImport(tableId, dir, failDir, setTime)), autoCleanup); + break; + } + case TABLE_COMPACT: { + TableOperation tableOp = TableOperation.COMPACT; + String tableId = validateTableIdArgument(arguments.get(0), tableOp, null); + byte[] startRow = ByteBufferUtil.toBytes(arguments.get(1)); + byte[] endRow = ByteBufferUtil.toBytes(arguments.get(2)); + List iterators = IteratorUtil.decodeIteratorSettings(ByteBufferUtil.toBytes(arguments.get(3))); + String namespaceId = Tables.getNamespaceId(master.getInstance(), tableId); + - if (!master.security.canCompact(c, tableId, namespaceId)) ++ final boolean canCompact; ++ try { ++ canCompact = master.security.canCompact(c, tableId, namespaceId); ++ } catch (ThriftSecurityException e) { ++ throwIfTableMissingSecurityException(e, tableId, null, TableOperation.COMPACT); ++ throw e; ++ } ++ ++ if (!canCompact) + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + + master.fate.seedTransaction(opid, new TraceRepo(new CompactRange(tableId, startRow, endRow, iterators)), autoCleanup); + break; + } + case TABLE_CANCEL_COMPACT: { + TableOperation tableOp = TableOperation.COMPACT_CANCEL; + String tableId = validateTableIdArgument(arguments.get(0), tableOp, null); + String namespaceId = Tables.getNamespaceId(master.getInstance(), tableId); + - if (!master.security.canCompact(c, tableId, namespaceId)) ++ final boolean canCancelCompact; ++ try { ++ canCancelCompact = master.security.canCompact(c, tableId, namespaceId); ++ } catch (ThriftSecurityException e) { ++ throwIfTableMissingSecurityException(e, tableId, null, TableOperation.COMPACT_CANCEL); ++ throw e; ++ } ++ ++ if (!canCancelCompact) + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + + master.fate.seedTransaction(opid, new TraceRepo(new CancelCompactions(tableId)), autoCleanup); + break; + } + case TABLE_IMPORT: { + TableOperation tableOp = TableOperation.IMPORT; + String tableName = validateTableNameArgument(arguments.get(0), tableOp, Tables.NOT_SYSTEM); + String exportDir = ByteBufferUtil.toString(arguments.get(1)); + String namespaceId; + try { + namespaceId = Namespaces.getNamespaceId(master.getInstance(), Tables.qualify(tableName).getFirst()); + } catch (NamespaceNotFoundException e) { + throw new ThriftTableOperationException(null, tableName, tableOp, TableOperationExceptionType.NAMESPACE_NOTFOUND, ""); + } + - if (!master.security.canImport(c, tableName, exportDir, namespaceId)) ++ final boolean canImport; ++ try { ++ canImport = master.security.canImport(c, tableName, exportDir, namespaceId); ++ } catch (ThriftSecurityException e) { ++ throwIfTableMissingSecurityException(e, null, tableName, TableOperation.IMPORT); ++ throw e; ++ } ++ ++ if (!canImport) + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + + master.fate.seedTransaction(opid, new TraceRepo(new ImportTable(c.getPrincipal(), tableName, exportDir, namespaceId)), autoCleanup); + break; + } + case TABLE_EXPORT: { + TableOperation tableOp = TableOperation.EXPORT; + String tableName = validateTableNameArgument(arguments.get(0), tableOp, Tables.NOT_SYSTEM); + String exportDir = ByteBufferUtil.toString(arguments.get(1)); + + String tableId = ClientServiceHandler.checkTableId(master.getInstance(), tableName, tableOp); + String namespaceId = Tables.getNamespaceId(master.getInstance(), tableId); - - if (!master.security.canExport(c, tableId, tableName, exportDir, namespaceId)) ++ ++ final boolean canExport; ++ try { ++ canExport = master.security.canExport(c, tableId, tableName, exportDir, namespaceId); ++ } catch (ThriftSecurityException e) { ++ throwIfTableMissingSecurityException(e, tableId, tableName, TableOperation.EXPORT); ++ throw e; ++ } ++ ++ if (!canExport) + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + + master.fate.seedTransaction(opid, new TraceRepo(new ExportTable(tableName, tableId, exportDir)), autoCleanup); + break; + } + default: + throw new UnsupportedOperationException(); + } + } + ++ /** ++ * Inspects the {@link ThriftSecurityException} and throws a {@link ThriftTableOperationException} if the {@link SecurityErrorCode} on the ++ * {@link ThriftSecurityException} was {code}TABLE_DOESNT_EXIST{code}. If the {@link ThriftSecurityException} is thrown because a table doesn't exist anymore, ++ * clients will likely see an {@link AccumuloSecurityException} instead of a {@link TableNotFoundException} as expected. If the ++ * {@link ThriftSecurityException} has a different {@link SecurityErrorCode}, this method does nothing and expects the caller to properly handle the original ++ * exception. ++ * ++ * @param e ++ * A caught ThriftSecurityException ++ * @param tableId ++ * Table ID being operated on, or null ++ * @param tableName ++ * Table name being operated on, or null ++ * @param op ++ * The TableOperation the Master was attempting to perform ++ * @throws ThriftTableOperationException ++ * Thrown if {@link e} was thrown because {@link SecurityErrorCode#TABLE_DOESNT_EXIST} ++ */ ++ private void throwIfTableMissingSecurityException(ThriftSecurityException e, String tableId, String tableName, TableOperation op) ++ throws ThriftTableOperationException { ++ // ACCUMULO-3135 Table can be deleted after we get table ID but before we can check permission ++ if (e.isSetCode() && SecurityErrorCode.TABLE_DOESNT_EXIST == e.getCode()) { ++ throw new ThriftTableOperationException(tableId, tableName, op, TableOperationExceptionType.NOTFOUND, "Table no longer exists"); ++ } ++ } ++ + @Override + public String waitForFateOperation(TInfo tinfo, TCredentials credentials, long opid) throws ThriftSecurityException, ThriftTableOperationException { + authenticate(credentials); + + TStatus status = master.fate.waitForCompletion(opid); + if (status == TStatus.FAILED) { + Exception e = master.fate.getException(opid); + if (e instanceof ThriftTableOperationException) + throw (ThriftTableOperationException) e; + else if (e instanceof ThriftSecurityException) + throw (ThriftSecurityException) e; + else if (e instanceof RuntimeException) + throw (RuntimeException) e; + else + throw new RuntimeException(e); + } + + String ret = master.fate.getReturn(opid); + if (ret == null) + ret = ""; // thrift does not like returning null + return ret; + } + + @Override + public void finishFateOperation(TInfo tinfo, TCredentials credentials, long opid) throws ThriftSecurityException { + authenticate(credentials); + master.fate.delete(opid); + } + + protected void authenticate(TCredentials credentials) throws ThriftSecurityException { + // this is a bit redundant, the credentials of the caller (the first arg) will throw an exception if it fails to authenticate + // before the second arg is checked (which would return true or false) + if (!master.security.authenticateUser(credentials, credentials)) + throw new ThriftSecurityException(credentials.getPrincipal(), SecurityErrorCode.BAD_CREDENTIALS); + } + + // Verify table name arguments are valid, and match any additional restrictions + private String validateTableIdArgument(ByteBuffer tableIdArg, TableOperation op, Validator userValidator) throws ThriftTableOperationException { + String tableId = tableIdArg == null ? null : ByteBufferUtil.toString(tableIdArg); + try { + return Tables.VALID_ID.and(userValidator).validate(tableId); + } catch (IllegalArgumentException e) { + String why = e.getMessage(); + log.warn(why); + throw new ThriftTableOperationException(tableId, null, op, TableOperationExceptionType.INVALID_NAME, why); + } + } + + // Verify table name arguments are valid, and match any additional restrictions + private String validateTableNameArgument(ByteBuffer tableNameArg, TableOperation op, Validator userValidator) throws ThriftTableOperationException { + String tableName = tableNameArg == null ? null : ByteBufferUtil.toString(tableNameArg); + return _validateArgument(tableName, op, Tables.VALID_NAME.and(userValidator)); + } + + // Verify namespace arguments are valid, and match any additional restrictions + private String validateNamespaceArgument(ByteBuffer namespaceArg, TableOperation op, Validator userValidator) throws ThriftTableOperationException { + String namespace = namespaceArg == null ? null : ByteBufferUtil.toString(namespaceArg); + return _validateArgument(namespace, op, Namespaces.VALID_NAME.and(userValidator)); + } + + // helper to handle the exception + private T _validateArgument(T arg, TableOperation op, Validator validator) throws ThriftTableOperationException { + try { + return validator.validate(arg); + } catch (IllegalArgumentException e) { + String why = e.getMessage(); + log.warn(why); + throw new ThriftTableOperationException(null, String.valueOf(arg), op, TableOperationExceptionType.INVALID_NAME, why); + } + } +}