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}