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}