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}