001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.mapreduce;
019
020import static org.junit.Assert.assertEquals;
021import static org.mockito.Matchers.any;
022import static org.mockito.Matchers.eq;
023import static org.mockito.Mockito.doNothing;
024import static org.mockito.Mockito.verify;
025
026import java.io.IOException;
027import java.util.Collection;
028import java.util.List;
029import java.util.Map;
030import java.util.Objects;
031import org.apache.hadoop.conf.Configuration;
032import org.apache.hadoop.fs.Path;
033import org.apache.hadoop.hbase.HBaseClassTestRule;
034import org.apache.hadoop.hbase.client.Scan;
035import org.apache.hadoop.hbase.testclassification.SmallTests;
036import org.apache.hadoop.hbase.util.Bytes;
037import org.apache.hadoop.hbase.util.FSUtils;
038import org.junit.Before;
039import org.junit.ClassRule;
040import org.junit.Test;
041import org.junit.experimental.categories.Category;
042import org.mockito.Mockito;
043
044import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableList;
045import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableMap;
046import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
047import org.apache.hbase.thirdparty.com.google.common.collect.Maps;
048
049@Category({ SmallTests.class })
050public class TestMultiTableSnapshotInputFormatImpl {
051
052  @ClassRule
053  public static final HBaseClassTestRule CLASS_RULE =
054      HBaseClassTestRule.forClass(TestMultiTableSnapshotInputFormatImpl.class);
055
056  private MultiTableSnapshotInputFormatImpl subject;
057  private Map<String, Collection<Scan>> snapshotScans;
058  private Path restoreDir;
059  private Configuration conf;
060  private Path rootDir;
061
062  @Before
063  public void setUp() throws Exception {
064    this.subject = Mockito.spy(new MultiTableSnapshotInputFormatImpl());
065
066    // mock out restoreSnapshot
067    // TODO: this is kind of meh; it'd be much nicer to just inject the RestoreSnapshotHelper
068    // dependency into the
069    // input format. However, we need a new RestoreSnapshotHelper per snapshot in the current
070    // design, and it *also*
071    // feels weird to introduce a RestoreSnapshotHelperFactory and inject that, which would
072    // probably be the more "pure"
073    // way of doing things. This is the lesser of two evils, perhaps?
074    doNothing().when(this.subject).
075        restoreSnapshot(any(), any(), any(),
076            any(), any());
077
078    this.conf = new Configuration();
079    this.rootDir = new Path("file:///test-root-dir");
080    FSUtils.setRootDir(conf, rootDir);
081    this.snapshotScans = ImmutableMap.<String, Collection<Scan>>of("snapshot1",
082        ImmutableList.of(new Scan(Bytes.toBytes("1"), Bytes.toBytes("2"))), "snapshot2",
083        ImmutableList.of(new Scan(Bytes.toBytes("3"), Bytes.toBytes("4")),
084            new Scan(Bytes.toBytes("5"), Bytes.toBytes("6"))));
085
086    this.restoreDir = new Path(FSUtils.getRootDir(conf), "restore-dir");
087
088  }
089
090  public void callSetInput() throws IOException {
091    subject.setInput(this.conf, snapshotScans, restoreDir);
092  }
093
094  public Map<String, Collection<ScanWithEquals>> toScanWithEquals(
095      Map<String, Collection<Scan>> snapshotScans) throws IOException {
096    Map<String, Collection<ScanWithEquals>> rtn = Maps.newHashMap();
097
098    for (Map.Entry<String, Collection<Scan>> entry : snapshotScans.entrySet()) {
099      List<ScanWithEquals> scans = Lists.newArrayList();
100
101      for (Scan scan : entry.getValue()) {
102        scans.add(new ScanWithEquals(scan));
103      }
104      rtn.put(entry.getKey(), scans);
105    }
106
107    return rtn;
108  }
109
110  public static class ScanWithEquals {
111
112    private final String startRow;
113    private final String stopRow;
114
115    /**
116     * Creates a new instance of this class while copying all values.
117     *
118     * @param scan The scan instance to copy from.
119     * @throws java.io.IOException When copying the values fails.
120     */
121    public ScanWithEquals(Scan scan) throws IOException {
122      this.startRow = Bytes.toStringBinary(scan.getStartRow());
123      this.stopRow = Bytes.toStringBinary(scan.getStopRow());
124    }
125
126    @Override
127    public boolean equals(Object obj) {
128      if (!(obj instanceof ScanWithEquals)) {
129        return false;
130      }
131      ScanWithEquals otherScan = (ScanWithEquals) obj;
132      return Objects.equals(this.startRow, otherScan.startRow) && Objects
133          .equals(this.stopRow, otherScan.stopRow);
134    }
135
136    @Override
137    public int hashCode() {
138      return Objects.hash(startRow, stopRow);
139    }
140
141    @Override
142    public String toString() {
143      return org.apache.hbase.thirdparty.com.google.common.base.MoreObjects.
144        toStringHelper(this).add("startRow", startRow)
145          .add("stopRow", stopRow).toString();
146    }
147  }
148
149  @Test
150  public void testSetInputSetsSnapshotToScans() throws Exception {
151
152    callSetInput();
153
154    Map<String, Collection<Scan>> actual = subject.getSnapshotsToScans(conf);
155
156    // convert to scans we can use .equals on
157    Map<String, Collection<ScanWithEquals>> actualWithEquals = toScanWithEquals(actual);
158    Map<String, Collection<ScanWithEquals>> expectedWithEquals = toScanWithEquals(snapshotScans);
159
160    assertEquals(expectedWithEquals, actualWithEquals);
161  }
162
163  @Test
164  public void testSetInputPushesRestoreDirectories() throws Exception {
165    callSetInput();
166
167    Map<String, Path> restoreDirs = subject.getSnapshotDirs(conf);
168
169    assertEquals(this.snapshotScans.keySet(), restoreDirs.keySet());
170  }
171
172  @Test
173  public void testSetInputCreatesRestoreDirectoriesUnderRootRestoreDir() throws Exception {
174    callSetInput();
175
176    Map<String, Path> restoreDirs = subject.getSnapshotDirs(conf);
177
178    for (Path snapshotDir : restoreDirs.values()) {
179      assertEquals("Expected " + snapshotDir + " to be a child of " + restoreDir, restoreDir,
180          snapshotDir.getParent());
181    }
182  }
183
184  @Test
185  public void testSetInputRestoresSnapshots() throws Exception {
186    callSetInput();
187
188    Map<String, Path> snapshotDirs = subject.getSnapshotDirs(conf);
189
190    for (Map.Entry<String, Path> entry : snapshotDirs.entrySet()) {
191      verify(this.subject).restoreSnapshot(eq(this.conf), eq(entry.getKey()), eq(this.rootDir),
192          eq(entry.getValue()), any());
193    }
194  }
195}