blob: 1471c3930452c48b35bc35e1343950b3e95fe79b [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.core.metadata.schema;
import static org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ServerColumnFamily.COMPACT_COLUMN;
import static org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ServerColumnFamily.DIRECTORY_COLUMN;
import static org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ServerColumnFamily.FLUSH_COLUMN;
import static org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ServerColumnFamily.TIME_COLUMN;
import static org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.TabletColumnFamily.PREV_ROW_COLUMN;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.apache.accumulo.core.client.AccumuloClient;
import org.apache.accumulo.core.client.IsolatedScanner;
import org.apache.accumulo.core.client.Scanner;
import org.apache.accumulo.core.client.TableNotFoundException;
import org.apache.accumulo.core.clientImpl.ClientContext;
import org.apache.accumulo.core.data.Range;
import org.apache.accumulo.core.data.TableId;
import org.apache.accumulo.core.dataImpl.KeyExtent;
import org.apache.accumulo.core.metadata.MetadataTable;
import org.apache.accumulo.core.metadata.RootTable;
import org.apache.accumulo.core.metadata.schema.Ample.DataLevel;
import org.apache.accumulo.core.metadata.schema.Ample.ReadConsistency;
import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection;
import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.BulkFileColumnFamily;
import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ClonedColumnFamily;
import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.CurrentLocationColumnFamily;
import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.DataFileColumnFamily;
import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.FutureLocationColumnFamily;
import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.LastLocationColumnFamily;
import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.LogColumnFamily;
import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ScanFileColumnFamily;
import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.SuspendLocationColumn;
import org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType;
import org.apache.accumulo.core.security.Authorizations;
import org.apache.accumulo.core.util.ColumnFQ;
import org.apache.accumulo.fate.zookeeper.ZooCache;
import org.apache.accumulo.fate.zookeeper.ZooReader;
import org.apache.hadoop.io.Text;
import org.apache.zookeeper.KeeperException;
import com.google.common.base.Preconditions;
/**
* An abstraction layer for reading tablet metadata from the accumulo.metadata and accumulo.root
* tables.
*/
public class TabletsMetadata implements Iterable<TabletMetadata>, AutoCloseable {
private static class Builder implements TableRangeOptions, TableOptions, RangeOptions, Options {
private List<Text> families = new ArrayList<>();
private List<ColumnFQ> qualifiers = new ArrayList<>();
private Ample.DataLevel level;
private String table;
private Range range;
private EnumSet<ColumnType> fetchedCols = EnumSet.noneOf(ColumnType.class);
private Text endRow;
private boolean checkConsistency = false;
private boolean saveKeyValues;
private TableId tableId;
private ReadConsistency readConsistency = ReadConsistency.IMMEDIATE;
@Override
public TabletsMetadata build(AccumuloClient client) {
Preconditions.checkState(level == null ^ table == null);
if (level == DataLevel.ROOT) {
ClientContext ctx = ((ClientContext) client);
return new TabletsMetadata(getRootMetadata(ctx, readConsistency));
} else {
return buildNonRoot(client);
}
}
private TabletsMetadata buildNonRoot(AccumuloClient client) {
try {
String resolvedTable = table == null ? level.metaTable() : table;
Scanner scanner =
new IsolatedScanner(client.createScanner(resolvedTable, Authorizations.EMPTY));
scanner.setRange(range);
if (checkConsistency && !fetchedCols.contains(ColumnType.PREV_ROW)) {
fetch(ColumnType.PREV_ROW);
}
for (Text fam : families) {
scanner.fetchColumnFamily(fam);
}
for (ColumnFQ col : qualifiers) {
col.fetch(scanner);
}
if (families.isEmpty() && qualifiers.isEmpty()) {
fetchedCols = EnumSet.allOf(ColumnType.class);
}
Iterable<TabletMetadata> tmi =
TabletMetadata.convert(scanner, fetchedCols, checkConsistency, saveKeyValues);
if (endRow != null) {
// create an iterable that will stop at the tablet which contains the endRow
return new TabletsMetadata(scanner,
() -> new TabletMetadataIterator(tmi.iterator(), endRow));
} else {
return new TabletsMetadata(scanner, tmi);
}
} catch (TableNotFoundException e) {
throw new RuntimeException(e);
}
}
@Override
public Options checkConsistency() {
this.checkConsistency = true;
return this;
}
@Override
public Options fetch(ColumnType... colsToFetch) {
Preconditions.checkArgument(colsToFetch.length > 0);
for (ColumnType colToFetch : colsToFetch) {
fetchedCols.add(colToFetch);
switch (colToFetch) {
case CLONED:
families.add(ClonedColumnFamily.NAME);
break;
case COMPACT_ID:
qualifiers.add(COMPACT_COLUMN);
break;
case DIR:
qualifiers.add(DIRECTORY_COLUMN);
break;
case FILES:
families.add(DataFileColumnFamily.NAME);
break;
case FLUSH_ID:
qualifiers.add(FLUSH_COLUMN);
break;
case LAST:
families.add(LastLocationColumnFamily.NAME);
break;
case LOADED:
families.add(BulkFileColumnFamily.NAME);
break;
case LOCATION:
families.add(CurrentLocationColumnFamily.NAME);
families.add(FutureLocationColumnFamily.NAME);
break;
case LOGS:
families.add(LogColumnFamily.NAME);
break;
case PREV_ROW:
qualifiers.add(PREV_ROW_COLUMN);
break;
case SCANS:
families.add(ScanFileColumnFamily.NAME);
break;
case SUSPEND:
families.add(SuspendLocationColumn.SUSPEND_COLUMN.getColumnFamily());
break;
case TIME:
qualifiers.add(TIME_COLUMN);
break;
default:
throw new IllegalArgumentException("Unknown col type " + colToFetch);
}
}
return this;
}
@Override
public Options forLevel(DataLevel level) {
this.level = level;
this.range = TabletsSection.getRange();
return this;
}
@Override
public TableRangeOptions forTable(TableId tableId) {
this.level = DataLevel.of(tableId);
this.tableId = tableId;
this.range = TabletsSection.getRange(tableId);
return this;
}
@Override
public Options forTablet(KeyExtent extent) {
forTable(extent.tableId());
this.range = new Range(extent.toMetaRow());
return this;
}
@Override
public Options overRange(Range range) {
this.range = TabletsSection.getRange().clip(range);
return this;
}
@Override
public Options overlapping(Text startRow, Text endRow) {
this.range = new KeyExtent(tableId, null, startRow).toMetaRange();
this.endRow = endRow;
return this;
}
@Override
public Options saveKeyValues() {
this.saveKeyValues = true;
return this;
}
@Override
public RangeOptions scanTable(String tableName) {
this.table = tableName;
this.range = TabletsSection.getRange();
return this;
}
@Override
public Options readConsistency(ReadConsistency readConsistency) {
this.readConsistency = Objects.requireNonNull(readConsistency);
return this;
}
}
public interface Options {
TabletsMetadata build(AccumuloClient client);
/**
* Checks that the metadata table forms a linked list and automatically backs up until it does.
*/
Options checkConsistency();
Options fetch(ColumnType... columnsToFetch);
/**
* Saves the key values seen in the metadata table for each tablet.
*/
Options saveKeyValues();
/**
* Controls how the data is read. If not, set then the default is
* {@link ReadConsistency#IMMEDIATE}
*/
Options readConsistency(ReadConsistency readConsistency);
}
public interface RangeOptions extends Options {
Options overRange(Range range);
}
public interface TableOptions {
/**
* Read all of the tablet metadata for this level.
*/
Options forLevel(DataLevel level);
/**
* Get the tablet metadata for this extents end row. This should only ever return a single
* tablet. No checking is done for prev row, so it could differ.
*/
Options forTablet(KeyExtent extent);
/**
* This method automatically determines where the metadata for the passed in table ID resides.
* For example if a user tablet ID is passed in, then the metadata table is scanned. If the
* metadata table ID is passed in then the root table is scanned. Defaults to returning all
* tablets for the table ID.
*/
TableRangeOptions forTable(TableId tableId);
/**
* Obtain tablet metadata by scanning the metadata table. Defaults to the range
* {@link TabletsSection#getRange()}
*/
default RangeOptions scanMetadataTable() {
return scanTable(MetadataTable.NAME);
}
/**
* Obtain tablet metadata by scanning an arbitrary table. Defaults to the range
* {@link TabletsSection#getRange()}
*/
RangeOptions scanTable(String tableName);
}
public interface TableRangeOptions extends Options {
/**
* @see #overlapping(Text, Text)
*/
default Options overlapping(byte[] startRow, byte[] endRow) {
return overlapping(startRow == null ? null : new Text(startRow),
endRow == null ? null : new Text(endRow));
}
/**
* Limit to tablets that overlap the range {@code (startRow, endRow]}. Can pass null
* representing -inf and +inf. The impl creates open ended ranges which may be problematic, see
* #813.
*/
Options overlapping(Text startRow, Text endRow);
}
private static class TabletMetadataIterator implements Iterator<TabletMetadata> {
private boolean sawLast = false;
private Iterator<TabletMetadata> iter;
private Text endRow;
TabletMetadataIterator(Iterator<TabletMetadata> source, Text endRow) {
this.iter = source;
this.endRow = endRow;
}
@Override
public boolean hasNext() {
return !sawLast && iter.hasNext();
}
@Override
public TabletMetadata next() {
if (sawLast) {
throw new NoSuchElementException();
}
TabletMetadata next = iter.next();
// impossible to construct a range that stops at the first tablet that contains endRow. That
// is why this specialized code exists.
if (next.getExtent().contains(endRow)) {
sawLast = true;
}
return next;
}
}
public static TableOptions builder() {
return new Builder();
}
private static TabletMetadata getRootMetadata(ClientContext ctx,
ReadConsistency readConsistency) {
String zkRoot = ctx.getZooKeeperRoot();
switch (readConsistency) {
case EVENTUAL:
return getRootMetadata(zkRoot, ctx.getZooCache());
case IMMEDIATE:
ZooReader zooReader = new ZooReader(ctx.getZooKeepers(), ctx.getZooKeepersSessionTimeOut());
try {
return RootTabletMetadata.fromJson(zooReader.getData(zkRoot + RootTable.ZROOT_TABLET))
.convertToTabletMetadata();
} catch (InterruptedException | KeeperException e) {
throw new RuntimeException(e);
}
default:
throw new IllegalArgumentException("Unknown consistency level " + readConsistency);
}
}
public static TabletMetadata getRootMetadata(String zkRoot, ZooCache zc) {
return RootTabletMetadata.fromJson(zc.get(zkRoot + RootTable.ZROOT_TABLET))
.convertToTabletMetadata();
}
private Scanner scanner;
private Iterable<TabletMetadata> tablets;
private TabletsMetadata(TabletMetadata tm) {
this.scanner = null;
this.tablets = Collections.singleton(tm);
}
private TabletsMetadata(Scanner scanner, Iterable<TabletMetadata> tmi) {
this.scanner = scanner;
this.tablets = tmi;
}
@Override
public void close() {
if (scanner != null) {
scanner.close();
}
}
@Override
public Iterator<TabletMetadata> iterator() {
return tablets.iterator();
}
public Stream<TabletMetadata> stream() {
return StreamSupport.stream(tablets.spliterator(), false);
}
}