/* * 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.util; import java.io.IOException; import java.net.URI; import java.util.Properties; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.NonNull; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.security.token.Token; import gobblin.configuration.ConfigurationKeys; import gobblin.configuration.State; /** * A cache for storing a mapping between Hadoop users and user {@link FileSystem} objects. * * <p> * This classes uses Guava's {@link Cache} for storing the user to {@link FileSystem} mapping, and creates the * {@link FileSystem}s using the {@link ProxiedFileSystemUtils} class. * </p> * * @see Cache * @see ProxiedFileSystemUtils */ public class ProxiedFileSystemCache { private static final String KEY_SEPARATOR = ";"; private static final String RATE_CONTROLLED_TOKEN = "RateControlled"; private static final int DEFAULT_MAX_CACHE_SIZE = 1000; private static final Cache<String, FileSystem> USER_NAME_TO_FILESYSTEM_CACHE = CacheBuilder.newBuilder().maximumSize(DEFAULT_MAX_CACHE_SIZE).build(); /** * Gets a {@link FileSystem} that can perform any operations allowed by the specified userNameToProxyAs. * * @param userNameToProxyAs The name of the user the super user should proxy as * @param properties {@link java.util.Properties} containing initialization properties. * @param fsURI The {@link URI} for the {@link FileSystem} that should be created. * @return a {@link FileSystem} that can execute commands on behalf of the specified userNameToProxyAs * @throws IOException * @deprecated use {@link #fromProperties} */ @Deprecated public static FileSystem getProxiedFileSystem(@NonNull final String userNameToProxyAs, Properties properties, URI fsURI) throws IOException { return getProxiedFileSystem(userNameToProxyAs, properties, fsURI, new Configuration()); } /** * Gets a {@link FileSystem} that can perform any operations allowed by the specified userNameToProxyAs. * * @param userNameToProxyAs The name of the user the super user should proxy as * @param properties {@link java.util.Properties} containing initialization properties. * @param conf The {@link Configuration} for the {@link FileSystem} that should be created. * @return a {@link FileSystem} that can execute commands on behalf of the specified userNameToProxyAs * @throws IOException * @deprecated use {@link #fromProperties} */ @Deprecated public static FileSystem getProxiedFileSystem(@NonNull final String userNameToProxyAs, Properties properties, Configuration conf) throws IOException { return getProxiedFileSystem(userNameToProxyAs, properties, FileSystem.getDefaultUri(conf), conf); } /** * Gets a {@link FileSystem} that can perform any operations allowed by the specified userNameToProxyAs. * * @param userNameToProxyAs The name of the user the super user should proxy as * @param properties {@link java.util.Properties} containing initialization properties. * @param fsURI The {@link URI} for the {@link FileSystem} that should be created. * @param configuration The {@link Configuration} for the {@link FileSystem} that should be created. * @return a {@link FileSystem} that can execute commands on behalf of the specified userNameToProxyAs * @throws IOException * @deprecated Use {@link #fromProperties} */ @Deprecated public static FileSystem getProxiedFileSystem(@NonNull final String userNameToProxyAs, final Properties properties, final URI fsURI, final Configuration configuration) throws IOException { return getProxiedFileSystem(userNameToProxyAs, properties, fsURI, configuration, null); } /** * Gets a {@link FileSystem} that can perform any operations allowed by the specified userNameToProxyAs. * * @param userNameToProxyAs The name of the user the super user should proxy as * @param properties {@link java.util.Properties} containing initialization properties. * @param fsURI The {@link URI} for the {@link FileSystem} that should be created. * @param configuration The {@link Configuration} for the {@link FileSystem} that should be created. * @param referenceFS reference {@link FileSystem}. Used to replicate certain decorators of the reference FS: * {@link RateControlledFileSystem}. * @return a {@link FileSystem} that can execute commands on behalf of the specified userNameToProxyAs * @throws IOException */ @Builder(builderClassName = "ProxiedFileSystemFromProperties", builderMethodName = "fromProperties") private static FileSystem getProxiedFileSystem(@NonNull String userNameToProxyAs, Properties properties, URI fsURI, Configuration configuration, FileSystem referenceFS) throws IOException { Preconditions.checkNotNull(userNameToProxyAs, "Must provide a user name to proxy as."); Preconditions.checkNotNull(properties, "Properties is a mandatory field for proxiedFileSystem generation."); URI actualURI = resolveUri(fsURI, configuration, referenceFS); Configuration actualConfiguration = resolveConfiguration(configuration, referenceFS); try { return USER_NAME_TO_FILESYSTEM_CACHE.get(getFileSystemKey(actualURI, userNameToProxyAs, referenceFS), new CreateProxiedFileSystemFromProperties(userNameToProxyAs, properties, actualURI, actualConfiguration, referenceFS)); } catch (ExecutionException ee) { throw new IOException("Failed to get proxied file system for user " + userNameToProxyAs, ee); } } /** * Cached version of {@link ProxiedFileSystemUtils#createProxiedFileSystemUsingKeytab(State, URI, Configuration)}. * @deprecated use {@link #fromKeytab}. */ @Deprecated public static FileSystem getProxiedFileSystemUsingKeytab(State state, URI fsURI, Configuration conf) throws ExecutionException { Preconditions.checkArgument(state.contains(ConfigurationKeys.FS_PROXY_AS_USER_NAME)); Preconditions.checkArgument(state.contains(ConfigurationKeys.SUPER_USER_NAME_TO_PROXY_AS_OTHERS)); Preconditions.checkArgument(state.contains(ConfigurationKeys.SUPER_USER_KEY_TAB_LOCATION)); return getProxiedFileSystemUsingKeytab(state.getProp(ConfigurationKeys.FS_PROXY_AS_USER_NAME), state.getProp(ConfigurationKeys.SUPER_USER_NAME_TO_PROXY_AS_OTHERS), new Path(state.getProp(ConfigurationKeys.SUPER_USER_KEY_TAB_LOCATION)), fsURI, conf); } /** * Cached version of {@link ProxiedFileSystemUtils#createProxiedFileSystemUsingKeytab(String, String, Path, URI, Configuration)}. * @deprecated use {@link #fromKeytab}. */ @Deprecated public static FileSystem getProxiedFileSystemUsingKeytab(@NonNull final String userNameToProxyAs, final String superUserName, final Path superUserKeytabLocation, final URI fsURI, final Configuration conf) throws ExecutionException { try { return getProxiedFileSystemUsingKeytab(userNameToProxyAs, superUserName, superUserKeytabLocation, fsURI, conf, null); } catch (IOException ioe) { throw new ExecutionException(ioe); } } /** * Cached version of {@link ProxiedFileSystemUtils#createProxiedFileSystemUsingKeytab(String, String, Path, URI, Configuration)}. */ @Builder(builderClassName = "ProxiedFileSystemFromKeytab", builderMethodName = "fromKeytab") private static FileSystem getProxiedFileSystemUsingKeytab(@NonNull final String userNameToProxyAs, final String superUserName, final Path superUserKeytabLocation, final URI fsURI, final Configuration conf, FileSystem referenceFS) throws IOException, ExecutionException { Preconditions.checkNotNull(userNameToProxyAs, "Must provide a user name to proxy as."); Preconditions.checkNotNull(superUserName, "Must provide a super user name."); Preconditions.checkNotNull(superUserKeytabLocation, "Must provide a keytab location."); URI actualURI = resolveUri(fsURI, conf, referenceFS); Configuration actualConfiguration = resolveConfiguration(conf, referenceFS); return USER_NAME_TO_FILESYSTEM_CACHE.get(getFileSystemKey(actualURI, userNameToProxyAs, referenceFS), new CreateProxiedFileSystemFromKeytab(userNameToProxyAs, superUserName, superUserKeytabLocation, actualURI, actualConfiguration, referenceFS)); } /** * Cached version of {@link ProxiedFileSystemUtils#createProxiedFileSystemUsingToken(String, Token, URI, Configuration)}. * @deprecated use {@link #fromToken}. */ @Deprecated public static FileSystem getProxiedFileSystemUsingToken(@NonNull final String userNameToProxyAs, final Token<?> userNameToken, final URI fsURI, final Configuration conf) throws ExecutionException { try { return getProxiedFileSystemUsingToken(userNameToProxyAs, userNameToken, fsURI, conf, null); } catch (IOException ioe) { throw new ExecutionException(ioe); } } /** * Cached version of {@link ProxiedFileSystemUtils#createProxiedFileSystemUsingToken(String, Token, URI, Configuration)}. */ @Builder(builderClassName = "ProxiedFileSystemFromToken", builderMethodName = "fromToken") private static FileSystem getProxiedFileSystemUsingToken(@NonNull String userNameToProxyAs, Token<?> userNameToken, URI fsURI, Configuration conf, FileSystem referenceFS) throws IOException, ExecutionException { Preconditions.checkNotNull(userNameToProxyAs, "Must provide a user name to proxy as."); Preconditions.checkNotNull(userNameToken, "Must provide token for user to proxy."); URI actualURI = resolveUri(fsURI, conf, referenceFS); Configuration actualConfiguration = resolveConfiguration(conf, referenceFS); return USER_NAME_TO_FILESYSTEM_CACHE.get(getFileSystemKey(actualURI, userNameToProxyAs, referenceFS), new CreateProxiedFileSystemFromToken(userNameToProxyAs, userNameToken, actualURI, actualConfiguration, referenceFS)); } @AllArgsConstructor private static class CreateProxiedFileSystemFromProperties implements Callable<FileSystem> { @NonNull private final String userNameToProxyAs; @NonNull private final Properties properties; @NonNull private final URI uri; @NonNull private final Configuration configuration; private final FileSystem referenceFS; @Override public FileSystem call() throws Exception { FileSystem fs = ProxiedFileSystemUtils.createProxiedFileSystem(this.userNameToProxyAs, this.properties, this.uri, this.configuration); if (this.referenceFS != null) { return decorateFilesystemFromReferenceFS(fs, this.referenceFS); } return fs; } } @AllArgsConstructor private static class CreateProxiedFileSystemFromKeytab implements Callable<FileSystem> { @NonNull private final String userNameToProxyAs; @NonNull private final String superUser; @NonNull private final Path keytabLocation; @NonNull private final URI uri; @NonNull private final Configuration configuration; private final FileSystem referenceFS; @Override public FileSystem call() throws Exception { FileSystem fs = ProxiedFileSystemUtils.createProxiedFileSystemUsingKeytab(this.userNameToProxyAs, this.superUser, this.keytabLocation, this.uri, this.configuration); if (this.referenceFS != null) { return decorateFilesystemFromReferenceFS(fs, this.referenceFS); } return fs; } } @AllArgsConstructor private static class CreateProxiedFileSystemFromToken implements Callable<FileSystem> { @NonNull private final String userNameToProxyAs; @NonNull private final Token<?> userNameToken; @NonNull private final URI uri; @NonNull private final Configuration configuration; private final FileSystem referenceFS; @Override public FileSystem call() throws Exception { FileSystem fs = ProxiedFileSystemUtils.createProxiedFileSystemUsingToken(this.userNameToProxyAs, this.userNameToken, this.uri, this.configuration); if (this.referenceFS != null) { return decorateFilesystemFromReferenceFS(fs, this.referenceFS); } return fs; } } private static URI resolveUri(URI uri, Configuration configuration, FileSystem fileSystem) throws IOException { if (uri != null) { return uri; } if (fileSystem != null) { return fileSystem.getUri(); } if (configuration != null) { return FileSystem.getDefaultUri(configuration); } throw new IOException("FileSystem URI could not be determined from available inputs."); } private static Configuration resolveConfiguration(Configuration configuration, FileSystem fileSystem) throws IOException { if (configuration != null) { return configuration; } if (fileSystem != null) { return fileSystem.getConf(); } throw new IOException("FileSystem configuration could not be determined from available inputs."); } private static String getFileSystemKey(URI uri, String user, FileSystem referenceFS) { StringBuilder keyBuilder = new StringBuilder(); keyBuilder.append(uri.toString()); keyBuilder.append(KEY_SEPARATOR); keyBuilder.append(user); if (referenceFS != null && RateControlledFileSystem.getRateIfRateControlled(referenceFS).isPresent()) { keyBuilder.append(KEY_SEPARATOR); keyBuilder.append(RATE_CONTROLLED_TOKEN); } return keyBuilder.toString(); } private static FileSystem decorateFilesystemFromReferenceFS(FileSystem newFS, FileSystem referenceFS) { FileSystem decoratedFs = newFS; Optional<Long> decoratedFSRateOpt = RateControlledFileSystem.getRateIfRateControlled(decoratedFs); if (!decoratedFSRateOpt.isPresent()) { Optional<Long> referenceRateOpt = RateControlledFileSystem.getRateIfRateControlled(referenceFS); if (referenceRateOpt.isPresent()) { decoratedFs = new RateControlledFileSystem(decoratedFs, referenceRateOpt.get()); } } return decoratedFs; } }