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.master;
019
020import static org.junit.Assert.assertArrayEquals;
021import static org.junit.Assert.assertEquals;
022import static org.junit.Assert.assertFalse;
023import static org.junit.Assert.assertTrue;
024import static org.junit.Assert.fail;
025
026import java.io.IOException;
027import java.util.List;
028import java.util.concurrent.atomic.AtomicReference;
029import org.apache.hadoop.fs.FileSystem;
030import org.apache.hadoop.fs.Path;
031import org.apache.hadoop.hbase.HBaseClassTestRule;
032import org.apache.hadoop.hbase.HBaseTestingUtility;
033import org.apache.hadoop.hbase.HColumnDescriptor;
034import org.apache.hadoop.hbase.HConstants;
035import org.apache.hadoop.hbase.HTableDescriptor;
036import org.apache.hadoop.hbase.MetaTableAccessor;
037import org.apache.hadoop.hbase.MiniHBaseCluster;
038import org.apache.hadoop.hbase.PleaseHoldException;
039import org.apache.hadoop.hbase.ServerName;
040import org.apache.hadoop.hbase.TableName;
041import org.apache.hadoop.hbase.UnknownRegionException;
042import org.apache.hadoop.hbase.client.Admin;
043import org.apache.hadoop.hbase.client.RegionInfo;
044import org.apache.hadoop.hbase.client.RegionInfoBuilder;
045import org.apache.hadoop.hbase.client.Result;
046import org.apache.hadoop.hbase.client.Table;
047import org.apache.hadoop.hbase.client.TableState;
048import org.apache.hadoop.hbase.testclassification.MasterTests;
049import org.apache.hadoop.hbase.testclassification.MediumTests;
050import org.apache.hadoop.hbase.util.Bytes;
051import org.apache.hadoop.hbase.util.HBaseFsck;
052import org.apache.hadoop.hbase.util.Pair;
053import org.apache.hadoop.util.StringUtils;
054import org.junit.AfterClass;
055import org.junit.BeforeClass;
056import org.junit.ClassRule;
057import org.junit.Rule;
058import org.junit.Test;
059import org.junit.experimental.categories.Category;
060import org.junit.rules.TestName;
061import org.slf4j.Logger;
062import org.slf4j.LoggerFactory;
063
064import org.apache.hbase.thirdparty.com.google.common.base.Joiner;
065
066@Category({MasterTests.class, MediumTests.class})
067public class TestMaster {
068
069  @ClassRule
070  public static final HBaseClassTestRule CLASS_RULE =
071      HBaseClassTestRule.forClass(TestMaster.class);
072
073  private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
074  private static final Logger LOG = LoggerFactory.getLogger(TestMaster.class);
075  private static final TableName TABLENAME = TableName.valueOf("TestMaster");
076  private static final byte[] FAMILYNAME = Bytes.toBytes("fam");
077  private static Admin admin;
078
079  @Rule
080  public TestName name = new TestName();
081
082  @BeforeClass
083  public static void beforeAllTests() throws Exception {
084    // we will retry operations when PleaseHoldException is thrown
085    TEST_UTIL.getConfiguration().setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 3);
086    // Start a cluster of two regionservers.
087    TEST_UTIL.startMiniCluster(2);
088    admin = TEST_UTIL.getAdmin();
089  }
090
091  @AfterClass
092  public static void afterAllTests() throws Exception {
093    TEST_UTIL.shutdownMiniCluster();
094  }
095
096  /**
097   * Return the region and current deployment for the region containing the given row. If the region
098   * cannot be found, returns null. If it is found, but not currently deployed, the second element
099   * of the pair may be null.
100   */
101  private Pair<RegionInfo, ServerName> getTableRegionForRow(HMaster master, TableName tableName,
102      byte[] rowKey) throws IOException {
103    final AtomicReference<Pair<RegionInfo, ServerName>> result = new AtomicReference<>(null);
104
105    MetaTableAccessor.Visitor visitor = new MetaTableAccessor.Visitor() {
106      @Override
107      public boolean visit(Result data) throws IOException {
108        if (data == null || data.size() <= 0) {
109          return true;
110        }
111        Pair<RegionInfo, ServerName> pair = new Pair<>(MetaTableAccessor.getRegionInfo(data),
112          MetaTableAccessor.getServerName(data, 0));
113        if (!pair.getFirst().getTable().equals(tableName)) {
114          return false;
115        }
116        result.set(pair);
117        return true;
118      }
119    };
120
121    MetaTableAccessor.scanMeta(master.getConnection(), visitor, tableName, rowKey, 1);
122    return result.get();
123  }
124
125  @Test
126  @SuppressWarnings("deprecation")
127  public void testMasterOpsWhileSplitting() throws Exception {
128    MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
129    HMaster m = cluster.getMaster();
130
131    try (Table ht = TEST_UTIL.createTable(TABLENAME, FAMILYNAME)) {
132      assertTrue(m.getTableStateManager().isTableState(TABLENAME, TableState.State.ENABLED));
133      TEST_UTIL.loadTable(ht, FAMILYNAME, false);
134    }
135
136    List<Pair<RegionInfo, ServerName>> tableRegions = MetaTableAccessor.getTableRegionsAndLocations(
137        m.getConnection(), TABLENAME);
138    LOG.info("Regions after load: " + Joiner.on(',').join(tableRegions));
139    assertEquals(1, tableRegions.size());
140    assertArrayEquals(HConstants.EMPTY_START_ROW,
141        tableRegions.get(0).getFirst().getStartKey());
142    assertArrayEquals(HConstants.EMPTY_END_ROW,
143        tableRegions.get(0).getFirst().getEndKey());
144
145    // Now trigger a split and stop when the split is in progress
146    LOG.info("Splitting table");
147    TEST_UTIL.getAdmin().split(TABLENAME);
148
149    LOG.info("Making sure we can call getTableRegions while opening");
150    while (tableRegions.size() < 3) {
151      tableRegions = MetaTableAccessor.getTableRegionsAndLocations(m.getConnection(),
152          TABLENAME, false);
153      Thread.sleep(100);
154    }
155    LOG.info("Regions: " + Joiner.on(',').join(tableRegions));
156    // We have three regions because one is split-in-progress
157    assertEquals(3, tableRegions.size());
158    LOG.info("Making sure we can call getTableRegionClosest while opening");
159    Pair<RegionInfo, ServerName> pair = getTableRegionForRow(m, TABLENAME, Bytes.toBytes("cde"));
160    LOG.info("Result is: " + pair);
161    Pair<RegionInfo, ServerName> tableRegionFromName =
162        MetaTableAccessor.getRegion(m.getConnection(),
163          pair.getFirst().getRegionName());
164    assertTrue(RegionInfo.COMPARATOR.compare(tableRegionFromName.getFirst(), pair.getFirst()) == 0);
165  }
166
167  @Test
168  public void testMoveRegionWhenNotInitialized() {
169    MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
170    HMaster m = cluster.getMaster();
171    try {
172      m.setInitialized(false); // fake it, set back later
173      RegionInfo meta = RegionInfoBuilder.FIRST_META_REGIONINFO;
174      m.move(meta.getEncodedNameAsBytes(), null);
175      fail("Region should not be moved since master is not initialized");
176    } catch (IOException ioe) {
177      assertTrue(ioe instanceof PleaseHoldException);
178    } finally {
179      m.setInitialized(true);
180    }
181  }
182
183  @Test
184  public void testMoveThrowsUnknownRegionException() throws IOException {
185    final TableName tableName = TableName.valueOf(name.getMethodName());
186    HTableDescriptor htd = new HTableDescriptor(tableName);
187    HColumnDescriptor hcd = new HColumnDescriptor("value");
188    htd.addFamily(hcd);
189
190    admin.createTable(htd, null);
191    try {
192      RegionInfo hri = RegionInfoBuilder.newBuilder(tableName)
193          .setStartKey(Bytes.toBytes("A"))
194          .setEndKey(Bytes.toBytes("Z"))
195          .build();
196      admin.move(hri.getEncodedNameAsBytes(), null);
197      fail("Region should not be moved since it is fake");
198    } catch (IOException ioe) {
199      assertTrue(ioe instanceof UnknownRegionException);
200    } finally {
201      TEST_UTIL.deleteTable(tableName);
202    }
203  }
204
205  @Test
206  public void testMoveThrowsPleaseHoldException() throws IOException {
207    final TableName tableName = TableName.valueOf(name.getMethodName());
208    HMaster master = TEST_UTIL.getMiniHBaseCluster().getMaster();
209    HTableDescriptor htd = new HTableDescriptor(tableName);
210    HColumnDescriptor hcd = new HColumnDescriptor("value");
211    htd.addFamily(hcd);
212
213    admin.createTable(htd, null);
214    try {
215      List<RegionInfo> tableRegions = admin.getRegions(tableName);
216
217      master.setInitialized(false); // fake it, set back later
218      admin.move(tableRegions.get(0).getEncodedNameAsBytes(), null);
219      fail("Region should not be moved since master is not initialized");
220    } catch (IOException ioe) {
221      assertTrue(StringUtils.stringifyException(ioe).contains("PleaseHoldException"));
222    } finally {
223      master.setInitialized(true);
224      TEST_UTIL.deleteTable(tableName);
225    }
226  }
227
228  @Test
229  public void testBlockingHbkc1WithLockFile() throws IOException {
230    // This is how the patch to the lock file is created inside in HBaseFsck. Too hard to use its
231    // actual method without disturbing HBaseFsck... Do the below mimic instead.
232    Path hbckLockPath = new Path(HBaseFsck.getTmpDir(TEST_UTIL.getConfiguration()),
233        HBaseFsck.HBCK_LOCK_FILE);
234    FileSystem fs = TEST_UTIL.getTestFileSystem();
235    assertTrue(fs.exists(hbckLockPath));
236    TEST_UTIL.getMiniHBaseCluster().
237        killMaster(TEST_UTIL.getMiniHBaseCluster().getMaster().getServerName());
238    assertTrue(fs.exists(hbckLockPath));
239    TEST_UTIL.getMiniHBaseCluster().startMaster();
240    TEST_UTIL.waitFor(30000, () -> TEST_UTIL.getMiniHBaseCluster().getMaster() != null &&
241        TEST_UTIL.getMiniHBaseCluster().getMaster().isInitialized());
242    assertTrue(fs.exists(hbckLockPath));
243    // Start a second Master. Should be fine.
244    TEST_UTIL.getMiniHBaseCluster().startMaster();
245    assertTrue(fs.exists(hbckLockPath));
246    fs.delete(hbckLockPath, true);
247    assertFalse(fs.exists(hbckLockPath));
248    // Kill all Masters.
249    TEST_UTIL.getMiniHBaseCluster().getLiveMasterThreads().stream().
250        map(sn -> sn.getMaster().getServerName()).forEach(sn -> {
251          try {
252            TEST_UTIL.getMiniHBaseCluster().killMaster(sn);
253          } catch (IOException e) {
254            e.printStackTrace();
255          }
256        });
257    // Start a new one.
258    TEST_UTIL.getMiniHBaseCluster().startMaster();
259    TEST_UTIL.waitFor(30000, () -> TEST_UTIL.getMiniHBaseCluster().getMaster() != null &&
260        TEST_UTIL.getMiniHBaseCluster().getMaster().isInitialized());
261    // Assert lock gets put in place again.
262    assertTrue(fs.exists(hbckLockPath));
263  }
264}
265