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 java.io.FileNotFoundException; 021import java.io.IOException; 022import java.util.Set; 023import org.apache.hadoop.fs.FSDataInputStream; 024import org.apache.hadoop.fs.FileSystem; 025import org.apache.hadoop.fs.Path; 026import org.apache.hadoop.hbase.ActiveClusterSuffix; 027import org.apache.hadoop.hbase.Coprocessor; 028import org.apache.hadoop.hbase.CoprocessorEnvironment; 029import org.apache.hadoop.hbase.HBaseInterfaceAudience; 030import org.apache.hadoop.hbase.HConstants; 031import org.apache.hadoop.hbase.TableName; 032import org.apache.hadoop.hbase.WriteAttemptedOnReadOnlyClusterException; 033import org.apache.hadoop.hbase.coprocessor.ObserverContext; 034import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment; 035import org.apache.hadoop.hbase.exceptions.DeserializationException; 036import org.apache.hadoop.hbase.master.MasterFileSystem; 037import org.apache.hadoop.hbase.master.region.MasterRegionFactory; 038import org.apache.hadoop.hbase.util.FSUtils; 039import org.apache.yetus.audience.InterfaceAudience; 040import org.slf4j.Logger; 041import org.slf4j.LoggerFactory; 042 043@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG) 044public abstract class AbstractReadOnlyController implements Coprocessor { 045 private static final Logger LOG = LoggerFactory.getLogger(AbstractReadOnlyController.class); 046 047 private static final Set<TableName> writableTables = 048 Set.of(TableName.META_TABLE_NAME, MasterRegionFactory.TABLE_NAME); 049 050 public static boolean 051 isWritableInReadOnlyMode(final ObserverContext<? extends RegionCoprocessorEnvironment> c) { 052 return writableTables.contains(c.getEnvironment().getRegionInfo().getTable()); 053 } 054 055 public static boolean isWritableInReadOnlyMode(final TableName tableName) { 056 return writableTables.contains(tableName); 057 } 058 059 protected void internalReadOnlyGuard() throws WriteAttemptedOnReadOnlyClusterException { 060 throw new WriteAttemptedOnReadOnlyClusterException("Operation not allowed in Read-Only Mode"); 061 } 062 063 @Override 064 public void start(CoprocessorEnvironment env) throws IOException { 065 } 066 067 @Override 068 public void stop(CoprocessorEnvironment env) { 069 } 070 071 /** 072 * Checks whether another cluster is currently active on this storage location by reading the 073 * {@value HConstants#ACTIVE_CLUSTER_SUFFIX_FILE_NAME} file on the filesystem. 074 * @param fs the filesystem to read from 075 * @param rootDir the HBase root directory 076 * @param localClusterSuffix the local cluster's ActiveClusterSuffix identity 077 * @return true if the active cluster file exists and belongs to a different cluster; false if the 078 * file does not exist or belongs to this cluster 079 */ 080 public static boolean isAnotherClusterActive(FileSystem fs, Path rootDir, 081 ActiveClusterSuffix localClusterSuffix) { 082 try { 083 ActiveClusterSuffix fileData = 084 FSUtils.getClusterIdFile(fs, rootDir, new ActiveClusterSuffix.Parser()); 085 if (fileData == null) { 086 return false; 087 } 088 return !localClusterSuffix.equals(fileData); 089 } catch (IOException e) { 090 LOG.warn( 091 "Failed to read active cluster suffix file from {} at {}. " 092 + "Assuming another cluster is active to prevent potential data corruption.", 093 HConstants.ACTIVE_CLUSTER_SUFFIX_FILE_NAME, rootDir, e); 094 return true; 095 } 096 } 097 098 public static void manageActiveClusterIdFile(boolean readOnlyEnabled, MasterFileSystem mfs) { 099 FileSystem fs = mfs.getFileSystem(); 100 Path rootDir = mfs.getRootDir(); 101 Path activeClusterFile = new Path(rootDir, HConstants.ACTIVE_CLUSTER_SUFFIX_FILE_NAME); 102 103 try { 104 if (readOnlyEnabled) { 105 // ENABLING READ-ONLY (false -> true), delete the active cluster file. 106 LOG.debug("Global read-only mode is being ENABLED. Deleting active cluster file: {}", 107 activeClusterFile); 108 try (FSDataInputStream in = fs.open(activeClusterFile)) { 109 ActiveClusterSuffix actualClusterFileData = 110 ActiveClusterSuffix.parseFrom(in.readAllBytes()); 111 ActiveClusterSuffix expectedClusterFileData = mfs.getActiveClusterSuffix(); 112 if (expectedClusterFileData.equals(actualClusterFileData)) { 113 fs.delete(activeClusterFile, false); 114 LOG.info("Successfully deleted active cluster file: {}", activeClusterFile); 115 } else { 116 LOG.debug( 117 "Active cluster file data does not match expected data. " 118 + "Not deleting the file to avoid potential inconsistency. " 119 + "Actual data: {}, Expected data: {}", 120 actualClusterFileData, expectedClusterFileData); 121 } 122 } catch (FileNotFoundException e) { 123 LOG.debug("Active cluster file does not exist at: {}. No need to delete.", 124 activeClusterFile); 125 } catch (IOException e) { 126 LOG.error( 127 "Failed to delete active cluster file: {}. " 128 + "Read-only flag will be updated, but file system state is inconsistent.", 129 activeClusterFile, e); 130 } catch (DeserializationException e) { 131 LOG.error("Failed to deserialize ActiveClusterSuffix from file {}", activeClusterFile, e); 132 } 133 } else { 134 // DISABLING READ-ONLY (true -> false), create the active cluster file id file 135 int wait = mfs.getConfiguration().getInt(HConstants.THREAD_WAKE_FREQUENCY, 10 * 1000); 136 if (!fs.exists(activeClusterFile)) { 137 FSUtils.setClusterIdFile(fs, rootDir, HConstants.ACTIVE_CLUSTER_SUFFIX_FILE_NAME, 138 mfs.getActiveClusterSuffix(), wait); 139 } else { 140 try (FSDataInputStream in = fs.open(activeClusterFile)) { 141 ActiveClusterSuffix existingData = ActiveClusterSuffix.parseFrom(in.readAllBytes()); 142 ActiveClusterSuffix localData = mfs.getActiveClusterSuffix(); 143 if (localData.equals(existingData)) { 144 LOG.debug("Active cluster file already exists at {} and belongs to this cluster. " 145 + "No need to create it again.", activeClusterFile); 146 } else { 147 LOG.error( 148 "Active cluster file at {} belongs to a different cluster. " 149 + "ID from active cluster file: {}, ID of this cluster: {}. " 150 + "Another cluster is already the active cluster.", 151 activeClusterFile, existingData, localData); 152 } 153 } catch (DeserializationException e) { 154 LOG.error("Failed to deserialize ActiveClusterSuffix from file {}", activeClusterFile, 155 e); 156 } 157 } 158 } 159 } catch (IOException e) { 160 // We still update the flag, but log that the operation failed. 161 LOG.error("Failed to perform file operation for read-only switch. " 162 + "Flag will be updated, but file system state may be inconsistent.", e); 163 } 164 } 165}