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.security.access; 019 020import static org.apache.hadoop.hbase.HConstants.HBASE_CLIENT_RETRIES_NUMBER; 021import static org.junit.jupiter.api.Assertions.assertThrows; 022import static org.junit.jupiter.api.Assertions.assertTrue; 023 024import java.io.IOException; 025import java.util.ArrayList; 026import java.util.List; 027import org.apache.hadoop.conf.Configuration; 028import org.apache.hadoop.hbase.HBaseTestingUtil; 029import org.apache.hadoop.hbase.HConstants; 030import org.apache.hadoop.hbase.SingleProcessHBaseCluster; 031import org.apache.hadoop.hbase.TableName; 032import org.apache.hadoop.hbase.client.Connection; 033import org.apache.hadoop.hbase.client.ConnectionFactory; 034import org.apache.hadoop.hbase.client.Delete; 035import org.apache.hadoop.hbase.client.Put; 036import org.apache.hadoop.hbase.client.Row; 037import org.apache.hadoop.hbase.client.Table; 038import org.apache.hadoop.hbase.master.HMaster; 039import org.apache.hadoop.hbase.regionserver.HRegionServer; 040import org.apache.hadoop.hbase.testclassification.LargeTests; 041import org.apache.hadoop.hbase.testclassification.SecurityTests; 042import org.apache.hadoop.hbase.util.Bytes; 043import org.junit.jupiter.api.AfterEach; 044import org.junit.jupiter.api.BeforeEach; 045import org.junit.jupiter.api.Tag; 046import org.junit.jupiter.api.Test; 047 048@Tag(SecurityTests.TAG) 049@Tag(LargeTests.TAG) 050@SuppressWarnings("deprecation") 051public class TestReadOnlyController { 052 053 private final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 054 private static final TableName TEST_TABLE = TableName.valueOf("read_only_test_table"); 055 private static final byte[] TEST_FAMILY = Bytes.toBytes("read_only_table_col_fam"); 056 private static HRegionServer hRegionServer; 057 private static HMaster hMaster; 058 private static Configuration conf; 059 private static Connection connection; 060 private static SingleProcessHBaseCluster cluster; 061 062 private static Table testTable; 063 064 @BeforeEach 065 public void beforeClass() throws Exception { 066 conf = TEST_UTIL.getConfiguration(); 067 068 // Shorten the run time of failed unit tests by limiting retries and the session timeout 069 // threshold 070 conf.setInt(HBASE_CLIENT_RETRIES_NUMBER, 1); 071 conf.setInt(HConstants.ZK_SESSION_TIMEOUT, 1000); 072 073 // Set up test class with Read-Only mode disabled so a table can be created 074 conf.setBoolean(HConstants.HBASE_GLOBAL_READONLY_ENABLED_KEY, false); 075 076 try { 077 // Start the test cluster 078 cluster = TEST_UTIL.startMiniCluster(1); 079 080 hMaster = cluster.getMaster(); 081 hRegionServer = cluster.getRegionServerThreads().get(0).getRegionServer(); 082 connection = ConnectionFactory.createConnection(conf); 083 084 // Create a test table 085 testTable = TEST_UTIL.createTable(TEST_TABLE, TEST_FAMILY); 086 } catch (Exception e) { 087 // Delete the created table, and clean up the connection and cluster before throwing an 088 // exception 089 disableReadOnlyMode(); 090 TEST_UTIL.deleteTable(TEST_TABLE); 091 connection.close(); 092 TEST_UTIL.shutdownMiniCluster(); 093 throw new RuntimeException(e); 094 } 095 } 096 097 @AfterEach 098 public void afterClass() throws Exception { 099 if (connection != null) { 100 connection.close(); 101 } 102 TEST_UTIL.shutdownMiniCluster(); 103 } 104 105 private static void enableReadOnlyMode() { 106 SecureTestUtil.enableReadOnlyMode(conf, hMaster, hRegionServer); 107 } 108 109 private static void disableReadOnlyMode() { 110 SecureTestUtil.disableReadOnlyMode(conf, hMaster, hRegionServer); 111 } 112 113 // The test case for successfully creating a table with Read-Only mode disabled happens when 114 // setting up the test class, so we only need a test function for a failed table creation. 115 @Test 116 public void testCannotCreateTableWithReadOnlyEnabled() throws IOException { 117 enableReadOnlyMode(); 118 TableName newTable = TableName.valueOf("bad_read_only_test_table"); 119 120 IOException exception = assertThrows(IOException.class, () -> { 121 TEST_UTIL.createTable(newTable, TEST_FAMILY); 122 }); 123 assertTrue(exception.getMessage().contains("Operation not allowed in Read-Only Mode")); 124 } 125 126 @Test 127 public void testPutWithReadOnlyDisabled() throws IOException { 128 // Successfully put a row in the table since Read-Only mode is disabled 129 disableReadOnlyMode(); 130 final byte[] row2 = Bytes.toBytes("row2"); 131 final byte[] value = Bytes.toBytes("efgh"); 132 Put put = new Put(row2); 133 put.addColumn(TEST_FAMILY, null, value); 134 testTable.put(put); 135 } 136 137 @Test 138 public void testCannotPutWithReadOnlyEnabled() throws IOException { 139 // Prepare a Put command with Read-Only mode enabled 140 enableReadOnlyMode(); 141 final byte[] row1 = Bytes.toBytes("row1"); 142 final byte[] value = Bytes.toBytes("abcd"); 143 Put put = new Put(row1); 144 put.addColumn(TEST_FAMILY, null, value); 145 146 IOException exception = assertThrows(IOException.class, () -> { 147 testTable.put(put); 148 }); 149 assertTrue(exception.getMessage().contains("Operation not allowed in Read-Only Mode")); 150 } 151 152 @Test 153 public void testBatchPutWithReadOnlyDisabled() throws IOException, InterruptedException { 154 // Successfully create and run a batch Put operation with Read-Only mode disabled 155 disableReadOnlyMode(); 156 List<Row> actions = new ArrayList<>(); 157 actions.add(new Put(Bytes.toBytes("row10")).addColumn(TEST_FAMILY, null, Bytes.toBytes("10"))); 158 actions.add(new Delete(Bytes.toBytes("row10"))); 159 testTable.batch(actions, null); 160 } 161 162 @Test 163 public void testCannotBatchPutWithReadOnlyEnabled() throws IOException, InterruptedException { 164 // Create a batch Put operation that is expected to fail with Read-Only mode enabled 165 enableReadOnlyMode(); 166 List<Row> actions = new ArrayList<>(); 167 actions.add(new Put(Bytes.toBytes("row11")).addColumn(TEST_FAMILY, null, Bytes.toBytes("11"))); 168 actions.add(new Delete(Bytes.toBytes("row11"))); 169 170 IOException exception = assertThrows(IOException.class, () -> { 171 testTable.batch(actions, null); 172 }); 173 assertTrue(exception.getMessage().contains("Operation not allowed in Read-Only Mode")); 174 } 175}