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}