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