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.util.concurrent.atomic.AtomicReference;
23  import java.util.concurrent.CountDownLatch;
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.HBaseCommonTestingUtility;
30  import org.apache.hadoop.hbase.ProcedureInfo;
31  import org.apache.hadoop.hbase.procedure2.store.ProcedureStore;
32  import org.apache.hadoop.hbase.testclassification.SmallTests;
33  import org.apache.hadoop.hbase.testclassification.MasterTests;
34  import org.apache.hadoop.hbase.security.User;
35  import org.apache.hadoop.hbase.util.Bytes;
36  import org.apache.hadoop.hbase.util.NonceKey;
37  import org.apache.hadoop.hbase.util.Threads;
38  
39  import org.junit.After;
40  import org.junit.Before;
41  import org.junit.Test;
42  import org.junit.experimental.categories.Category;
43  
44  import static org.junit.Assert.assertEquals;
45  import static org.junit.Assert.assertFalse;
46  import static org.junit.Assert.assertTrue;
47  import static org.junit.Assert.fail;
48  
49  @Category({MasterTests.class, SmallTests.class})
50  public class TestProcedureNonce {
51    private static final Log LOG = LogFactory.getLog(TestProcedureNonce.class);
52  
53    private static final int PROCEDURE_EXECUTOR_SLOTS = 2;
54  
55    private static TestProcEnv procEnv;
56    private static ProcedureExecutor<TestProcEnv> procExecutor;
57    private static ProcedureStore procStore;
58  
59    private HBaseCommonTestingUtility htu;
60    private FileSystem fs;
61    private Path logDir;
62  
63    @Before
64    public void setUp() throws IOException {
65      htu = new HBaseCommonTestingUtility();
66      Path testDir = htu.getDataTestDir();
67      fs = testDir.getFileSystem(htu.getConfiguration());
68      assertTrue(testDir.depth() > 1);
69  
70      logDir = new Path(testDir, "proc-logs");
71      procEnv = new TestProcEnv();
72      procStore = ProcedureTestingUtility.createStore(htu.getConfiguration(), logDir);
73      procExecutor = new ProcedureExecutor(htu.getConfiguration(), procEnv, procStore);
74      procExecutor.testing = new ProcedureExecutor.Testing();
75      procStore.start(PROCEDURE_EXECUTOR_SLOTS);
76      procExecutor.start(PROCEDURE_EXECUTOR_SLOTS, true);
77    }
78  
79    @After
80    public void tearDown() throws IOException {
81      procExecutor.stop();
82      procStore.stop(false);
83      fs.delete(logDir, true);
84    }
85  
86    @Test(timeout=30000)
87    public void testCompletedProcWithSameNonce() throws Exception {
88      final long nonceGroup = 123;
89      final long nonce = 2222;
90  
91      // register the nonce
92      final NonceKey nonceKey = procExecutor.createNonceKey(nonceGroup, nonce);
93      assertFalse(procExecutor.registerNonce(nonceKey) >= 0);
94  
95      // Submit a proc and wait for its completion
96      Procedure proc = new TestSingleStepProcedure();
97      long procId = procExecutor.submitProcedure(proc, nonceKey);
98      ProcedureTestingUtility.waitProcedure(procExecutor, procId);
99  
100     // Restart
101     ProcedureTestingUtility.restart(procExecutor);
102     ProcedureTestingUtility.waitProcedure(procExecutor, procId);
103 
104     // try to register a procedure with the same nonce
105     // we should get back the old procId
106     assertEquals(procId, procExecutor.registerNonce(nonceKey));
107 
108     ProcedureInfo result = procExecutor.getResult(procId);
109     ProcedureTestingUtility.assertProcNotFailed(result);
110   }
111 
112   @Test(timeout=30000)
113   public void testRunningProcWithSameNonce() throws Exception {
114     final long nonceGroup = 456;
115     final long nonce = 33333;
116 
117     // register the nonce
118     final NonceKey nonceKey = procExecutor.createNonceKey(nonceGroup, nonce);
119     assertFalse(procExecutor.registerNonce(nonceKey) >= 0);
120 
121     // Submit a proc and use a latch to prevent the step execution until we submitted proc2
122     CountDownLatch latch = new CountDownLatch(1);
123     TestSingleStepProcedure proc = new TestSingleStepProcedure();
124     procEnv.setWaitLatch(latch);
125     long procId = procExecutor.submitProcedure(proc, nonceKey);
126     while (proc.step != 1) Threads.sleep(25);
127 
128     // try to register a procedure with the same nonce
129     // we should get back the old procId
130     assertEquals(procId, procExecutor.registerNonce(nonceKey));
131 
132     // complete the procedure
133     latch.countDown();
134 
135     // Restart, the procedure is not completed yet
136     ProcedureTestingUtility.restart(procExecutor);
137     ProcedureTestingUtility.waitProcedure(procExecutor, procId);
138 
139     // try to register a procedure with the same nonce
140     // we should get back the old procId
141     assertEquals(procId, procExecutor.registerNonce(nonceKey));
142 
143     ProcedureInfo result = procExecutor.getResult(procId);
144     ProcedureTestingUtility.assertProcNotFailed(result);
145   }
146 
147   @Test
148   public void testSetFailureResultForNonce() throws IOException {
149     final long nonceGroup = 234;
150     final long nonce = 55555;
151 
152     // check and register the request nonce
153     final NonceKey nonceKey = procExecutor.createNonceKey(nonceGroup, nonce);
154     assertFalse(procExecutor.registerNonce(nonceKey) >= 0);
155 
156     procExecutor.setFailureResultForNonce(nonceKey, "testProc", User.getCurrent(),
157       new IOException("test failure"));
158 
159     final long procId = procExecutor.registerNonce(nonceKey);
160     ProcedureInfo result = procExecutor.getResult(procId);
161     ProcedureTestingUtility.assertProcFailed(result);
162   }
163 
164   @Test(timeout=30000)
165   public void testConcurrentNonceRegistration() throws IOException {
166     testConcurrentNonceRegistration(true, 567, 44444);
167   }
168 
169   @Test(timeout=30000)
170   public void testConcurrentNonceRegistrationWithRollback() throws IOException {
171     testConcurrentNonceRegistration(false, 890, 55555);
172   }
173 
174   private void testConcurrentNonceRegistration(final boolean submitProcedure,
175       final long nonceGroup, final long nonce) throws IOException {
176     // register the nonce
177     final NonceKey nonceKey = procExecutor.createNonceKey(nonceGroup, nonce);
178 
179     final AtomicReference<Throwable> t1Exception = new AtomicReference();
180     final AtomicReference<Throwable> t2Exception = new AtomicReference();
181 
182     final CountDownLatch t1NonceRegisteredLatch = new CountDownLatch(1);
183     final CountDownLatch t2BeforeNonceRegisteredLatch = new CountDownLatch(1);
184     final Thread[] threads = new Thread[2];
185     threads[0] = new Thread() {
186       @Override
187       public void run() {
188         try {
189           // release the nonce and wake t2
190           assertFalse("unexpected already registered nonce",
191             procExecutor.registerNonce(nonceKey) >= 0);
192           t1NonceRegisteredLatch.countDown();
193 
194           // hold the submission until t2 is registering the nonce
195           t2BeforeNonceRegisteredLatch.await();
196           Threads.sleep(1000);
197 
198           if (submitProcedure) {
199             CountDownLatch latch = new CountDownLatch(1);
200             TestSingleStepProcedure proc = new TestSingleStepProcedure();
201             procEnv.setWaitLatch(latch);
202 
203             procExecutor.submitProcedure(proc, nonceKey);
204             Threads.sleep(100);
205 
206             // complete the procedure
207             latch.countDown();
208           } else {
209             procExecutor.unregisterNonceIfProcedureWasNotSubmitted(nonceKey);
210           }
211         } catch (Throwable e) {
212           t1Exception.set(e);
213         } finally {
214           t1NonceRegisteredLatch.countDown();
215           t2BeforeNonceRegisteredLatch.countDown();
216         }
217       }
218     };
219 
220     threads[1] = new Thread() {
221       @Override
222       public void run() {
223         try {
224           // wait until t1 has registered the nonce
225           t1NonceRegisteredLatch.await();
226 
227           // register the nonce
228           t2BeforeNonceRegisteredLatch.countDown();
229           assertFalse("unexpected non registered nonce",
230             procExecutor.registerNonce(nonceKey) < 0);
231         } catch (Throwable e) {
232           t2Exception.set(e);
233         } finally {
234           t1NonceRegisteredLatch.countDown();
235           t2BeforeNonceRegisteredLatch.countDown();
236         }
237       }
238     };
239 
240     for (int i = 0; i < threads.length; ++i) threads[i].start();
241     for (int i = 0; i < threads.length; ++i) Threads.shutdown(threads[i]);
242     ProcedureTestingUtility.waitNoProcedureRunning(procExecutor);
243     assertEquals(null, t1Exception.get());
244     assertEquals(null, t2Exception.get());
245   }
246 
247   public static class TestSingleStepProcedure extends SequentialProcedure<TestProcEnv> {
248     private int step = 0;
249 
250     public TestSingleStepProcedure() { }
251 
252     @Override
253     protected Procedure[] execute(TestProcEnv env) throws InterruptedException {
254       step++;
255       env.waitOnLatch();
256       LOG.debug("execute procedure " + this + " step=" + step);
257       step++;
258       setResult(Bytes.toBytes(step));
259       return null;
260     }
261 
262     @Override
263     protected void rollback(TestProcEnv env) { }
264 
265     @Override
266     protected boolean abort(TestProcEnv env) { return true; }
267   }
268 
269   private static class TestProcEnv {
270     private CountDownLatch latch = null;
271 
272     /**
273      * set/unset a latch. every procedure execute() step will wait on the latch if any.
274      */
275     public void setWaitLatch(CountDownLatch latch) {
276       this.latch = latch;
277     }
278 
279     public void waitOnLatch() throws InterruptedException {
280       if (latch != null) {
281         latch.await();
282       }
283     }
284   }
285 }