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 desc) { 110 postHookCalls.incrementAndGet(); 111 } 112 113 @Override 114 public void postCreateNamespace( 115 ObserverContext<MasterCoprocessorEnvironment> ctx, NamespaceDescriptor desc) { 116 postHookCalls.incrementAndGet(); 117 } 118 119 @Override 120 public void postCreateTable( 121 ObserverContext<MasterCoprocessorEnvironment> ctx, TableDescriptor td, 122 RegionInfo[] regions) { 123 postHookCalls.incrementAndGet(); 124 } 125 126 @Override 127 public void postModifyTable( 128 ObserverContext<MasterCoprocessorEnvironment> ctx, TableName tn, 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 = master.getMasterCoprocessorHost().findCoprocessor( 156 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 = master.getMasterCoprocessorHost().findCoprocessor( 189 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.modifyNamespace( 204 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 = master.getMasterCoprocessorHost().findCoprocessor( 216 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).setColumnFamily( 243 ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("f1")).build()).build(); 244 245 HMaster master = UTIL.getMiniHBaseCluster().getMaster(); 246 MasterObserverForTest observer = master.getMasterCoprocessorHost().findCoprocessor( 247 MasterObserverForTest.class); 248 249 // Validate that the post hook is called 250 int preCount = observer.postHookCalls.get(); 251 admin.createTable(td); 252 int postCount = observer.postHookCalls.get(); 253 assertEquals("Expected 1 invocation of postCreateTable", preCount + 1, postCount); 254 255 // Then, validate that it's not called when the call fails 256 preCount = observer.postHookCalls.get(); 257 try { 258 admin.createTable(td); 259 fail("Creating an already present table should fail"); 260 } catch (IOException e) { 261 // Pass 262 } 263 postCount = observer.postHookCalls.get(); 264 assertEquals("Expected no invocations of postCreateTable when the operation fails", 265 preCount, postCount); 266 } 267 268 @Test 269 public void testPostModifyTable() throws IOException { 270 final Admin admin = UTIL.getAdmin(); 271 final TableName tn = TableName.valueOf("postmodifytable"); 272 final TableDescriptor td = TableDescriptorBuilder.newBuilder(tn).setColumnFamily( 273 ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("f1")).build()).build(); 274 275 HMaster master = UTIL.getMiniHBaseCluster().getMaster(); 276 MasterObserverForTest observer = master.getMasterCoprocessorHost().findCoprocessor( 277 MasterObserverForTest.class); 278 279 // Create the table 280 admin.createTable(td); 281 282 // Validate that the post hook is called 283 int preCount = observer.postHookCalls.get(); 284 admin.modifyTable(td); 285 int postCount = observer.postHookCalls.get(); 286 assertEquals("Expected 1 invocation of postModifyTable", preCount + 1, postCount); 287 288 // Then, validate that it's not called when the call fails 289 preCount = observer.postHookCalls.get(); 290 try { 291 admin.modifyTable(TableDescriptorBuilder.newBuilder(TableName.valueOf("missing")) 292 .setColumnFamily(td.getColumnFamily(Bytes.toBytes("f1"))).build()); 293 fail("Modifying a missing table should fail"); 294 } catch (IOException e) { 295 // Pass 296 } 297 postCount = observer.postHookCalls.get(); 298 assertEquals("Expected no invocations of postModifyTable when the operation fails", 299 preCount, postCount); 300 } 301 302 @Test 303 public void testPostDisableTable() throws IOException { 304 final Admin admin = UTIL.getAdmin(); 305 final TableName tn = TableName.valueOf("postdisabletable"); 306 final TableDescriptor td = TableDescriptorBuilder.newBuilder(tn).setColumnFamily( 307 ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("f1")).build()).build(); 308 309 HMaster master = UTIL.getMiniHBaseCluster().getMaster(); 310 MasterObserverForTest observer = master.getMasterCoprocessorHost().findCoprocessor( 311 MasterObserverForTest.class); 312 313 // Create the table and disable it 314 admin.createTable(td); 315 316 // Validate that the post hook is called 317 int preCount = observer.postHookCalls.get(); 318 admin.disableTable(td.getTableName()); 319 int postCount = observer.postHookCalls.get(); 320 assertEquals("Expected 1 invocation of postDisableTable", preCount + 1, postCount); 321 322 // Then, validate that it's not called when the call fails 323 preCount = observer.postHookCalls.get(); 324 try { 325 admin.disableTable(TableName.valueOf("Missing")); 326 fail("Disabling a missing table should fail"); 327 } catch (IOException e) { 328 // Pass 329 } 330 postCount = observer.postHookCalls.get(); 331 assertEquals("Expected no invocations of postDisableTable when the operation fails", 332 preCount, postCount); 333 } 334 335 @Test 336 public void testPostDeleteTable() throws IOException { 337 final Admin admin = UTIL.getAdmin(); 338 final TableName tn = TableName.valueOf("postdeletetable"); 339 final TableDescriptor td = TableDescriptorBuilder.newBuilder(tn).setColumnFamily( 340 ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("f1")).build()).build(); 341 342 HMaster master = UTIL.getMiniHBaseCluster().getMaster(); 343 MasterObserverForTest observer = master.getMasterCoprocessorHost().findCoprocessor( 344 MasterObserverForTest.class); 345 346 // Create the table and disable it 347 admin.createTable(td); 348 admin.disableTable(td.getTableName()); 349 350 // Validate that the post hook is called 351 int preCount = observer.postHookCalls.get(); 352 admin.deleteTable(td.getTableName()); 353 int postCount = observer.postHookCalls.get(); 354 assertEquals("Expected 1 invocation of postDeleteTable", preCount + 1, postCount); 355 356 // Then, validate that it's not called when the call fails 357 preCount = observer.postHookCalls.get(); 358 try { 359 admin.deleteTable(TableName.valueOf("missing")); 360 fail("Deleting a missing table should fail"); 361 } catch (IOException e) { 362 // Pass 363 } 364 postCount = observer.postHookCalls.get(); 365 assertEquals("Expected no invocations of postDeleteTable when the operation fails", 366 preCount, postCount); 367 } 368}