View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase.mapreduce;
20  
21  import com.google.common.collect.ImmutableList;
22  import com.google.common.collect.ImmutableMap;
23  import com.google.common.collect.Lists;
24  import com.google.common.collect.Maps;
25  import org.apache.hadoop.conf.Configuration;
26  import org.apache.hadoop.fs.FileSystem;
27  import org.apache.hadoop.fs.Path;
28  import org.apache.hadoop.hbase.client.Scan;
29  import org.apache.hadoop.hbase.testclassification.SmallTests;
30  import org.apache.hadoop.hbase.util.Bytes;
31  import org.apache.hadoop.hbase.util.FSUtils;
32  import org.junit.Before;
33  import org.junit.Test;
34  import org.junit.experimental.categories.Category;
35  import org.mockito.Mockito;
36  
37  import java.io.IOException;
38  import java.util.Collection;
39  import java.util.List;
40  import java.util.Map;
41  
42  import static org.junit.Assert.assertEquals;
43  import static org.mockito.Matchers.any;
44  import static org.mockito.Matchers.eq;
45  import static org.mockito.Mockito.doNothing;
46  import static org.mockito.Mockito.verify;
47  
48  @Category({ SmallTests.class })
49  public class TestMultiTableSnapshotInputFormatImpl {
50  
51    private MultiTableSnapshotInputFormatImpl subject;
52    private Map<String, Collection<Scan>> snapshotScans;
53    private Path restoreDir;
54    private Configuration conf;
55    private Path rootDir;
56  
57    @Before
58    public void setUp() throws Exception {
59      this.subject = Mockito.spy(new MultiTableSnapshotInputFormatImpl());
60  
61      // mock out restoreSnapshot
62      // TODO: this is kind of meh; it'd be much nicer to just inject the RestoreSnapshotHelper
63      // dependency into the
64      // input format. However, we need a new RestoreSnapshotHelper per snapshot in the current
65      // design, and it *also*
66      // feels weird to introduce a RestoreSnapshotHelperFactory and inject that, which would
67      // probably be the more "pure"
68      // way of doing things. This is the lesser of two evils, perhaps?
69      doNothing().when(this.subject).
70          restoreSnapshot(any(Configuration.class), any(String.class), any(Path.class),
71              any(Path.class), any(FileSystem.class));
72  
73      this.conf = new Configuration();
74      this.rootDir = new Path("file:///test-root-dir");
75      FSUtils.setRootDir(conf, rootDir);
76      this.snapshotScans = ImmutableMap.<String, Collection<Scan>>of("snapshot1",
77          ImmutableList.of(new Scan(Bytes.toBytes("1"), Bytes.toBytes("2"))), "snapshot2",
78          ImmutableList.of(new Scan(Bytes.toBytes("3"), Bytes.toBytes("4")),
79              new Scan(Bytes.toBytes("5"), Bytes.toBytes("6"))));
80  
81      this.restoreDir = new Path(FSUtils.getRootDir(conf), "restore-dir");
82  
83    }
84  
85    public void callSetInput() throws IOException {
86      subject.setInput(this.conf, snapshotScans, restoreDir);
87    }
88  
89    public Map<String, Collection<ScanWithEquals>> toScanWithEquals(
90        Map<String, Collection<Scan>> snapshotScans) throws IOException {
91      Map<String, Collection<ScanWithEquals>> rtn = Maps.newHashMap();
92  
93      for (Map.Entry<String, Collection<Scan>> entry : snapshotScans.entrySet()) {
94        List<ScanWithEquals> scans = Lists.newArrayList();
95  
96        for (Scan scan : entry.getValue()) {
97          scans.add(new ScanWithEquals(scan));
98        }
99        rtn.put(entry.getKey(), scans);
100     }
101 
102     return rtn;
103   }
104 
105   public static class ScanWithEquals {
106 
107     private final String startRow;
108     private final String stopRow;
109 
110     /**
111      * Creates a new instance of this class while copying all values.
112      *
113      * @param scan The scan instance to copy from.
114      * @throws java.io.IOException When copying the values fails.
115      */
116     public ScanWithEquals(Scan scan) throws IOException {
117       this.startRow = Bytes.toStringBinary(scan.getStartRow());
118       this.stopRow = Bytes.toStringBinary(scan.getStopRow());
119     }
120 
121     @Override
122     public int hashCode() {
123       final int prime = 31;
124       int result = 1;
125       result = prime * result + ((startRow == null) ? 0 : startRow.hashCode());
126       result = prime * result + ((stopRow == null) ? 0 : stopRow.hashCode());
127       return result;
128     }
129 
130     @Override
131     public boolean equals(Object obj) {
132       if (this == obj) {
133         return true;
134       }
135       if (obj == null) {
136         return false;
137       }
138       if (getClass() != obj.getClass()) {
139         return false;
140       }
141       ScanWithEquals other = (ScanWithEquals) obj;
142       if (startRow == null) {
143         if (other.startRow != null) {
144           return false;
145         }
146       } else if (!startRow.equals(other.startRow)) {
147         return false;
148       }
149       if (stopRow == null) {
150         if (other.stopRow != null) {
151           return false;
152         }
153       } else if (!stopRow.equals(other.stopRow)) {
154         return false;
155       }
156       return true;
157     }
158 
159     @Override
160     public String toString() {
161       return com.google.common.base.Objects.toStringHelper(this).add("startRow", startRow)
162           .add("stopRow", stopRow).toString();
163     }
164   }
165 
166   @Test
167   public void testSetInputSetsSnapshotToScans() throws Exception {
168 
169     callSetInput();
170 
171     Map<String, Collection<Scan>> actual = subject.getSnapshotsToScans(conf);
172 
173     // convert to scans we can use .equals on
174     Map<String, Collection<ScanWithEquals>> actualWithEquals = toScanWithEquals(actual);
175     Map<String, Collection<ScanWithEquals>> expectedWithEquals = toScanWithEquals(snapshotScans);
176 
177     assertEquals(expectedWithEquals, actualWithEquals);
178   }
179 
180   @Test
181   public void testSetInputPushesRestoreDirectories() throws Exception {
182     callSetInput();
183 
184     Map<String, Path> restoreDirs = subject.getSnapshotDirs(conf);
185 
186     assertEquals(this.snapshotScans.keySet(), restoreDirs.keySet());
187   }
188 
189   @Test
190   public void testSetInputCreatesRestoreDirectoriesUnderRootRestoreDir() throws Exception {
191     callSetInput();
192 
193     Map<String, Path> restoreDirs = subject.getSnapshotDirs(conf);
194 
195     for (Path snapshotDir : restoreDirs.values()) {
196       assertEquals("Expected " + snapshotDir + " to be a child of " + restoreDir, restoreDir,
197           snapshotDir.getParent());
198     }
199   }
200 
201   @Test
202   public void testSetInputRestoresSnapshots() throws Exception {
203     callSetInput();
204 
205     Map<String, Path> snapshotDirs = subject.getSnapshotDirs(conf);
206 
207     for (Map.Entry<String, Path> entry : snapshotDirs.entrySet()) {
208       verify(this.subject).restoreSnapshot(eq(this.conf), eq(entry.getKey()), eq(this.rootDir),
209           eq(entry.getValue()), any(FileSystem.class));
210     }
211   }
212 }