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
056 * was successful.
057 */
058@Category({ MasterTests.class, MediumTests.class })
059public class TestMasterObserverPostCalls {
060
061  @ClassRule
062  public static final HBaseClassTestRule CLASS_RULE =
063    HBaseClassTestRule.forClass(TestMasterObserverPostCalls.class);
064
065  private static final Logger LOG = LoggerFactory.getLogger(TestMasterObserverPostCalls.class);
066  protected static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
067
068  @BeforeClass
069  public static void setupCluster() throws Exception {
070    setupConf(UTIL.getConfiguration());
071    UTIL.startMiniCluster(1);
072  }
073
074  private static void setupConf(Configuration conf) {
075    conf.setInt(MasterProcedureConstants.MASTER_PROCEDURE_THREADS, 1);
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(ObserverContext<MasterCoprocessorEnvironment> ctx,
110      NamespaceDescriptor desc) {
111      postHookCalls.incrementAndGet();
112    }
113
114    @Override
115    public void postCreateNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
116      NamespaceDescriptor desc) {
117      postHookCalls.incrementAndGet();
118    }
119
120    @Override
121    public void postCreateTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
122      TableDescriptor td, RegionInfo[] regions) {
123      postHookCalls.incrementAndGet();
124    }
125
126    @Override
127    public void postModifyTable(ObserverContext<MasterCoprocessorEnvironment> ctx, TableName tn,
128      TableDescriptor td) {
129      postHookCalls.incrementAndGet();
130    }
131
132    @Override
133    public void postDisableTable(ObserverContext<MasterCoprocessorEnvironment> ctx, TableName tn) {
134      postHookCalls.incrementAndGet();
135    }
136
137    @Override
138    public void postDeleteTable(ObserverContext<MasterCoprocessorEnvironment> ctx, TableName tn) {
139      postHookCalls.incrementAndGet();
140    }
141  }
142
143  @Test
144  public void testPostDeleteNamespace() throws IOException {
145    final Admin admin = UTIL.getAdmin();
146    final String ns = "postdeletens";
147    final TableName tn1 = TableName.valueOf(ns, "table1");
148
149    admin.createNamespace(NamespaceDescriptor.create(ns).build());
150    admin.createTable(TableDescriptorBuilder.newBuilder(tn1)
151      .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("f1")).build())
152      .build());
153
154    HMaster master = UTIL.getMiniHBaseCluster().getMaster();
155    MasterObserverForTest observer =
156      master.getMasterCoprocessorHost().findCoprocessor(MasterObserverForTest.class);
157    int preCount = observer.postHookCalls.get();
158    try {
159      admin.deleteNamespace(ns);
160      fail("Deleting a non-empty namespace should be disallowed");
161    } catch (IOException e) {
162      // Pass
163    }
164    int postCount = observer.postHookCalls.get();
165    assertEquals("Expected no invocations of postDeleteNamespace when the operation fails",
166      preCount, postCount);
167
168    // Disable and delete the table so that we can delete the NS.
169    admin.disableTable(tn1);
170    admin.deleteTable(tn1);
171
172    // Validate that the postDeletNS hook is invoked
173    preCount = observer.postHookCalls.get();
174    admin.deleteNamespace(ns);
175    postCount = observer.postHookCalls.get();
176    assertEquals("Expected 1 invocation of postDeleteNamespace", preCount + 1, postCount);
177  }
178
179  @Test
180  public void testPostModifyNamespace() throws IOException {
181    final Admin admin = UTIL.getAdmin();
182    final String ns = "postmodifyns";
183
184    NamespaceDescriptor nsDesc = NamespaceDescriptor.create(ns).build();
185    admin.createNamespace(nsDesc);
186
187    HMaster master = UTIL.getMiniHBaseCluster().getMaster();
188    MasterObserverForTest observer =
189      master.getMasterCoprocessorHost().findCoprocessor(MasterObserverForTest.class);
190    int preCount = observer.postHookCalls.get();
191    try {
192      admin.modifyNamespace(NamespaceDescriptor.create("nonexistent").build());
193      fail("Modifying a missing namespace should fail");
194    } catch (IOException e) {
195      // Pass
196    }
197    int postCount = observer.postHookCalls.get();
198    assertEquals("Expected no invocations of postModifyNamespace when the operation fails",
199      preCount, postCount);
200
201    // Validate that the postDeletNS hook is invoked
202    preCount = observer.postHookCalls.get();
203    admin
204      .modifyNamespace(NamespaceDescriptor.create(nsDesc).addConfiguration("foo", "bar").build());
205    postCount = observer.postHookCalls.get();
206    assertEquals("Expected 1 invocation of postModifyNamespace", preCount + 1, postCount);
207  }
208
209  @Test
210  public void testPostCreateNamespace() throws IOException {
211    final Admin admin = UTIL.getAdmin();
212    final String ns = "postcreatens";
213
214    HMaster master = UTIL.getMiniHBaseCluster().getMaster();
215    MasterObserverForTest observer =
216      master.getMasterCoprocessorHost().findCoprocessor(MasterObserverForTest.class);
217
218    // Validate that the post hook is called
219    int preCount = observer.postHookCalls.get();
220    NamespaceDescriptor nsDesc = NamespaceDescriptor.create(ns).build();
221    admin.createNamespace(nsDesc);
222    int postCount = observer.postHookCalls.get();
223    assertEquals("Expected 1 invocation of postModifyNamespace", preCount + 1, postCount);
224
225    // Then, validate that it's not called when the call fails
226    preCount = observer.postHookCalls.get();
227    try {
228      admin.createNamespace(nsDesc);
229      fail("Creating an already present namespace should fail");
230    } catch (IOException e) {
231      // Pass
232    }
233    postCount = observer.postHookCalls.get();
234    assertEquals("Expected no invocations of postModifyNamespace when the operation fails",
235      preCount, postCount);
236  }
237
238  @Test
239  public void testPostCreateTable() throws IOException {
240    final Admin admin = UTIL.getAdmin();
241    final TableName tn = TableName.valueOf("postcreatetable");
242    final TableDescriptor td = TableDescriptorBuilder.newBuilder(tn)
243      .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("f1")).build())
244      .build();
245
246    HMaster master = UTIL.getMiniHBaseCluster().getMaster();
247    MasterObserverForTest observer =
248      master.getMasterCoprocessorHost().findCoprocessor(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", preCount,
266      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)
274      .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("f1")).build())
275      .build();
276
277    HMaster master = UTIL.getMiniHBaseCluster().getMaster();
278    MasterObserverForTest observer =
279      master.getMasterCoprocessorHost().findCoprocessor(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", preCount,
301      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)
309      .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("f1")).build())
310      .build();
311
312    HMaster master = UTIL.getMiniHBaseCluster().getMaster();
313    MasterObserverForTest observer =
314      master.getMasterCoprocessorHost().findCoprocessor(MasterObserverForTest.class);
315
316    // Create the table and disable it
317    admin.createTable(td);
318
319    // Validate that the post hook is called
320    int preCount = observer.postHookCalls.get();
321    admin.disableTable(td.getTableName());
322    int postCount = observer.postHookCalls.get();
323    assertEquals("Expected 1 invocation of postDisableTable", preCount + 1, postCount);
324
325    // Then, validate that it's not called when the call fails
326    preCount = observer.postHookCalls.get();
327    try {
328      admin.disableTable(TableName.valueOf("Missing"));
329      fail("Disabling a missing table should fail");
330    } catch (IOException e) {
331      // Pass
332    }
333    postCount = observer.postHookCalls.get();
334    assertEquals("Expected no invocations of postDisableTable when the operation fails", preCount,
335      postCount);
336  }
337
338  @Test
339  public void testPostDeleteTable() throws IOException {
340    final Admin admin = UTIL.getAdmin();
341    final TableName tn = TableName.valueOf("postdeletetable");
342    final TableDescriptor td = TableDescriptorBuilder.newBuilder(tn)
343      .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("f1")).build())
344      .build();
345
346    HMaster master = UTIL.getMiniHBaseCluster().getMaster();
347    MasterObserverForTest observer =
348      master.getMasterCoprocessorHost().findCoprocessor(MasterObserverForTest.class);
349
350    // Create the table and disable it
351    admin.createTable(td);
352    admin.disableTable(td.getTableName());
353
354    // Validate that the post hook is called
355    int preCount = observer.postHookCalls.get();
356    admin.deleteTable(td.getTableName());
357    int postCount = observer.postHookCalls.get();
358    assertEquals("Expected 1 invocation of postDeleteTable", preCount + 1, postCount);
359
360    // Then, validate that it's not called when the call fails
361    preCount = observer.postHookCalls.get();
362    try {
363      admin.deleteTable(TableName.valueOf("missing"));
364      fail("Deleting a missing table should fail");
365    } catch (IOException e) {
366      // Pass
367    }
368    postCount = observer.postHookCalls.get();
369    assertEquals("Expected no invocations of postDeleteTable when the operation fails", preCount,
370      postCount);
371  }
372}