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

com.aliyun.fs.oss.nat.NativeOssFileSystem Maven / Gradle / Ivy

There is a newer version: 1.6.0
Show newest version
/**
 * 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 com.aliyun.fs.oss.nat;

import java.io.*;
import java.net.URI;
import java.util.*;
import java.util.concurrent.TimeUnit;

import com.aliyun.fs.oss.common.*;
import com.aliyun.fs.oss.utils.Utils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.io.retry.RetryPolicies;
import org.apache.hadoop.io.retry.RetryPolicy;
import org.apache.hadoop.io.retry.RetryProxy;
import org.apache.hadoop.util.Progressable;

public class NativeOssFileSystem extends FileSystem {

    public static final Log LOG =
            LogFactory.getLog(NativeOssFileSystem.class);

    public static final long MAX_OSS_FILE_SIZE = 5 * 1024 * 1024 * 1024L;
    public static final String PATH_DELIMITER = Path.SEPARATOR;
    public static final int OSS_MAX_LISTING_LENGTH = 1000;

    private class NativeOssFsInputStream extends FSInputStream {

        private InputStream in;
        private final String key;
        private long pos = 0;

        public NativeOssFsInputStream(InputStream in, String key) {
            this.in = in;
            this.key = key;
        }

        @Override
        public synchronized int read() throws IOException {
            int result = in.read();
            if (result != -1) {
                pos++;
            }
            return result;
        }
        @Override
        public synchronized int read(byte[] b, int off, int len)
                throws IOException {

            int result = in.read(b, off, len);
            if (result > 0) {
                pos += result;
            }
            return result;
        }

        @Override
        public void close() throws IOException {
            in.close();
        }

        @Override
        public synchronized void seek(long pos) throws IOException {
            in.close();
            LOG.info("Opening key '" + key + "' for reading at position '" + pos + "'");
            in = store.retrieve(key, pos);
            this.pos = pos;
        }
        @Override
        public synchronized long getPos() throws IOException {
            return pos;
        }
        @Override
        public boolean seekToNewSource(long targetPos) throws IOException {
            return false;
        }
    }

    private class NativeOssFsOutputStream extends OutputStream {

        private Configuration conf;
        private String key;
        private File backupFile;
        private OutputStream backupStream;
        private boolean closed;
        private boolean append;

        public NativeOssFsOutputStream(Configuration conf, NativeFileSystemStore store, String key,
                                       boolean append, Progressable progress, int bufferSize) throws IOException {
            this.conf = conf;
            this.key = key;
            this.append = append;
            this.backupFile = newBackupFile();
            LOG.info("OutputStream for key '" + key + "' writing to tempfile '" + this.backupFile + "'");
            this.backupStream = new BufferedOutputStream(new FileOutputStream(backupFile));
        }

        private File newBackupFile() throws IOException {
            File dir = Utils.getTempBufferDir(conf);
            if (!dir.mkdirs() && !dir.exists()) {
                throw new IOException("Cannot create OSS buffer directory: " + dir);
            }
            File result = File.createTempFile("output-", ".data", dir);
            result.deleteOnExit();
            return result;
        }

        @Override
        public void flush() throws IOException {
            backupStream.flush();
        }

        @Override
        public synchronized void close() throws IOException {
            if (closed) {
                return;
            }

            backupStream.close();
            LOG.info("OutputStream for key '" + key + "' closed. Now beginning upload");

            try {
                store.storeFile(key, backupFile, append);
            } finally {
                if (!backupFile.delete()) {
                    LOG.warn("Could not delete temporary OSS file: " + backupFile);
                }
                super.close();
                closed = true;
            }
            LOG.info("OutputStream for key '" + key + "' upload complete");
        }

        @Override
        public void write(int b) throws IOException {
            backupStream.write(b);
        }

        @Override
        public void write(byte[] b, int off, int len) throws IOException {
            backupStream.write(b, off, len);
        }
    }

    private URI uri;
    private NativeFileSystemStore store;
    private Path workingDir = new Path(".");

    public NativeOssFileSystem() {
        // set store in initialize()
    }

    public NativeOssFileSystem(NativeFileSystemStore store) {
        this.store = store;
    }

    @Override
    public void initialize(URI uri, Configuration conf) throws IOException {
        super.initialize(uri, conf);
        if (store == null) {
            store = createDefaultStore(conf);
        }
        try {
            store.initialize(uri, conf);
        } catch (Exception e) {
            e.printStackTrace();
            throw new IOException(e);
        }
        setConf(conf);
        this.uri = URI.create(uri.getScheme() + "://" + uri.getAuthority());
    }

    private static NativeFileSystemStore createDefaultStore(Configuration conf) {
        NativeFileSystemStore store = new JetOssNativeFileSystemStore();

        RetryPolicy basePolicy = RetryPolicies.retryUpToMaximumCountWithFixedSleep(
                conf.getInt("fs.oss.maxRetries", 4),
                conf.getLong("fs.oss.sleepTimeSeconds", 10), TimeUnit.SECONDS);
        Map, RetryPolicy> exceptionToPolicyMap =
                new HashMap, RetryPolicy>();
        exceptionToPolicyMap.put(IOException.class, basePolicy);
        exceptionToPolicyMap.put(OssException.class, basePolicy);

        RetryPolicy methodPolicy = RetryPolicies.retryByException(
                RetryPolicies.TRY_ONCE_THEN_FAIL, exceptionToPolicyMap);
        Map methodNameToPolicyMap =
                new HashMap();
        methodNameToPolicyMap.put("storeFile", methodPolicy);
        methodNameToPolicyMap.put("rename", methodPolicy);

        return (NativeFileSystemStore)
                RetryProxy.create(NativeFileSystemStore.class, store,
                        methodNameToPolicyMap);
    }

    private static String pathToKey(Path path) {
        if (!path.isAbsolute()) {
            throw new IllegalArgumentException("Path must be absolute: " + path);
        }
        // OSS File Path can not start with "/", so we need to scratch the first "/".
        String absolutePath = path.toUri().getPath();
        return absolutePath.substring(1);
    }

    private static Path keyToPath(String key) {
        return new Path(key);
    }

    private Path makeAbsolute(Path path) {
        // TODO: here need to review
        return path;
    }

    @Override
    public FSDataOutputStream append(Path f, int bufferSize,
                                     Progressable progress) throws IOException {
        Path absolutePath = makeAbsolute(f);
        String key = pathToKey(absolutePath);
        return new FSDataOutputStream(new NativeOssFsOutputStream(getConf(), store,
                key, true, progress, bufferSize), statistics);
    }

    @Override
    public FSDataOutputStream create(Path f, FsPermission permission, boolean overwrite, int bufferSize,
                                     short replication, long blockSize, Progressable progress)
            throws IOException {
        if (exists(f) && !overwrite) {
            throw new IOException("File already exists:"+f);
        }
        Path absolutePath = makeAbsolute(f);
        String key = pathToKey(absolutePath);
        return new FSDataOutputStream(new NativeOssFsOutputStream(getConf(), store,
                key, false, progress, bufferSize), statistics);
    }

    @Override
    @Deprecated
    public boolean delete(Path path) throws IOException {
        return delete(path, true);
    }

    @Override
    public boolean delete(Path f, boolean recurse) throws IOException {
        FileStatus status;
        try {
            status = getFileStatus(f);
        } catch (FileNotFoundException e) {
            LOG.debug("Delete called for '" + f + "' but file does not exist, so returning false");
            return false;
        }
        Path absolutePath = makeAbsolute(f);
        String key = pathToKey(absolutePath);
        if (status.isDir()) {
            if (!recurse && listStatus(f).length > 0) {
                throw new IOException("Can not delete " + f + " at is a not empty directory and recurse option is false");
            }

            createParent(f);

            LOG.debug("Deleting directory '" + f  + "'");
            String priorLastKey = null;
            do {
                PartialListing listing = store.list(key, OSS_MAX_LISTING_LENGTH, priorLastKey, true);
                for (FileMetadata file : listing.getFiles()) {
                    store.delete(file.getKey());
                }
                priorLastKey = listing.getPriorLastKey();
            } while (priorLastKey != null);
        } else {
            LOG.debug("Deleting file '" + f + "'");
            createParent(f);
            store.delete(key);
        }
        return true;
    }

    @Override
    public FileStatus getFileStatus(Path f) throws IOException {
        Path absolutePath = makeAbsolute(f);
        String key = pathToKey(absolutePath);

        if (key.length() == 0) { // root always exists
            return newDirectory(absolutePath);
        }

        LOG.debug("getFileStatus retrieving metadata for key '" + key + "'");
        FileMetadata meta = store.retrieveMetadata(key);
        if (meta != null) {
            LOG.debug("getFileStatus returning 'file' for key '" + key + "'");
            return newFile(meta, absolutePath);
        }

        LOG.debug("getFileStatus listing key '" + key + "'");
        PartialListing listing = store.list(key, 1);
        if (listing.getFiles().length > 0 ||
                listing.getCommonPrefixes().length > 0) {
            LOG.debug("getFileStatus returning 'directory' for key '" + key + "' as it has contents");
            return newDirectory(absolutePath);
        }

        LOG.debug("getFileStatus could not find key '" + key + "'");
        throw new FileNotFoundException("No such file or directory '" + absolutePath + "'");
    }

    @Override
    public String getScheme() {
        return "oss";
    }

    @Override
    public URI getUri() {
        return uri;
    }

    /**
     * 

* If f is a file, this method will make a single call to Oss. * If f is a directory, this method will make a maximum of * (n / 1000) + 2 calls to Oss, where n is the total number of * files and directories contained directly in f. *

*/ @Override public FileStatus[] listStatus(Path f) throws IOException { Path absolutePath = makeAbsolute(f); String key = pathToKey(absolutePath); if (key.length() > 0) { FileMetadata meta = store.retrieveMetadata(key); if (meta != null) { return new FileStatus[] { newFile(meta, absolutePath) }; } } URI pathUri = absolutePath.toUri(); Set status = new TreeSet(); String priorLastKey = null; do { PartialListing listing = store.list(key, OSS_MAX_LISTING_LENGTH, priorLastKey, false); for (FileMetadata fileMetadata : listing.getFiles()) { Path subpath = keyToPath(fileMetadata.getKey()); if (fileMetadata.getKey().equals(key + "/")) { // this is just the directory we have been asked to list } else { // Here, we need to convert "file/path" to "/file/path". Otherwise, Path.makeQualified will // throw `URISyntaxException`. Path modifiedPath = new Path("/" + subpath.toString()); status.add(newFile(fileMetadata, modifiedPath)); } } for (String commonPrefix : listing.getCommonPrefixes()) { Path subpath = keyToPath(commonPrefix); String relativePath = pathUri.relativize(subpath.toUri()).getPath(); status.add(newDirectory(new Path("/" + relativePath))); } priorLastKey = listing.getPriorLastKey(); } while (priorLastKey != null); if (status.isEmpty()) { return new FileStatus[0]; } return status.toArray(new FileStatus[status.size()]); } private FileStatus newFile(FileMetadata meta, Path path) { return new FileStatus(meta.getLength(), false, 1, MAX_OSS_FILE_SIZE, meta.getLastModified(), path.makeQualified(this)); } private FileStatus newDirectory(Path path) { return new FileStatus(0, true, 1, MAX_OSS_FILE_SIZE, 0, path.makeQualified(this)); } @Override public boolean mkdirs(Path f, FsPermission permission) throws IOException { Path absolutePath = makeAbsolute(f); List paths = new ArrayList(); do { paths.add(0, absolutePath); absolutePath = absolutePath.getParent(); } while (absolutePath != null); boolean result = true; for (Path path : paths) { result &= mkdir(path); } return result; } public boolean mkdir(Path f) throws IOException { try { FileStatus fileStatus = getFileStatus(f); if (!fileStatus.isDir()) { throw new IOException(String.format( "Can't make directory for path '%s' since it is a file.", f)); } } catch (FileNotFoundException e) { LOG.debug("Making dir '" + f + "' in OSS"); String key = pathToKey(f); store.storeEmptyFile(key + PATH_DELIMITER); } return true; } @Override public FSDataInputStream open(Path f, int bufferSize) throws IOException { FileStatus fs = getFileStatus(f); // will throw if the file doesn't exist if (fs.isDir()) { throw new IOException("'" + f + "' is a directory"); } LOG.info("Opening '" + f + "' for reading"); Path absolutePath = makeAbsolute(f); String key = pathToKey(absolutePath); return new FSDataInputStream(new BufferedFSInputStream( new NativeOssFsInputStream(store.retrieve(key), key), bufferSize)); } // rename() and delete() use this method to ensure that the parent directory // of the source does not vanish. private void createParent(Path path) throws IOException { Path parent = path.getParent(); if (parent != null) { String key = pathToKey(makeAbsolute(parent)); if (key.length() > 0) { store.storeEmptyFile(key + PATH_DELIMITER); } } } @Override public boolean rename(Path src, Path dst) throws IOException { String srcKey = pathToKey(makeAbsolute(src)); if (srcKey.length() == 0) { // Cannot rename root of file system return false; } final String debugPreamble = "Renaming '" + src + "' to '" + dst + "' - "; // Figure out the final destination String dstKey; try { boolean dstIsFile = !getFileStatus(dst).isDir(); if (dstIsFile) { LOG.debug(debugPreamble + "returning false as dst is an already existing file"); return false; } else { LOG.debug(debugPreamble + "using dst as output directory"); dstKey = pathToKey(makeAbsolute(new Path(dst, src.getName()))); } } catch (FileNotFoundException e) { LOG.debug(debugPreamble + "using dst as output destination"); dstKey = pathToKey(makeAbsolute(dst)); try { if (!getFileStatus(dst.getParent()).isDir()) { LOG.debug(debugPreamble + "returning false as dst parent exists and is a file"); return false; } } catch (FileNotFoundException ex) { LOG.debug(debugPreamble + "returning false as dst parent does not exist"); return false; } } boolean srcIsFile; try { srcIsFile = !getFileStatus(src).isDir(); } catch (FileNotFoundException e) { LOG.debug(debugPreamble + "returning false as src does not exist"); return false; } if (srcIsFile) { LOG.debug(debugPreamble + "src is file, so doing copy then delete in Oss"); store.copy(srcKey, dstKey); store.delete(srcKey); } else { LOG.debug(debugPreamble + "src is directory, so copying contents"); store.storeEmptyFile(dstKey + PATH_DELIMITER); List keysToDelete = new ArrayList(); String priorLastKey = null; do { PartialListing listing = store.list(srcKey, OSS_MAX_LISTING_LENGTH, priorLastKey, true); for (FileMetadata file : listing.getFiles()) { keysToDelete.add(file.getKey()); store.copy(file.getKey(), dstKey + file.getKey().substring(srcKey.length())); } priorLastKey = listing.getPriorLastKey(); } while (priorLastKey != null); LOG.debug(debugPreamble + "all files in src copied, now removing src files"); for (String key: keysToDelete) { store.delete(key); } LOG.debug(debugPreamble + "done"); } return true; } /** * Set the working directory to the given directory. */ @Override public void setWorkingDirectory(Path newDir) { workingDir = newDir; } @Override public Path getWorkingDirectory() { return workingDir; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy