/*
* 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.hadoop.hbase.mapreduce;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.testclassification.SmallTests;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.FSUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.mockito.Mockito;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.verify;
@Category({ SmallTests.class })
public class TestMultiTableSnapshotInputFormatImpl {
private MultiTableSnapshotInputFormatImpl subject;
private Map<String, Collection<Scan>> snapshotScans;
private Path restoreDir;
private Configuration conf;
private Path rootDir;
@Before
public void setUp() throws Exception {
this.subject = Mockito.spy(new MultiTableSnapshotInputFormatImpl());
// mock out restoreSnapshot
// TODO: this is kind of meh; it'd be much nicer to just inject the RestoreSnapshotHelper
// dependency into the
// input format. However, we need a new RestoreSnapshotHelper per snapshot in the current
// design, and it *also*
// feels weird to introduce a RestoreSnapshotHelperFactory and inject that, which would
// probably be the more "pure"
// way of doing things. This is the lesser of two evils, perhaps?
doNothing().when(this.subject).
restoreSnapshot(any(Configuration.class), any(String.class), any(Path.class),
any(Path.class), any(FileSystem.class));
this.conf = new Configuration();
this.rootDir = new Path("file:///test-root-dir");
FSUtils.setRootDir(conf, rootDir);
this.snapshotScans = ImmutableMap.<String, Collection<Scan>>of("snapshot1",
ImmutableList.of(new Scan(Bytes.toBytes("1"), Bytes.toBytes("2"))), "snapshot2",
ImmutableList.of(new Scan(Bytes.toBytes("3"), Bytes.toBytes("4")),
new Scan(Bytes.toBytes("5"), Bytes.toBytes("6"))));
this.restoreDir = new Path(FSUtils.getRootDir(conf), "restore-dir");
}
public void callSetInput() throws IOException {
subject.setInput(this.conf, snapshotScans, restoreDir);
}
public Map<String, Collection<ScanWithEquals>> toScanWithEquals(
Map<String, Collection<Scan>> snapshotScans) throws IOException {
Map<String, Collection<ScanWithEquals>> rtn = Maps.newHashMap();
for (Map.Entry<String, Collection<Scan>> entry : snapshotScans.entrySet()) {
List<ScanWithEquals> scans = Lists.newArrayList();
for (Scan scan : entry.getValue()) {
scans.add(new ScanWithEquals(scan));
}
rtn.put(entry.getKey(), scans);
}
return rtn;
}
public static class ScanWithEquals {
private final String startRow;
private final String stopRow;
/**
* Creates a new instance of this class while copying all values.
*
* @param scan The scan instance to copy from.
* @throws java.io.IOException When copying the values fails.
*/
public ScanWithEquals(Scan scan) throws IOException {
this.startRow = Bytes.toStringBinary(scan.getStartRow());
this.stopRow = Bytes.toStringBinary(scan.getStopRow());
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ScanWithEquals)) {
return false;
}
ScanWithEquals otherScan = (ScanWithEquals) obj;
return Objects.equals(this.startRow, otherScan.startRow) && Objects
.equals(this.stopRow, otherScan.stopRow);
}
@Override
public String toString() {
return com.google.common.base.Objects.toStringHelper(this).add("startRow", startRow)
.add("stopRow", stopRow).toString();
}
}
@Test
public void testSetInputSetsSnapshotToScans() throws Exception {
callSetInput();
Map<String, Collection<Scan>> actual = subject.getSnapshotsToScans(conf);
// convert to scans we can use .equals on
Map<String, Collection<ScanWithEquals>> actualWithEquals = toScanWithEquals(actual);
Map<String, Collection<ScanWithEquals>> expectedWithEquals = toScanWithEquals(snapshotScans);
assertEquals(expectedWithEquals, actualWithEquals);
}
@Test
public void testSetInputPushesRestoreDirectories() throws Exception {
callSetInput();
Map<String, Path> restoreDirs = subject.getSnapshotDirs(conf);
assertEquals(this.snapshotScans.keySet(), restoreDirs.keySet());
}
@Test
public void testSetInputCreatesRestoreDirectoriesUnderRootRestoreDir() throws Exception {
callSetInput();
Map<String, Path> restoreDirs = subject.getSnapshotDirs(conf);
for (Path snapshotDir : restoreDirs.values()) {
assertEquals("Expected " + snapshotDir + " to be a child of " + restoreDir, restoreDir,
snapshotDir.getParent());
}
}
@Test
public void testSetInputRestoresSnapshots() throws Exception {
callSetInput();
Map<String, Path> snapshotDirs = subject.getSnapshotDirs(conf);
for (Map.Entry<String, Path> entry : snapshotDirs.entrySet()) {
verify(this.subject).restoreSnapshot(eq(this.conf), eq(entry.getKey()), eq(this.rootDir),
eq(entry.getValue()), any(FileSystem.class));
}
}
}