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.junit.jupiter.api.Assertions.assertFalse; 021import static org.junit.jupiter.api.Assertions.assertTrue; 022 023import java.io.IOException; 024import org.apache.hadoop.conf.Configuration; 025import org.apache.hadoop.fs.FSDataOutputStream; 026import org.apache.hadoop.fs.FileSystem; 027import org.apache.hadoop.fs.Path; 028import org.apache.hadoop.hbase.HBaseTestingUtil; 029import org.apache.hadoop.hbase.HConstants; 030import org.apache.hadoop.hbase.master.HMaster; 031import org.apache.hadoop.hbase.master.MasterFileSystem; 032import org.apache.hadoop.hbase.regionserver.HRegionServer; 033import org.apache.hadoop.hbase.testclassification.MediumTests; 034import org.apache.hadoop.hbase.testclassification.SecurityTests; 035import org.junit.jupiter.api.AfterEach; 036import org.junit.jupiter.api.BeforeEach; 037import org.junit.jupiter.api.Tag; 038import org.junit.jupiter.api.Test; 039import org.slf4j.Logger; 040import org.slf4j.LoggerFactory; 041 042@Tag(SecurityTests.TAG) 043@Tag(MediumTests.TAG) 044public class TestReadOnlyManageActiveClusterFile { 045 046 private static final Logger LOG = 047 LoggerFactory.getLogger(TestReadOnlyManageActiveClusterFile.class); 048 private final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 049 private static final int NUM_RS = 1; 050 private static Configuration conf; 051 HMaster master; 052 HRegionServer regionServer; 053 054 MasterFileSystem mfs; 055 Path rootDir; 056 FileSystem fs; 057 Path activeClusterFile; 058 059 @BeforeEach 060 public void setup() throws Exception { 061 conf = TEST_UTIL.getConfiguration(); 062 063 // Set up test class with Read-Only mode disabled so a table can be created 064 conf.setBoolean(HConstants.HBASE_GLOBAL_READONLY_ENABLED_KEY, false); 065 // Start the test cluster 066 TEST_UTIL.startMiniCluster(NUM_RS); 067 master = TEST_UTIL.getMiniHBaseCluster().getMaster(); 068 regionServer = TEST_UTIL.getMiniHBaseCluster().getRegionServer(0); 069 070 mfs = master.getMasterFileSystem(); 071 rootDir = mfs.getRootDir(); 072 fs = mfs.getFileSystem(); 073 activeClusterFile = new Path(rootDir, HConstants.ACTIVE_CLUSTER_SUFFIX_FILE_NAME); 074 } 075 076 @AfterEach 077 public void tearDown() throws Exception { 078 TEST_UTIL.shutdownMiniCluster(); 079 } 080 081 private void setReadOnlyMode(boolean enabled) { 082 conf.setBoolean(HConstants.HBASE_GLOBAL_READONLY_ENABLED_KEY, enabled); 083 master.getConfigurationManager().notifyAllObservers(conf); 084 regionServer.getConfigurationManager().notifyAllObservers(conf); 085 } 086 087 private void restartCluster() throws IOException, InterruptedException { 088 TEST_UTIL.getMiniHBaseCluster().shutdown(); 089 TEST_UTIL.restartHBaseCluster(NUM_RS); 090 TEST_UTIL.waitUntilNoRegionsInTransition(); 091 092 master = TEST_UTIL.getMiniHBaseCluster().getMaster(); 093 regionServer = TEST_UTIL.getMiniHBaseCluster().getRegionServer(0); 094 095 MasterFileSystem mfs = master.getMasterFileSystem(); 096 fs = mfs.getFileSystem(); 097 activeClusterFile = new Path(mfs.getRootDir(), HConstants.ACTIVE_CLUSTER_SUFFIX_FILE_NAME); 098 } 099 100 private void overwriteExistingFile() throws IOException { 101 try (FSDataOutputStream out = fs.create(activeClusterFile, true)) { 102 out.writeBytes("newClusterId"); 103 } 104 } 105 106 private boolean activeClusterIdFileExists() throws IOException { 107 return fs.exists(activeClusterFile); 108 } 109 110 @Test 111 public void testActiveClusterIdFileCreationWhenReadOnlyDisabled() 112 throws IOException, InterruptedException { 113 setReadOnlyMode(false); 114 assertTrue(activeClusterIdFileExists()); 115 } 116 117 @Test 118 public void testActiveClusterIdFileDeletionWhenReadOnlyEnabled() 119 throws IOException, InterruptedException { 120 setReadOnlyMode(true); 121 assertFalse(activeClusterIdFileExists()); 122 } 123 124 @Test 125 public void testDeleteActiveClusterIdFileWhenSwitchingToReadOnlyIfOwnedByCluster() 126 throws IOException, InterruptedException { 127 // At the start cluster is in active mode hence set readonly mode and restart 128 conf.setBoolean(HConstants.HBASE_GLOBAL_READONLY_ENABLED_KEY, true); 129 // Restart the cluster to trigger the deletion of the active cluster ID file 130 restartCluster(); 131 // Should delete the active cluster ID file since it is owned by the cluster 132 assertFalse(activeClusterIdFileExists()); 133 } 134 135 @Test 136 public void testDoNotDeleteActiveClusterIdFileWhenSwitchingToReadOnlyIfNotOwnedByCluster() 137 throws IOException, InterruptedException { 138 // Change the content of Active Cluster file to simulate the scenario where the file is not 139 // owned by the cluster and then set readonly mode and restart 140 overwriteExistingFile(); 141 // At the start cluster is in active mode hence set readonly mode and restart 142 conf.setBoolean(HConstants.HBASE_GLOBAL_READONLY_ENABLED_KEY, true); 143 // Restart the cluster to trigger the deletion of the active cluster ID file 144 restartCluster(); 145 // As Active cluster file is not owned by the cluster, it should not be deleted even when 146 // switching to readonly mode 147 assertTrue(activeClusterIdFileExists()); 148 } 149}