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.master.procedure;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertNull;
022import static org.junit.Assert.assertTrue;
023
024import org.apache.hadoop.conf.Configuration;
025import org.apache.hadoop.hbase.HBaseClassTestRule;
026import org.apache.hadoop.hbase.HBaseTestingUtil;
027import org.apache.hadoop.hbase.NamespaceDescriptor;
028import org.apache.hadoop.hbase.NamespaceNotFoundException;
029import org.apache.hadoop.hbase.client.TableDescriptor;
030import org.apache.hadoop.hbase.constraint.ConstraintException;
031import org.apache.hadoop.hbase.procedure2.Procedure;
032import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
033import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
034import org.apache.hadoop.hbase.testclassification.MasterTests;
035import org.apache.hadoop.hbase.testclassification.MediumTests;
036import org.junit.After;
037import org.junit.AfterClass;
038import org.junit.Before;
039import org.junit.BeforeClass;
040import org.junit.ClassRule;
041import org.junit.Test;
042import org.junit.experimental.categories.Category;
043import org.slf4j.Logger;
044import org.slf4j.LoggerFactory;
045
046@Category({ MasterTests.class, MediumTests.class })
047public class TestModifyNamespaceProcedure {
048
049  @ClassRule
050  public static final HBaseClassTestRule CLASS_RULE =
051    HBaseClassTestRule.forClass(TestModifyNamespaceProcedure.class);
052
053  private static final Logger LOG = LoggerFactory.getLogger(TestModifyNamespaceProcedure.class);
054
055  protected static final HBaseTestingUtil UTIL = new HBaseTestingUtil();
056
057  private static void setupConf(Configuration conf) {
058    conf.setInt(MasterProcedureConstants.MASTER_PROCEDURE_THREADS, 1);
059  }
060
061  @BeforeClass
062  public static void setupCluster() throws Exception {
063    setupConf(UTIL.getConfiguration());
064    UTIL.startMiniCluster(1);
065  }
066
067  @AfterClass
068  public static void cleanupTest() throws Exception {
069    try {
070      UTIL.shutdownMiniCluster();
071    } catch (Exception e) {
072      LOG.warn("failure shutting down cluster", e);
073    }
074  }
075
076  @Before
077  public void setup() throws Exception {
078    ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(getMasterProcedureExecutor(), false);
079  }
080
081  @After
082  public void tearDown() throws Exception {
083    ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(getMasterProcedureExecutor(), false);
084    for (TableDescriptor htd : UTIL.getAdmin().listTableDescriptors()) {
085      LOG.info("Tear down, remove table=" + htd.getTableName());
086      UTIL.deleteTable(htd.getTableName());
087    }
088  }
089
090  @Test
091  public void testModifyNamespace() throws Exception {
092    final NamespaceDescriptor nsd = NamespaceDescriptor.create("testModifyNamespace").build();
093    final String nsKey1 = "hbase.namespace.quota.maxregions";
094    final String nsValue1before = "1111";
095    final String nsValue1after = "9999";
096    final String nsKey2 = "hbase.namespace.quota.maxtables";
097    final String nsValue2 = "10";
098    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
099
100    nsd.setConfiguration(nsKey1, nsValue1before);
101    createNamespaceForTesting(nsd);
102
103    // Before modify
104    NamespaceDescriptor currentNsDescriptor = UTIL.getAdmin().getNamespaceDescriptor(nsd.getName());
105    assertEquals(nsValue1before, currentNsDescriptor.getConfigurationValue(nsKey1));
106    assertNull(currentNsDescriptor.getConfigurationValue(nsKey2));
107
108    // Update
109    nsd.setConfiguration(nsKey1, nsValue1after);
110    nsd.setConfiguration(nsKey2, nsValue2);
111
112    long procId1 =
113      procExec.submitProcedure(new ModifyNamespaceProcedure(procExec.getEnvironment(), nsd));
114    // Wait the completion
115    ProcedureTestingUtility.waitProcedure(procExec, procId1);
116    ProcedureTestingUtility.assertProcNotFailed(procExec, procId1);
117
118    // Verify the namespace is updated.
119    currentNsDescriptor = UTIL.getAdmin().getNamespaceDescriptor(nsd.getName());
120    assertEquals(nsValue1after, nsd.getConfigurationValue(nsKey1));
121    assertEquals(nsValue2, currentNsDescriptor.getConfigurationValue(nsKey2));
122  }
123
124  @Test
125  public void testModifyNonExistNamespace() throws Exception {
126    final String namespaceName = "testModifyNonExistNamespace";
127    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
128
129    try {
130      NamespaceDescriptor nsDescriptor = UTIL.getAdmin().getNamespaceDescriptor(namespaceName);
131      assertNull(nsDescriptor);
132    } catch (NamespaceNotFoundException nsnfe) {
133      // Expected
134      LOG.debug("The namespace " + namespaceName + " does not exist.  This is expected.");
135    }
136
137    final NamespaceDescriptor nsd = NamespaceDescriptor.create(namespaceName).build();
138
139    long procId =
140      procExec.submitProcedure(new ModifyNamespaceProcedure(procExec.getEnvironment(), nsd));
141    // Wait the completion
142    ProcedureTestingUtility.waitProcedure(procExec, procId);
143
144    // Expect fail with NamespaceNotFoundException
145    Procedure<?> result = procExec.getResult(procId);
146    assertTrue(result.isFailed());
147    LOG.debug("modify namespace failed with exception: " + result.getException());
148    assertTrue(
149      ProcedureTestingUtility.getExceptionCause(result) instanceof NamespaceNotFoundException);
150  }
151
152  @Test
153  public void testModifyNamespaceWithInvalidRegionCount() throws Exception {
154    final NamespaceDescriptor nsd =
155      NamespaceDescriptor.create("testModifyNamespaceWithInvalidRegionCount").build();
156    final String nsKey = "hbase.namespace.quota.maxregions";
157    final String nsValue = "-1";
158    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
159
160    createNamespaceForTesting(nsd);
161
162    // Modify
163    nsd.setConfiguration(nsKey, nsValue);
164
165    long procId =
166      procExec.submitProcedure(new ModifyNamespaceProcedure(procExec.getEnvironment(), nsd));
167    // Wait the completion
168    ProcedureTestingUtility.waitProcedure(procExec, procId);
169    Procedure<?> result = procExec.getResult(procId);
170    assertTrue(result.isFailed());
171    LOG.debug("Modify namespace failed with exception: " + result.getException());
172    assertTrue(ProcedureTestingUtility.getExceptionCause(result) instanceof ConstraintException);
173  }
174
175  @Test
176  public void testModifyNamespaceWithInvalidTableCount() throws Exception {
177    final NamespaceDescriptor nsd =
178      NamespaceDescriptor.create("testModifyNamespaceWithInvalidTableCount").build();
179    final String nsKey = "hbase.namespace.quota.maxtables";
180    final String nsValue = "-1";
181    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
182
183    createNamespaceForTesting(nsd);
184
185    // Modify
186    nsd.setConfiguration(nsKey, nsValue);
187
188    long procId =
189      procExec.submitProcedure(new ModifyNamespaceProcedure(procExec.getEnvironment(), nsd));
190    // Wait the completion
191    ProcedureTestingUtility.waitProcedure(procExec, procId);
192    Procedure<?> result = procExec.getResult(procId);
193    assertTrue(result.isFailed());
194    LOG.debug("Modify namespace failed with exception: " + result.getException());
195    assertTrue(ProcedureTestingUtility.getExceptionCause(result) instanceof ConstraintException);
196  }
197
198  @Test
199  public void testRecoveryAndDoubleExecution() throws Exception {
200    final NamespaceDescriptor nsd =
201      NamespaceDescriptor.create("testRecoveryAndDoubleExecution").build();
202    final String nsKey = "foo";
203    final String nsValue = "bar";
204    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
205
206    createNamespaceForTesting(nsd);
207    ProcedureTestingUtility.waitNoProcedureRunning(procExec);
208    ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
209
210    // Modify
211    nsd.setConfiguration(nsKey, nsValue);
212
213    // Start the Modify procedure && kill the executor
214    long procId =
215      procExec.submitProcedure(new ModifyNamespaceProcedure(procExec.getEnvironment(), nsd));
216
217    // Restart the executor and execute the step twice
218    MasterProcedureTestingUtility.testRecoveryAndDoubleExecution(procExec, procId);
219
220    ProcedureTestingUtility.assertProcNotFailed(procExec, procId);
221    // Validate
222    NamespaceDescriptor currentNsDescriptor = UTIL.getAdmin().getNamespaceDescriptor(nsd.getName());
223    assertEquals(nsValue, currentNsDescriptor.getConfigurationValue(nsKey));
224  }
225
226  @Test
227  public void testRollbackAndDoubleExecution() throws Exception {
228    final NamespaceDescriptor nsd =
229      NamespaceDescriptor.create("testRollbackAndDoubleExecution").build();
230    final String nsKey = "foo";
231    final String nsValue = "bar";
232    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
233
234    createNamespaceForTesting(nsd);
235    ProcedureTestingUtility.waitNoProcedureRunning(procExec);
236    ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
237
238    // Modify
239    // Start the Modify procedure && kill the executor
240    long procId = procExec.submitProcedure(new ModifyNamespaceProcedure(procExec.getEnvironment(),
241      NamespaceDescriptor.create(nsd).addConfiguration(nsKey, nsValue).build()));
242
243    int lastStep = 2; // failing before MODIFY_NAMESPACE_UPDATE_NS_TABLE
244    MasterProcedureTestingUtility.testRollbackAndDoubleExecution(procExec, procId, lastStep);
245
246    // Validate
247    NamespaceDescriptor currentNsDescriptor = UTIL.getAdmin().getNamespaceDescriptor(nsd.getName());
248    assertNull(currentNsDescriptor.getConfigurationValue(nsKey));
249  }
250
251  private ProcedureExecutor<MasterProcedureEnv> getMasterProcedureExecutor() {
252    return UTIL.getHBaseCluster().getMaster().getMasterProcedureExecutor();
253  }
254
255  private void createNamespaceForTesting(NamespaceDescriptor nsDescriptor) throws Exception {
256    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
257
258    long procId = procExec
259      .submitProcedure(new CreateNamespaceProcedure(procExec.getEnvironment(), nsDescriptor));
260    // Wait the completion
261    ProcedureTestingUtility.waitProcedure(procExec, procId);
262    ProcedureTestingUtility.assertProcNotFailed(procExec, procId);
263  }
264}