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  import java.util.TreeSet;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.apache.hadoop.fs.FileSystem;
29  import org.apache.hadoop.fs.Path;
30  import org.apache.hadoop.hbase.HColumnDescriptor;
31  import org.apache.hadoop.hbase.HRegionInfo;
32  import org.apache.hadoop.hbase.HRegionLocation;
33  import org.apache.hadoop.hbase.HTableDescriptor;
34  import org.apache.hadoop.hbase.MetaTableAccessor;
35  import org.apache.hadoop.hbase.RegionLocations;
36  import org.apache.hadoop.hbase.ServerName;
37  import org.apache.hadoop.hbase.TableName;
38  import org.apache.hadoop.hbase.TableStateManager;
39  import org.apache.hadoop.hbase.client.BufferedMutator;
40  import org.apache.hadoop.hbase.client.Connection;
41  import org.apache.hadoop.hbase.client.Durability;
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  
60  public class MasterProcedureTestingUtility {
61    private static final Log LOG = LogFactory.getLog(MasterProcedureTestingUtility.class);
62  
63    private MasterProcedureTestingUtility() {
64    }
65  
66    public static HTableDescriptor createHTD(final TableName tableName, final String... family) {
67      HTableDescriptor htd = new HTableDescriptor(tableName);
68      for (int i = 0; i < family.length; ++i) {
69        htd.addFamily(new HColumnDescriptor(family[i]));
70      }
71      return htd;
72    }
73  
74    public static HRegionInfo[] createTable(final ProcedureExecutor<MasterProcedureEnv> procExec,
75        final TableName tableName, final byte[][] splitKeys, String... family) throws IOException {
76      HTableDescriptor htd = createHTD(tableName, family);
77      return createTable(procExec, htd, splitKeys);
78    }
79  
80    public static HRegionInfo[] createTable(final ProcedureExecutor<MasterProcedureEnv> procExec,
81        HTableDescriptor htd, final byte[][] splitKeys) throws IOException {
82      HRegionInfo[] regions = ModifyRegionUtils.createHRegionInfos(htd, splitKeys);
83      long procId = ProcedureTestingUtility.submitAndWait(procExec,
84        new CreateTableProcedure(procExec.getEnvironment(), htd, regions));
85      ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId));
86      return regions;
87    }
88  
89    public static void validateTableCreation(final HMaster master, final TableName tableName,
90        final HRegionInfo[] regions, String... family) throws IOException {
91      validateTableCreation(master, tableName, regions, true, family);
92    }
93  
94    public static void validateTableCreation(final HMaster master, final TableName tableName,
95        final HRegionInfo[] regions, boolean hasFamilyDirs, String... family) throws IOException {
96      // check filesystem
97      final FileSystem fs = master.getMasterFileSystem().getFileSystem();
98      final Path tableDir = FSUtils.getTableDir(master.getMasterFileSystem().getRootDir(), tableName);
99      assertTrue(fs.exists(tableDir));
100     FSUtils.logFileSystemState(fs, tableDir, LOG);
101     List<Path> allRegionDirs = FSUtils.getRegionDirs(fs, tableDir);
102     for (int i = 0; i < regions.length; ++i) {
103       Path regionDir = new Path(tableDir, regions[i].getEncodedName());
104       assertTrue(regions[i] + " region dir does not exist", fs.exists(regionDir));
105       assertTrue(allRegionDirs.remove(regionDir));
106       List<Path> allFamilyDirs = FSUtils.getFamilyDirs(fs, regionDir);
107       for (int j = 0; j < family.length; ++j) {
108         final Path familyDir = new Path(regionDir, family[j]);
109         if (hasFamilyDirs) {
110           assertTrue(family[j] + " family dir does not exist", fs.exists(familyDir));
111           assertTrue(allFamilyDirs.remove(familyDir));
112         } else {
113           // TODO: WARN: Modify Table/Families does not create a family dir
114           if (!fs.exists(familyDir)) {
115             LOG.warn(family[j] + " family dir does not exist");
116           }
117           allFamilyDirs.remove(familyDir);
118         }
119       }
120       assertTrue("found extraneous families: " + allFamilyDirs, allFamilyDirs.isEmpty());
121     }
122     assertTrue("found extraneous regions: " + allRegionDirs, allRegionDirs.isEmpty());
123 
124     // check meta
125     assertTrue(MetaTableAccessor.tableExists(master.getConnection(), tableName));
126     assertEquals(regions.length, countMetaRegions(master, tableName));
127 
128     // check htd
129     HTableDescriptor htd = master.getTableDescriptors().get(tableName);
130     assertTrue("table descriptor not found", htd != null);
131     for (int i = 0; i < family.length; ++i) {
132       assertTrue("family not found " + family[i], htd.getFamily(Bytes.toBytes(family[i])) != null);
133     }
134     assertEquals(family.length, htd.getFamilies().size());
135   }
136 
137   public static void validateTableDeletion(final HMaster master, final TableName tableName,
138       final HRegionInfo[] regions, String... family) throws IOException {
139     // check filesystem
140     final FileSystem fs = master.getMasterFileSystem().getFileSystem();
141     final Path tableDir = FSUtils.getTableDir(master.getMasterFileSystem().getRootDir(), tableName);
142     assertFalse(fs.exists(tableDir));
143 
144     // check meta
145     assertFalse(MetaTableAccessor.tableExists(master.getConnection(), tableName));
146     assertEquals(0, countMetaRegions(master, tableName));
147 
148     // check htd
149     assertTrue("found htd of deleted table",
150       master.getTableDescriptors().get(tableName) == null);
151   }
152 
153   private static int countMetaRegions(final HMaster master, final TableName tableName)
154       throws IOException {
155     final AtomicInteger actualRegCount = new AtomicInteger(0);
156     final MetaScannerVisitor visitor = new MetaScannerVisitorBase() {
157       @Override
158       public boolean processRow(Result rowResult) throws IOException {
159         RegionLocations list = MetaTableAccessor.getRegionLocations(rowResult);
160         if (list == null) {
161           LOG.warn("No serialized HRegionInfo in " + rowResult);
162           return true;
163         }
164         HRegionLocation l = list.getRegionLocation();
165         if (l == null) {
166           return true;
167         }
168         if (!l.getRegionInfo().getTable().equals(tableName)) {
169           return false;
170         }
171         if (l.getRegionInfo().isOffline() || l.getRegionInfo().isSplit()) return true;
172         HRegionLocation[] locations = list.getRegionLocations();
173         for (HRegionLocation location : locations) {
174           if (location == null) continue;
175           ServerName serverName = location.getServerName();
176           // Make sure that regions are assigned to server
177           if (serverName != null && serverName.getHostAndPort() != null) {
178             actualRegCount.incrementAndGet();
179           }
180         }
181         return true;
182       }
183     };
184     MetaScanner.metaScan(master.getConnection(), visitor, tableName);
185     return actualRegCount.get();
186   }
187 
188   public static void validateTableIsEnabled(final HMaster master, final TableName tableName)
189       throws IOException {
190     TableStateManager tsm = master.getAssignmentManager().getTableStateManager();
191     assertTrue(tsm.isTableState(tableName, ZooKeeperProtos.Table.State.ENABLED));
192   }
193 
194   public static void validateTableIsDisabled(final HMaster master, final TableName tableName)
195       throws IOException {
196     TableStateManager tsm = master.getAssignmentManager().getTableStateManager();
197     assertTrue(tsm.isTableState(tableName, ZooKeeperProtos.Table.State.DISABLED));
198   }
199 
200   /**
201    * Run through all procedure flow states TWICE while also restarting procedure executor at each
202    * step; i.e force a reread of procedure store.
203    *
204    *<p>It does
205    * <ol><li>Execute step N - kill the executor before store update
206    * <li>Restart executor/store
207    * <li>Execute step N - and then save to store
208    * </ol>
209    *
210    *<p>This is a good test for finding state that needs persisting and steps that are not
211    * idempotent. Use this version of the test when a procedure executes all flow steps from start to
212    * finish.
213    * @see #testRecoveryAndDoubleExecution(ProcedureExecutor, long)
214    */
215   public static <TState> void testRecoveryAndDoubleExecution(
216       final ProcedureExecutor<MasterProcedureEnv> procExec, final long procId,
217       final int numSteps, final TState[] states) throws Exception {
218     ProcedureTestingUtility.waitProcedure(procExec, procId);
219     assertEquals(false, procExec.isRunning());
220     for (int i = 0; i < numSteps; ++i) {
221       LOG.info("Restart "+ i +" exec state: " + states[i]);
222       ProcedureTestingUtility.assertProcNotYetCompleted(procExec, procId);
223       ProcedureTestingUtility.restart(procExec);
224       ProcedureTestingUtility.waitProcedure(procExec, procId);
225     }
226     assertEquals(true, procExec.isRunning());
227     ProcedureTestingUtility.assertProcNotFailed(procExec, procId);
228   }
229 
230   /**
231    * Run through all procedure flow states TWICE while also restarting procedure executor at each
232    * step; i.e force a reread of procedure store.
233    *
234    *<p>It does
235    * <ol><li>Execute step N - kill the executor before store update
236    * <li>Restart executor/store
237    * <li>Execute step N - and then save to store
238    * </ol>
239    *
240    *<p>This is a good test for finding state that needs persisting and steps that are not
241    * idempotent. Use this version of the test when the order in which flow steps are executed is
242    * not start to finish; where the procedure may vary the flow steps dependent on circumstance
243    * found.
244    * @see #testRecoveryAndDoubleExecution(ProcedureExecutor, long, int, Object[])
245    */
246   public static <TState> void testRecoveryAndDoubleExecution(
247       final ProcedureExecutor<MasterProcedureEnv> procExec, final long procId)
248   throws Exception {
249     ProcedureTestingUtility.waitProcedure(procExec, procId);
250     assertEquals(false, procExec.isRunning());
251     while (!procExec.isFinished(procId)) {
252       ProcedureTestingUtility.restart(procExec);
253       ProcedureTestingUtility.waitProcedure(procExec, procId);
254     }
255     assertEquals(true, procExec.isRunning());
256     ProcedureTestingUtility.assertProcNotFailed(procExec, procId);
257   }
258 
259   public static <TState> void testRollbackAndDoubleExecution(
260       final ProcedureExecutor<MasterProcedureEnv> procExec, final long procId,
261       final int lastStep, final TState[] states) throws Exception {
262     ProcedureTestingUtility.waitProcedure(procExec, procId);
263 
264     // Restart the executor and execute the step twice
265     //   execute step N - kill before store update
266     //   restart executor/store
267     //   execute step N - save on store
268     for (int i = 0; i < lastStep; ++i) {
269       LOG.info("Restart "+ i +" exec state: " + states[i]);
270       ProcedureTestingUtility.assertProcNotYetCompleted(procExec, procId);
271       ProcedureTestingUtility.restart(procExec);
272       ProcedureTestingUtility.waitProcedure(procExec, procId);
273     }
274 
275     // Restart the executor and rollback the step twice
276     //   rollback step N - kill before store update
277     //   restart executor/store
278     //   rollback step N - save on store
279     InjectAbortOnLoadListener abortListener = new InjectAbortOnLoadListener(procExec);
280     procExec.registerListener(abortListener);
281     try {
282       for (int i = lastStep + 1; i >= 0; --i) {
283         LOG.info("Restart " + i +" rollback state: "+ states[i]);
284         ProcedureTestingUtility.assertProcNotYetCompleted(procExec, procId);
285         ProcedureTestingUtility.restart(procExec);
286         ProcedureTestingUtility.waitProcedure(procExec, procId);
287       }
288     } finally {
289       assertTrue(procExec.unregisterListener(abortListener));
290     }
291 
292     ProcedureTestingUtility.assertIsAbortException(procExec.getResult(procId));
293   }
294 
295   public static <TState> void testRollbackAndDoubleExecutionAfterPONR(
296       final ProcedureExecutor<MasterProcedureEnv> procExec, final long procId,
297       final int lastStep, final TState[] states) throws Exception {
298     ProcedureTestingUtility.waitProcedure(procExec, procId);
299 
300     // Restart the executor and execute the step twice
301     //   execute step N - kill before store update
302     //   restart executor/store
303     //   execute step N - save on store
304     for (int i = 0; i < lastStep; ++i) {
305       LOG.info("Restart "+ i +" exec state: " + states[i]);
306       ProcedureTestingUtility.assertProcNotYetCompleted(procExec, procId);
307       ProcedureTestingUtility.restart(procExec);
308       ProcedureTestingUtility.waitProcedure(procExec, procId);
309     }
310 
311     // try to inject the abort
312     ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, false);
313     InjectAbortOnLoadListener abortListener = new InjectAbortOnLoadListener(procExec);
314     procExec.registerListener(abortListener);
315     try {
316       ProcedureTestingUtility.assertProcNotYetCompleted(procExec, procId);
317       ProcedureTestingUtility.restart(procExec);
318       LOG.info("Restart and execute");
319       ProcedureTestingUtility.waitProcedure(procExec, procId);
320     } finally {
321       assertTrue(procExec.unregisterListener(abortListener));
322     }
323 
324     assertEquals(true, procExec.isRunning());
325     ProcedureTestingUtility.assertProcNotFailed(procExec, procId);
326   }
327 
328   public static <TState> void testRollbackRetriableFailure(
329       final ProcedureExecutor<MasterProcedureEnv> procExec, final long procId,
330       final int lastStep, final TState[] states) throws Exception {
331     ProcedureTestingUtility.waitProcedure(procExec, procId);
332 
333     // Restart the executor and execute the step twice
334     //   execute step N - kill before store update
335     //   restart executor/store
336     //   execute step N - save on store
337     for (int i = 0; i < lastStep; ++i) {
338       LOG.info("Restart "+ i +" exec state: " + states[i]);
339       ProcedureTestingUtility.assertProcNotYetCompleted(procExec, procId);
340       ProcedureTestingUtility.restart(procExec);
341       ProcedureTestingUtility.waitProcedure(procExec, procId);
342     }
343 
344     // execute the rollback
345     ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, false);
346     InjectAbortOnLoadListener abortListener = new InjectAbortOnLoadListener(procExec);
347     procExec.registerListener(abortListener);
348     try {
349       ProcedureTestingUtility.assertProcNotYetCompleted(procExec, procId);
350       ProcedureTestingUtility.restart(procExec);
351       LOG.info("Restart and rollback");
352       ProcedureTestingUtility.waitProcedure(procExec, procId);
353     } finally {
354       assertTrue(procExec.unregisterListener(abortListener));
355     }
356 
357     ProcedureTestingUtility.assertIsAbortException(procExec.getResult(procId));
358   }
359 
360   public static void testRestartWithAbort(ProcedureExecutor<MasterProcedureEnv> procExec,
361       long procId) throws Exception {
362     InjectAbortOnLoadListener abortListener = new InjectAbortOnLoadListener(procExec);
363     abortListener.addProcId(procId);
364     procExec.registerListener(abortListener);
365     try {
366       ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, false);
367       ProcedureTestingUtility.restart(procExec);
368       ProcedureTestingUtility.waitProcedure(procExec, procId);
369     } finally {
370       assertTrue(procExec.unregisterListener(abortListener));
371     }
372   }
373 
374   public static void validateColumnFamilyAddition(final HMaster master, final TableName tableName,
375       final String family) throws IOException {
376     HTableDescriptor htd = master.getTableDescriptors().get(tableName);
377     assertTrue(htd != null);
378     assertTrue(htd.hasFamily(family.getBytes()));
379   }
380 
381   public static void validateColumnFamilyDeletion(final HMaster master, final TableName tableName,
382       final String family) throws IOException {
383     // verify htd
384     HTableDescriptor htd = master.getTableDescriptors().get(tableName);
385     assertTrue(htd != null);
386     assertFalse(htd.hasFamily(family.getBytes()));
387 
388     // verify fs
389     final FileSystem fs = master.getMasterFileSystem().getFileSystem();
390     final Path tableDir = FSUtils.getTableDir(master.getMasterFileSystem().getRootDir(), tableName);
391     for (Path regionDir: FSUtils.getRegionDirs(fs, tableDir)) {
392       final Path familyDir = new Path(regionDir, family);
393       assertFalse(family + " family dir should not exist", fs.exists(familyDir));
394     }
395   }
396 
397   public static void validateColumnFamilyModification(final HMaster master,
398       final TableName tableName, final String family, HColumnDescriptor columnDescriptor)
399       throws IOException {
400     HTableDescriptor htd = master.getTableDescriptors().get(tableName);
401     assertTrue(htd != null);
402 
403     HColumnDescriptor hcfd = htd.getFamily(family.getBytes());
404     assertTrue(hcfd.equals(columnDescriptor));
405   }
406 
407   public static void loadData(final Connection connection, final TableName tableName,
408       int rows, final byte[][] splitKeys,  final String... sfamilies) throws IOException {
409     byte[][] families = new byte[sfamilies.length][];
410     for (int i = 0; i < families.length; ++i) {
411       families[i] = Bytes.toBytes(sfamilies[i]);
412     }
413 
414     BufferedMutator mutator = connection.getBufferedMutator(tableName);
415 
416     // Ensure one row per region
417     assertTrue(rows >= splitKeys.length);
418     for (byte[] k: splitKeys) {
419       byte[] value = Bytes.add(Bytes.toBytes(System.currentTimeMillis()), k);
420       byte[] key = Bytes.add(k, Bytes.toBytes(MD5Hash.getMD5AsHex(value)));
421       mutator.mutate(createPut(families, key, value));
422       rows--;
423     }
424 
425     // Add other extra rows. more rows, more files
426     while (rows-- > 0) {
427       byte[] value = Bytes.add(Bytes.toBytes(System.currentTimeMillis()), Bytes.toBytes(rows));
428       byte[] key = Bytes.toBytes(MD5Hash.getMD5AsHex(value));
429       mutator.mutate(createPut(families, key, value));
430     }
431     mutator.flush();
432   }
433 
434   private static Put createPut(final byte[][] families, final byte[] key, final byte[] value) {
435     byte[] q = Bytes.toBytes("q");
436     Put put = new Put(key);
437     put.setDurability(Durability.SKIP_WAL);
438     for (byte[] family: families) {
439       put.add(family, q, value);
440     }
441     return put;
442   }
443 
444   public static long generateNonceGroup(final HMaster master) {
445     return master.getConnection().getNonceGenerator().getNonceGroup();
446   }
447 
448   public static long generateNonce(final HMaster master) {
449     return master.getConnection().getNonceGenerator().newNonce();
450   }
451 
452   public static class InjectAbortOnLoadListener
453       implements ProcedureExecutor.ProcedureExecutorListener {
454     private final ProcedureExecutor<MasterProcedureEnv> procExec;
455     private TreeSet<Long> procsToAbort = null;
456 
457     public InjectAbortOnLoadListener(final ProcedureExecutor<MasterProcedureEnv> procExec) {
458       this.procExec = procExec;
459     }
460 
461     public void addProcId(long procId) {
462       if (procsToAbort == null) {
463         procsToAbort = new TreeSet<Long>();
464       }
465       procsToAbort.add(procId);
466     }
467 
468     @Override
469     public void procedureLoaded(long procId) {
470       if (procsToAbort != null && !procsToAbort.contains(procId)) {
471         return;
472       }
473       procExec.abort(procId);
474     }
475 
476     @Override
477     public void procedureAdded(long procId) { /* no-op */ }
478 
479     @Override
480     public void procedureFinished(long procId) { /* no-op */ }
481   }
482 }