001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.procedure;
019
020import static org.apache.hadoop.hbase.coprocessor.CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY;
021import static org.junit.Assert.fail;
022
023import java.io.IOException;
024import java.util.List;
025import java.util.Optional;
026import org.apache.hadoop.conf.Configuration;
027import org.apache.hadoop.hbase.HBaseClassTestRule;
028import org.apache.hadoop.hbase.HBaseTestingUtility;
029import org.apache.hadoop.hbase.TableName;
030import org.apache.hadoop.hbase.client.RegionInfo;
031import org.apache.hadoop.hbase.client.TableDescriptor;
032import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor;
033import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
034import org.apache.hadoop.hbase.coprocessor.MasterObserver;
035import org.apache.hadoop.hbase.coprocessor.ObserverContext;
036import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv;
037import org.apache.hadoop.hbase.procedure2.Procedure;
038import org.apache.hadoop.hbase.security.AccessDeniedException;
039import org.apache.hadoop.hbase.testclassification.LargeTests;
040import org.apache.hadoop.hbase.util.Bytes;
041import org.junit.After;
042import org.junit.BeforeClass;
043import org.junit.ClassRule;
044import org.junit.Test;
045import org.junit.experimental.categories.Category;
046import org.slf4j.Logger;
047import org.slf4j.LoggerFactory;
048
049import org.apache.hadoop.hbase.shaded.protobuf.generated.ProcedureProtos;
050
051/**
052 * Check if CompletedProcedureCleaner cleans up failed nonce procedures.
053 */
054@Category(LargeTests.class)
055public class TestFailedProcCleanup {
056
057  @ClassRule
058  public static final HBaseClassTestRule CLASS_RULE =
059    HBaseClassTestRule.forClass(TestFailedProcCleanup.class);
060
061  private static final Logger LOG = LoggerFactory.getLogger(TestFailedProcCleanup.class);
062
063  protected static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
064  private static Configuration conf;
065  private static final TableName TABLE = TableName.valueOf("test");
066  private static final byte[] FAMILY = Bytes.toBytesBinary("f");
067  private static final int evictionDelay = 10 * 1000;
068
069  @BeforeClass
070  public static void setUpBeforeClass() {
071    conf = TEST_UTIL.getConfiguration();
072    conf.setInt("hbase.procedure.cleaner.evict.ttl", evictionDelay);
073    conf.setInt("hbase.procedure.cleaner.evict.batch.size", 1);
074  }
075
076  @After
077  public void tearDown() throws Exception {
078    TEST_UTIL.shutdownMiniCluster();
079  }
080
081  @Test
082  public void testFailCreateTable() throws Exception {
083    conf.set(MASTER_COPROCESSOR_CONF_KEY, CreateFailObserver.class.getName());
084    TEST_UTIL.startMiniCluster(3);
085    try {
086      TEST_UTIL.createTable(TABLE, FAMILY);
087    } catch (AccessDeniedException e) {
088      LOG.debug("Ignoring exception: ", e);
089      Thread.sleep(evictionDelay * 3);
090    }
091    List<Procedure<MasterProcedureEnv>> procedureInfos =
092      TEST_UTIL.getMiniHBaseCluster().getMaster().getMasterProcedureExecutor().getProcedures();
093    for (Procedure procedureInfo : procedureInfos) {
094      if (
095        procedureInfo.getProcName().equals("CreateTableProcedure")
096          && procedureInfo.getState() == ProcedureProtos.ProcedureState.ROLLEDBACK
097      ) {
098        fail("Found procedure " + procedureInfo + " that hasn't been cleaned up");
099      }
100    }
101  }
102
103  @Test
104  public void testFailCreateTableAction() throws Exception {
105    conf.set(MASTER_COPROCESSOR_CONF_KEY, CreateFailObserverHandler.class.getName());
106    TEST_UTIL.startMiniCluster(3);
107    try {
108      TEST_UTIL.createTable(TABLE, FAMILY);
109      fail("Table shouldn't be created");
110    } catch (AccessDeniedException e) {
111      LOG.debug("Ignoring exception: ", e);
112      Thread.sleep(evictionDelay * 3);
113    }
114    List<Procedure<MasterProcedureEnv>> procedureInfos =
115      TEST_UTIL.getMiniHBaseCluster().getMaster().getMasterProcedureExecutor().getProcedures();
116    for (Procedure procedureInfo : procedureInfos) {
117      if (
118        procedureInfo.getProcName().equals("CreateTableProcedure")
119          && procedureInfo.getState() == ProcedureProtos.ProcedureState.ROLLEDBACK
120      ) {
121        fail("Found procedure " + procedureInfo + " that hasn't been cleaned up");
122      }
123    }
124  }
125
126  public static class CreateFailObserver implements MasterCoprocessor, MasterObserver {
127
128    @Override
129    public void preCreateTable(ObserverContext<MasterCoprocessorEnvironment> env,
130      TableDescriptor desc, RegionInfo[] regions) throws IOException {
131
132      if (desc.getTableName().equals(TABLE)) {
133        throw new AccessDeniedException("Don't allow creation of table");
134      }
135    }
136
137    @Override
138    public Optional<MasterObserver> getMasterObserver() {
139      return Optional.of(this);
140    }
141  }
142
143  public static class CreateFailObserverHandler implements MasterCoprocessor, MasterObserver {
144
145    @Override
146    public void preCreateTableAction(final ObserverContext<MasterCoprocessorEnvironment> ctx,
147      final TableDescriptor desc, final RegionInfo[] regions) throws IOException {
148
149      if (desc.getTableName().equals(TABLE)) {
150        throw new AccessDeniedException("Don't allow creation of table");
151      }
152    }
153
154    @Override
155    public Optional<MasterObserver> getMasterObserver() {
156      return Optional.of(this);
157    }
158  }
159}