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.procedure2;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.OutputStream;
24  import java.util.ArrayList;
25  import java.util.Set;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.hadoop.conf.Configuration;
30  import org.apache.hadoop.fs.FileSystem;
31  import org.apache.hadoop.fs.Path;
32  import org.apache.hadoop.hbase.HConstants;
33  import org.apache.hadoop.hbase.ProcedureInfo;
34  import org.apache.hadoop.hbase.exceptions.IllegalArgumentIOException;
35  import org.apache.hadoop.hbase.exceptions.TimeoutIOException;
36  import org.apache.hadoop.hbase.io.util.StreamUtils;
37  import org.apache.hadoop.hbase.procedure2.store.ProcedureStore;
38  import org.apache.hadoop.hbase.procedure2.store.ProcedureStore.ProcedureIterator;
39  import org.apache.hadoop.hbase.procedure2.store.NoopProcedureStore;
40  import org.apache.hadoop.hbase.procedure2.store.wal.WALProcedureStore;
41  import org.apache.hadoop.hbase.protobuf.generated.ErrorHandlingProtos.ForeignExceptionMessage;
42  import org.apache.hadoop.hbase.protobuf.generated.ProcedureProtos.ProcedureState;
43  import org.apache.hadoop.hbase.util.NonceKey;
44  import org.apache.hadoop.hbase.util.Threads;
45  
46  import static org.junit.Assert.assertEquals;
47  import static org.junit.Assert.assertFalse;
48  import static org.junit.Assert.assertTrue;
49  
50  public class ProcedureTestingUtility {
51    private static final Log LOG = LogFactory.getLog(ProcedureTestingUtility.class);
52  
53    private ProcedureTestingUtility() {
54    }
55  
56    public static ProcedureStore createStore(final Configuration conf, final Path dir)
57        throws IOException {
58      return createWalStore(conf, dir);
59    }
60  
61    public static WALProcedureStore createWalStore(final Configuration conf, final Path dir)
62        throws IOException {
63      return new WALProcedureStore(conf, dir, new WALProcedureStore.LeaseRecovery() {
64        @Override
65        public void recoverFileLease(FileSystem fs, Path path) throws IOException {
66          // no-op
67        }
68      });
69    }
70  
71    public static <TEnv> void restart(ProcedureExecutor<TEnv> procExecutor)
72        throws Exception {
73      restart(procExecutor, null, true);
74    }
75  
76    public static <TEnv> void restart(ProcedureExecutor<TEnv> procExecutor,
77        Runnable beforeStartAction, boolean failOnCorrupted) throws Exception {
78      ProcedureStore procStore = procExecutor.getStore();
79      int storeThreads = procExecutor.getNumThreads();
80      int execThreads = procExecutor.getNumThreads();
81      // stop
82      procExecutor.stop();
83      procExecutor.join();
84      procStore.stop(false);
85      // nothing running...
86      if (beforeStartAction != null) {
87        beforeStartAction.run();
88      }
89      // re-start
90      procStore.start(storeThreads);
91      procExecutor.start(execThreads, failOnCorrupted);
92    }
93  
94    public static void storeRestart(ProcedureStore procStore, ProcedureStore.ProcedureLoader loader)
95        throws Exception {
96      procStore.stop(false);
97      procStore.start(procStore.getNumThreads());
98      procStore.recoverLease();
99      procStore.load(loader);
100   }
101 
102   public static void storeRestartAndAssert(ProcedureStore procStore, long maxProcId,
103       long runnableCount, int completedCount, int corruptedCount) throws Exception {
104     final LoadCounter loader = new LoadCounter();
105     storeRestart(procStore, loader);
106     assertEquals(maxProcId, loader.getMaxProcId());
107     assertEquals(runnableCount, loader.getRunnableCount());
108     assertEquals(completedCount, loader.getCompletedCount());
109     assertEquals(corruptedCount, loader.getCorruptedCount());
110   }
111 
112   public static <TEnv> void setKillBeforeStoreUpdate(ProcedureExecutor<TEnv> procExecutor,
113       boolean value) {
114     if (procExecutor.testing == null) {
115       procExecutor.testing = new ProcedureExecutor.Testing();
116     }
117     procExecutor.testing.killBeforeStoreUpdate = value;
118     LOG.warn("Set Kill before store update to: " + procExecutor.testing.killBeforeStoreUpdate);
119   }
120 
121   public static <TEnv> void setToggleKillBeforeStoreUpdate(ProcedureExecutor<TEnv> procExecutor,
122       boolean value) {
123     if (procExecutor.testing == null) {
124       procExecutor.testing = new ProcedureExecutor.Testing();
125     }
126     procExecutor.testing.toggleKillBeforeStoreUpdate = value;
127   }
128 
129   public static <TEnv> void toggleKillBeforeStoreUpdate(ProcedureExecutor<TEnv> procExecutor) {
130     if (procExecutor.testing == null) {
131       procExecutor.testing = new ProcedureExecutor.Testing();
132     }
133     procExecutor.testing.killBeforeStoreUpdate = !procExecutor.testing.killBeforeStoreUpdate;
134     LOG.warn("Set Kill before store update to: " + procExecutor.testing.killBeforeStoreUpdate);
135   }
136 
137   public static <TEnv> void setKillAndToggleBeforeStoreUpdate(ProcedureExecutor<TEnv> procExecutor,
138       boolean value) {
139     ProcedureTestingUtility.setKillBeforeStoreUpdate(procExecutor, value);
140     ProcedureTestingUtility.setToggleKillBeforeStoreUpdate(procExecutor, value);
141   }
142 
143   public static <TEnv> long submitAndWait(Configuration conf, TEnv env, Procedure<TEnv> proc)
144       throws IOException {
145     NoopProcedureStore procStore = new NoopProcedureStore();
146     ProcedureExecutor<TEnv> procExecutor = new ProcedureExecutor<TEnv>(conf, env, procStore);
147     procStore.start(1);
148     procExecutor.start(1, false);
149     try {
150       return submitAndWait(procExecutor, proc, HConstants.NO_NONCE, HConstants.NO_NONCE);
151     } finally {
152       procStore.stop(false);
153       procExecutor.stop();
154     }
155   }
156 
157   public static <TEnv> long submitAndWait(ProcedureExecutor<TEnv> procExecutor, Procedure proc) {
158     return submitAndWait(procExecutor, proc, HConstants.NO_NONCE, HConstants.NO_NONCE);
159   }
160 
161   public static <TEnv> long submitAndWait(ProcedureExecutor<TEnv> procExecutor, Procedure proc,
162       final long nonceGroup, final long nonce) {
163     long procId = submitProcedure(procExecutor, proc, nonceGroup, nonce);
164     waitProcedure(procExecutor, procId);
165     return procId;
166   }
167 
168   public static <TEnv> long submitProcedure(ProcedureExecutor<TEnv> procExecutor, Procedure proc,
169       final long nonceGroup, final long nonce) {
170     final NonceKey nonceKey = procExecutor.createNonceKey(nonceGroup, nonce);
171     long procId = procExecutor.registerNonce(nonceKey);
172     assertFalse(procId >= 0);
173     return procExecutor.submitProcedure(proc, nonceKey);
174   }
175 
176   public static <TEnv> void waitProcedure(ProcedureExecutor<TEnv> procExecutor, Procedure proc) {
177     while (proc.getState() == ProcedureState.INITIALIZING) {
178       Threads.sleepWithoutInterrupt(250);
179     }
180     waitProcedure(procExecutor, proc.getProcId());
181   }
182 
183   public static <TEnv> void waitProcedure(ProcedureExecutor<TEnv> procExecutor, long procId) {
184     while (!procExecutor.isFinished(procId) && procExecutor.isRunning()) {
185       Threads.sleepWithoutInterrupt(250);
186     }
187   }
188 
189   public static <TEnv> void waitNoProcedureRunning(ProcedureExecutor<TEnv> procExecutor) {
190     int stableRuns = 0;
191     while (stableRuns < 10) {
192       if (procExecutor.getActiveExecutorCount() > 0 || procExecutor.getRunnableSet().size() > 0) {
193         stableRuns = 0;
194         Threads.sleepWithoutInterrupt(100);
195       } else {
196         stableRuns++;
197         Threads.sleepWithoutInterrupt(25);
198       }
199     }
200   }
201 
202   public static <TEnv> void assertProcNotYetCompleted(ProcedureExecutor<TEnv> procExecutor,
203       long procId) {
204     assertFalse("expected a running proc", procExecutor.isFinished(procId));
205     assertEquals(null, procExecutor.getResult(procId));
206   }
207 
208   public static <TEnv> void assertProcNotFailed(ProcedureExecutor<TEnv> procExecutor,
209       long procId) {
210     ProcedureInfo result = procExecutor.getResult(procId);
211     assertTrue("expected procedure result", result != null);
212     assertProcNotFailed(result);
213   }
214 
215   public static void assertProcNotFailed(final ProcedureInfo result) {
216     ForeignExceptionMessage exception = result.getForeignExceptionMessage();
217     String msg = exception != null ? result.getExceptionFullMessage() : "no exception found";
218     assertFalse(msg, result.isFailed());
219   }
220 
221   public static Throwable assertProcFailed(final ProcedureInfo result) {
222     assertEquals(true, result.isFailed());
223     LOG.info("procId=" + result.getProcId() + " exception: " + result.getException().getMessage());
224     return getExceptionCause(result);
225   }
226 
227   public static void assertIsAbortException(final ProcedureInfo result) {
228     assertEquals(true, result.isFailed());
229     LOG.info(result.getExceptionFullMessage());
230     Throwable cause = getExceptionCause(result);
231     assertTrue("expected abort exception, got " + cause,
232       cause instanceof ProcedureAbortedException);
233   }
234 
235   public static void assertIsTimeoutException(final ProcedureInfo result) {
236     assertEquals(true, result.isFailed());
237     LOG.info(result.getExceptionFullMessage());
238     Throwable cause = getExceptionCause(result);
239     assertTrue("expected TimeoutIOException, got " + cause, cause instanceof TimeoutIOException);
240   }
241 
242   public static void assertIsIllegalArgumentException(final ProcedureInfo result) {
243     assertEquals(true, result.isFailed());
244     LOG.info(result.getExceptionFullMessage());
245     Throwable cause = ProcedureTestingUtility.getExceptionCause(result);
246     assertTrue("expected IllegalArgumentIOException, got " + cause,
247       cause instanceof IllegalArgumentIOException);
248   }
249 
250   public static Throwable getExceptionCause(final ProcedureInfo procInfo) {
251     assert procInfo.getForeignExceptionMessage() != null;
252     return RemoteProcedureException.fromProto(procInfo.getForeignExceptionMessage()).getCause();
253   }
254 
255   public static class TestProcedure extends Procedure<Void> {
256     private byte[] data = null;
257 
258     public TestProcedure() {}
259 
260     public TestProcedure(long procId) {
261       this(procId, 0);
262     }
263 
264     public TestProcedure(long procId, long parentId) {
265       this(procId, parentId, null);
266     }
267 
268     public TestProcedure(long procId, long parentId, byte[] data) {
269       setData(data);
270       setProcId(procId);
271       if (parentId > 0) {
272         setParentProcId(parentId);
273       }
274     }
275 
276     public void addStackId(final int index) {
277       addStackIndex(index);
278     }
279 
280     public void setFinishedState() {
281       setState(ProcedureState.FINISHED);
282     }
283 
284     public void setData(final byte[] data) {
285       this.data = data;
286     }
287 
288     @Override
289     protected Procedure[] execute(Void env) { return null; }
290 
291     @Override
292     protected void rollback(Void env) { }
293 
294     @Override
295     protected boolean abort(Void env) { return false; }
296 
297     @Override
298     protected void serializeStateData(final OutputStream stream) throws IOException {
299       StreamUtils.writeRawVInt32(stream, data != null ? data.length : 0);
300       if (data != null) stream.write(data);
301     }
302 
303     @Override
304     protected void deserializeStateData(final InputStream stream) throws IOException {
305       int len = StreamUtils.readRawVarint32(stream);
306       if (len > 0) {
307         data = new byte[len];
308         stream.read(data);
309       } else {
310         data = null;
311       }
312     }
313   }
314 
315   public static class LoadCounter implements ProcedureStore.ProcedureLoader {
316     private final ArrayList<Procedure> corrupted = new ArrayList<Procedure>();
317     private final ArrayList<ProcedureInfo> completed = new ArrayList<ProcedureInfo>();
318     private final ArrayList<Procedure> runnable = new ArrayList<Procedure>();
319 
320     private Set<Long> procIds;
321     private long maxProcId = 0;
322 
323     public LoadCounter() {
324       this(null);
325     }
326 
327     public LoadCounter(final Set<Long> procIds) {
328       this.procIds = procIds;
329     }
330 
331     public void reset() {
332       reset(null);
333     }
334 
335     public void reset(final Set<Long> procIds) {
336       corrupted.clear();
337       completed.clear();
338       runnable.clear();
339       this.procIds = procIds;
340       this.maxProcId = 0;
341     }
342 
343     public long getMaxProcId() {
344       return maxProcId;
345     }
346 
347     public ArrayList<Procedure> getRunnables() {
348       return runnable;
349     }
350 
351     public int getRunnableCount() {
352       return runnable.size();
353     }
354 
355     public ArrayList<ProcedureInfo> getCompleted() {
356       return completed;
357     }
358 
359     public int getCompletedCount() {
360       return completed.size();
361     }
362 
363     public int getLoadedCount() {
364       return runnable.size() + completed.size();
365     }
366 
367     public ArrayList<Procedure> getCorrupted() {
368       return corrupted;
369     }
370 
371     public int getCorruptedCount() {
372       return corrupted.size();
373     }
374 
375     @Override
376     public void setMaxProcId(long maxProcId) {
377       this.maxProcId = maxProcId;
378     }
379 
380     @Override
381     public void load(ProcedureIterator procIter) throws IOException {
382       while (procIter.hasNext()) {
383         long procId;
384         if (procIter.isNextCompleted()) {
385           ProcedureInfo proc = procIter.nextAsProcedureInfo();
386           procId = proc.getProcId();
387           LOG.debug("loading completed procId=" + procId + ": " + proc);
388           completed.add(proc);
389         } else {
390           Procedure proc = procIter.nextAsProcedure();
391           procId = proc.getProcId();
392           LOG.debug("loading runnable procId=" + procId + ": " + proc);
393           runnable.add(proc);
394         }
395         if (procIds != null) {
396           assertTrue("procId=" + procId + " unexpected", procIds.contains(procId));
397         }
398       }
399     }
400 
401     @Override
402     public void handleCorrupted(ProcedureIterator procIter) throws IOException {
403       while (procIter.hasNext()) {
404         Procedure proc = procIter.nextAsProcedure();
405         LOG.debug("corrupted procId=" + proc.getProcId() + ": " + proc);
406         corrupted.add(proc);
407       }
408     }
409   }
410 }