/* * 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.flink.runtime.checkpoint.savepoint; import org.apache.flink.api.common.JobID; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.core.fs.FSDataOutputStream; import org.apache.flink.core.fs.FileStatus; import org.apache.flink.core.fs.FileSystem; import org.apache.flink.core.fs.FileSystem.WriteMode; import org.apache.flink.core.fs.Path; import org.apache.flink.core.memory.DataInputViewStreamWrapper; import org.apache.flink.runtime.state.StreamStateHandle; import org.apache.flink.runtime.state.filesystem.FileStateHandle; import org.apache.flink.util.FileUtils; import org.apache.flink.util.Preconditions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import static org.apache.flink.util.Preconditions.checkNotNull; /** * Utilities for storing and loading savepoint meta data files. * * <p>Stored savepoints have the following format: * <pre> * MagicNumber SavepointVersion Savepoint * - MagicNumber => int * - SavepointVersion => int (returned by Savepoint#getVersion()) * - Savepoint => bytes (serialized via version-specific SavepointSerializer) * </pre> */ public class SavepointStore { private static final Logger LOG = LoggerFactory.getLogger(SavepointStore.class); /** Magic number for sanity checks against stored savepoints. */ public static final int MAGIC_NUMBER = 0x4960672d; private static final String SAVEPOINT_METADATA_FILE = "_metadata"; /** * Metadata file for an externalized checkpoint, random suffix added * during store, because the parent directory is not unique. */ static final String EXTERNALIZED_CHECKPOINT_METADATA_FILE = "checkpoint_metadata-"; /** * Creates a savepoint directory. * * @param baseDirectory Base target directory for the savepoint * @param jobId Optional JobID the savepoint belongs to * @return The created savepoint directory * @throws IOException FileSystem operation failures are forwarded */ public static String createSavepointDirectory(@Nonnull String baseDirectory, @Nullable JobID jobId) throws IOException { final Path basePath = new Path(baseDirectory); final FileSystem fs = basePath.getFileSystem(); final String prefix; if (jobId == null) { prefix = "savepoint-"; } else { prefix = String.format("savepoint-%s-", jobId.toString().substring(0, 6)); } Exception latestException = null; // Try to create a FS output stream for (int attempt = 0; attempt < 10; attempt++) { Path path = new Path(basePath, FileUtils.getRandomFilename(prefix)); try { if (fs.mkdirs(path)) { return path.toString(); } } catch (Exception e) { latestException = e; } } throw new IOException("Failed to create savepoint directory at " + baseDirectory, latestException); } /** * Deletes a savepoint directory. * * @param savepointDirectory Recursively deletes the given directory * @throws IOException FileSystem operation failures are forwarded */ public static void deleteSavepointDirectory(@Nonnull String savepointDirectory) throws IOException { Path path = new Path(savepointDirectory); FileSystem fs = FileSystem.get(path.toUri()); fs.delete(path, true); } /** * Stores the savepoint metadata file. * * @param <T> Savepoint type * @param directory Target directory to store savepoint in * @param savepoint Savepoint to be stored * @return Path of stored savepoint * @throws IOException Failures during store are forwarded */ public static <T extends Savepoint> String storeSavepoint(String directory, T savepoint) throws IOException { // write and create the file handle FileStateHandle metadataFileHandle = storeSavepointToHandle(directory, SAVEPOINT_METADATA_FILE, savepoint); // we return the savepoint directory path here! // The directory path also works to resume from and is more elegant than the direct // metadata file pointer return metadataFileHandle.getFilePath().getParent().toString(); } /** * Stores the savepoint metadata file to a state handle. * * @param directory Target directory to store savepoint in * @param savepoint Savepoint to be stored * * @return State handle to the checkpoint metadata * @throws IOException Failures during store are forwarded */ public static <T extends Savepoint> FileStateHandle storeSavepointToHandle(String directory, T savepoint) throws IOException { return storeSavepointToHandle(directory, SAVEPOINT_METADATA_FILE, savepoint); } /** * Stores the externalized checkpoint metadata file to a state handle. * * @param directory Target directory to store savepoint in * @param savepoint Savepoint to be stored * * @return State handle to the checkpoint metadata * @throws IOException Failures during store are forwarded */ public static <T extends Savepoint> FileStateHandle storeExternalizedCheckpointToHandle(String directory, T savepoint) throws IOException { String fileName = FileUtils.getRandomFilename(EXTERNALIZED_CHECKPOINT_METADATA_FILE); return storeSavepointToHandle(directory, fileName, savepoint); } /** * Stores the savepoint metadata file to a state handle. * * @param directory Target directory to store savepoint in * @param savepoint Savepoint to be stored * * @return State handle to the checkpoint metadata * @throws IOException Failures during store are forwarded */ static <T extends Savepoint> FileStateHandle storeSavepointToHandle( String directory, String filename, T savepoint) throws IOException { checkNotNull(directory, "Target directory"); checkNotNull(savepoint, "Savepoint"); final Path basePath = new Path(directory); final Path metadataFilePath = new Path(basePath, filename); final FileSystem fs = FileSystem.get(basePath.toUri()); boolean success = false; try (FSDataOutputStream fdos = fs.create(metadataFilePath, WriteMode.NO_OVERWRITE); DataOutputStream dos = new DataOutputStream(fdos)) { // Write header dos.writeInt(MAGIC_NUMBER); dos.writeInt(savepoint.getVersion()); // Write savepoint SavepointSerializer<T> serializer = SavepointSerializers.getSerializer(savepoint); serializer.serialize(savepoint, dos); // construct result handle FileStateHandle handle = new FileStateHandle(metadataFilePath, dos.size()); // all good! success = true; return handle; } finally { if (!success && fs.exists(metadataFilePath)) { if (!fs.delete(metadataFilePath, true)) { LOG.warn("Failed to delete file {} after failed metadata write.", metadataFilePath); } } } } /** * Loads the savepoint at the specified path. * * @param savepointFileOrDirectory Path to the parent savepoint directory or the meta data file. * @param classLoader The class loader used to resolve serialized classes from legacy savepoint formats. * @return The loaded savepoint * * @throws IOException Failures during load are forwarded */ public static Savepoint loadSavepoint(String savepointFileOrDirectory, ClassLoader classLoader) throws IOException { return loadSavepointWithHandle(savepointFileOrDirectory, classLoader).f0; } /** * Loads the savepoint at the specified path. This methods returns the savepoint, as well as the * handle to the metadata. * * @param savepointFileOrDirectory Path to the parent savepoint directory or the meta data file. * @param classLoader The class loader used to resolve serialized classes from legacy savepoint formats. * @return The loaded savepoint * * @throws IOException Failures during load are forwarded */ public static Tuple2<Savepoint, StreamStateHandle> loadSavepointWithHandle( String savepointFileOrDirectory, ClassLoader classLoader) throws IOException { checkNotNull(savepointFileOrDirectory, "savepointFileOrDirectory"); checkNotNull(classLoader, "classLoader"); Path path = new Path(savepointFileOrDirectory); LOG.info("Loading savepoint from {}", path); FileSystem fs = FileSystem.get(path.toUri()); FileStatus status = fs.getFileStatus(path); // If this is a directory, we need to find the meta data file if (status.isDir()) { Path candidatePath = new Path(path, SAVEPOINT_METADATA_FILE); if (fs.exists(candidatePath)) { path = candidatePath; LOG.info("Using savepoint file in {}", path); } else { throw new IOException("Cannot find meta data file in directory " + path + ". Please try to load the savepoint directly from the meta data file " + "instead of the directory."); } } // load the savepoint final Savepoint savepoint; try (DataInputStream dis = new DataInputViewStreamWrapper(fs.open(path))) { int magicNumber = dis.readInt(); if (magicNumber == MAGIC_NUMBER) { int version = dis.readInt(); SavepointSerializer<?> serializer = SavepointSerializers.getSerializer(version); savepoint = serializer.deserialize(dis, classLoader); } else { throw new RuntimeException("Unexpected magic number. This can have multiple reasons: " + "(1) You are trying to load a Flink 1.0 savepoint, which is not supported by this " + "version of Flink. (2) The file you were pointing to is not a savepoint at all. " + "(3) The savepoint file has been corrupted."); } } // construct the stream handle to the metadata file // we get the size best-effort long size = 0; try { size = fs.getFileStatus(path).getLen(); } catch (Exception ignored) { // we don't know the size, but we don't want to fail the savepoint loading for that } StreamStateHandle metadataHandle = new FileStateHandle(path, size); return new Tuple2<>(savepoint, metadataHandle); } /** * Removes the savepoint meta data w/o loading and disposing it. * * @param path Path of savepoint to remove * @throws IOException Failures during disposal are forwarded */ public static void removeSavepointFile(String path) throws IOException { Preconditions.checkNotNull(path, "Path"); try { LOG.info("Removing savepoint: {}.", path); Path filePath = new Path(path); FileSystem fs = FileSystem.get(filePath.toUri()); if (fs.exists(filePath)) { if (!fs.delete(filePath, true)) { throw new IOException("Failed to delete " + filePath + "."); } } else { throw new IllegalArgumentException("Invalid path '" + filePath.toUri() + "'."); } } catch (Throwable t) { throw new IOException("Failed to dispose savepoint " + path + ".", t); } } }