/*
* 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 gobblin.config.store.hdfs;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import com.google.common.base.Charsets;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigRenderOptions;
import com.typesafe.config.ConfigValueFactory;
import gobblin.config.store.api.ConfigStore;
import gobblin.config.store.deploy.FsDeploymentConfig;
import gobblin.util.HadoopUtils;
/**
* A metadata accessor for an HDFS based {@link ConfigStore}. A HDFS based {@link ConfigStore} will have a file named
* {@link #CONFIG_STORE_METADATA_FILENAME} that contains store metadata as key/value pairs. This class helps adding more
* key/value pairs to the store metadata file and helps reading key/value pairs from the store metadata file. For
* instance the current active version of the store is stored at {@link #CONFIG_STORE_METADATA_CURRENT_VERSION_KEY}.
*/
public class SimpleHDFSStoreMetadata {
private static final String CONFIG_STORE_METADATA_FILENAME = "store-metadata.conf";
private static final String CONFIG_STORE_METADATA_CURRENT_VERSION_KEY = "config.hdfs.store.version.current";
private final FileSystem fs;
private final Path storeMetadataFilePath;
/**
* Create a new {@link SimpleHDFSStoreMetadata} to read and write store metadata
*
* @param fs where metadata is stored
* @param configStoreDir path to {@link SimpleHDFSConfigStore#CONFIG_STORE_NAME}
*/
SimpleHDFSStoreMetadata(final FileSystem fs, final Path configStoreDir) {
this.storeMetadataFilePath = new Path(configStoreDir, CONFIG_STORE_METADATA_FILENAME);
this.fs = fs;
}
/**
* Writes the <code>config</code> to {@link #storeMetadataFilePath}. Creates a backup file at
* <code>storeMetadataFilePath + ".bkp"</code> to recover old metadata in case of unexpected deployment failures
*
* @param config to be serialized
* @throws IOException if there was any problem writing the <code>config</code> to the store metadata file.
*/
void writeMetadata(Config config) throws IOException {
Path storeMetadataFileBkpPath =
new Path(this.storeMetadataFilePath.getParent(), this.storeMetadataFilePath.getName() + ".bkp");
// Delete old backup file if exists
HadoopUtils.deleteIfExists(this.fs, storeMetadataFileBkpPath, true);
// Move current storeMetadataFile to backup
if (this.fs.exists(this.storeMetadataFilePath)) {
HadoopUtils.renamePath(this.fs, this.storeMetadataFilePath, storeMetadataFileBkpPath);
}
// Write new storeMetadataFile
try (FSDataOutputStream outputStream =
FileSystem.create(this.fs, this.storeMetadataFilePath, FsDeploymentConfig.DEFAULT_STORE_PERMISSIONS);) {
outputStream.write(config.root().render(ConfigRenderOptions.concise()).getBytes(Charsets.UTF_8));
} catch (Exception e) {
// Restore from backup
HadoopUtils.deleteIfExists(this.fs, this.storeMetadataFilePath, true);
HadoopUtils.renamePath(this.fs, storeMetadataFileBkpPath, this.storeMetadataFilePath);
throw new IOException(
String.format("Failed to write store metadata at %s. Restored existing store metadata file from backup",
this.storeMetadataFilePath),
e);
}
}
private void addMetadata(String key, String value) throws IOException {
Config newConfig;
if (isStoreMetadataFilePresent()) {
newConfig = readMetadata().withValue(key, ConfigValueFactory.fromAnyRef(value));
} else {
newConfig = ConfigFactory.empty().withValue(key, ConfigValueFactory.fromAnyRef(value));
}
writeMetadata(newConfig);
}
/**
* Update the current version of the store in {@link #CONFIG_STORE_METADATA_FILENAME} file at {@link #storeMetadataFilePath}
*
* @param version to be updated
*/
void setCurrentVersion(String version) throws IOException {
addMetadata(CONFIG_STORE_METADATA_CURRENT_VERSION_KEY, version);
}
/**
* Get the current version from {@link #CONFIG_STORE_METADATA_FILENAME} file at {@link #storeMetadataFilePath}
*
*/
String getCurrentVersion() throws IOException {
return readMetadata().getString(CONFIG_STORE_METADATA_CURRENT_VERSION_KEY);
}
/**
* Get all metadata from the {@link #CONFIG_STORE_METADATA_FILENAME} file at {@link #storeMetadataFilePath}
*
*/
Config readMetadata() throws IOException {
if (!isStoreMetadataFilePresent()) {
throw new IOException("Store metadata file does not exist at " + this.storeMetadataFilePath);
}
try (InputStream storeMetadataInputStream = this.fs.open(this.storeMetadataFilePath)) {
return ConfigFactory.parseReader(new InputStreamReader(storeMetadataInputStream, Charsets.UTF_8));
}
}
private boolean isStoreMetadataFilePresent() throws IOException {
return this.fs.exists(this.storeMetadataFilePath);
}
}