View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.master;
20  
21  import static org.junit.Assert.assertEquals;
22  
23  import java.io.IOException;
24  import java.util.Collection;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  import java.util.concurrent.atomic.AtomicInteger;
31  
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.apache.hadoop.conf.Configuration;
35  import org.apache.hadoop.hbase.HBaseTestingUtility;
36  import org.apache.hadoop.hbase.HColumnDescriptor;
37  import org.apache.hadoop.hbase.HConstants;
38  import org.apache.hadoop.hbase.HRegionInfo;
39  import org.apache.hadoop.hbase.HRegionLocation;
40  import org.apache.hadoop.hbase.HTableDescriptor;
41  import org.apache.hadoop.hbase.testclassification.MediumTests;
42  import org.apache.hadoop.hbase.RegionLocations;
43  import org.apache.hadoop.hbase.ServerName;
44  import org.apache.hadoop.hbase.TableName;
45  import org.apache.hadoop.hbase.MetaTableAccessor;
46  import org.apache.hadoop.hbase.MetaTableAccessor.Visitor;
47  import org.apache.hadoop.hbase.client.Admin;
48  import org.apache.hadoop.hbase.client.Connection;
49  import org.apache.hadoop.hbase.client.ConnectionFactory;
50  import org.apache.hadoop.hbase.client.Delete;
51  import org.apache.hadoop.hbase.client.HTable;
52  import org.apache.hadoop.hbase.client.RegionReplicaUtil;
53  import org.apache.hadoop.hbase.client.Result;
54  import org.apache.hadoop.hbase.client.Table;
55  import org.apache.hadoop.hbase.util.Bytes;
56  import org.junit.AfterClass;
57  import org.junit.BeforeClass;
58  import org.junit.Test;
59  import org.junit.experimental.categories.Category;
60  
61  @Category(MediumTests.class)
62  public class TestMasterOperationsForRegionReplicas {
63    final static Log LOG = LogFactory.getLog(TestRegionPlacement.class);
64    private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
65    private static Connection CONNECTION = null;
66    private static Admin ADMIN;
67    private static int numSlaves = 2;
68    private static Configuration conf;
69  
70    @BeforeClass
71    public static void setupBeforeClass() throws Exception {
72      conf = TEST_UTIL.getConfiguration();
73      conf.setBoolean("hbase.tests.use.shortcircuit.reads", false);
74      TEST_UTIL.startMiniCluster(numSlaves);
75      CONNECTION = ConnectionFactory.createConnection(TEST_UTIL.getConfiguration());
76      ADMIN = CONNECTION.getAdmin();
77      while(ADMIN.getClusterStatus().getServers().size() < numSlaves) {
78        Thread.sleep(100);
79      }
80      TEST_UTIL.waitUntilAllSystemRegionsAssigned();
81    }
82  
83    @AfterClass
84    public static void tearDownAfterClass() throws Exception {
85      if (ADMIN != null) ADMIN.close();
86      if (CONNECTION != null && !CONNECTION.isClosed()) CONNECTION.close();
87      TEST_UTIL.shutdownMiniCluster();
88    }
89  
90    @Test
91    public void testCreateTableWithSingleReplica() throws Exception {
92      final int numRegions = 3;
93      final int numReplica = 1;
94      final TableName table = TableName.valueOf("singleReplicaTable");
95      try {
96        HTableDescriptor desc = new HTableDescriptor(table);
97        desc.setRegionReplication(numReplica);
98        desc.addFamily(new HColumnDescriptor("family"));
99        ADMIN.createTable(desc, Bytes.toBytes("A"), Bytes.toBytes("Z"), numRegions);
100 
101       validateNumberOfRowsInMeta(table, numRegions, ADMIN.getConnection());
102       List<HRegionInfo> hris = MetaTableAccessor.getTableRegions(TEST_UTIL.getZooKeeperWatcher(),
103         ADMIN.getConnection(), table);
104       assert(hris.size() == numRegions * numReplica);
105     } finally {
106       ADMIN.disableTable(table);
107       ADMIN.deleteTable(table);
108     }
109   }
110 
111   @Test
112   public void testCreateTableWithMultipleReplicas() throws Exception {
113     final TableName table = TableName.valueOf("fooTable");
114     final int numRegions = 3;
115     final int numReplica = 2;
116     try {
117       HTableDescriptor desc = new HTableDescriptor(table);
118       desc.setRegionReplication(numReplica);
119       desc.addFamily(new HColumnDescriptor("family"));
120       ADMIN.createTable(desc, Bytes.toBytes("A"), Bytes.toBytes("Z"), numRegions);
121       TEST_UTIL.waitTableEnabled(table);
122       validateNumberOfRowsInMeta(table, numRegions, ADMIN.getConnection());
123 
124       List<HRegionInfo> hris = MetaTableAccessor.getTableRegions(
125         TEST_UTIL.getZooKeeperWatcher(), ADMIN.getConnection(), table);
126       assert(hris.size() == numRegions * numReplica);
127       // check that the master created expected number of RegionState objects
128       for (int i = 0; i < numRegions; i++) {
129         for (int j = 0; j < numReplica; j++) {
130           HRegionInfo replica = RegionReplicaUtil.getRegionInfoForReplica(hris.get(i), j);
131           RegionState state = TEST_UTIL.getHBaseCluster().getMaster().getAssignmentManager()
132               .getRegionStates().getRegionState(replica);
133           assert (state != null);
134         }
135       }
136 
137       List<Result> metaRows = MetaTableAccessor.fullScanOfMeta(ADMIN.getConnection());
138       int numRows = 0;
139       for (Result result : metaRows) {
140         RegionLocations locations = MetaTableAccessor.getRegionLocations(result);
141         HRegionInfo hri = locations.getRegionLocation().getRegionInfo();
142         if (!hri.getTable().equals(table)) continue;
143         numRows += 1;
144         HRegionLocation[] servers = locations.getRegionLocations();
145         // have two locations for the replicas of a region, and the locations should be different
146         assert(servers.length == 2);
147         assert(!servers[0].equals(servers[1]));
148       }
149       assert(numRows == numRegions);
150 
151       // The same verification of the meta as above but with the SnapshotOfRegionAssignmentFromMeta
152       // class
153       validateFromSnapshotFromMeta(TEST_UTIL, table, numRegions, numReplica,
154         ADMIN.getConnection());
155 
156       // Now kill the master, restart it and see if the assignments are kept
157       ServerName master = TEST_UTIL.getHBaseClusterInterface().getClusterStatus().getMaster();
158       TEST_UTIL.getHBaseClusterInterface().stopMaster(master);
159       TEST_UTIL.getHBaseClusterInterface().waitForMasterToStop(master, 30000);
160       TEST_UTIL.getHBaseClusterInterface().startMaster(master.getHostname(), master.getPort());
161       TEST_UTIL.getHBaseClusterInterface().waitForActiveAndReadyMaster();
162       for (int i = 0; i < numRegions; i++) {
163         for (int j = 0; j < numReplica; j++) {
164           HRegionInfo replica = RegionReplicaUtil.getRegionInfoForReplica(hris.get(i), j);
165           RegionState state = TEST_UTIL.getHBaseCluster().getMaster().getAssignmentManager()
166               .getRegionStates().getRegionState(replica);
167           assert (state != null);
168         }
169       }
170       validateFromSnapshotFromMeta(TEST_UTIL, table, numRegions, numReplica,
171         ADMIN.getConnection());
172 
173       // Now shut the whole cluster down, and verify the assignments are kept so that the
174       // availability constraints are met.
175       TEST_UTIL.getConfiguration().setBoolean("hbase.master.startup.retainassign", true);
176       TEST_UTIL.shutdownMiniHBaseCluster();
177       TEST_UTIL.startMiniHBaseCluster(1, numSlaves);
178       TEST_UTIL.waitTableEnabled(table);
179       validateFromSnapshotFromMeta(TEST_UTIL, table, numRegions, numReplica,
180         ADMIN.getConnection());
181 
182       // Now shut the whole cluster down, and verify regions are assigned even if there is only
183       // one server running
184       TEST_UTIL.shutdownMiniHBaseCluster();
185       TEST_UTIL.startMiniHBaseCluster(1, 1);
186       TEST_UTIL.waitTableEnabled(table);
187       validateSingleRegionServerAssignment(ADMIN.getConnection(), numRegions, numReplica);
188       for (int i = 1; i < numSlaves; i++) { //restore the cluster
189         TEST_UTIL.getMiniHBaseCluster().startRegionServer();
190       }
191 
192       //check on alter table
193       ADMIN.disableTable(table);
194       assert(ADMIN.isTableDisabled(table));
195       //increase the replica
196       desc.setRegionReplication(numReplica + 1);
197       ADMIN.modifyTable(table, desc);
198       ADMIN.enableTable(table);
199       assert(ADMIN.isTableEnabled(table));
200       List<HRegionInfo> regions = TEST_UTIL.getMiniHBaseCluster().getMaster()
201           .getAssignmentManager().getRegionStates().getRegionsOfTable(table);
202       assert(regions.size() == numRegions * (numReplica + 1));
203 
204       //decrease the replica(earlier, table was modified to have a replica count of numReplica + 1)
205       ADMIN.disableTable(table);
206       desc.setRegionReplication(numReplica);
207       ADMIN.modifyTable(table, desc);
208       ADMIN.enableTable(table);
209       assert(ADMIN.isTableEnabled(table));
210       regions = TEST_UTIL.getMiniHBaseCluster().getMaster()
211           .getAssignmentManager().getRegionStates().getRegionsOfTable(table);
212       assert(regions.size() == numRegions * numReplica);
213       //also make sure the meta table has the replica locations removed
214       hris = MetaTableAccessor.getTableRegions(TEST_UTIL.getZooKeeperWatcher(),
215         ADMIN.getConnection(), table);
216       assert(hris.size() == numRegions * numReplica);
217       //just check that the number of default replica regions in the meta table are the same
218       //as the number of regions the table was created with, and the count of the
219       //replicas is numReplica for each region
220       Map<HRegionInfo, Integer> defaultReplicas = new HashMap<HRegionInfo, Integer>();
221       for (HRegionInfo hri : hris) {
222         Integer i;
223         HRegionInfo regionReplica0 = RegionReplicaUtil.getRegionInfoForDefaultReplica(hri);
224         defaultReplicas.put(regionReplica0,
225             (i = defaultReplicas.get(regionReplica0)) == null ? 1 : i + 1);
226       }
227       assert(defaultReplicas.size() == numRegions);
228       Collection<Integer> counts = new HashSet<Integer>(defaultReplicas.values());
229       assert(counts.size() == 1 && counts.contains(new Integer(numReplica)));
230     } finally {
231       ADMIN.disableTable(table);
232       ADMIN.deleteTable(table);
233     }
234   }
235 
236   //@Test (TODO: enable when we have support for alter_table- HBASE-10361).
237   public void testIncompleteMetaTableReplicaInformation() throws Exception {
238     final TableName table = TableName.valueOf("fooTableTest1");
239     final int numRegions = 3;
240     final int numReplica = 2;
241     try {
242       // Create a table and let the meta table be updated with the location of the
243       // region locations.
244       HTableDescriptor desc = new HTableDescriptor(table);
245       desc.setRegionReplication(numReplica);
246       desc.addFamily(new HColumnDescriptor("family"));
247       ADMIN.createTable(desc, Bytes.toBytes("A"), Bytes.toBytes("Z"), numRegions);
248       TEST_UTIL.waitTableEnabled(table);
249       Set<byte[]> tableRows = new HashSet<byte[]>();
250       List<HRegionInfo> hris = MetaTableAccessor.getTableRegions(TEST_UTIL.getZooKeeperWatcher(),
251         ADMIN.getConnection(), table);
252       for (HRegionInfo hri : hris) {
253         tableRows.add(hri.getRegionName());
254       }
255       ADMIN.disableTable(table);
256       // now delete one replica info from all the rows
257       // this is to make the meta appear to be only partially updated
258       Table metaTable = new HTable(TableName.META_TABLE_NAME, ADMIN.getConnection());
259       for (byte[] row : tableRows) {
260         Delete deleteOneReplicaLocation = new Delete(row);
261         deleteOneReplicaLocation.deleteColumns(HConstants.CATALOG_FAMILY,
262           MetaTableAccessor.getServerColumn(1));
263         deleteOneReplicaLocation.deleteColumns(HConstants.CATALOG_FAMILY,
264           MetaTableAccessor.getSeqNumColumn(1));
265         deleteOneReplicaLocation.deleteColumns(HConstants.CATALOG_FAMILY,
266           MetaTableAccessor.getStartCodeColumn(1));
267         metaTable.delete(deleteOneReplicaLocation);
268       }
269       metaTable.close();
270       // even if the meta table is partly updated, when we re-enable the table, we should
271       // get back the desired number of replicas for the regions
272       ADMIN.enableTable(table);
273       assert(ADMIN.isTableEnabled(table));
274       List<HRegionInfo> regions = TEST_UTIL.getMiniHBaseCluster().getMaster()
275           .getAssignmentManager().getRegionStates().getRegionsOfTable(table);
276       assert(regions.size() == numRegions * numReplica);
277     } finally {
278       ADMIN.disableTable(table);
279       ADMIN.deleteTable(table);
280     }
281   }
282 
283   private String printRegions(List<HRegionInfo> regions) {
284     StringBuffer strBuf = new StringBuffer();
285     for (HRegionInfo r : regions) {
286       strBuf.append(" ____ " + r.toString());
287     }
288     return strBuf.toString();
289   }
290 
291   private void validateNumberOfRowsInMeta(final TableName table, int numRegions,
292       Connection connection) throws IOException {
293     assert(ADMIN.tableExists(table));
294     final AtomicInteger count = new AtomicInteger();
295     Visitor visitor = new Visitor() {
296       @Override
297       public boolean visit(Result r) throws IOException {
298         if (HRegionInfo.getHRegionInfo(r).getTable().equals(table)) count.incrementAndGet();
299         return true;
300       }
301     };
302     MetaTableAccessor.fullScan(connection, visitor);
303     assert(count.get() == numRegions);
304   }
305 
306   private void validateFromSnapshotFromMeta(HBaseTestingUtility util, TableName table,
307       int numRegions, int numReplica, Connection connection) throws IOException {
308     SnapshotOfRegionAssignmentFromMeta snapshot = new SnapshotOfRegionAssignmentFromMeta(
309       connection);
310     snapshot.initialize();
311     Map<HRegionInfo, ServerName> regionToServerMap = snapshot.getRegionToRegionServerMap();
312     assert(regionToServerMap.size() == numRegions * numReplica + 1); //'2' for the ns, backup
313     Map<ServerName, List<HRegionInfo>> serverToRegionMap = snapshot.getRegionServerToRegionMap();
314     for (Map.Entry<ServerName, List<HRegionInfo>> entry : serverToRegionMap.entrySet()) {
315       if (entry.getKey().equals(util.getHBaseCluster().getMaster().getServerName())) {
316         continue;
317       }
318       List<HRegionInfo> regions = entry.getValue();
319       Set<byte[]> setOfStartKeys = new HashSet<byte[]>();
320       for (HRegionInfo region : regions) {
321         byte[] startKey = region.getStartKey();
322         if (region.getTable().equals(table)) {
323           setOfStartKeys.add(startKey); //ignore other tables
324           LOG.info("--STARTKEY " + new String(startKey)+"--");
325         }
326       }
327       // the number of startkeys will be equal to the number of regions hosted in each server
328       // (each server will be hosting one replica of a region)
329       assertEquals(numRegions, setOfStartKeys.size());
330     }
331   }
332 
333   private void validateSingleRegionServerAssignment(Connection connection, int numRegions,
334       int numReplica) throws IOException {
335     SnapshotOfRegionAssignmentFromMeta snapshot = new SnapshotOfRegionAssignmentFromMeta(
336       connection);
337     snapshot.initialize();
338     Map<HRegionInfo, ServerName>  regionToServerMap = snapshot.getRegionToRegionServerMap();
339     assertEquals(regionToServerMap.size(), numRegions * numReplica + 1); //'2' for the namespace
340     //, backup
341     Map<ServerName, List<HRegionInfo>> serverToRegionMap = snapshot.getRegionServerToRegionMap();
342     assertEquals(serverToRegionMap.keySet().size(), 1); // master by default not used
343     for (Map.Entry<ServerName, List<HRegionInfo>> entry : serverToRegionMap.entrySet()) {
344       if (entry.getKey().equals(TEST_UTIL.getHBaseCluster().getMaster().getServerName())) {
345         continue;
346       }
347       assertEquals(entry.getValue().size(), numRegions * numReplica + 1); //'2' for the namespace,
348       // backup
349     }
350   }
351 }