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.regionserver.compactions;
019
020import static org.apache.hadoop.hbase.regionserver.StripeStoreFileManager.STRIPE_END_KEY;
021import static org.apache.hadoop.hbase.regionserver.StripeStoreFileManager.STRIPE_START_KEY;
022import static org.junit.Assert.assertArrayEquals;
023import static org.junit.Assert.assertEquals;
024import static org.junit.Assert.assertFalse;
025import static org.junit.Assert.assertNotNull;
026import static org.junit.Assert.assertNull;
027import static org.junit.Assert.assertTrue;
028import static org.mockito.Matchers.any;
029import static org.mockito.Matchers.anyBoolean;
030import static org.mockito.Matchers.anyLong;
031import static org.mockito.Mockito.doAnswer;
032import static org.mockito.Mockito.mock;
033import static org.mockito.Mockito.when;
034
035import java.io.IOException;
036import java.util.ArrayList;
037import java.util.Arrays;
038import java.util.List;
039import java.util.TreeMap;
040
041import org.apache.hadoop.fs.Path;
042import org.apache.hadoop.hbase.Cell;
043import org.apache.hadoop.hbase.KeyValue;
044import org.apache.hadoop.hbase.io.hfile.HFile;
045import org.apache.hadoop.hbase.regionserver.BloomType;
046import org.apache.hadoop.hbase.regionserver.HStoreFile;
047import org.apache.hadoop.hbase.regionserver.InternalScanner;
048import org.apache.hadoop.hbase.regionserver.ScannerContext;
049import org.apache.hadoop.hbase.regionserver.StoreFileReader;
050import org.apache.hadoop.hbase.regionserver.StoreFileScanner;
051import org.apache.hadoop.hbase.regionserver.StoreFileWriter;
052import org.apache.hadoop.hbase.regionserver.StripeMultiFileWriter;
053import org.apache.hadoop.hbase.util.Bytes;
054import org.mockito.invocation.InvocationOnMock;
055import org.mockito.stubbing.Answer;
056
057public class TestCompactor {
058
059  public static HStoreFile createDummyStoreFile(long maxSequenceId) throws Exception {
060    // "Files" are totally unused, it's Scanner class below that gives compactor fake KVs.
061    // But compaction depends on everything under the sun, so stub everything with dummies.
062    HStoreFile sf = mock(HStoreFile.class);
063    StoreFileReader r = mock(StoreFileReader.class);
064    when(r.length()).thenReturn(1L);
065    when(r.getBloomFilterType()).thenReturn(BloomType.NONE);
066    when(r.getHFileReader()).thenReturn(mock(HFile.Reader.class));
067    when(r.getStoreFileScanner(anyBoolean(), anyBoolean(), anyBoolean(), anyLong(), anyLong(),
068      anyBoolean())).thenReturn(mock(StoreFileScanner.class));
069    when(sf.getReader()).thenReturn(r);
070    when(sf.getMaxSequenceId()).thenReturn(maxSequenceId);
071    return sf;
072  }
073
074  public static CompactionRequestImpl createDummyRequest() throws Exception {
075    return new CompactionRequestImpl(Arrays.asList(createDummyStoreFile(1L)));
076  }
077
078  // StoreFile.Writer has private ctor and is unwieldy, so this has to be convoluted.
079  public static class StoreFileWritersCapture
080      implements Answer<StoreFileWriter>, StripeMultiFileWriter.WriterFactory {
081    public static class Writer {
082      public ArrayList<KeyValue> kvs = new ArrayList<>();
083      public TreeMap<byte[], byte[]> data = new TreeMap<>(Bytes.BYTES_COMPARATOR);
084      public boolean hasMetadata;
085    }
086
087    private List<Writer> writers = new ArrayList<>();
088
089    @Override
090    public StoreFileWriter createWriter() throws IOException {
091      final Writer realWriter = new Writer();
092      writers.add(realWriter);
093      StoreFileWriter writer = mock(StoreFileWriter.class);
094      doAnswer(new Answer<Object>() {
095        @Override
096        public Object answer(InvocationOnMock invocation) {
097          return realWriter.kvs.add((KeyValue) invocation.getArgument(0));
098        }
099      }).when(writer).append(any());
100      doAnswer(new Answer<Object>() {
101        @Override
102        public Object answer(InvocationOnMock invocation) {
103          Object[] args = invocation.getArguments();
104          return realWriter.data.put((byte[]) args[0], (byte[]) args[1]);
105        }
106      }).when(writer).appendFileInfo(any(), any());
107      doAnswer(new Answer<Void>() {
108        @Override
109        public Void answer(InvocationOnMock invocation) throws Throwable {
110          realWriter.hasMetadata = true;
111          return null;
112        }
113      }).when(writer).appendMetadata(anyLong(), anyBoolean());
114      doAnswer(new Answer<Path>() {
115        @Override
116        public Path answer(InvocationOnMock invocation) throws Throwable {
117          return new Path("foo");
118        }
119      }).when(writer).getPath();
120      return writer;
121    }
122
123    @Override
124    public StoreFileWriter answer(InvocationOnMock invocation) throws Throwable {
125      return createWriter();
126    }
127
128    public void verifyKvs(KeyValue[][] kvss, boolean allFiles, boolean requireMetadata) {
129      if (allFiles) {
130        assertEquals(kvss.length, writers.size());
131      }
132      int skippedWriters = 0;
133      for (int i = 0; i < kvss.length; ++i) {
134        KeyValue[] kvs = kvss[i];
135        if (kvs != null) {
136          Writer w = writers.get(i - skippedWriters);
137          if (requireMetadata) {
138            assertNotNull(w.data.get(STRIPE_START_KEY));
139            assertNotNull(w.data.get(STRIPE_END_KEY));
140          } else {
141            assertNull(w.data.get(STRIPE_START_KEY));
142            assertNull(w.data.get(STRIPE_END_KEY));
143          }
144          assertEquals(kvs.length, w.kvs.size());
145          for (int j = 0; j < kvs.length; ++j) {
146            assertEquals(kvs[j], w.kvs.get(j));
147          }
148        } else {
149          assertFalse(allFiles);
150          ++skippedWriters;
151        }
152      }
153    }
154
155    public void verifyBoundaries(byte[][] boundaries) {
156      assertEquals(boundaries.length - 1, writers.size());
157      for (int i = 0; i < writers.size(); ++i) {
158        assertArrayEquals("i = " + i, boundaries[i], writers.get(i).data.get(STRIPE_START_KEY));
159        assertArrayEquals("i = " + i, boundaries[i + 1], writers.get(i).data.get(STRIPE_END_KEY));
160      }
161    }
162
163    public void verifyKvs(KeyValue[][] kvss, boolean allFiles, List<Long> boundaries) {
164      if (allFiles) {
165        assertEquals(kvss.length, writers.size());
166      }
167      int skippedWriters = 0;
168      for (int i = 0; i < kvss.length; ++i) {
169        KeyValue[] kvs = kvss[i];
170        if (kvs != null) {
171          Writer w = writers.get(i - skippedWriters);
172          assertEquals(kvs.length, w.kvs.size());
173          for (int j = 0; j < kvs.length; ++j) {
174            assertTrue(kvs[j].getTimestamp() >= boundaries.get(i));
175            assertTrue(kvs[j].getTimestamp() < boundaries.get(i + 1));
176            assertEquals(kvs[j], w.kvs.get(j));
177          }
178        } else {
179          assertFalse(allFiles);
180          ++skippedWriters;
181        }
182      }
183    }
184
185    public List<Writer> getWriters() {
186      return writers;
187    }
188  }
189
190  public static class Scanner implements InternalScanner {
191    private final ArrayList<KeyValue> kvs;
192
193    public Scanner(KeyValue... kvs) {
194      this.kvs = new ArrayList<>(Arrays.asList(kvs));
195    }
196
197    @Override
198    public boolean next(List<Cell> result, ScannerContext scannerContext) throws IOException {
199      if (kvs.isEmpty()) return false;
200      result.add(kvs.remove(0));
201      return !kvs.isEmpty();
202    }
203
204    @Override
205    public void close() throws IOException {
206    }
207  }
208}