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.fail;
022
023import java.io.IOException;
024import java.util.Optional;
025import java.util.concurrent.atomic.AtomicInteger;
026import org.apache.hadoop.conf.Configuration;
027import org.apache.hadoop.hbase.CoprocessorEnvironment;
028import org.apache.hadoop.hbase.HBaseClassTestRule;
029import org.apache.hadoop.hbase.HBaseTestingUtility;
030import org.apache.hadoop.hbase.NamespaceDescriptor;
031import org.apache.hadoop.hbase.TableName;
032import org.apache.hadoop.hbase.client.Admin;
033import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
034import org.apache.hadoop.hbase.client.RegionInfo;
035import org.apache.hadoop.hbase.client.TableDescriptor;
036import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
037import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor;
038import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
039import org.apache.hadoop.hbase.coprocessor.MasterObserver;
040import org.apache.hadoop.hbase.coprocessor.ObserverContext;
041import org.apache.hadoop.hbase.master.HMaster;
042import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
043import org.apache.hadoop.hbase.testclassification.MasterTests;
044import org.apache.hadoop.hbase.testclassification.MediumTests;
045import org.apache.hadoop.hbase.util.Bytes;
046import org.junit.AfterClass;
047import org.junit.BeforeClass;
048import org.junit.ClassRule;
049import org.junit.Test;
050import org.junit.experimental.categories.Category;
051import org.slf4j.Logger;
052import org.slf4j.LoggerFactory;
053
054/**
055 * Tests class that validates that "post" observer hook methods are only invoked when the operation was successful.
056 */
057@Category({MasterTests.class, MediumTests.class})
058public class TestMasterObserverPostCalls {
059
060  @ClassRule
061  public static final HBaseClassTestRule CLASS_RULE =
062      HBaseClassTestRule.forClass(TestMasterObserverPostCalls.class);
063
064  private static final Logger LOG = LoggerFactory.getLogger(TestMasterObserverPostCalls.class);
065  protected static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
066
067  @BeforeClass
068  public static void setupCluster() throws Exception {
069    setupConf(UTIL.getConfiguration());
070    UTIL.startMiniCluster(1);
071  }
072
073  private static void setupConf(Configuration conf) {
074    conf.setInt(MasterProcedureConstants.MASTER_PROCEDURE_THREADS, 1);
075    conf.setInt(MasterProcedureConstants.MASTER_URGENT_PROCEDURE_THREADS, 0);
076    conf.set(MasterCoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
077        MasterObserverForTest.class.getName());
078  }
079
080  @AfterClass
081  public static void cleanupTest() throws Exception {
082    try {
083      UTIL.shutdownMiniCluster();
084    } catch (Exception e) {
085      LOG.warn("failure shutting down cluster", e);
086    }
087  }
088
089  public static class MasterObserverForTest implements MasterCoprocessor, MasterObserver {
090    private AtomicInteger postHookCalls = null;
091
092    @Override
093    public Optional<MasterObserver> getMasterObserver() {
094      return Optional.of(this);
095    }
096
097    @Override
098    public void start(@SuppressWarnings("rawtypes") CoprocessorEnvironment ctx) throws IOException {
099      this.postHookCalls = new AtomicInteger(0);
100    }
101
102    @Override
103    public void postDeleteNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
104        String namespace) {
105      postHookCalls.incrementAndGet();
106    }
107
108    @Override
109    public void postModifyNamespace(
110        ObserverContext<MasterCoprocessorEnvironment> ctx, NamespaceDescriptor desc) {
111      postHookCalls.incrementAndGet();
112    }
113
114    @Override
115    public void postCreateNamespace(
116        ObserverContext<MasterCoprocessorEnvironment> ctx, NamespaceDescriptor desc) {
117      postHookCalls.incrementAndGet();
118    }
119
120    @Override
121    public void postCreateTable(
122        ObserverContext<MasterCoprocessorEnvironment> ctx, TableDescriptor td,
123        RegionInfo[] regions) {
124      postHookCalls.incrementAndGet();
125    }
126
127    @Override
128    public void postModifyTable(
129        ObserverContext<MasterCoprocessorEnvironment> ctx, TableName tn, TableDescriptor td) {
130      postHookCalls.incrementAndGet();
131    }
132
133    @Override
134    public void postDisableTable(ObserverContext<MasterCoprocessorEnvironment> ctx, TableName tn) {
135      postHookCalls.incrementAndGet();
136    }
137
138    @Override
139    public void postDeleteTable(ObserverContext<MasterCoprocessorEnvironment> ctx, TableName tn) {
140      postHookCalls.incrementAndGet();
141    }
142  }
143
144  @Test
145  public void testPostDeleteNamespace() throws IOException {
146    final Admin admin = UTIL.getAdmin();
147    final String ns = "postdeletens";
148    final TableName tn1 = TableName.valueOf(ns, "table1");
149
150    admin.createNamespace(NamespaceDescriptor.create(ns).build());
151    admin.createTable(TableDescriptorBuilder.newBuilder(tn1)
152        .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("f1")).build())
153        .build());
154
155    HMaster master = UTIL.getMiniHBaseCluster().getMaster();
156    MasterObserverForTest observer = master.getMasterCoprocessorHost().findCoprocessor(
157        MasterObserverForTest.class);
158    int preCount = observer.postHookCalls.get();
159    try {
160      admin.deleteNamespace(ns);
161      fail("Deleting a non-empty namespace should be disallowed");
162    } catch (IOException e) {
163      // Pass
164    }
165    int postCount = observer.postHookCalls.get();
166    assertEquals("Expected no invocations of postDeleteNamespace when the operation fails",
167        preCount, postCount);
168
169    // Disable and delete the table so that we can delete the NS.
170    admin.disableTable(tn1);
171    admin.deleteTable(tn1);
172
173    // Validate that the postDeletNS hook is invoked
174    preCount = observer.postHookCalls.get();
175    admin.deleteNamespace(ns);
176    postCount = observer.postHookCalls.get();
177    assertEquals("Expected 1 invocation of postDeleteNamespace", preCount + 1, postCount);
178  }
179
180  @Test
181  public void testPostModifyNamespace() throws IOException {
182    final Admin admin = UTIL.getAdmin();
183    final String ns = "postmodifyns";
184
185    NamespaceDescriptor nsDesc = NamespaceDescriptor.create(ns).build();
186    admin.createNamespace(nsDesc);
187
188    HMaster master = UTIL.getMiniHBaseCluster().getMaster();
189    MasterObserverForTest observer = master.getMasterCoprocessorHost().findCoprocessor(
190        MasterObserverForTest.class);
191    int preCount = observer.postHookCalls.get();
192    try {
193      admin.modifyNamespace(NamespaceDescriptor.create("nonexistent").build());
194      fail("Modifying a missing namespace should fail");
195    } catch (IOException e) {
196      // Pass
197    }
198    int postCount = observer.postHookCalls.get();
199    assertEquals("Expected no invocations of postModifyNamespace when the operation fails",
200        preCount, postCount);
201
202    // Validate that the postDeletNS hook is invoked
203    preCount = observer.postHookCalls.get();
204    admin.modifyNamespace(
205        NamespaceDescriptor.create(nsDesc).addConfiguration("foo", "bar").build());
206    postCount = observer.postHookCalls.get();
207    assertEquals("Expected 1 invocation of postModifyNamespace", preCount + 1, postCount);
208  }
209
210  @Test
211  public void testPostCreateNamespace() throws IOException {
212    final Admin admin = UTIL.getAdmin();
213    final String ns = "postcreatens";
214
215    HMaster master = UTIL.getMiniHBaseCluster().getMaster();
216    MasterObserverForTest observer = master.getMasterCoprocessorHost().findCoprocessor(
217        MasterObserverForTest.class);
218
219    // Validate that the post hook is called
220    int preCount = observer.postHookCalls.get();
221    NamespaceDescriptor nsDesc = NamespaceDescriptor.create(ns).build();
222    admin.createNamespace(nsDesc);
223    int postCount = observer.postHookCalls.get();
224    assertEquals("Expected 1 invocation of postModifyNamespace", preCount + 1, postCount);
225
226    // Then, validate that it's not called when the call fails
227    preCount = observer.postHookCalls.get();
228    try {
229      admin.createNamespace(nsDesc);
230      fail("Creating an already present namespace should fail");
231    } catch (IOException e) {
232      // Pass
233    }
234    postCount = observer.postHookCalls.get();
235    assertEquals("Expected no invocations of postModifyNamespace when the operation fails",
236        preCount, postCount);
237  }
238
239  @Test
240  public void testPostCreateTable() throws IOException {
241    final Admin admin = UTIL.getAdmin();
242    final TableName tn = TableName.valueOf("postcreatetable");
243    final TableDescriptor td = TableDescriptorBuilder.newBuilder(tn).setColumnFamily(
244        ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("f1")).build()).build();
245
246    HMaster master = UTIL.getMiniHBaseCluster().getMaster();
247    MasterObserverForTest observer = master.getMasterCoprocessorHost().findCoprocessor(
248        MasterObserverForTest.class);
249
250    // Validate that the post hook is called
251    int preCount = observer.postHookCalls.get();
252    admin.createTable(td);
253    int postCount = observer.postHookCalls.get();
254    assertEquals("Expected 1 invocation of postCreateTable", preCount + 1, postCount);
255
256    // Then, validate that it's not called when the call fails
257    preCount = observer.postHookCalls.get();
258    try {
259      admin.createTable(td);
260      fail("Creating an already present table should fail");
261    } catch (IOException e) {
262      // Pass
263    }
264    postCount = observer.postHookCalls.get();
265    assertEquals("Expected no invocations of postCreateTable when the operation fails",
266        preCount, postCount);
267  }
268
269  @Test
270  public void testPostModifyTable() throws IOException {
271    final Admin admin = UTIL.getAdmin();
272    final TableName tn = TableName.valueOf("postmodifytable");
273    final TableDescriptor td = TableDescriptorBuilder.newBuilder(tn).setColumnFamily(
274        ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("f1")).build()).build();
275
276    HMaster master = UTIL.getMiniHBaseCluster().getMaster();
277    MasterObserverForTest observer = master.getMasterCoprocessorHost().findCoprocessor(
278        MasterObserverForTest.class);
279
280    // Create the table
281    admin.createTable(td);
282
283    // Validate that the post hook is called
284    int preCount = observer.postHookCalls.get();
285    admin.modifyTable(td);
286    int postCount = observer.postHookCalls.get();
287    assertEquals("Expected 1 invocation of postModifyTable", preCount + 1, postCount);
288
289    // Then, validate that it's not called when the call fails
290    preCount = observer.postHookCalls.get();
291    try {
292      admin.modifyTable(TableDescriptorBuilder.newBuilder(TableName.valueOf("missing"))
293          .setColumnFamily(td.getColumnFamily(Bytes.toBytes("f1"))).build());
294      fail("Modifying a missing table should fail");
295    } catch (IOException e) {
296      // Pass
297    }
298    postCount = observer.postHookCalls.get();
299    assertEquals("Expected no invocations of postModifyTable when the operation fails",
300        preCount, postCount);
301  }
302
303  @Test
304  public void testPostDisableTable() throws IOException {
305    final Admin admin = UTIL.getAdmin();
306    final TableName tn = TableName.valueOf("postdisabletable");
307    final TableDescriptor td = TableDescriptorBuilder.newBuilder(tn).setColumnFamily(
308        ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("f1")).build()).build();
309
310    HMaster master = UTIL.getMiniHBaseCluster().getMaster();
311    MasterObserverForTest observer = master.getMasterCoprocessorHost().findCoprocessor(
312        MasterObserverForTest.class);
313
314    // Create the table and disable it
315    admin.createTable(td);
316
317    // Validate that the post hook is called
318    int preCount = observer.postHookCalls.get();
319    admin.disableTable(td.getTableName());
320    int postCount = observer.postHookCalls.get();
321    assertEquals("Expected 1 invocation of postDisableTable", preCount + 1, postCount);
322
323    // Then, validate that it's not called when the call fails
324    preCount = observer.postHookCalls.get();
325    try {
326      admin.disableTable(TableName.valueOf("Missing"));
327      fail("Disabling a missing table should fail");
328    } catch (IOException e) {
329      // Pass
330    }
331    postCount = observer.postHookCalls.get();
332    assertEquals("Expected no invocations of postDisableTable when the operation fails",
333        preCount, postCount);
334  }
335
336  @Test
337  public void testPostDeleteTable() throws IOException {
338    final Admin admin = UTIL.getAdmin();
339    final TableName tn = TableName.valueOf("postdeletetable");
340    final TableDescriptor td = TableDescriptorBuilder.newBuilder(tn).setColumnFamily(
341        ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("f1")).build()).build();
342
343    HMaster master = UTIL.getMiniHBaseCluster().getMaster();
344    MasterObserverForTest observer = master.getMasterCoprocessorHost().findCoprocessor(
345        MasterObserverForTest.class);
346
347    // Create the table and disable it
348    admin.createTable(td);
349    admin.disableTable(td.getTableName());
350
351    // Validate that the post hook is called
352    int preCount = observer.postHookCalls.get();
353    admin.deleteTable(td.getTableName());
354    int postCount = observer.postHookCalls.get();
355    assertEquals("Expected 1 invocation of postDeleteTable", preCount + 1, postCount);
356
357    // Then, validate that it's not called when the call fails
358    preCount = observer.postHookCalls.get();
359    try {
360      admin.deleteTable(TableName.valueOf("missing"));
361      fail("Deleting a missing table should fail");
362    } catch (IOException e) {
363      // Pass
364    }
365    postCount = observer.postHookCalls.get();
366    assertEquals("Expected no invocations of postDeleteTable when the operation fails",
367        preCount, postCount);
368  }
369}