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.assertEquals;
021import static org.junit.Assert.assertNotEquals;
022import static org.junit.Assert.assertNotNull;
023import static org.junit.Assert.assertTrue;
024
025import java.io.IOException;
026import java.nio.charset.StandardCharsets;
027import java.util.Collection;
028import java.util.EnumSet;
029import java.util.HashMap;
030import java.util.HashSet;
031import java.util.List;
032import java.util.Map;
033import java.util.Set;
034import java.util.concurrent.atomic.AtomicInteger;
035import org.apache.hadoop.conf.Configuration;
036import org.apache.hadoop.hbase.ClusterMetrics.Option;
037import org.apache.hadoop.hbase.HBaseClassTestRule;
038import org.apache.hadoop.hbase.HBaseTestingUtility;
039import org.apache.hadoop.hbase.HConstants;
040import org.apache.hadoop.hbase.HRegionLocation;
041import org.apache.hadoop.hbase.MetaTableAccessor;
042import org.apache.hadoop.hbase.MetaTableAccessor.Visitor;
043import org.apache.hadoop.hbase.RegionLocations;
044import org.apache.hadoop.hbase.ServerName;
045import org.apache.hadoop.hbase.TableName;
046import org.apache.hadoop.hbase.client.Admin;
047import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
048import org.apache.hadoop.hbase.client.Connection;
049import org.apache.hadoop.hbase.client.ConnectionFactory;
050import org.apache.hadoop.hbase.client.Delete;
051import org.apache.hadoop.hbase.client.RegionInfo;
052import org.apache.hadoop.hbase.client.RegionReplicaUtil;
053import org.apache.hadoop.hbase.client.Result;
054import org.apache.hadoop.hbase.client.Table;
055import org.apache.hadoop.hbase.client.TableDescriptor;
056import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
057import org.apache.hadoop.hbase.testclassification.MasterTests;
058import org.apache.hadoop.hbase.testclassification.MediumTests;
059import org.apache.hadoop.hbase.util.Bytes;
060import org.junit.AfterClass;
061import org.junit.BeforeClass;
062import org.junit.ClassRule;
063import org.junit.Ignore;
064import org.junit.Rule;
065import org.junit.Test;
066import org.junit.experimental.categories.Category;
067import org.junit.rules.TestName;
068import org.slf4j.Logger;
069import org.slf4j.LoggerFactory;
070
071import org.apache.hbase.thirdparty.com.google.common.io.Closeables;
072
073@Category({ MasterTests.class, MediumTests.class })
074public class TestMasterOperationsForRegionReplicas {
075
076  @ClassRule
077  public static final HBaseClassTestRule CLASS_RULE =
078    HBaseClassTestRule.forClass(TestMasterOperationsForRegionReplicas.class);
079
080  private static final Logger LOG = LoggerFactory.getLogger(TestRegionPlacement.class);
081  private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
082  private static Connection CONNECTION = null;
083  private static Admin ADMIN;
084  private static int numSlaves = 2;
085  private static Configuration conf;
086
087  @Rule
088  public TestName name = new TestName();
089
090  @BeforeClass
091  public static void setupBeforeClass() throws Exception {
092    conf = TEST_UTIL.getConfiguration();
093    conf.setBoolean("hbase.tests.use.shortcircuit.reads", false);
094    TEST_UTIL.startMiniCluster(numSlaves);
095    TEST_UTIL.getAdmin().balancerSwitch(false, true);
096    CONNECTION = ConnectionFactory.createConnection(TEST_UTIL.getConfiguration());
097    ADMIN = CONNECTION.getAdmin();
098    while (ADMIN.getClusterMetrics(EnumSet.of(Option.LIVE_SERVERS)).getLiveServerMetrics()
099      .size() < numSlaves) {
100      Thread.sleep(100);
101    }
102  }
103
104  @AfterClass
105  public static void tearDownAfterClass() throws Exception {
106    Closeables.close(ADMIN, true);
107    Closeables.close(CONNECTION, true);
108    TEST_UTIL.shutdownMiniCluster();
109  }
110
111  @Test
112  public void testCreateTableWithSingleReplica() throws Exception {
113    final int numRegions = 3;
114    final int numReplica = 1;
115    final TableName tableName = TableName.valueOf(name.getMethodName());
116    try {
117      TableDescriptor desc =
118        TableDescriptorBuilder.newBuilder(tableName).setRegionReplication(numReplica)
119          .setColumnFamily(ColumnFamilyDescriptorBuilder.of("family")).build();
120      ADMIN.createTable(desc, Bytes.toBytes("A"), Bytes.toBytes("Z"), numRegions);
121      TEST_UTIL.waitUntilAllRegionsAssigned(tableName);
122      TEST_UTIL.waitUntilNoRegionsInTransition();
123
124      validateNumberOfRowsInMeta(tableName, numRegions, ADMIN.getConnection());
125      List<RegionInfo> hris = MetaTableAccessor.getTableRegions(ADMIN.getConnection(), tableName);
126      assertEquals(numRegions * numReplica, hris.size());
127    } finally {
128      ADMIN.disableTable(tableName);
129      ADMIN.deleteTable(tableName);
130    }
131  }
132
133  @Test
134  public void testCreateTableWithMultipleReplicas() throws Exception {
135    final TableName tableName = TableName.valueOf(name.getMethodName());
136    final int numRegions = 3;
137    final int numReplica = 2;
138    try {
139      TableDescriptor desc =
140        TableDescriptorBuilder.newBuilder(tableName).setRegionReplication(numReplica)
141          .setColumnFamily(ColumnFamilyDescriptorBuilder.of("family")).build();
142      ADMIN.createTable(desc, Bytes.toBytes("A"), Bytes.toBytes("Z"), numRegions);
143      TEST_UTIL.waitUntilAllRegionsAssigned(tableName);
144      TEST_UTIL.waitUntilNoRegionsInTransition();
145      validateNumberOfRowsInMeta(tableName, numRegions, ADMIN.getConnection());
146
147      List<RegionInfo> hris = MetaTableAccessor.getTableRegions(ADMIN.getConnection(), tableName);
148      assertEquals(numRegions * numReplica, hris.size());
149      // check that the master created expected number of RegionState objects
150      for (int i = 0; i < numRegions; i++) {
151        for (int j = 0; j < numReplica; j++) {
152          RegionInfo replica = RegionReplicaUtil.getRegionInfoForReplica(hris.get(i), j);
153          RegionState state = TEST_UTIL.getHBaseCluster().getMaster().getAssignmentManager()
154            .getRegionStates().getRegionState(replica);
155          assertNotNull(state);
156        }
157      }
158
159      List<Result> metaRows = MetaTableAccessor.fullScanRegions(ADMIN.getConnection());
160      int numRows = 0;
161      for (Result result : metaRows) {
162        RegionLocations locations = MetaTableAccessor.getRegionLocations(result);
163        RegionInfo hri = locations.getRegionLocation().getRegion();
164        if (!hri.getTable().equals(tableName)) continue;
165        numRows += 1;
166        HRegionLocation[] servers = locations.getRegionLocations();
167        // have two locations for the replicas of a region, and the locations should be different
168        assertEquals(2, servers.length);
169        assertNotEquals(servers[1], servers[0]);
170      }
171      assertEquals(numRegions, numRows);
172
173      // The same verification of the meta as above but with the SnapshotOfRegionAssignmentFromMeta
174      // class
175      validateFromSnapshotFromMeta(TEST_UTIL, tableName, numRegions, numReplica,
176        ADMIN.getConnection());
177
178      // Now kill the master, restart it and see if the assignments are kept
179      ServerName master = TEST_UTIL.getHBaseClusterInterface().getClusterMetrics().getMasterName();
180      TEST_UTIL.getHBaseClusterInterface().stopMaster(master);
181      TEST_UTIL.getHBaseClusterInterface().waitForMasterToStop(master, 30000);
182      TEST_UTIL.getHBaseClusterInterface().startMaster(master.getHostname(), master.getPort());
183      TEST_UTIL.getHBaseClusterInterface().waitForActiveAndReadyMaster();
184      TEST_UTIL.waitUntilAllRegionsAssigned(tableName);
185      TEST_UTIL.waitUntilNoRegionsInTransition();
186      for (int i = 0; i < numRegions; i++) {
187        for (int j = 0; j < numReplica; j++) {
188          RegionInfo replica = RegionReplicaUtil.getRegionInfoForReplica(hris.get(i), j);
189          RegionState state = TEST_UTIL.getHBaseCluster().getMaster().getAssignmentManager()
190            .getRegionStates().getRegionState(replica);
191          assertNotNull(state);
192        }
193      }
194      validateFromSnapshotFromMeta(TEST_UTIL, tableName, numRegions, numReplica,
195        ADMIN.getConnection());
196
197      // Now shut the whole cluster down, and verify regions are assigned even if there is only
198      // one server running
199      TEST_UTIL.shutdownMiniHBaseCluster();
200      TEST_UTIL.startMiniHBaseCluster(1, 1);
201      TEST_UTIL.waitUntilAllRegionsAssigned(tableName);
202      TEST_UTIL.waitUntilNoRegionsInTransition();
203      validateSingleRegionServerAssignment(ADMIN.getConnection(), numRegions, numReplica);
204      for (int i = 1; i < numSlaves; i++) { // restore the cluster
205        TEST_UTIL.getMiniHBaseCluster().startRegionServer();
206      }
207
208      // Check on alter table
209      ADMIN.disableTable(tableName);
210      assertTrue(ADMIN.isTableDisabled(tableName));
211      // increase the replica
212      ADMIN.modifyTable(
213        TableDescriptorBuilder.newBuilder(desc).setRegionReplication(numReplica + 1).build());
214      ADMIN.enableTable(tableName);
215      LOG.info(ADMIN.getDescriptor(tableName).toString());
216      assertTrue(ADMIN.isTableEnabled(tableName));
217      TEST_UTIL.waitUntilAllRegionsAssigned(tableName);
218      TEST_UTIL.waitUntilNoRegionsInTransition();
219      List<RegionInfo> regions = TEST_UTIL.getMiniHBaseCluster().getMaster().getAssignmentManager()
220        .getRegionStates().getRegionsOfTable(tableName);
221      assertTrue("regions.size=" + regions.size() + ", numRegions=" + numRegions + ", numReplica=" +
222        numReplica, regions.size() == numRegions * (numReplica + 1));
223
224      // decrease the replica(earlier, table was modified to have a replica count of numReplica + 1)
225      ADMIN.disableTable(tableName);
226      ADMIN.modifyTable(
227        TableDescriptorBuilder.newBuilder(desc).setRegionReplication(numReplica).build());
228      ADMIN.enableTable(tableName);
229      assertTrue(ADMIN.isTableEnabled(tableName));
230      TEST_UTIL.waitUntilAllRegionsAssigned(tableName);
231      TEST_UTIL.waitUntilNoRegionsInTransition();
232      regions = TEST_UTIL.getMiniHBaseCluster().getMaster().getAssignmentManager().getRegionStates()
233        .getRegionsOfTable(tableName);
234      assertEquals(numRegions * numReplica, regions.size());
235      // also make sure the meta table has the replica locations removed
236      hris = MetaTableAccessor.getTableRegions(ADMIN.getConnection(), tableName);
237      assertEquals(numRegions * numReplica, hris.size());
238      // just check that the number of default replica regions in the meta table are the same
239      // as the number of regions the table was created with, and the count of the
240      // replicas is numReplica for each region
241      Map<RegionInfo, Integer> defaultReplicas = new HashMap<>();
242      for (RegionInfo hri : hris) {
243        RegionInfo regionReplica0 = RegionReplicaUtil.getRegionInfoForDefaultReplica(hri);
244        Integer i = defaultReplicas.get(regionReplica0);
245        defaultReplicas.put(regionReplica0, i == null ? 1 : i + 1);
246      }
247      assertEquals(numRegions, defaultReplicas.size());
248      Collection<Integer> counts = new HashSet<>(defaultReplicas.values());
249      assertEquals(1, counts.size());
250      assertTrue(counts.contains(numReplica));
251    } finally {
252      ADMIN.disableTable(tableName);
253      ADMIN.deleteTable(tableName);
254    }
255  }
256
257  @Test
258  @Ignore("Enable when we have support for alter_table- HBASE-10361")
259  public void testIncompleteMetaTableReplicaInformation() throws Exception {
260    final TableName tableName = TableName.valueOf(name.getMethodName());
261    final int numRegions = 3;
262    final int numReplica = 2;
263    try {
264      // Create a table and let the meta table be updated with the location of the
265      // region locations.
266      TableDescriptor desc =
267        TableDescriptorBuilder.newBuilder(tableName).setRegionReplication(numReplica)
268          .setColumnFamily(ColumnFamilyDescriptorBuilder.of("family")).build();
269      ADMIN.createTable(desc, Bytes.toBytes("A"), Bytes.toBytes("Z"), numRegions);
270      TEST_UTIL.waitUntilAllRegionsAssigned(tableName);
271      TEST_UTIL.waitUntilNoRegionsInTransition();
272      Set<byte[]> tableRows = new HashSet<>();
273      List<RegionInfo> hris = MetaTableAccessor.getTableRegions(ADMIN.getConnection(), tableName);
274      for (RegionInfo hri : hris) {
275        tableRows.add(hri.getRegionName());
276      }
277      ADMIN.disableTable(tableName);
278      // now delete one replica info from all the rows
279      // this is to make the meta appear to be only partially updated
280      Table metaTable = ADMIN.getConnection().getTable(TableName.META_TABLE_NAME);
281      for (byte[] row : tableRows) {
282        Delete deleteOneReplicaLocation = new Delete(row);
283        deleteOneReplicaLocation.addColumns(HConstants.CATALOG_FAMILY,
284          MetaTableAccessor.getServerColumn(1));
285        deleteOneReplicaLocation.addColumns(HConstants.CATALOG_FAMILY,
286          MetaTableAccessor.getSeqNumColumn(1));
287        deleteOneReplicaLocation.addColumns(HConstants.CATALOG_FAMILY,
288          MetaTableAccessor.getStartCodeColumn(1));
289        metaTable.delete(deleteOneReplicaLocation);
290      }
291      metaTable.close();
292      // even if the meta table is partly updated, when we re-enable the table, we should
293      // get back the desired number of replicas for the regions
294      ADMIN.enableTable(tableName);
295      assertTrue(ADMIN.isTableEnabled(tableName));
296      TEST_UTIL.waitUntilAllRegionsAssigned(tableName);
297      TEST_UTIL.waitUntilNoRegionsInTransition();
298      List<RegionInfo> regions = TEST_UTIL.getMiniHBaseCluster().getMaster().getAssignmentManager()
299        .getRegionStates().getRegionsOfTable(tableName);
300      assertEquals(numRegions * numReplica, regions.size());
301    } finally {
302      ADMIN.disableTable(tableName);
303      ADMIN.deleteTable(tableName);
304    }
305  }
306
307  private void validateNumberOfRowsInMeta(final TableName table, int numRegions,
308      Connection connection) throws IOException {
309    assert (ADMIN.tableExists(table));
310    final AtomicInteger count = new AtomicInteger();
311    Visitor visitor = new Visitor() {
312      @Override
313      public boolean visit(Result r) throws IOException {
314        if (MetaTableAccessor.getRegionInfo(r).getTable().equals(table)) count.incrementAndGet();
315        return true;
316      }
317    };
318    MetaTableAccessor.fullScanRegions(connection, visitor);
319    assertEquals(numRegions, count.get());
320  }
321
322  private void validateFromSnapshotFromMeta(HBaseTestingUtility util, TableName table,
323      int numRegions, int numReplica, Connection connection) throws IOException {
324    SnapshotOfRegionAssignmentFromMeta snapshot =
325      new SnapshotOfRegionAssignmentFromMeta(connection);
326    snapshot.initialize();
327    Map<RegionInfo, ServerName> regionToServerMap = snapshot.getRegionToRegionServerMap();
328    assert (regionToServerMap.size() == numRegions * numReplica + 1); // '1' for the namespace
329    Map<ServerName, List<RegionInfo>> serverToRegionMap = snapshot.getRegionServerToRegionMap();
330    for (Map.Entry<ServerName, List<RegionInfo>> entry : serverToRegionMap.entrySet()) {
331      if (entry.getKey().equals(util.getHBaseCluster().getMaster().getServerName())) {
332        continue;
333      }
334      List<RegionInfo> regions = entry.getValue();
335      Set<byte[]> setOfStartKeys = new HashSet<>();
336      for (RegionInfo region : regions) {
337        byte[] startKey = region.getStartKey();
338        if (region.getTable().equals(table)) {
339          setOfStartKeys.add(startKey); // ignore other tables
340          LOG.info("--STARTKEY {}--", new String(startKey, StandardCharsets.UTF_8));
341        }
342      }
343      // the number of startkeys will be equal to the number of regions hosted in each server
344      // (each server will be hosting one replica of a region)
345      assertEquals(numRegions, setOfStartKeys.size());
346    }
347  }
348
349  private void validateSingleRegionServerAssignment(Connection connection, int numRegions,
350      int numReplica) throws IOException {
351    SnapshotOfRegionAssignmentFromMeta snapshot =
352      new SnapshotOfRegionAssignmentFromMeta(connection);
353    snapshot.initialize();
354    Map<RegionInfo, ServerName> regionToServerMap = snapshot.getRegionToRegionServerMap();
355    assertEquals(regionToServerMap.size(), numRegions * numReplica + 1);
356    Map<ServerName, List<RegionInfo>> serverToRegionMap = snapshot.getRegionServerToRegionMap();
357    assertEquals("One Region Only", 1, serverToRegionMap.keySet().size());
358    for (Map.Entry<ServerName, List<RegionInfo>> entry : serverToRegionMap.entrySet()) {
359      if (entry.getKey().equals(TEST_UTIL.getHBaseCluster().getMaster().getServerName())) {
360        continue;
361      }
362      assertEquals(entry.getValue().size(), numRegions * numReplica + 1);
363    }
364  }
365}