blob: 43ef312221a9582bfc5c872a4d63074b9ded83e7 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.ozone.om;
import com.google.common.annotations.VisibleForTesting;
import org.apache.hadoop.hdds.conf.ConfigurationSource;
import org.apache.hadoop.hdds.server.ServerUtils;
import org.apache.hadoop.ozone.OzoneConsts;
import org.apache.hadoop.ozone.om.ratis.OzoneManagerStateMachine;
import org.apache.hadoop.ozone.om.ratis.OzoneManagerRatisServer;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Type;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.PrepareStatusResponse.PrepareStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
/**
* Controls the prepare state of the {@link OzoneManager} containing the
* instance. When prepared, an ozone manager should have no Ratis logs
* remaining, disallow all write requests except prepare and cancel prepare,
* and have a marker file present on disk that will cause it to remain prepared
* on restart.
*/
public final class OzoneManagerPrepareState {
public static final long NO_PREPARE_INDEX = -1;
private static final Logger LOG =
LoggerFactory.getLogger(OzoneManagerPrepareState.class);
private boolean prepareGateEnabled;
private long prepareIndex;
private PrepareStatus status;
private final ConfigurationSource conf;
public OzoneManagerPrepareState(ConfigurationSource conf) {
prepareGateEnabled = false;
prepareIndex = NO_PREPARE_INDEX;
status = PrepareStatus.PREPARE_NOT_STARTED;
this.conf = conf;
}
/**
* Turns on the prepare gate flag, clears the prepare index, and moves the
* prepare status to {@link PrepareStatus#PREPARE_IN_PROGRESS}.
*
* Turning on the prepare gate flag will enable a gate in the
* {@link OzoneManagerStateMachine#preAppendTransaction} (called on leader
* OM only) and {@link OzoneManagerRatisServer#submitRequest}
* (called on all OMs) that block write requests from reaching the OM and
* fail them with error responses to the client.
*/
public synchronized void enablePrepareGate() {
prepareGateEnabled = true;
prepareIndex = NO_PREPARE_INDEX;
status = PrepareStatus.PREPARE_IN_PROGRESS;
}
/**
* Removes the prepare marker file, clears the prepare index, turns off
* the prepare gate, and moves the prepare status to
* {@link PrepareStatus#PREPARE_NOT_STARTED}.
* This can be called from any state to clear the current prepare state.
*
* @throws IOException If the prepare marker file exists but cannot be
* deleted.
*/
public synchronized void cancelPrepare()
throws IOException {
deletePrepareMarkerFile();
prepareIndex = NO_PREPARE_INDEX;
prepareGateEnabled = false;
status = PrepareStatus.PREPARE_NOT_STARTED;
}
/**
* Enables the prepare gate, writes the prepare marker file, sets the in
* memory prepare index, and
* moves the prepare status to {@link PrepareStatus#PREPARE_COMPLETED}.
* This can be called from any state to move the OM into prepare mode.
*
* @param index The log index to prepare the OM on.
* @throws IOException If the marker file cannot be written.
*/
public synchronized void finishPrepare(long index) throws IOException {
finishPrepare(index, true);
}
private void finishPrepare(long index, boolean writeFile) throws IOException {
// Enabling the prepare gate is idempotent, and may have already been
// performed if we are the leader. If we are a follower, we must ensure this
// is run now case we become the leader.
enablePrepareGate();
if (writeFile) {
writePrepareMarkerFile(index);
}
prepareIndex = index;
status = PrepareStatus.PREPARE_COMPLETED;
}
/**
* Uses the on disk marker file to determine the OM's prepare state.
* If the marker file exists and contains an index matching {@code
* expectedPrepareIndex}, the necessary steps will be taken to finish
* preparation and the state will be moved to
* {@link PrepareStatus#PREPARE_COMPLETED}.
* Else, the status will be moved to
* {@link PrepareStatus#PREPARE_NOT_STARTED} and any preparation steps will
* be cancelled.
*
* @return The status the OM is in after this method call.
* @throws IOException If the marker file cannot be read, and it cannot be
* deleted as part of moving to the
* {@link PrepareStatus#PREPARE_NOT_STARTED} state.
*/
public synchronized PrepareStatus restorePrepare(long expectedPrepareIndex)
throws IOException {
boolean prepareIndexRead = true;
long prepareMarkerIndex = NO_PREPARE_INDEX;
File prepareMarkerFile = getPrepareMarkerFile();
if (prepareMarkerFile.exists()) {
byte[] data = new byte[(int) prepareMarkerFile.length()];
try(FileInputStream stream = new FileInputStream(prepareMarkerFile)) {
stream.read(data);
} catch (IOException e) {
LOG.error("Failed to read prepare marker file {} while restoring OM.",
prepareMarkerFile.getAbsolutePath());
prepareIndexRead = false;
}
try {
prepareMarkerIndex = Long.parseLong(
new String(data, StandardCharsets.UTF_8));
} catch (NumberFormatException e) {
LOG.error("Failed to parse log index from prepare marker file {} " +
"while restoring OM.", prepareMarkerFile.getAbsolutePath());
prepareIndexRead = false;
}
} else {
// No marker file found.
prepareIndexRead = false;
}
boolean prepareRestored = false;
if (prepareIndexRead) {
if (prepareMarkerIndex != expectedPrepareIndex) {
LOG.error("Failed to restore OM prepare state, because the expected " +
"prepare index {} does not match the index {} written to the " +
"marker file.", expectedPrepareIndex, prepareMarkerIndex);
} else {
// Prepare state can only be restored if we read the expected index
// from the marker file.
prepareRestored = true;
}
}
if (prepareRestored) {
// Do not rewrite the marker file, since we verified it already exists.
finishPrepare(prepareMarkerIndex, false);
} else {
// If the potentially faulty marker file cannot be deleted,
// propagate the IOException.
// If there is no marker file, this call sets the in memory state only.
cancelPrepare();
}
return status;
}
/**
* If the prepare gate is enabled, always returns true.
* If the prepare gate is disabled, returns true only if {@code
* requestType} is {@code Prepare} or {@code CancelPrepare}. Returns false
* otherwise.
*/
public synchronized boolean requestAllowed(Type requestType) {
boolean requestAllowed = true;
if (prepareGateEnabled) {
requestAllowed =
(requestType == Type.Prepare) || (requestType == Type.CancelPrepare);
}
return requestAllowed;
}
/**
* @return the current log index and status of preparation.
* Both fields are returned together to provide a consistent view of the
* state, which would not be guaranteed if they had to be retrieved through
* separate getters.
*/
public synchronized State getState() {
return new State(prepareIndex, status);
}
/**
* Creates a prepare marker file inside the OM metadata directory which
* contains the log index {@code index}.
* If a marker file already exists, it will be overwritten.
*/
private void writePrepareMarkerFile(long index) throws IOException {
File markerFile = getPrepareMarkerFile();
File parentDir = markerFile.getParentFile();
Files.createDirectories(parentDir.toPath());
try(FileOutputStream stream = new FileOutputStream(markerFile)) {
stream.write(Long.toString(index).getBytes(StandardCharsets.UTF_8));
}
LOG.info("Prepare marker file written with log index {} to file {}", index,
markerFile.getAbsolutePath());
}
private void deletePrepareMarkerFile()
throws IOException {
File markerFile = getPrepareMarkerFile();
if (markerFile.exists()) {
Files.delete(markerFile.toPath());
LOG.info("Deleted prepare marker file: {}", markerFile.getAbsolutePath());
} else {
LOG.info("Request to delete prepare marker file that does not exist: {}",
markerFile.getAbsolutePath());
}
}
/**
* Returns a {@link File} object representing the prepare marker file,
* which may or may not actually exist on disk.
* This method should be used for testing only.
*/
@VisibleForTesting
public File getPrepareMarkerFile() {
File markerFileDir = new File(ServerUtils.getOzoneMetaDirPath(conf),
OMStorage.STORAGE_DIR_CURRENT);
return new File(markerFileDir, OzoneConsts.PREPARE_MARKER);
}
/**
* The current state of preparation is defined by the status and the
* prepare index that are currently set.
*/
public static class State {
private final long index;
private final PrepareStatus status;
public State(long index, PrepareStatus status) {
this.index = index;
this.status = status;
}
public long getIndex() {
return index;
}
public PrepareStatus getStatus() {
return status;
}
}
}