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 public static void manageActiveClusterIdFile(boolean readOnlyEnabled, MasterFileSystem mfs) { 072 FileSystem fs = mfs.getFileSystem(); 073 Path rootDir = mfs.getRootDir(); 074 Path activeClusterFile = new Path(rootDir, HConstants.ACTIVE_CLUSTER_SUFFIX_FILE_NAME); 075 076 try { 077 if (readOnlyEnabled) { 078 // ENABLING READ-ONLY (false -> true), delete the active cluster file. 079 LOG.debug("Global read-only mode is being ENABLED. Deleting active cluster file: {}", 080 activeClusterFile); 081 try (FSDataInputStream in = fs.open(activeClusterFile)) { 082 ActiveClusterSuffix actualClusterFileData = 083 ActiveClusterSuffix.parseFrom(in.readAllBytes()); 084 ActiveClusterSuffix expectedClusterFileData = mfs.getActiveClusterSuffix(); 085 if (expectedClusterFileData.equals(actualClusterFileData)) { 086 fs.delete(activeClusterFile, false); 087 LOG.info("Successfully deleted active cluster file: {}", activeClusterFile); 088 } else { 089 LOG.debug( 090 "Active cluster file data does not match expected data. " 091 + "Not deleting the file to avoid potential inconsistency. " 092 + "Actual data: {}, Expected data: {}", 093 actualClusterFileData, expectedClusterFileData); 094 } 095 } catch (FileNotFoundException e) { 096 LOG.debug("Active cluster file does not exist at: {}. No need to delete.", 097 activeClusterFile); 098 } catch (IOException e) { 099 LOG.error( 100 "Failed to delete active cluster file: {}. " 101 + "Read-only flag will be updated, but file system state is inconsistent.", 102 activeClusterFile, e); 103 } catch (DeserializationException e) { 104 LOG.error("Failed to deserialize ActiveClusterSuffix from file {}", activeClusterFile, e); 105 } 106 } else { 107 // DISABLING READ-ONLY (true -> false), create the active cluster file id file 108 int wait = mfs.getConfiguration().getInt(HConstants.THREAD_WAKE_FREQUENCY, 10 * 1000); 109 if (!fs.exists(activeClusterFile)) { 110 FSUtils.setClusterIdFile(fs, rootDir, HConstants.ACTIVE_CLUSTER_SUFFIX_FILE_NAME, 111 mfs.getActiveClusterSuffix(), wait); 112 } else { 113 LOG.debug("Active cluster file already exists at: {}. No need to create it again.", 114 activeClusterFile); 115 } 116 } 117 } catch (IOException e) { 118 // We still update the flag, but log that the operation failed. 119 LOG.error("Failed to perform file operation for read-only switch. " 120 + "Flag will be updated, but file system state may be inconsistent.", e); 121 } 122 } 123}