All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.hadoop.fs.PrestoFileSystemCache Maven / Gradle / Ivy

There is a newer version: 3.2.0-9
Show newest version
/*
 * Licensed 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.fs;

import io.prestosql.hadoop.$internal.com.google.common.collect.ImmutableSet;
import io.prestosql.hadoop.FileSystemFactory;
import io.prestosql.hadoop.$internal.org.apache.commons.logging.Log;
import io.prestosql.hadoop.$internal.org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod;
import org.apache.hadoop.util.Progressable;
import org.apache.hadoop.util.ReflectionUtils;

import java.io.IOException;
import java.net.URI;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;

import static io.prestosql.hadoop.$internal.com.google.common.base.Objects.toStringHelper;
import static io.prestosql.hadoop.$internal.com.google.common.base.Strings.nullToEmpty;
import static java.lang.String.format;
import static java.util.Locale.ENGLISH;
import static java.util.Objects.requireNonNull;
import static org.apache.hadoop.fs.FileSystem.getFileSystemClass;
import static org.apache.hadoop.security.UserGroupInformationShim.getSubject;

public final class PrestoFileSystemCache
        extends FileSystem.Cache
{
    public static final Log log = LogFactory.getLog(PrestoFileSystemCache.class);
    public static final String CACHE_KEY = "fs.cache.credentials";

    private final AtomicLong unique = new AtomicLong();
    private final Map map = new HashMap<>();

    @Override
    FileSystem get(URI uri, Configuration conf)
            throws IOException
    {
        if (conf instanceof FileSystemFactory) {
            return ((FileSystemFactory) conf).createFileSystem(uri);
        }

        return getInternal(uri, conf, 0);
    }

    @Override
    FileSystem getUnique(URI uri, Configuration conf)
            throws IOException
    {
        if (conf instanceof FileSystemFactory) {
            return ((FileSystemFactory) conf).createFileSystem(uri);
        }

        return getInternal(uri, conf, unique.incrementAndGet());
    }

    private synchronized FileSystem getInternal(URI uri, Configuration conf, long unique)
            throws IOException
    {
        UserGroupInformation userGroupInformation = UserGroupInformation.getCurrentUser();
        FileSystemKey key = createFileSystemKey(uri, userGroupInformation, unique);
        Set privateCredentials = getPrivateCredentials(userGroupInformation);

        FileSystemHolder fileSystemHolder = map.get(key);
        if (fileSystemHolder == null) {
            int maxSize = conf.getInt("fs.cache.max-size", 1000);
            if (map.size() >= maxSize) {
                throw new IOException(format("FileSystem max cache size has been reached: %s", maxSize));
            }
            FileSystem fileSystem = createFileSystem(uri, conf);
            fileSystemHolder = new FileSystemHolder(fileSystem, privateCredentials);
            map.put(key, fileSystemHolder);
        }

        // Update file system instance when credentials change.
        // - Private credentials are only set when using Kerberos authentication.
        // When the user is the same, but the private credentials are different,
        // that means that Kerberos ticket has expired and re-login happened.
        // To prevent cache leak in such situation, the privateCredentials are not
        // a part of the FileSystemKey, but part of the FileSystemHolder. When a
        // Kerberos re-login occurs, re-create the file system and cache it using
        // the same key.
        // - Extra credentials are used to authenticate with certain file systems.
        if ((isHdfs(uri) && !fileSystemHolder.getPrivateCredentials().equals(privateCredentials)) ||
                extraCredentialsChanged(fileSystemHolder.getFileSystem(), conf)) {
            map.remove(key);
            FileSystem fileSystem = createFileSystem(uri, conf);
            fileSystemHolder = new FileSystemHolder(fileSystem, privateCredentials);
            map.put(key, fileSystemHolder);
        }

        return fileSystemHolder.getFileSystem();
    }

    private static FileSystem createFileSystem(URI uri, Configuration conf)
            throws IOException
    {
        Class clazz = getFileSystemClass(uri.getScheme(), conf);
        if (clazz == null) {
            throw new IOException("No FileSystem for scheme: " + uri.getScheme());
        }
        final FileSystem original = (FileSystem) ReflectionUtils.newInstance(clazz, conf);
        original.initialize(uri, conf);
        FilterFileSystem wrapper = new FileSystemWrapper(original);
        FinalizerService.getInstance().addFinalizer(wrapper, new Runnable()
        {
            @Override
            public void run()
            {
                try {
                    original.close();
                }
                catch (IOException e) {
                    log.error("Error occurred when finalizing file system", e);
                }
            }
        });
        return wrapper;
    }

    @Override
    synchronized void remove(Key ignored, FileSystem fileSystem)
    {
        if (fileSystem == null) {
            return;
        }
        Iterator> iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            if (fileSystem.equals(iterator.next().getValue().getFileSystem())) {
                iterator.remove();
                break;
            }
        }
    }

    @Override
    synchronized void closeAll()
            throws IOException
    {
        for (FileSystemHolder fileSystemHolder : map.values()) {
            fileSystemHolder.getFileSystem().close();
        }
        map.clear();
    }

    @Override
    synchronized void closeAll(boolean onlyAutomatic)
            throws IOException
    {
        throw new UnsupportedOperationException();
    }

    @Override
    synchronized void closeAll(UserGroupInformation ugi)
            throws IOException
    {
        throw new UnsupportedOperationException();
    }

    private static FileSystemKey createFileSystemKey(URI uri, UserGroupInformation userGroupInformation, long unique)
    {
        String scheme = nullToEmpty(uri.getScheme()).toLowerCase(ENGLISH);
        String authority = nullToEmpty(uri.getAuthority()).toLowerCase(ENGLISH);
        String realUser;
        String proxyUser;
        AuthenticationMethod authenticationMethod = userGroupInformation.getAuthenticationMethod();
        switch (authenticationMethod) {
            case SIMPLE:
            case KERBEROS:
                realUser = userGroupInformation.getUserName();
                proxyUser = null;
                break;
            case PROXY:
                realUser = userGroupInformation.getRealUser().getUserName();
                proxyUser = userGroupInformation.getUserName();
                break;
            default:
                throw new IllegalArgumentException("Unsupported authentication method: " + authenticationMethod);
        }
        return new FileSystemKey(scheme, authority, unique, realUser, proxyUser);
    }

    private static Set getPrivateCredentials(UserGroupInformation userGroupInformation)
    {
        AuthenticationMethod authenticationMethod = userGroupInformation.getAuthenticationMethod();
        switch (authenticationMethod) {
            case SIMPLE:
                return ImmutableSet.of();
            case KERBEROS:
                return ImmutableSet.copyOf(getSubject(userGroupInformation).getPrivateCredentials());
            case PROXY:
                return getPrivateCredentials(userGroupInformation.getRealUser());
            default:
                throw new IllegalArgumentException("Unsupported authentication method: " + authenticationMethod);
        }
    }

    private static boolean isHdfs(URI uri)
    {
        String scheme = uri.getScheme();
        return "hdfs".equals(scheme) || "viewfs".equals(scheme);
    }

    private static boolean extraCredentialsChanged(FileSystem fileSystem, Configuration configuration)
    {
        return !configuration.get(CACHE_KEY, "").equals(fileSystem.getConf().get(CACHE_KEY, ""));
    }

    private static class FileSystemKey
    {
        private final String scheme;
        private final String authority;
        private final long unique;
        private final String realUser;
        private final String proxyUser;

        public FileSystemKey(String scheme, String authority, long unique, String realUser, String proxyUser)
        {
            this.scheme = requireNonNull(scheme, "scheme is null");
            this.authority = requireNonNull(authority, "authority is null");
            this.unique = unique;
            this.realUser = requireNonNull(realUser, "realUser");
            this.proxyUser = proxyUser;
        }

        @Override
        public boolean equals(Object o)
        {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            FileSystemKey that = (FileSystemKey) o;
            return Objects.equals(scheme, that.scheme) &&
                    Objects.equals(authority, that.authority) &&
                    Objects.equals(unique, that.unique) &&
                    Objects.equals(realUser, that.realUser) &&
                    Objects.equals(proxyUser, that.proxyUser);
        }

        @Override
        public int hashCode()
        {
            return Objects.hash(scheme, authority, unique, realUser, proxyUser);
        }

        @Override
        public String toString()
        {
            return toStringHelper(this)
                    .add("scheme", scheme)
                    .add("authority", authority)
                    .add("unique", unique)
                    .add("realUser", realUser)
                    .add("proxyUser", proxyUser)
                    .toString();
        }
    }

    private static class FileSystemHolder
    {
        private final FileSystem fileSystem;
        private final Set privateCredentials;

        public FileSystemHolder(FileSystem fileSystem, Set privateCredentials)
        {
            this.fileSystem = requireNonNull(fileSystem, "fileSystem is null");
            this.privateCredentials = ImmutableSet.copyOf(requireNonNull(privateCredentials, "privateCredentials is null"));
        }

        public FileSystem getFileSystem()
        {
            return fileSystem;
        }

        public Set getPrivateCredentials()
        {
            return privateCredentials;
        }

        @Override
        public String toString()
        {
            return toStringHelper(this)
                    .add("fileSystem", fileSystem)
                    .add("privateCredentials", privateCredentials)
                    .toString();
        }
    }

    private static class FileSystemWrapper
            extends FilterFileSystem
    {
        public FileSystemWrapper(FileSystem fs)
        {
            super(fs);
        }

        @Override
        public FSDataInputStream open(Path f, int bufferSize)
                throws IOException
        {
            return new InputStreamWrapper(getRawFileSystem().open(f, bufferSize), this);
        }

        @Override
        public FSDataOutputStream append(Path f, int bufferSize, Progressable progress)
                throws IOException
        {
            return new OutputStreamWrapper(getRawFileSystem().append(f, bufferSize, progress), this);
        }

        @Override
        public FSDataOutputStream create(Path f, FsPermission permission, boolean overwrite, int bufferSize, short replication, long blockSize, Progressable progress)
                throws IOException
        {
            return new OutputStreamWrapper(getRawFileSystem().create(f, permission, overwrite, bufferSize, replication, blockSize, progress), this);
        }

        @Override
        public FSDataOutputStream create(Path f, FsPermission permission, EnumSet flags, int bufferSize, short replication, long blockSize, Progressable progress, Options.ChecksumOpt checksumOpt)
                throws IOException
        {
            return new OutputStreamWrapper(getRawFileSystem().create(f, permission, flags, bufferSize, replication, blockSize, progress, checksumOpt), this);
        }

        @Override
        public FSDataOutputStream createNonRecursive(Path f, FsPermission permission, EnumSet flags, int bufferSize, short replication, long blockSize, Progressable progress)
                throws IOException
        {
            return new OutputStreamWrapper(getRawFileSystem().createNonRecursive(f, permission, flags, bufferSize, replication, blockSize, progress), this);
        }
    }

    private static class OutputStreamWrapper
            extends FSDataOutputStream
    {
        private final FileSystem fileSystem;

        public OutputStreamWrapper(FSDataOutputStream delegate, FileSystem fileSystem)
                throws IOException
        {
            super(delegate, null, delegate.getPos());
            this.fileSystem = fileSystem;
        }
    }

    private static class InputStreamWrapper
            extends FSDataInputStream
    {
        private final FileSystem fileSystem;

        public InputStreamWrapper(FSDataInputStream inputStream, FileSystem fileSystem)
        {
            super(inputStream);
            this.fileSystem = fileSystem;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy