View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase.master.procedure;
20  
21  import java.io.IOException;
22  import java.util.concurrent.atomic.AtomicInteger;
23  import java.util.List;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.apache.hadoop.fs.FileSystem;
28  import org.apache.hadoop.fs.Path;
29  import org.apache.hadoop.hbase.HColumnDescriptor;
30  import org.apache.hadoop.hbase.HRegionInfo;
31  import org.apache.hadoop.hbase.HRegionLocation;
32  import org.apache.hadoop.hbase.HTableDescriptor;
33  import org.apache.hadoop.hbase.MetaTableAccessor;
34  import org.apache.hadoop.hbase.RegionLocations;
35  import org.apache.hadoop.hbase.ServerName;
36  import org.apache.hadoop.hbase.TableName;
37  import org.apache.hadoop.hbase.TableStateManager;
38  import org.apache.hadoop.hbase.client.BufferedMutator;
39  import org.apache.hadoop.hbase.client.Connection;
40  import org.apache.hadoop.hbase.client.Durability;
41  import org.apache.hadoop.hbase.client.NonceGenerator;
42  import org.apache.hadoop.hbase.client.Put;
43  import org.apache.hadoop.hbase.client.MetaScanner;
44  import org.apache.hadoop.hbase.client.MetaScanner.MetaScannerVisitor;
45  import org.apache.hadoop.hbase.client.MetaScanner.MetaScannerVisitorBase;
46  import org.apache.hadoop.hbase.client.Result;
47  import org.apache.hadoop.hbase.master.HMaster;
48  import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
49  import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
50  import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos;
51  import org.apache.hadoop.hbase.util.ModifyRegionUtils;
52  import org.apache.hadoop.hbase.util.Bytes;
53  import org.apache.hadoop.hbase.util.FSUtils;
54  import org.apache.hadoop.hbase.util.MD5Hash;
55  
56  import static org.junit.Assert.assertEquals;
57  import static org.junit.Assert.assertFalse;
58  import static org.junit.Assert.assertTrue;
59  import static org.junit.Assert.fail;
60  
61  public class MasterProcedureTestingUtility {
62    private static final Log LOG = LogFactory.getLog(MasterProcedureTestingUtility.class);
63  
64    private MasterProcedureTestingUtility() {
65    }
66  
67    public static HTableDescriptor createHTD(final TableName tableName, final String... family) {
68      HTableDescriptor htd = new HTableDescriptor(tableName);
69      for (int i = 0; i < family.length; ++i) {
70        htd.addFamily(new HColumnDescriptor(family[i]));
71      }
72      return htd;
73    }
74  
75    public static HRegionInfo[] createTable(final ProcedureExecutor<MasterProcedureEnv> procExec,
76        final TableName tableName, final byte[][] splitKeys, String... family) throws IOException {
77      HTableDescriptor htd = createHTD(tableName, family);
78      HRegionInfo[] regions = ModifyRegionUtils.createHRegionInfos(htd, splitKeys);
79      long procId = ProcedureTestingUtility.submitAndWait(procExec,
80        new CreateTableProcedure(procExec.getEnvironment(), htd, regions));
81      ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId));
82      return regions;
83    }
84  
85    public static void validateTableCreation(final HMaster master, final TableName tableName,
86        final HRegionInfo[] regions, String... family) throws IOException {
87      validateTableCreation(master, tableName, regions, true, family);
88    }
89  
90    public static void validateTableCreation(final HMaster master, final TableName tableName,
91        final HRegionInfo[] regions, boolean hasFamilyDirs, String... family) throws IOException {
92      // check filesystem
93      final FileSystem fs = master.getMasterFileSystem().getFileSystem();
94      final Path tableDir = FSUtils.getTableDir(master.getMasterFileSystem().getRootDir(), tableName);
95      assertTrue(fs.exists(tableDir));
96      FSUtils.logFileSystemState(fs, tableDir, LOG);
97      List<Path> allRegionDirs = FSUtils.getRegionDirs(fs, tableDir);
98      for (int i = 0; i < regions.length; ++i) {
99        Path regionDir = new Path(tableDir, regions[i].getEncodedName());
100       assertTrue(regions[i] + " region dir does not exist", fs.exists(regionDir));
101       assertTrue(allRegionDirs.remove(regionDir));
102       List<Path> allFamilyDirs = FSUtils.getFamilyDirs(fs, regionDir);
103       for (int j = 0; j < family.length; ++j) {
104         final Path familyDir = new Path(regionDir, family[j]);
105         if (hasFamilyDirs) {
106           assertTrue(family[j] + " family dir does not exist", fs.exists(familyDir));
107           assertTrue(allFamilyDirs.remove(familyDir));
108         } else {
109           // TODO: WARN: Modify Table/Families does not create a family dir
110           if (!fs.exists(familyDir)) {
111             LOG.warn(family[j] + " family dir does not exist");
112           }
113           allFamilyDirs.remove(familyDir);
114         }
115       }
116       assertTrue("found extraneous families: " + allFamilyDirs, allFamilyDirs.isEmpty());
117     }
118     assertTrue("found extraneous regions: " + allRegionDirs, allRegionDirs.isEmpty());
119 
120     // check meta
121     assertTrue(MetaTableAccessor.tableExists(master.getConnection(), tableName));
122     assertEquals(regions.length, countMetaRegions(master, tableName));
123 
124     // check htd
125     HTableDescriptor htd = master.getTableDescriptors().get(tableName);
126     assertTrue("table descriptor not found", htd != null);
127     for (int i = 0; i < family.length; ++i) {
128       assertTrue("family not found " + family[i], htd.getFamily(Bytes.toBytes(family[i])) != null);
129     }
130     assertEquals(family.length, htd.getFamilies().size());
131   }
132 
133   public static void validateTableDeletion(final HMaster master, final TableName tableName,
134       final HRegionInfo[] regions, String... family) throws IOException {
135     // check filesystem
136     final FileSystem fs = master.getMasterFileSystem().getFileSystem();
137     final Path tableDir = FSUtils.getTableDir(master.getMasterFileSystem().getRootDir(), tableName);
138     assertFalse(fs.exists(tableDir));
139 
140     // check meta
141     assertFalse(MetaTableAccessor.tableExists(master.getConnection(), tableName));
142     assertEquals(0, countMetaRegions(master, tableName));
143 
144     // check htd
145     assertTrue("found htd of deleted table",
146       master.getTableDescriptors().get(tableName) == null);
147   }
148 
149   private static int countMetaRegions(final HMaster master, final TableName tableName)
150       throws IOException {
151     final AtomicInteger actualRegCount = new AtomicInteger(0);
152     final MetaScannerVisitor visitor = new MetaScannerVisitorBase() {
153       @Override
154       public boolean processRow(Result rowResult) throws IOException {
155         RegionLocations list = MetaTableAccessor.getRegionLocations(rowResult);
156         if (list == null) {
157           LOG.warn("No serialized HRegionInfo in " + rowResult);
158           return true;
159         }
160         HRegionLocation l = list.getRegionLocation();
161         if (l == null) {
162           return true;
163         }
164         if (!l.getRegionInfo().getTable().equals(tableName)) {
165           return false;
166         }
167         if (l.getRegionInfo().isOffline() || l.getRegionInfo().isSplit()) return true;
168         HRegionLocation[] locations = list.getRegionLocations();
169         for (HRegionLocation location : locations) {
170           if (location == null) continue;
171           ServerName serverName = location.getServerName();
172           // Make sure that regions are assigned to server
173           if (serverName != null && serverName.getHostAndPort() != null) {
174             actualRegCount.incrementAndGet();
175           }
176         }
177         return true;
178       }
179     };
180     MetaScanner.metaScan(master.getConnection(), visitor, tableName);
181     return actualRegCount.get();
182   }
183 
184   public static void validateTableIsEnabled(final HMaster master, final TableName tableName)
185       throws IOException {
186     TableStateManager tsm = master.getAssignmentManager().getTableStateManager();
187     assertTrue(tsm.isTableState(tableName, ZooKeeperProtos.Table.State.ENABLED));
188   }
189 
190   public static void validateTableIsDisabled(final HMaster master, final TableName tableName)
191       throws IOException {
192     TableStateManager tsm = master.getAssignmentManager().getTableStateManager();
193     assertTrue(tsm.isTableState(tableName, ZooKeeperProtos.Table.State.DISABLED));
194   }
195 
196   public static <TState> void testRecoveryAndDoubleExecution(
197       final ProcedureExecutor<MasterProcedureEnv> procExec, final long procId,
198       final int numSteps, final TState[] states) throws Exception {
199     ProcedureTestingUtility.waitProcedure(procExec, procId);
200     assertEquals(false, procExec.isRunning());
201     // Restart the executor and execute the step twice
202     //   execute step N - kill before store update
203     //   restart executor/store
204     //   execute step N - save on store
205     for (int i = 0; i < numSteps; ++i) {
206       LOG.info("Restart "+ i +" exec state: " + states[i]);
207       ProcedureTestingUtility.assertProcNotYetCompleted(procExec, procId);
208       ProcedureTestingUtility.restart(procExec);
209       ProcedureTestingUtility.waitProcedure(procExec, procId);
210     }
211     assertEquals(true, procExec.isRunning());
212     ProcedureTestingUtility.assertProcNotFailed(procExec, procId);
213   }
214 
215   public static <TState> void testRollbackAndDoubleExecution(
216       final ProcedureExecutor<MasterProcedureEnv> procExec, final long procId,
217       final int lastStep, final TState[] states) throws Exception {
218     ProcedureTestingUtility.waitProcedure(procExec, procId);
219 
220     // Restart the executor and execute the step twice
221     //   execute step N - kill before store update
222     //   restart executor/store
223     //   execute step N - save on store
224     for (int i = 0; i < lastStep; ++i) {
225       LOG.info("Restart "+ i +" exec state: " + states[i]);
226       ProcedureTestingUtility.assertProcNotYetCompleted(procExec, procId);
227       ProcedureTestingUtility.restart(procExec);
228       ProcedureTestingUtility.waitProcedure(procExec, procId);
229     }
230 
231     // Restart the executor and rollback the step twice
232     //   rollback step N - kill before store update
233     //   restart executor/store
234     //   rollback step N - save on store
235     MasterProcedureTestingUtility.InjectAbortOnLoadListener abortListener =
236       new MasterProcedureTestingUtility.InjectAbortOnLoadListener(procExec);
237     procExec.registerListener(abortListener);
238     try {
239       for (int i = lastStep + 1; i >= 0; --i) {
240         LOG.info("Restart " + i +" rollback state: "+ states[i]);
241         ProcedureTestingUtility.assertProcNotYetCompleted(procExec, procId);
242         ProcedureTestingUtility.restart(procExec);
243         ProcedureTestingUtility.waitProcedure(procExec, procId);
244       }
245     } finally {
246       assertTrue(procExec.unregisterListener(abortListener));
247     }
248 
249     ProcedureTestingUtility.assertIsAbortException(procExec.getResult(procId));
250   }
251 
252   public static <TState> void testRollbackAndDoubleExecutionAfterPONR(
253       final ProcedureExecutor<MasterProcedureEnv> procExec, final long procId,
254       final int lastStep, final TState[] states) throws Exception {
255     ProcedureTestingUtility.waitProcedure(procExec, procId);
256 
257     // Restart the executor and execute the step twice
258     //   execute step N - kill before store update
259     //   restart executor/store
260     //   execute step N - save on store
261     for (int i = 0; i < lastStep; ++i) {
262       LOG.info("Restart "+ i +" exec state: " + states[i]);
263       ProcedureTestingUtility.assertProcNotYetCompleted(procExec, procId);
264       ProcedureTestingUtility.restart(procExec);
265       ProcedureTestingUtility.waitProcedure(procExec, procId);
266     }
267 
268     // try to inject the abort
269     ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, false);
270     MasterProcedureTestingUtility.InjectAbortOnLoadListener abortListener =
271       new MasterProcedureTestingUtility.InjectAbortOnLoadListener(procExec);
272     procExec.registerListener(abortListener);
273     try {
274       ProcedureTestingUtility.assertProcNotYetCompleted(procExec, procId);
275       ProcedureTestingUtility.restart(procExec);
276       LOG.info("Restart and execute");
277       ProcedureTestingUtility.waitProcedure(procExec, procId);
278     } finally {
279       assertTrue(procExec.unregisterListener(abortListener));
280     }
281 
282     assertEquals(true, procExec.isRunning());
283     ProcedureTestingUtility.assertProcNotFailed(procExec, procId);
284   }
285 
286   public static <TState> void testRollbackRetriableFailure(
287       final ProcedureExecutor<MasterProcedureEnv> procExec, final long procId,
288       final int lastStep, final TState[] states) throws Exception {
289     ProcedureTestingUtility.waitProcedure(procExec, procId);
290 
291     // Restart the executor and execute the step twice
292     //   execute step N - kill before store update
293     //   restart executor/store
294     //   execute step N - save on store
295     for (int i = 0; i < lastStep; ++i) {
296       LOG.info("Restart "+ i +" exec state: " + states[i]);
297       ProcedureTestingUtility.assertProcNotYetCompleted(procExec, procId);
298       ProcedureTestingUtility.restart(procExec);
299       ProcedureTestingUtility.waitProcedure(procExec, procId);
300     }
301 
302     // execute the rollback
303     ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, false);
304     MasterProcedureTestingUtility.InjectAbortOnLoadListener abortListener =
305       new MasterProcedureTestingUtility.InjectAbortOnLoadListener(procExec);
306     procExec.registerListener(abortListener);
307     try {
308       ProcedureTestingUtility.assertProcNotYetCompleted(procExec, procId);
309       ProcedureTestingUtility.restart(procExec);
310       LOG.info("Restart and rollback");
311       ProcedureTestingUtility.waitProcedure(procExec, procId);
312     } finally {
313       assertTrue(procExec.unregisterListener(abortListener));
314     }
315 
316     ProcedureTestingUtility.assertIsAbortException(procExec.getResult(procId));
317   }
318 
319   public static void validateColumnFamilyAddition(final HMaster master, final TableName tableName,
320       final String family) throws IOException {
321     HTableDescriptor htd = master.getTableDescriptors().get(tableName);
322     assertTrue(htd != null);
323     assertTrue(htd.hasFamily(family.getBytes()));
324   }
325 
326   public static void validateColumnFamilyDeletion(final HMaster master, final TableName tableName,
327       final String family) throws IOException {
328     // verify htd
329     HTableDescriptor htd = master.getTableDescriptors().get(tableName);
330     assertTrue(htd != null);
331     assertFalse(htd.hasFamily(family.getBytes()));
332 
333     // verify fs
334     final FileSystem fs = master.getMasterFileSystem().getFileSystem();
335     final Path tableDir = FSUtils.getTableDir(master.getMasterFileSystem().getRootDir(), tableName);
336     for (Path regionDir: FSUtils.getRegionDirs(fs, tableDir)) {
337       final Path familyDir = new Path(regionDir, family);
338       assertFalse(family + " family dir should not exist", fs.exists(familyDir));
339     }
340   }
341 
342   public static void validateColumnFamilyModification(final HMaster master,
343       final TableName tableName, final String family, HColumnDescriptor columnDescriptor)
344       throws IOException {
345     HTableDescriptor htd = master.getTableDescriptors().get(tableName);
346     assertTrue(htd != null);
347 
348     HColumnDescriptor hcfd = htd.getFamily(family.getBytes());
349     assertTrue(hcfd.equals(columnDescriptor));
350   }
351 
352   public static void loadData(final Connection connection, final TableName tableName,
353       int rows, final byte[][] splitKeys,  final String... sfamilies) throws IOException {
354     byte[][] families = new byte[sfamilies.length][];
355     for (int i = 0; i < families.length; ++i) {
356       families[i] = Bytes.toBytes(sfamilies[i]);
357     }
358 
359     BufferedMutator mutator = connection.getBufferedMutator(tableName);
360 
361     // Ensure one row per region
362     assertTrue(rows >= splitKeys.length);
363     for (byte[] k: splitKeys) {
364       byte[] value = Bytes.add(Bytes.toBytes(System.currentTimeMillis()), k);
365       byte[] key = Bytes.add(k, Bytes.toBytes(MD5Hash.getMD5AsHex(value)));
366       mutator.mutate(createPut(families, key, value));
367       rows--;
368     }
369 
370     // Add other extra rows. more rows, more files
371     while (rows-- > 0) {
372       byte[] value = Bytes.add(Bytes.toBytes(System.currentTimeMillis()), Bytes.toBytes(rows));
373       byte[] key = Bytes.toBytes(MD5Hash.getMD5AsHex(value));
374       mutator.mutate(createPut(families, key, value));
375     }
376     mutator.flush();
377   }
378 
379   private static Put createPut(final byte[][] families, final byte[] key, final byte[] value) {
380     byte[] q = Bytes.toBytes("q");
381     Put put = new Put(key);
382     put.setDurability(Durability.SKIP_WAL);
383     for (byte[] family: families) {
384       put.add(family, q, value);
385     }
386     return put;
387   }
388 
389   public static long generateNonceGroup(final HMaster master) {
390     return master.getConnection().getNonceGenerator().getNonceGroup();
391   }
392 
393   public static long generateNonce(final HMaster master) {
394     return master.getConnection().getNonceGenerator().newNonce();
395   }
396 
397   public static class InjectAbortOnLoadListener
398       implements ProcedureExecutor.ProcedureExecutorListener {
399     private final ProcedureExecutor<MasterProcedureEnv> procExec;
400 
401     public InjectAbortOnLoadListener(final ProcedureExecutor<MasterProcedureEnv> procExec) {
402       this.procExec = procExec;
403     }
404 
405     @Override
406     public void procedureLoaded(long procId) {
407       procExec.abort(procId);
408     }
409 
410     @Override
411     public void procedureAdded(long procId) { /* no-op */ }
412 
413     @Override
414     public void procedureFinished(long procId) { /* no-op */ }
415   }
416 }