blob: d74b71a3e970db1b8964a1d3bf82e5e9f6de3467 [file] [log] [blame]
/*
* 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.manager.tableOps.delete;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Map.Entry;
import org.apache.accumulo.core.client.AccumuloClient;
import org.apache.accumulo.core.client.BatchScanner;
import org.apache.accumulo.core.client.IteratorSetting;
import org.apache.accumulo.core.client.Scanner;
import org.apache.accumulo.core.clientImpl.Tables;
import org.apache.accumulo.core.clientImpl.thrift.ThriftSecurityException;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.NamespaceId;
import org.apache.accumulo.core.data.Range;
import org.apache.accumulo.core.data.TableId;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.dataImpl.KeyExtent;
import org.apache.accumulo.core.iterators.user.GrepIterator;
import org.apache.accumulo.core.metadata.MetadataTable;
import org.apache.accumulo.core.metadata.TabletLocationState;
import org.apache.accumulo.core.metadata.TabletState;
import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection;
import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.DataFileColumnFamily;
import org.apache.accumulo.core.security.Authorizations;
import org.apache.accumulo.fate.Repo;
import org.apache.accumulo.manager.Manager;
import org.apache.accumulo.manager.tableOps.ManagerRepo;
import org.apache.accumulo.manager.tableOps.Utils;
import org.apache.accumulo.server.ServerConstants;
import org.apache.accumulo.server.fs.VolumeManager;
import org.apache.accumulo.server.manager.state.MetaDataTableScanner;
import org.apache.accumulo.server.problems.ProblemReports;
import org.apache.accumulo.server.security.AuditedSecurityOperation;
import org.apache.accumulo.server.util.MetadataTableUtil;
import org.apache.hadoop.fs.Path;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class CleanUp extends ManagerRepo {
private static final Logger log = LoggerFactory.getLogger(CleanUp.class);
private static final long serialVersionUID = 1L;
private TableId tableId;
private NamespaceId namespaceId;
private long creationTime;
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
// handle the case where we start executing on a new machine where the current time is in the
// past relative to the previous machine
// if the new machine has time in the future, that will work ok w/ hasCycled
if (System.currentTimeMillis() < creationTime) {
creationTime = System.currentTimeMillis();
}
}
public CleanUp(TableId tableId, NamespaceId namespaceId) {
this.tableId = tableId;
this.namespaceId = namespaceId;
creationTime = System.currentTimeMillis();
}
@Override
public long isReady(long tid, Manager manager) throws Exception {
if (!manager.hasCycled(creationTime)) {
return 50;
}
boolean done = true;
Range tableRange = new KeyExtent(tableId, null, null).toMetaRange();
Scanner scanner = manager.getContext().createScanner(MetadataTable.NAME, Authorizations.EMPTY);
MetaDataTableScanner.configureScanner(scanner, manager);
scanner.setRange(tableRange);
for (Entry<Key,Value> entry : scanner) {
TabletLocationState locationState =
MetaDataTableScanner.createTabletLocationState(entry.getKey(), entry.getValue());
TabletState state = locationState.getState(manager.onlineTabletServers());
if (!state.equals(TabletState.UNASSIGNED)) {
// This code will even wait on tablets that are assigned to dead tablets servers. This is
// intentional because the manager may make metadata writes for these tablets. See #587
log.debug("Still waiting for table to be deleted: " + tableId + " locationState: "
+ locationState);
done = false;
break;
}
}
if (!done)
return 50;
return 0;
}
@Override
public Repo<Manager> call(long tid, Manager manager) {
manager.clearMigrations(tableId);
int refCount = 0;
try {
// look for other tables that references this table's files
AccumuloClient client = manager.getContext();
try (BatchScanner bs =
client.createBatchScanner(MetadataTable.NAME, Authorizations.EMPTY, 8)) {
Range allTables = TabletsSection.getRange();
Range tableRange = TabletsSection.getRange(tableId);
Range beforeTable =
new Range(allTables.getStartKey(), true, tableRange.getStartKey(), false);
Range afterTable = new Range(tableRange.getEndKey(), false, allTables.getEndKey(), true);
bs.setRanges(Arrays.asList(beforeTable, afterTable));
bs.fetchColumnFamily(DataFileColumnFamily.NAME);
IteratorSetting cfg = new IteratorSetting(40, "grep", GrepIterator.class);
GrepIterator.setTerm(cfg, "/" + tableId + "/");
bs.addScanIterator(cfg);
for (Entry<Key,Value> entry : bs) {
if (entry.getKey().getColumnQualifier().toString().contains("/" + tableId + "/")) {
refCount++;
}
}
}
} catch (Exception e) {
refCount = -1;
log.error("Failed to scan " + MetadataTable.NAME + " looking for references to deleted table "
+ tableId, e);
}
// remove metadata table entries
try {
// Intentionally do not pass manager lock. If manager loses lock, this operation may complete
// before manager can kill itself.
// If the manager lock passed to deleteTable, it is possible that the delete mutations will be
// dropped. If the delete operations
// are dropped and the operation completes, then the deletes will not be repeated.
MetadataTableUtil.deleteTable(tableId, refCount != 0, manager.getContext(), null);
} catch (Exception e) {
log.error("error deleting " + tableId + " from metadata table", e);
}
// remove any problem reports the table may have
try {
ProblemReports.getInstance(manager.getContext()).deleteProblemReports(tableId);
} catch (Exception e) {
log.error("Failed to delete problem reports for table " + tableId, e);
}
if (refCount == 0) {
// delete the map files
try {
VolumeManager fs = manager.getVolumeManager();
for (String dir : ServerConstants.getTablesDirs(manager.getContext())) {
fs.deleteRecursively(new Path(dir, tableId.canonical()));
}
} catch (IOException e) {
log.error("Unable to remove deleted table directory", e);
} catch (IllegalArgumentException exception) {
if (exception.getCause() instanceof UnknownHostException) {
/* Thrown if HDFS encounters a DNS problem in some edge cases */
log.error("Unable to remove deleted table directory", exception);
} else {
throw exception;
}
}
}
// remove table from zookeeper
try {
manager.getTableManager().removeTable(tableId);
Tables.clearCache(manager.getContext());
} catch (Exception e) {
log.error("Failed to find table id in zookeeper", e);
}
// remove any permissions associated with this table
try {
AuditedSecurityOperation.getInstance(manager.getContext())
.deleteTable(manager.getContext().rpcCreds(), tableId, namespaceId);
} catch (ThriftSecurityException e) {
log.error("{}", e.getMessage(), e);
}
Utils.unreserveTable(manager, tableId, tid, true);
Utils.unreserveNamespace(manager, namespaceId, tid, false);
LoggerFactory.getLogger(CleanUp.class).debug("Deleted table " + tableId);
return null;
}
@Override
public void undo(long tid, Manager environment) {
// nothing to do
}
}