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