/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hbase.procedure2;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HBaseCommonTestingUtility;
import org.apache.hadoop.hbase.io.util.StreamUtils;
import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility.NoopProcedure;
import org.apache.hadoop.hbase.procedure2.store.ProcedureStore;
import org.apache.hadoop.hbase.shaded.protobuf.generated.ProcedureProtos.ProcedureState;
import org.apache.hadoop.hbase.testclassification.SmallTests;
import org.apache.hadoop.hbase.testclassification.MasterTests;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@Category({MasterTests.class, SmallTests.class})
public class TestProcedureEvents {
private static final Log LOG = LogFactory.getLog(TestProcedureEvents.class);
private TestProcEnv procEnv;
private ProcedureStore procStore;
private ProcedureExecutor<TestProcEnv> procExecutor;
private HBaseCommonTestingUtility htu;
private FileSystem fs;
private Path logDir;
@Before
public void setUp() throws IOException {
htu = new HBaseCommonTestingUtility();
Path testDir = htu.getDataTestDir();
fs = testDir.getFileSystem(htu.getConfiguration());
logDir = new Path(testDir, "proc-logs");
procEnv = new TestProcEnv();
procStore = ProcedureTestingUtility.createWalStore(htu.getConfiguration(), fs, logDir);
procExecutor = new ProcedureExecutor(htu.getConfiguration(), procEnv, procStore);
procStore.start(1);
procExecutor.start(1, true);
}
@After
public void tearDown() throws IOException {
procExecutor.stop();
procStore.stop(false);
procExecutor.join();
fs.delete(logDir, true);
}
@Test(timeout=30000)
public void testTimeoutEventProcedure() throws Exception {
final int NTIMEOUTS = 5;
TestTimeoutEventProcedure proc = new TestTimeoutEventProcedure(500, NTIMEOUTS);
procExecutor.submitProcedure(proc);
ProcedureTestingUtility.waitProcedure(procExecutor, proc.getProcId());
ProcedureTestingUtility.assertIsAbortException(procExecutor.getResult(proc.getProcId()));
assertEquals(NTIMEOUTS + 1, proc.getTimeoutsCount());
}
@Test(timeout=30000)
public void testTimeoutEventProcedureDoubleExecution() throws Exception {
testTimeoutEventProcedureDoubleExecution(false);
}
@Test(timeout=30000)
public void testTimeoutEventProcedureDoubleExecutionKillIfSuspended() throws Exception {
testTimeoutEventProcedureDoubleExecution(true);
}
private void testTimeoutEventProcedureDoubleExecution(final boolean killIfSuspended)
throws Exception {
TestTimeoutEventProcedure proc = new TestTimeoutEventProcedure(1000, 3);
ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExecutor, true);
ProcedureTestingUtility.setKillIfSuspended(procExecutor, killIfSuspended);
long procId = procExecutor.submitProcedure(proc);
ProcedureTestingUtility.testRecoveryAndDoubleExecution(procExecutor, procId, true);
ProcedureTestingUtility.assertIsAbortException(procExecutor.getResult(proc.getProcId()));
}
public static class TestTimeoutEventProcedure extends NoopProcedure<TestProcEnv> {
private final ProcedureEvent event = new ProcedureEvent("timeout-event");
private final AtomicInteger ntimeouts = new AtomicInteger(0);
private int maxTimeouts = 1;
public TestTimeoutEventProcedure() {}
public TestTimeoutEventProcedure(final int timeoutMsec, final int maxTimeouts) {
this.maxTimeouts = maxTimeouts;
setTimeout(timeoutMsec);
}
public int getTimeoutsCount() {
return ntimeouts.get();
}
@Override
protected Procedure[] execute(final TestProcEnv env) throws ProcedureSuspendedException {
LOG.info("EXECUTE " + this + " ntimeouts=" + ntimeouts);
if (ntimeouts.get() > maxTimeouts) {
setAbortFailure("test", "give up after " + ntimeouts.get());
return null;
}
env.getProcedureScheduler().suspendEvent(event);
if (env.getProcedureScheduler().waitEvent(event, this)) {
setState(ProcedureState.WAITING_TIMEOUT);
throw new ProcedureSuspendedException();
}
return null;
}
@Override
protected boolean setTimeoutFailure(final TestProcEnv env) {
int n = ntimeouts.incrementAndGet();
LOG.info("HANDLE TIMEOUT " + this + " ntimeouts=" + n);
setState(ProcedureState.RUNNABLE);
env.getProcedureScheduler().wakeEvent(event);
return false;
}
@Override
protected void afterReplay(final TestProcEnv env) {
if (getState() == ProcedureState.WAITING_TIMEOUT) {
env.getProcedureScheduler().suspendEvent(event);
env.getProcedureScheduler().waitEvent(event, this);
}
}
@Override
protected void serializeStateData(final OutputStream stream) throws IOException {
StreamUtils.writeRawVInt32(stream, ntimeouts.get());
StreamUtils.writeRawVInt32(stream, maxTimeouts);
}
@Override
protected void deserializeStateData(final InputStream stream) throws IOException {
ntimeouts.set(StreamUtils.readRawVarint32(stream));
maxTimeouts = StreamUtils.readRawVarint32(stream);
}
}
private class TestProcEnv {
public ProcedureScheduler getProcedureScheduler() {
return procExecutor.getScheduler();
}
}
}