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}