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.set(MasterCoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
076        MasterObserverForTest.class.getName());
077  }
078
079  @AfterClass
080  public static void cleanupTest() throws Exception {
081    try {
082      UTIL.shutdownMiniCluster();
083    } catch (Exception e) {
084      LOG.warn("failure shutting down cluster", e);
085    }
086  }
087
088  public static class MasterObserverForTest implements MasterCoprocessor, MasterObserver {
089    private AtomicInteger postHookCalls = null;
090
091    @Override
092    public Optional<MasterObserver> getMasterObserver() {
093      return Optional.of(this);
094    }
095
096    @Override
097    public void start(@SuppressWarnings("rawtypes") CoprocessorEnvironment ctx) throws IOException {
098      this.postHookCalls = new AtomicInteger(0);
099    }
100
101    @Override
102    public void postDeleteNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
103        String namespace) {
104      postHookCalls.incrementAndGet();
105    }
106
107    @Override
108    public void postModifyNamespace(
109        ObserverContext<MasterCoprocessorEnvironment> ctx, NamespaceDescriptor oldNsDesc,
110        NamespaceDescriptor currentNsDesc) {
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,
130        TableDescriptor oldDescriptor, TableDescriptor currentDescriptor) {
131      postHookCalls.incrementAndGet();
132    }
133
134    @Override
135    public void postDisableTable(ObserverContext<MasterCoprocessorEnvironment> ctx, TableName tn) {
136      postHookCalls.incrementAndGet();
137    }
138
139    @Override
140    public void postDeleteTable(ObserverContext<MasterCoprocessorEnvironment> ctx, TableName tn) {
141      postHookCalls.incrementAndGet();
142    }
143  }
144
145  @Test
146  public void testPostDeleteNamespace() throws IOException {
147    final Admin admin = UTIL.getAdmin();
148    final String ns = "postdeletens";
149    final TableName tn1 = TableName.valueOf(ns, "table1");
150
151    admin.createNamespace(NamespaceDescriptor.create(ns).build());
152    admin.createTable(TableDescriptorBuilder.newBuilder(tn1)
153        .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("f1")).build())
154        .build());
155
156    HMaster master = UTIL.getMiniHBaseCluster().getMaster();
157    MasterObserverForTest observer = master.getMasterCoprocessorHost().findCoprocessor(
158        MasterObserverForTest.class);
159    int preCount = observer.postHookCalls.get();
160    try {
161      admin.deleteNamespace(ns);
162      fail("Deleting a non-empty namespace should be disallowed");
163    } catch (IOException e) {
164      // Pass
165    }
166    int postCount = observer.postHookCalls.get();
167    assertEquals("Expected no invocations of postDeleteNamespace when the operation fails",
168        preCount, postCount);
169
170    // Disable and delete the table so that we can delete the NS.
171    admin.disableTable(tn1);
172    admin.deleteTable(tn1);
173
174    // Validate that the postDeletNS hook is invoked
175    preCount = observer.postHookCalls.get();
176    admin.deleteNamespace(ns);
177    postCount = observer.postHookCalls.get();
178    assertEquals("Expected 1 invocation of postDeleteNamespace", preCount + 1, postCount);
179  }
180
181  @Test
182  public void testPostModifyNamespace() throws IOException {
183    final Admin admin = UTIL.getAdmin();
184    final String ns = "postmodifyns";
185
186    NamespaceDescriptor nsDesc = NamespaceDescriptor.create(ns).build();
187    admin.createNamespace(nsDesc);
188
189    HMaster master = UTIL.getMiniHBaseCluster().getMaster();
190    MasterObserverForTest observer = master.getMasterCoprocessorHost().findCoprocessor(
191        MasterObserverForTest.class);
192    int preCount = observer.postHookCalls.get();
193    try {
194      admin.modifyNamespace(NamespaceDescriptor.create("nonexistent").build());
195      fail("Modifying a missing namespace should fail");
196    } catch (IOException e) {
197      // Pass
198    }
199    int postCount = observer.postHookCalls.get();
200    assertEquals("Expected no invocations of postModifyNamespace when the operation fails",
201        preCount, postCount);
202
203    // Validate that the postDeletNS hook is invoked
204    preCount = observer.postHookCalls.get();
205    admin.modifyNamespace(
206        NamespaceDescriptor.create(nsDesc).addConfiguration("foo", "bar").build());
207    postCount = observer.postHookCalls.get();
208    assertEquals("Expected 1 invocation of postModifyNamespace", preCount + 1, postCount);
209  }
210
211  @Test
212  public void testPostCreateNamespace() throws IOException {
213    final Admin admin = UTIL.getAdmin();
214    final String ns = "postcreatens";
215
216    HMaster master = UTIL.getMiniHBaseCluster().getMaster();
217    MasterObserverForTest observer = master.getMasterCoprocessorHost().findCoprocessor(
218        MasterObserverForTest.class);
219
220    // Validate that the post hook is called
221    int preCount = observer.postHookCalls.get();
222    NamespaceDescriptor nsDesc = NamespaceDescriptor.create(ns).build();
223    admin.createNamespace(nsDesc);
224    int postCount = observer.postHookCalls.get();
225    assertEquals("Expected 1 invocation of postModifyNamespace", preCount + 1, postCount);
226
227    // Then, validate that it's not called when the call fails
228    preCount = observer.postHookCalls.get();
229    try {
230      admin.createNamespace(nsDesc);
231      fail("Creating an already present namespace should fail");
232    } catch (IOException e) {
233      // Pass
234    }
235    postCount = observer.postHookCalls.get();
236    assertEquals("Expected no invocations of postModifyNamespace when the operation fails",
237        preCount, postCount);
238  }
239
240  @Test
241  public void testPostCreateTable() throws IOException {
242    final Admin admin = UTIL.getAdmin();
243    final TableName tn = TableName.valueOf("postcreatetable");
244    final TableDescriptor td = TableDescriptorBuilder.newBuilder(tn).setColumnFamily(
245        ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("f1")).build()).build();
246
247    HMaster master = UTIL.getMiniHBaseCluster().getMaster();
248    MasterObserverForTest observer = master.getMasterCoprocessorHost().findCoprocessor(
249        MasterObserverForTest.class);
250
251    // Validate that the post hook is called
252    int preCount = observer.postHookCalls.get();
253    admin.createTable(td);
254    int postCount = observer.postHookCalls.get();
255    assertEquals("Expected 1 invocation of postCreateTable", preCount + 1, postCount);
256
257    // Then, validate that it's not called when the call fails
258    preCount = observer.postHookCalls.get();
259    try {
260      admin.createTable(td);
261      fail("Creating an already present table should fail");
262    } catch (IOException e) {
263      // Pass
264    }
265    postCount = observer.postHookCalls.get();
266    assertEquals("Expected no invocations of postCreateTable when the operation fails",
267        preCount, postCount);
268  }
269
270  @Test
271  public void testPostModifyTable() throws IOException {
272    final Admin admin = UTIL.getAdmin();
273    final TableName tn = TableName.valueOf("postmodifytable");
274    final TableDescriptor td = TableDescriptorBuilder.newBuilder(tn).setColumnFamily(
275        ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("f1")).build()).build();
276
277    HMaster master = UTIL.getMiniHBaseCluster().getMaster();
278    MasterObserverForTest observer = master.getMasterCoprocessorHost().findCoprocessor(
279        MasterObserverForTest.class);
280
281    // Create the table
282    admin.createTable(td);
283
284    // Validate that the post hook is called
285    int preCount = observer.postHookCalls.get();
286    admin.modifyTable(td);
287    int postCount = observer.postHookCalls.get();
288    assertEquals("Expected 1 invocation of postModifyTable", preCount + 1, postCount);
289
290    // Then, validate that it's not called when the call fails
291    preCount = observer.postHookCalls.get();
292    try {
293      admin.modifyTable(TableDescriptorBuilder.newBuilder(TableName.valueOf("missing"))
294          .setColumnFamily(td.getColumnFamily(Bytes.toBytes("f1"))).build());
295      fail("Modifying a missing table should fail");
296    } catch (IOException e) {
297      // Pass
298    }
299    postCount = observer.postHookCalls.get();
300    assertEquals("Expected no invocations of postModifyTable when the operation fails",
301        preCount, postCount);
302  }
303
304  @Test
305  public void testPostDisableTable() throws IOException {
306    final Admin admin = UTIL.getAdmin();
307    final TableName tn = TableName.valueOf("postdisabletable");
308    final TableDescriptor td = TableDescriptorBuilder.newBuilder(tn).setColumnFamily(
309        ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("f1")).build()).build();
310
311    HMaster master = UTIL.getMiniHBaseCluster().getMaster();
312    MasterObserverForTest observer = master.getMasterCoprocessorHost().findCoprocessor(
313        MasterObserverForTest.class);
314
315    // Create the table and disable it
316    admin.createTable(td);
317
318    // Validate that the post hook is called
319    int preCount = observer.postHookCalls.get();
320    admin.disableTable(td.getTableName());
321    int postCount = observer.postHookCalls.get();
322    assertEquals("Expected 1 invocation of postDisableTable", preCount + 1, postCount);
323
324    // Then, validate that it's not called when the call fails
325    preCount = observer.postHookCalls.get();
326    try {
327      admin.disableTable(TableName.valueOf("Missing"));
328      fail("Disabling a missing table should fail");
329    } catch (IOException e) {
330      // Pass
331    }
332    postCount = observer.postHookCalls.get();
333    assertEquals("Expected no invocations of postDisableTable when the operation fails",
334        preCount, postCount);
335  }
336
337  @Test
338  public void testPostDeleteTable() throws IOException {
339    final Admin admin = UTIL.getAdmin();
340    final TableName tn = TableName.valueOf("postdeletetable");
341    final TableDescriptor td = TableDescriptorBuilder.newBuilder(tn).setColumnFamily(
342        ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("f1")).build()).build();
343
344    HMaster master = UTIL.getMiniHBaseCluster().getMaster();
345    MasterObserverForTest observer = master.getMasterCoprocessorHost().findCoprocessor(
346        MasterObserverForTest.class);
347
348    // Create the table and disable it
349    admin.createTable(td);
350    admin.disableTable(td.getTableName());
351
352    // Validate that the post hook is called
353    int preCount = observer.postHookCalls.get();
354    admin.deleteTable(td.getTableName());
355    int postCount = observer.postHookCalls.get();
356    assertEquals("Expected 1 invocation of postDeleteTable", preCount + 1, postCount);
357
358    // Then, validate that it's not called when the call fails
359    preCount = observer.postHookCalls.get();
360    try {
361      admin.deleteTable(TableName.valueOf("missing"));
362      fail("Deleting a missing table should fail");
363    } catch (IOException e) {
364      // Pass
365    }
366    postCount = observer.postHookCalls.get();
367    assertEquals("Expected no invocations of postDeleteTable when the operation fails",
368        preCount, postCount);
369  }
370}