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.backup;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertNotNull;
022import static org.junit.Assert.assertNull;
023import static org.junit.Assert.assertTrue;
024
025import java.io.IOException;
026import java.util.ArrayList;
027import java.util.Arrays;
028import java.util.Collections;
029import java.util.HashMap;
030import java.util.HashSet;
031import java.util.Iterator;
032import java.util.List;
033import java.util.Map;
034import java.util.Set;
035import java.util.TreeSet;
036import org.apache.hadoop.conf.Configuration;
037import org.apache.hadoop.hbase.HBaseClassTestRule;
038import org.apache.hadoop.hbase.HBaseTestingUtility;
039import org.apache.hadoop.hbase.MiniHBaseCluster;
040import org.apache.hadoop.hbase.TableName;
041import org.apache.hadoop.hbase.backup.BackupInfo.BackupState;
042import org.apache.hadoop.hbase.backup.impl.BackupManager;
043import org.apache.hadoop.hbase.backup.impl.BackupSystemTable;
044import org.apache.hadoop.hbase.client.Admin;
045import org.apache.hadoop.hbase.client.Connection;
046import org.apache.hadoop.hbase.testclassification.MediumTests;
047import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
048import org.junit.After;
049import org.junit.AfterClass;
050import org.junit.Before;
051import org.junit.BeforeClass;
052import org.junit.ClassRule;
053import org.junit.Test;
054import org.junit.experimental.categories.Category;
055
056/**
057 * Test cases for backup system table API
058 */
059@Category(MediumTests.class)
060public class TestBackupSystemTable {
061
062  @ClassRule
063  public static final HBaseClassTestRule CLASS_RULE =
064    HBaseClassTestRule.forClass(TestBackupSystemTable.class);
065
066  private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
067  protected static Configuration conf = UTIL.getConfiguration();
068  protected static MiniHBaseCluster cluster;
069  protected static Connection conn;
070  protected BackupSystemTable table;
071
072  @BeforeClass
073  public static void setUp() throws Exception {
074    conf.setBoolean(BackupRestoreConstants.BACKUP_ENABLE_KEY, true);
075    BackupManager.decorateMasterConfiguration(conf);
076    BackupManager.decorateRegionServerConfiguration(conf);
077    cluster = UTIL.startMiniCluster();
078    conn = UTIL.getConnection();
079  }
080
081  @Before
082  public void before() throws IOException {
083    table = new BackupSystemTable(conn);
084  }
085
086  @After
087  public void after() {
088    if (table != null) {
089      table.close();
090    }
091
092  }
093
094  @Test
095  public void testUpdateReadDeleteBackupStatus() throws IOException {
096    BackupInfo ctx = createBackupInfo();
097    table.updateBackupInfo(ctx);
098    BackupInfo readCtx = table.readBackupInfo(ctx.getBackupId());
099    assertTrue(compare(ctx, readCtx));
100    // try fake backup id
101    readCtx = table.readBackupInfo("fake");
102    assertNull(readCtx);
103    // delete backup info
104    table.deleteBackupInfo(ctx.getBackupId());
105    readCtx = table.readBackupInfo(ctx.getBackupId());
106    assertNull(readCtx);
107    cleanBackupTable();
108  }
109
110  @Test
111  public void testWriteReadBackupStartCode() throws IOException {
112    long code = 100L;
113    table.writeBackupStartCode(code, "root");
114    String readCode = table.readBackupStartCode("root");
115    assertEquals(code, Long.parseLong(readCode));
116    cleanBackupTable();
117  }
118
119  private void cleanBackupTable() throws IOException {
120    Admin admin = UTIL.getAdmin();
121    admin.disableTable(BackupSystemTable.getTableName(conf));
122    admin.truncateTable(BackupSystemTable.getTableName(conf), true);
123    if (admin.isTableDisabled(BackupSystemTable.getTableName(conf))) {
124      admin.enableTable(BackupSystemTable.getTableName(conf));
125    }
126  }
127
128  @Test
129  public void testBackupHistory() throws Exception {
130    int n = 10;
131    List<BackupInfo> list = createBackupInfoList(n);
132
133    // Load data
134    for (BackupInfo bc : list) {
135      // Make sure we set right status
136      bc.setState(BackupState.COMPLETE);
137      table.updateBackupInfo(bc);
138    }
139
140    // Reverse list for comparison
141    Collections.reverse(list);
142    List<BackupInfo> history = table.getBackupHistory();
143    assertTrue(history.size() == n);
144
145    for (int i = 0; i < n; i++) {
146      BackupInfo ctx = list.get(i);
147      BackupInfo data = history.get(i);
148      assertTrue(compare(ctx, data));
149    }
150
151    cleanBackupTable();
152
153  }
154
155  @Test
156  public void testBackupDelete() throws Exception {
157    try (BackupSystemTable table = new BackupSystemTable(conn)) {
158      int n = 10;
159      List<BackupInfo> list = createBackupInfoList(n);
160
161      // Load data
162      for (BackupInfo bc : list) {
163        // Make sure we set right status
164        bc.setState(BackupState.COMPLETE);
165        table.updateBackupInfo(bc);
166      }
167
168      // Verify exists
169      for (BackupInfo bc : list) {
170        assertNotNull(table.readBackupInfo(bc.getBackupId()));
171      }
172
173      // Delete all
174      for (BackupInfo bc : list) {
175        table.deleteBackupInfo(bc.getBackupId());
176      }
177
178      // Verify do not exists
179      for (BackupInfo bc : list) {
180        assertNull(table.readBackupInfo(bc.getBackupId()));
181      }
182
183      cleanBackupTable();
184    }
185
186  }
187
188  @Test
189  public void testRegionServerLastLogRollResults() throws IOException {
190    String[] servers = new String[] { "server1", "server2", "server3" };
191    Long[] timestamps = new Long[] { 100L, 102L, 107L };
192
193    // validate the prefix scan in readRegionServerlastLogRollResult will get the right timestamps
194    // when a backup root with the same prefix is present
195    for (int i = 0; i < servers.length; i++) {
196      table.writeRegionServerLastLogRollResult(servers[i], timestamps[i], "root");
197      table.writeRegionServerLastLogRollResult(servers[i], timestamps[i], "root/backup");
198    }
199
200    HashMap<String, Long> result = table.readRegionServerLastLogRollResult("root");
201    assertTrue(servers.length == result.size());
202    Set<String> keys = result.keySet();
203    String[] keysAsArray = new String[keys.size()];
204    keys.toArray(keysAsArray);
205    Arrays.sort(keysAsArray);
206
207    for (int i = 0; i < keysAsArray.length; i++) {
208      assertEquals(keysAsArray[i], servers[i]);
209      Long ts1 = timestamps[i];
210      Long ts2 = result.get(keysAsArray[i]);
211      assertEquals(ts1, ts2);
212    }
213
214    cleanBackupTable();
215  }
216
217  @Test
218  public void testIncrementalBackupTableSet() throws IOException {
219    TreeSet<TableName> tables1 = new TreeSet<>();
220
221    tables1.add(TableName.valueOf("t1"));
222    tables1.add(TableName.valueOf("t2"));
223    tables1.add(TableName.valueOf("t3"));
224
225    TreeSet<TableName> tables2 = new TreeSet<>();
226
227    tables2.add(TableName.valueOf("t3"));
228    tables2.add(TableName.valueOf("t4"));
229    tables2.add(TableName.valueOf("t5"));
230
231    table.addIncrementalBackupTableSet(tables1, "root");
232
233    try (BackupSystemTable systemTable = new BackupSystemTable(conn)) {
234      TreeSet<TableName> res1 =
235        (TreeSet<TableName>) systemTable.getIncrementalBackupTableSet("root");
236      assertTrue(tables1.size() == res1.size());
237      Iterator<TableName> desc1 = tables1.descendingIterator();
238      Iterator<TableName> desc2 = res1.descendingIterator();
239      while (desc1.hasNext()) {
240        assertEquals(desc1.next(), desc2.next());
241      }
242      systemTable.addIncrementalBackupTableSet(tables2, "root");
243      TreeSet<TableName> res2 =
244        (TreeSet<TableName>) systemTable.getIncrementalBackupTableSet("root");
245      assertTrue((tables2.size() + tables1.size() - 1) == res2.size());
246      tables1.addAll(tables2);
247      desc1 = tables1.descendingIterator();
248      desc2 = res2.descendingIterator();
249      while (desc1.hasNext()) {
250        assertEquals(desc1.next(), desc2.next());
251      }
252    }
253
254    cleanBackupTable();
255  }
256
257  @Test
258  public void testRegionServerLogTimestampMap() throws IOException {
259    TreeSet<TableName> tables = new TreeSet<>();
260
261    tables.add(TableName.valueOf("t1"));
262    tables.add(TableName.valueOf("t2"));
263    tables.add(TableName.valueOf("t3"));
264
265    HashMap<String, Long> rsTimestampMap = new HashMap<>();
266
267    rsTimestampMap.put("rs1:100", 100L);
268    rsTimestampMap.put("rs2:100", 101L);
269    rsTimestampMap.put("rs3:100", 103L);
270
271    // validate the prefix scan in readLogTimestampMap will get the right timestamps
272    // when a backup root with the same prefix is present
273    table.writeRegionServerLogTimestamp(tables, rsTimestampMap, "root");
274    table.writeRegionServerLogTimestamp(tables, rsTimestampMap, "root/backup");
275
276    Map<TableName, Map<String, Long>> result = table.readLogTimestampMap("root");
277
278    assertTrue(tables.size() == result.size());
279
280    for (TableName t : tables) {
281      Map<String, Long> rstm = result.get(t);
282      assertNotNull(rstm);
283      assertEquals(rstm.get("rs1:100"), Long.valueOf(100L));
284      assertEquals(rstm.get("rs2:100"), Long.valueOf(101L));
285      assertEquals(rstm.get("rs3:100"), Long.valueOf(103L));
286    }
287
288    Set<TableName> tables1 = new TreeSet<>();
289
290    tables1.add(TableName.valueOf("t3"));
291    tables1.add(TableName.valueOf("t4"));
292    tables1.add(TableName.valueOf("t5"));
293
294    HashMap<String, Long> rsTimestampMap1 = new HashMap<>();
295
296    rsTimestampMap1.put("rs1:100", 200L);
297    rsTimestampMap1.put("rs2:100", 201L);
298    rsTimestampMap1.put("rs3:100", 203L);
299
300    // validate the prefix scan in readLogTimestampMap will get the right timestamps
301    // when a backup root with the same prefix is present
302    table.writeRegionServerLogTimestamp(tables1, rsTimestampMap1, "root");
303    table.writeRegionServerLogTimestamp(tables1, rsTimestampMap, "root/backup");
304
305    result = table.readLogTimestampMap("root");
306
307    assertTrue(5 == result.size());
308
309    for (TableName t : tables) {
310      Map<String, Long> rstm = result.get(t);
311      assertNotNull(rstm);
312      if (t.equals(TableName.valueOf("t3")) == false) {
313        assertEquals(rstm.get("rs1:100"), Long.valueOf(100L));
314        assertEquals(rstm.get("rs2:100"), Long.valueOf(101L));
315        assertEquals(rstm.get("rs3:100"), Long.valueOf(103L));
316      } else {
317        assertEquals(rstm.get("rs1:100"), Long.valueOf(200L));
318        assertEquals(rstm.get("rs2:100"), Long.valueOf(201L));
319        assertEquals(rstm.get("rs3:100"), Long.valueOf(203L));
320      }
321    }
322
323    for (TableName t : tables1) {
324      Map<String, Long> rstm = result.get(t);
325      assertNotNull(rstm);
326      assertEquals(rstm.get("rs1:100"), Long.valueOf(200L));
327      assertEquals(rstm.get("rs2:100"), Long.valueOf(201L));
328      assertEquals(rstm.get("rs3:100"), Long.valueOf(203L));
329    }
330
331    cleanBackupTable();
332
333  }
334
335  /**
336   * Backup set tests
337   */
338
339  @Test
340  public void testBackupSetAddNotExists() throws IOException {
341    try (BackupSystemTable table = new BackupSystemTable(conn)) {
342
343      String[] tables = new String[] { "table1", "table2", "table3" };
344      String setName = "name";
345      table.addToBackupSet(setName, tables);
346      List<TableName> tnames = table.describeBackupSet(setName);
347      assertTrue(tnames != null);
348      assertTrue(tnames.size() == tables.length);
349      for (int i = 0; i < tnames.size(); i++) {
350        assertTrue(tnames.get(i).getNameAsString().equals(tables[i]));
351      }
352      cleanBackupTable();
353    }
354
355  }
356
357  @Test
358  public void testBackupSetAddExists() throws IOException {
359    try (BackupSystemTable table = new BackupSystemTable(conn)) {
360
361      String[] tables = new String[] { "table1", "table2", "table3" };
362      String setName = "name";
363      table.addToBackupSet(setName, tables);
364      String[] addTables = new String[] { "table4", "table5", "table6" };
365      table.addToBackupSet(setName, addTables);
366
367      Set<String> expectedTables =
368        new HashSet<>(Arrays.asList("table1", "table2", "table3", "table4", "table5", "table6"));
369
370      List<TableName> tnames = table.describeBackupSet(setName);
371      assertTrue(tnames != null);
372      assertTrue(tnames.size() == expectedTables.size());
373      for (TableName tableName : tnames) {
374        assertTrue(expectedTables.remove(tableName.getNameAsString()));
375      }
376      cleanBackupTable();
377    }
378  }
379
380  @Test
381  public void testBackupSetAddExistsIntersects() throws IOException {
382    try (BackupSystemTable table = new BackupSystemTable(conn)) {
383
384      String[] tables = new String[] { "table1", "table2", "table3" };
385      String setName = "name";
386      table.addToBackupSet(setName, tables);
387      String[] addTables = new String[] { "table3", "table4", "table5", "table6" };
388      table.addToBackupSet(setName, addTables);
389
390      Set<String> expectedTables =
391        new HashSet<>(Arrays.asList("table1", "table2", "table3", "table4", "table5", "table6"));
392
393      List<TableName> tnames = table.describeBackupSet(setName);
394      assertTrue(tnames != null);
395      assertTrue(tnames.size() == expectedTables.size());
396      for (TableName tableName : tnames) {
397        assertTrue(expectedTables.remove(tableName.getNameAsString()));
398      }
399      cleanBackupTable();
400    }
401  }
402
403  @Test
404  public void testBackupSetRemoveSomeNotExists() throws IOException {
405    try (BackupSystemTable table = new BackupSystemTable(conn)) {
406
407      String[] tables = new String[] { "table1", "table2", "table3", "table4" };
408      String setName = "name";
409      table.addToBackupSet(setName, tables);
410      String[] removeTables = new String[] { "table4", "table5", "table6" };
411      table.removeFromBackupSet(setName, removeTables);
412
413      Set<String> expectedTables = new HashSet<>(Arrays.asList("table1", "table2", "table3"));
414
415      List<TableName> tnames = table.describeBackupSet(setName);
416      assertTrue(tnames != null);
417      assertTrue(tnames.size() == expectedTables.size());
418      for (TableName tableName : tnames) {
419        assertTrue(expectedTables.remove(tableName.getNameAsString()));
420      }
421      cleanBackupTable();
422    }
423  }
424
425  @Test
426  public void testBackupSetRemove() throws IOException {
427    try (BackupSystemTable table = new BackupSystemTable(conn)) {
428
429      String[] tables = new String[] { "table1", "table2", "table3", "table4" };
430      String setName = "name";
431      table.addToBackupSet(setName, tables);
432      String[] removeTables = new String[] { "table4", "table3" };
433      table.removeFromBackupSet(setName, removeTables);
434
435      Set<String> expectedTables = new HashSet<>(Arrays.asList("table1", "table2"));
436
437      List<TableName> tnames = table.describeBackupSet(setName);
438      assertTrue(tnames != null);
439      assertTrue(tnames.size() == expectedTables.size());
440      for (TableName tableName : tnames) {
441        assertTrue(expectedTables.remove(tableName.getNameAsString()));
442      }
443      cleanBackupTable();
444    }
445  }
446
447  @Test
448  public void testBackupSetDelete() throws IOException {
449    try (BackupSystemTable table = new BackupSystemTable(conn)) {
450
451      String[] tables = new String[] { "table1", "table2", "table3", "table4" };
452      String setName = "name";
453      table.addToBackupSet(setName, tables);
454      table.deleteBackupSet(setName);
455
456      List<TableName> tnames = table.describeBackupSet(setName);
457      assertTrue(tnames == null);
458      cleanBackupTable();
459    }
460  }
461
462  @Test
463  public void testBackupSetList() throws IOException {
464    try (BackupSystemTable table = new BackupSystemTable(conn)) {
465
466      String[] tables = new String[] { "table1", "table2", "table3", "table4" };
467      String setName1 = "name1";
468      String setName2 = "name2";
469      table.addToBackupSet(setName1, tables);
470      table.addToBackupSet(setName2, tables);
471
472      List<String> list = table.listBackupSets();
473
474      assertTrue(list.size() == 2);
475      assertTrue(list.get(0).equals(setName1));
476      assertTrue(list.get(1).equals(setName2));
477
478      cleanBackupTable();
479    }
480  }
481
482  private boolean compare(BackupInfo one, BackupInfo two) {
483    return one.getBackupId().equals(two.getBackupId()) && one.getType().equals(two.getType())
484      && one.getBackupRootDir().equals(two.getBackupRootDir())
485      && one.getStartTs() == two.getStartTs() && one.getCompleteTs() == two.getCompleteTs();
486  }
487
488  private BackupInfo createBackupInfo() {
489    BackupInfo ctxt = new BackupInfo("backup_" + System.nanoTime(), BackupType.FULL,
490      new TableName[] { TableName.valueOf("t1"), TableName.valueOf("t2"), TableName.valueOf("t3") },
491      "/hbase/backup");
492    ctxt.setStartTs(EnvironmentEdgeManager.currentTime());
493    ctxt.setCompleteTs(EnvironmentEdgeManager.currentTime() + 1);
494    return ctxt;
495  }
496
497  private List<BackupInfo> createBackupInfoList(int size) throws InterruptedException {
498    List<BackupInfo> list = new ArrayList<>();
499    for (int i = 0; i < size; i++) {
500      list.add(createBackupInfo());
501      // XXX Why do we need this sleep?
502      Thread.sleep(10);
503    }
504    return list;
505  }
506
507  @AfterClass
508  public static void tearDown() throws IOException {
509    if (cluster != null) {
510      cluster.shutdown();
511    }
512  }
513}