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

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

Go to download

This module contains code to support integration with Tencent Cloud COS. It also declares the dependencies needed to work with COS.

There is a newer version: 8.2.7
Show newest version
package org.apache.hadoop.fs;

import com.qcloud.cos.COSClient;
import com.qcloud.cos.COSEncryptionClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.endpoint.SuffixEndpointBuilder;
import com.qcloud.cos.exception.CosClientException;
import com.qcloud.cos.exception.CosServiceException;
import com.qcloud.cos.exception.ResponseNotCompleteException;
import com.qcloud.cos.http.HttpProtocol;
import com.qcloud.cos.internal.SkipMd5CheckStrategy;
import com.qcloud.cos.internal.crypto.CryptoConfiguration;
import com.qcloud.cos.internal.crypto.CryptoMode;
import com.qcloud.cos.internal.crypto.CryptoStorageMode;
import com.qcloud.cos.internal.crypto.EncryptionMaterials;
import com.qcloud.cos.internal.crypto.StaticEncryptionMaterialsProvider;
import com.qcloud.cos.model.AbortMultipartUploadRequest;
import com.qcloud.cos.model.COSObject;
import com.qcloud.cos.model.COSObjectSummary;
import com.qcloud.cos.model.CompleteMultipartUploadRequest;
import com.qcloud.cos.model.CompleteMultipartUploadResult;
import com.qcloud.cos.model.CopyObjectRequest;
import com.qcloud.cos.model.CopyPartRequest;
import com.qcloud.cos.model.CopyPartResult;
import com.qcloud.cos.model.DeleteObjectRequest;
import com.qcloud.cos.model.GetObjectMetadataRequest;
import com.qcloud.cos.model.GetObjectRequest;
import com.qcloud.cos.model.GetSymlinkRequest;
import com.qcloud.cos.model.GetSymlinkResult;
import com.qcloud.cos.model.HeadBucketRequest;
import com.qcloud.cos.model.HeadBucketResult;
import com.qcloud.cos.model.InitiateMultipartUploadRequest;
import com.qcloud.cos.model.InitiateMultipartUploadResult;
import com.qcloud.cos.model.ListObjectsRequest;
import com.qcloud.cos.model.ObjectListing;
import com.qcloud.cos.model.ObjectMetadata;
import com.qcloud.cos.model.PartETag;
import com.qcloud.cos.model.PutObjectRequest;
import com.qcloud.cos.model.PutObjectResult;
import com.qcloud.cos.model.PutSymlinkRequest;
import com.qcloud.cos.model.PutSymlinkResult;
import com.qcloud.cos.model.RenameRequest;
import com.qcloud.cos.model.SSEAlgorithm;
import com.qcloud.cos.model.SSECOSKeyManagementParams;
import com.qcloud.cos.model.SSECustomerKey;
import com.qcloud.cos.model.StorageClass;
import com.qcloud.cos.model.UploadPartRequest;
import com.qcloud.cos.model.UploadPartResult;
import com.qcloud.cos.model.ListPartsRequest;
import com.qcloud.cos.model.PartSummary;
import com.qcloud.cos.model.PartListing;
import com.qcloud.cos.region.Region;
import com.qcloud.cos.utils.Base64;
import com.qcloud.cos.utils.IOUtils;
import com.qcloud.cos.utils.Jackson;
import com.qcloud.cos.utils.StringUtils;
import org.apache.commons.codec.binary.Hex;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.auth.COSCredentialProviderList;
import org.apache.hadoop.fs.cosn.Constants;
import org.apache.hadoop.fs.cosn.CustomerDomainEndpointResolver;
import org.apache.hadoop.fs.cosn.ResettableFileInputStream;
import org.apache.hadoop.fs.cosn.TencentCloudL5EndpointResolver;
import org.apache.hadoop.fs.cosn.CosNPartListing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.LinkedList;
import java.util.concurrent.ThreadLocalRandom;

@InterfaceAudience.Private
@InterfaceStability.Unstable
public class CosNativeFileSystemStore implements NativeFileSystemStore {
    public static final Logger LOG =
            LoggerFactory.getLogger(CosNativeFileSystemStore.class);

    private static final String XATTR_PREFIX = "cosn-xattr-";
    private static  final String CLIENT_SIDE_ENCRYPTION_PREFIX = "client-side-encryption";
    private static  final String COS_TAG_LEN_PREFIX = "x-cos-tag-len";
    private static  final String CSE_SINGLE_UPLOAD_FILE_SIZE = "client-side-encryption-unencrypted-content-length";
    public static  final String CSE_MPU_FILE_SIZE = "client-side-encryption-data-size";
    public static  final String CSE_MPU_KEY_SUFFIX = "._CSE_";

    private COSClient cosClient;
    private String bucketName;
    // default is normal bucket.
    private boolean isPosixBucket = false;

    private StorageClass storageClass;
    private int maxRetryTimes;
    private int trafficLimit;
    private boolean crc32cEnabled;
    private boolean completeMPUCheckEnabled;
    private boolean partConflictCheckEnabled;
    private long partSize;
    private boolean clientEncryptionEnabled;
    private CosNEncryptionSecrets encryptionSecrets;
    private TencentCloudL5EndpointResolver l5EndpointResolver;
    private RangerCredentialsClient rangerCredentialsClient = null;
    private boolean useL5Id = false;
    private int l5UpdateMaxRetryTimes;

    private void initCOSClient(URI uri, Configuration conf) throws IOException {
        COSCredentialProviderList cosCredentialProviderList =
                CosNUtils.createCosCredentialsProviderSet(uri, conf, this.rangerCredentialsClient);

        ClientConfig config;

        String customerDomain = conf.get(CosNConfigKeys.CUSTOMER_DOMAIN);

        if (null == customerDomain) {
            String region = conf.get(CosNConfigKeys.COSN_REGION_KEY);
            if (null == region) {
                region = conf.get(CosNConfigKeys.COSN_REGION_PREV_KEY);
            }

            String endpointSuffix =
                    conf.get(CosNConfigKeys.COSN_ENDPOINT_SUFFIX_KEY);
            if (null == endpointSuffix) {
                endpointSuffix =
                        conf.get(CosNConfigKeys.COSN_ENDPOINT_SUFFIX_PREV_KEY);
            }

            if (null == region && null == endpointSuffix) {
                String exceptionMsg = String.format("config '%s' and '%s' specify at least one.",
                        CosNConfigKeys.COSN_REGION_KEY,
                        CosNConfigKeys.COSN_ENDPOINT_SUFFIX_KEY);
                throw new IOException(exceptionMsg);
            }

            if (null == region) {
                config = new ClientConfig(new Region(""));
                config.setEndPointSuffix(endpointSuffix);
            } else {
                config = new ClientConfig(new Region(region));
            }

            // 以上 region 和 endpoint 配置设置和使用是有问题的, 新版本用以下配置纠正, 后期慢慢退化上面的 endpoint suffix 配置
            String customEndpointSuffix = conf.get(CosNConfigKeys.COSN_CUSTOM_ENDPOINT_SUFFIX);
            if (customEndpointSuffix != null && !customEndpointSuffix.isEmpty()) {
                if (null == region) {
                    String exceptionMsg = String.format("missing config '%s' or '%s'.",
                            CosNConfigKeys.COSN_REGION_KEY,
                            CosNConfigKeys.COSN_REGION_PREV_KEY);
                    throw new IOException(exceptionMsg);
                }
                config = new ClientConfig(new Region(region));
                SuffixEndpointBuilder suffixEndPointBuilder = new SuffixEndpointBuilder(customEndpointSuffix);
                config.setEndpointBuilder(suffixEndPointBuilder);
                boolean distinguishHost = conf.getBoolean(CosNConfigKeys.COSN_DISTINGUISH_HOST_FLAG,
                        CosNConfigKeys.DEFAULT_COSN_DISTINGUISH_HOST_FLAG);
                LOG.info("{}: {}", CosNConfigKeys.COSN_DISTINGUISH_HOST_FLAG, distinguishHost);
                if (distinguishHost) {
                    // 域名未备案时不设置 header host
                    config.setIsDistinguishHost(true);
                }
            }

            this.useL5Id = conf.getBoolean(
                    CosNConfigKeys.COSN_USE_L5_ENABLE,
                    CosNConfigKeys.DEFAULT_COSN_USE_L5_ENABLE);
            this.l5UpdateMaxRetryTimes = conf.getInt(CosNConfigKeys.COSN_L5_UPDATE_MAX_RETRIES_KEY,
                    CosNConfigKeys.DEFAULT_COSN_L5_UPDATE_MAX_RETRIES);
            if (useL5Id) {
                String l5Id = conf.get(CosNConfigKeys.COSN_L5_KEY);
                if (null != l5Id) {
                    int l5modId = Integer.parseInt(l5Id.split(",")[0]);
                    int l5cmdId = Integer.parseInt(l5Id.split(",")[1]);

                    Class l5EndpointResolverClass;
                    try {
                        l5EndpointResolverClass = Class.forName("org.apache.hadoop.fs.cosn.TencentCloudL5EndpointResolverImpl");
                        this.l5EndpointResolver = (TencentCloudL5EndpointResolver) l5EndpointResolverClass.newInstance();
                        this.l5EndpointResolver.setModId(l5modId);
                        this.l5EndpointResolver.setCmdId(l5cmdId);
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
                        throw new IOException("The current version does not support L5 resolver.", e);
                    }
                    config.setEndpointResolver(l5EndpointResolver);
                    // used by cos java sdk to handle
                    config.turnOnRefreshEndpointAddrSwitch();
                    config.setHandlerAfterProcess(l5EndpointResolver);
                }
            }
        } else {
            config = new ClientConfig(new Region(""));
            LOG.info("Use Customer Domain is {}", customerDomain);
            CustomerDomainEndpointResolver customerDomainEndpointResolver
                    = new CustomerDomainEndpointResolver();
            customerDomainEndpointResolver.setEndpoint(customerDomain);
            config.setEndpointBuilder(customerDomainEndpointResolver);
        }
        boolean useHttps = conf.getBoolean(
                CosNConfigKeys.COSN_USE_HTTPS_KEY,
                CosNConfigKeys.DEFAULT_USE_HTTPS);
        if (useHttps) {
            config.setHttpProtocol(HttpProtocol.https);
        } else {
            // 这里 config 的默认值改过了,默认值变成了https了。
            config.setHttpProtocol(HttpProtocol.http);
        }

        int socketTimeoutSec = conf.getInt(
                CosNConfigKeys.COSN_CLIENT_SOCKET_TIMEOUTSEC,
                CosNConfigKeys.DEFAULT_CLIENT_SOCKET_TIMEOUTSEC);
        config.setSocketTimeout(socketTimeoutSec * 1000);

        this.crc32cEnabled = conf.getBoolean(CosNConfigKeys.CRC32C_CHECKSUM_ENABLED,
                CosNConfigKeys.DEFAULT_CRC32C_CHECKSUM_ENABLED);
        this.completeMPUCheckEnabled = conf.getBoolean(CosNConfigKeys.COSN_COMPLETE_MPU_CHECK,
                CosNConfigKeys.DEFAULT_COSN_COMPLETE_MPU_CHECK_ENABLE);
        this.clientEncryptionEnabled = conf.getBoolean(CosNConfigKeys.COSN_CLIENT_SIDE_ENCRYPTION_ENABLED,
                CosNConfigKeys.DEFAULT_COSN_CLIENT_SIDE_ENCRYPTION_ENABLED);

        // Proxy settings
        String httpProxyIp = conf.getTrimmed(CosNConfigKeys.HTTP_PROXY_IP);
        int httpProxyPort = conf.getInt(CosNConfigKeys.HTTP_PROXY_PORT, CosNConfigKeys.DEFAULT_HTTP_PROXY_PORT);
        if (null != httpProxyIp && !httpProxyIp.isEmpty()) {
            config.setHttpProxyIp(httpProxyIp);
            if (httpProxyPort >= 0) {
                config.setHttpProxyPort(httpProxyPort);
            } else {
                if (useHttps) {
                    LOG.warn("Proxy IP set without port. Using HTTPS default port 443");
                    config.setHttpProxyPort(443);
                } else {
                    LOG.warn("Proxy IP set without port, Using HTTP default port 80");
                    config.setHttpProxyPort(80);
                }
            }
            // setting the proxy username and password
            String proxyUserName = conf.get(CosNConfigKeys.HTTP_PROXY_USERNAME);
            String proxyPassword = conf.get(CosNConfigKeys.HTTP_PROXY_PASSWORD);
            if ((null == proxyUserName || proxyUserName.isEmpty())
                    != (null == proxyPassword || proxyPassword.isEmpty())) {
                String exceptionMessage = String.format("Proxy error: '%s' or '%s' set without the other.",
                        CosNConfigKeys.HTTP_PROXY_USERNAME, CosNConfigKeys.HTTP_PROXY_PASSWORD);
                throw new IllegalArgumentException(exceptionMessage);
            }
            config.setProxyUsername(proxyUserName);
            config.setProxyPassword(proxyPassword);
        }

        String versionNum = getPluginVersionInfo();
        String versionInfo = versionNum.equals("unknown") ? CosNConfigKeys.DEFAULT_USER_AGENT : "cos-hadoop-plugin-v" + versionNum;
        String userAgent = conf.get(CosNConfigKeys.USER_AGENT, versionInfo);
        String emrVersion = conf.get(CosNConfigKeys.TENCENT_EMR_VERSION_KEY);
        if (null != emrVersion && !emrVersion.isEmpty()) {
            userAgent = String.format("%s on %s", userAgent, emrVersion);
        }
        config.setUserAgent(userAgent);

        this.maxRetryTimes = conf.getInt(
                CosNConfigKeys.COSN_MAX_RETRIES_KEY,
                CosNConfigKeys.DEFAULT_MAX_RETRIES);


        //用于客户端加密
        this.partSize = conf.getLong(
                CosNConfigKeys.COSN_UPLOAD_PART_SIZE_KEY, CosNConfigKeys.DEFAULT_UPLOAD_PART_SIZE);
        if (partSize < Constants.MIN_PART_SIZE) {
            LOG.warn("The minimum size of a single block is limited to " +
                    "greater than or equal to {}.", Constants.MIN_PART_SIZE);
            this.partSize = Constants.MIN_PART_SIZE;
        } else if (partSize > Constants.MAX_PART_SIZE) {
            LOG.warn("The maximum size of a single block is limited to " +
                    "smaller than or equal to {}.", Constants.MAX_PART_SIZE);
            this.partSize = Constants.MAX_PART_SIZE;
        }

        // 设置COSClient的最大重试次数
        int clientMaxRetryTimes = conf.getInt(
                CosNConfigKeys.CLIENT_MAX_RETRIES_KEY,
                CosNConfigKeys.DEFAULT_CLIENT_MAX_RETRIES);
        config.setMaxErrorRetry(clientMaxRetryTimes);
        LOG.info("hadoop cos retry times: {}, cos client retry times: {}",
                this.maxRetryTimes, clientMaxRetryTimes);

        // 设置连接池的最大连接数目
        config.setMaxConnectionsCount(
                conf.getInt(
                        CosNConfigKeys.MAX_CONNECTION_NUM,
                        CosNConfigKeys.DEFAULT_MAX_CONNECTION_NUM));

        // 设置是否进行服务器端加密
        String serverSideEncryptionAlgorithm = conf.get(CosNConfigKeys.COSN_SERVER_SIDE_ENCRYPTION_ALGORITHM, "");
        CosNEncryptionMethods cosSSE = CosNEncryptionMethods.getMethod(
                serverSideEncryptionAlgorithm);
        String sseKey = conf.get(
                CosNConfigKeys.COSN_SERVER_SIDE_ENCRYPTION_KEY, "");
        String sseContext = conf.get(
                CosNConfigKeys.COSN_SERVER_SIDE_ENCRYPTION_CONTEXT, "");
        checkEncryptionMethod(config, cosSSE, sseKey);
        this.encryptionSecrets = new CosNEncryptionSecrets(cosSSE, sseKey, sseContext);

        // Set the traffic limit
        this.trafficLimit = conf.getInt(CosNConfigKeys.TRAFFIC_LIMIT, CosNConfigKeys.DEFAULT_TRAFFIC_LIMIT);
        if (this.trafficLimit >= 0
                && (this.trafficLimit < 100 * 1024 * 8 || this.trafficLimit > 100 * 1024 * 1024 * 8)) {
            String exceptionMessage = String.format("The '%s' needs to be between %d and %d. but %d.",
                    CosNConfigKeys.TRAFFIC_LIMIT, 100 * 1024 * 8, 100 * 1024 * 1024 * 8, this.trafficLimit);
            throw new IllegalArgumentException(exceptionMessage);
        }

        // 设置是否进行客户端加密
        if (clientEncryptionEnabled) {

            LOG.info("client side encryption enabled");
            // 为防止请求头部被篡改导致的数据无法解密,强烈建议只使用 https 协议发起请求
            config.setHttpProtocol(HttpProtocol.https);
            KeyPair asymKeyPair = null;

            try {
                // 加载保存在文件中的秘钥, 如果不存在,请先使用buildAndSaveAsymKeyPair生成秘钥
                asymKeyPair = CosNUtils.loadAsymKeyPair(conf.get(CosNConfigKeys.COSN_CLIENT_SIDE_ENCRYPTION_PUBLIC_KEY_PATH),
                        conf.get(CosNConfigKeys.COSN_CLIENT_SIDE_ENCRYPTION_PRIVATE_KEY_PATH));
            } catch (Exception e) {
                throw new CosClientException(e);
            }
            // 初始化 KMS 加密材料
            EncryptionMaterials encryptionMaterials = new EncryptionMaterials(asymKeyPair);
            // 使用AES/GCM模式,并将加密信息存储在文件元信息中.
            CryptoConfiguration cryptoConf = new CryptoConfiguration(CryptoMode.AuthenticatedEncryption)
                    .withStorageMode(CryptoStorageMode.ObjectMetadata);
            // 生成加密客户端EncryptionClient, COSEncryptionClient是COSClient的子类, 所有COSClient支持的接口他都支持。
            // EncryptionClient覆盖了COSClient上传下载逻辑,操作内部会执行加密操作,其他操作执行逻辑和COSClient一致
            this.cosClient =
                    new COSEncryptionClient(cosCredentialProviderList,
                            new StaticEncryptionMaterialsProvider(encryptionMaterials), config,
                            cryptoConf);
            return;
        }
        this.cosClient = new COSClient(cosCredentialProviderList, config);
    }

    @Override
    public void initialize(URI uri, Configuration conf) throws IOException {
        String bucket = uri.getHost();
        RangerCredentialsClient rangerCredentialsClient = new RangerCredentialsClient();
        rangerCredentialsClient.doInitialize(conf, bucket);
        this.initialize(uri, conf, rangerCredentialsClient);
    }

    @Override
    public void initialize(URI uri, Configuration conf, RangerCredentialsClient rangerClient) throws IOException {
        // Close previously unreleased resources first
        this.preClose();

        // Begin to initialize.
        try {
            if (null == rangerClient) {
                // avoid NPE
                throw new IOException("native store ranger client param is null");
            }
            this.rangerCredentialsClient = rangerClient;
            initCOSClient(uri, conf);
            if (null == this.bucketName) {
                this.bucketName = uri.getHost();
            }
            String storageClass = conf.get(CosNConfigKeys.COSN_STORAGE_CLASS_KEY);
            if (null != storageClass && !storageClass.isEmpty()) {
                try {
                    this.storageClass = StorageClass.fromValue(storageClass);
                } catch (IllegalArgumentException e) {
                    String exceptionMessage = String.format("The specified storage class [%s] is invalid. " +
                                    "The supported storage classes are: %s, %s, %s, %s and %s.", storageClass,
                            StorageClass.Standard, StorageClass.Standard_IA,
                            StorageClass.Maz_Standard, StorageClass.Maz_Standard_IA,
                            StorageClass.Archive);

                    throw new IllegalArgumentException(exceptionMessage);
                }
            }
            if (null != this.storageClass && StorageClass.Archive == this.storageClass) {
                LOG.warn("The storage class of the CosN FileSystem is set to {}. Some file operations may not be " +
                        "supported.", this.storageClass);
            }
        } catch (Exception e) {
            LOG.error("Failed to initialize the COS native filesystem store.", e);
            handleException(e, "");
        }
    }

    private void storeFileWithRetry(String key, InputStream inputStream,
                                    byte[] md5Hash, long length)
            throws IOException {
        try {
            ObjectMetadata objectMetadata = new ObjectMetadata();
            if (null != md5Hash) {
                objectMetadata.setContentMD5(Base64.encodeAsString(md5Hash));
            }
            objectMetadata.setContentLength(length);
            if (crc32cEnabled) {
                objectMetadata.setHeader(Constants.CRC32C_REQ_HEADER, Constants.CRC32C_REQ_HEADER_VAL);
            }

            PutObjectRequest putObjectRequest =
                    new PutObjectRequest(bucketName, key, inputStream,
                            objectMetadata);
            if (null != this.storageClass) {
                putObjectRequest.setStorageClass(this.storageClass);
            }
            if (this.trafficLimit >= 0) {
                putObjectRequest.setTrafficLimit(this.trafficLimit);
            }
            this.setEncryptionMetadata(putObjectRequest, objectMetadata);

            PutObjectResult putObjectResult =
                    (PutObjectResult) callCOSClientWithRetry(putObjectRequest);
            LOG.debug("Store the file successfully. cos key: {}, ETag: {}.", key, putObjectResult.getETag());
        } catch (CosServiceException cse) {
            // 避免并发上传的问题
            int statusCode = cse.getStatusCode();
            if (statusCode == 409) {
                // Check一下这个文件是否已经存在
                FileMetadata fileMetadata = this.queryObjectMetadata(key);
                if (null == fileMetadata) {
                    // 如果文件不存在,则需要抛出异常
                    handleException(cse, key);
                }
                LOG.warn("Upload the cos key [{}] concurrently", key);
            } else {
                // 其他错误都要抛出来
                handleException(cse, key);
            }
        } catch (Exception e) {
            String errMsg =
                    String.format("Store the file failed, cos key: %s, exception: %s.", key, e);
            handleException(new Exception(errMsg), key);
        }
    }

    @Override
    public HeadBucketResult headBucket(String bucketName) throws IOException {
        HeadBucketRequest headBucketRequest = new HeadBucketRequest(bucketName);
        try {
            HeadBucketResult result = (HeadBucketResult) callCOSClientWithRetry(headBucketRequest);
            return result;
        } catch (Exception e) {
            String errMsg = String.format("head bucket [%s] occurs an exception: %s.",
                    bucketName, e);
            handleException(new Exception(errMsg), bucketName);
        }
        return null; // never will get here
    }

    @Override
    public void storeFile(String key, File file, byte[] md5Hash) throws IOException {
        if (null != md5Hash) {
            LOG.debug("Store the file, local path: {}, length: {}, md5hash: {}.",
                    file.getCanonicalPath(), file.length(),
                    Hex.encodeHexString(md5Hash));
        }
        storeFileWithRetry(key,
                new ResettableFileInputStream(file), md5Hash, file.length());
    }

    @Override
    public void storeFile(String key, InputStream inputStream,
                          byte[] md5Hash, long contentLength) throws IOException {
        if (null != md5Hash) {
            LOG.debug("Store the file to the cos key: {}, input stream md5 hash: {}, content length: {}.", key,
                    Hex.encodeHexString(md5Hash),
                    contentLength);
        }
        storeFileWithRetry(key, inputStream, md5Hash, contentLength);
    }

    // for cos, storeEmptyFile means create a directory
    @Override
    public void storeEmptyFile(String key) throws IOException {
        LOG.debug("Store an empty file to the key: {}.", key);

        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentLength(0);
        if (crc32cEnabled) {
            objectMetadata.setHeader(Constants.CRC32C_REQ_HEADER, Constants.CRC32C_REQ_HEADER_VAL);
        }

        InputStream input = new ByteArrayInputStream(new byte[0]);
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName,
                key, input, objectMetadata);
        if (null != this.storageClass) {
            putObjectRequest.setStorageClass(this.storageClass);
        }
        try {
            PutObjectResult putObjectResult =
                    (PutObjectResult) callCOSClientWithRetry(putObjectRequest);
            LOG.debug("Store the empty file successfully, cos key: {}, ETag: {}.", key, putObjectResult.getETag());
        } catch (CosServiceException cse) {
            int statusCode = cse.getStatusCode();
            if (statusCode == 409) {
                // 并发上传文件导致,再check一遍文件是否存在
                FileMetadata fileMetadata = this.queryObjectMetadata(key);
                if (null == fileMetadata) {
                    // 文件还是不存在,必须要抛出异常
                    handleException(cse, key);
                }
                LOG.warn("Upload the file [{}] concurrently.", key);
            } else {
                // 其他错误必须抛出
                handleException(cse, key);
            }
        } catch (Exception e) {
            String errMsg =
                    String.format("Store the empty file failed, cos key: %s, " +
                            "exception: %s", key, e);
            handleException(new Exception(errMsg), key);
        }
    }

    public PartETag uploadPart(File file, String key, String uploadId,
                               int partNum, byte[] md5Hash, Boolean isLastPart) throws IOException {
        InputStream inputStream = new ResettableFileInputStream(file);
        return uploadPart(inputStream, key, uploadId, partNum, file.length(), md5Hash, isLastPart);
    }

    @Override
    public PartETag uploadPart(InputStream inputStream, String key, String uploadId,
                               int partNum, long partSize, byte[] md5Hash,
                               Boolean isLastPart) throws IOException {
        LOG.debug("Upload the part to the cos key [{}]. upload id: {}, part number: {}, part size: {}",
                key, uploadId, partNum, partSize);
        ObjectMetadata objectMetadata = new ObjectMetadata();
        if (crc32cEnabled) {
            objectMetadata.setHeader(Constants.CRC32C_REQ_HEADER, Constants.CRC32C_REQ_HEADER_VAL);
        }

        UploadPartRequest uploadPartRequest = new UploadPartRequest();
        uploadPartRequest.setBucketName(this.bucketName);
        uploadPartRequest.setUploadId(uploadId);
        uploadPartRequest.setInputStream(inputStream);
        uploadPartRequest.setPartNumber(partNum);
        uploadPartRequest.setPartSize(partSize);
        uploadPartRequest.setObjectMetadata(objectMetadata);
        if(isLastPart == null && clientEncryptionEnabled) {
            throw new IOException("when client encryption is enabled, isLastPart can't be null");
        }
        if(isLastPart != null) {
            uploadPartRequest.setLastPart(isLastPart);
        }
        if (null != md5Hash && !this.clientEncryptionEnabled) {
            uploadPartRequest.setMd5Digest(Base64.encodeAsString(md5Hash));
        }
        uploadPartRequest.setKey(key);
        if (this.trafficLimit >= 0) {
            uploadPartRequest.setTrafficLimit(this.trafficLimit);
        }
        this.setEncryptionMetadata(uploadPartRequest, objectMetadata);

        try {
            UploadPartResult uploadPartResult =
                    (UploadPartResult) callCOSClientWithRetry(uploadPartRequest);
            return uploadPartResult.getPartETag();
        } catch (CosServiceException cse) {
            String errMsg = String.format("The current thread:%d, "
                            + "cos key: %s, upload id: %s, part num: %d, exception: %s",
                    Thread.currentThread().getId(), key, uploadId, partNum, cse);
            int statusCode = cse.getStatusCode();
            if (409 == statusCode) {
                // conflict upload same upload part, because of nginx syn retry,
                // sometimes 31s reconnect to cgi, but client side default 30s timeout,
                // which may cause the retry request and previous request arrive at cgi at same time.
                // so we for now use list parts to double-check this part whether exist.
                CosNPartListing partListing = listParts(key, uploadId);
                PartETag partETag = isPartExist(partListing, partNum, partSize);
                if (null == partETag) {
                    handleException(new Exception(errMsg), key);
                }
                LOG.warn("Upload the file [{}] uploadId [{}], part [{}] concurrently." +
                        key, uploadId, partNum);
                return partETag;
            } else {
                handleException(new Exception(errMsg), key);
            }
        } catch (Exception e) {
            String errMsg = String.format("The current thread:%d, "
                            + "cos key: %s, upload id: %s, part num: %d, exception: %s",
                    Thread.currentThread().getId(), key, uploadId, partNum, e);
            handleException(new Exception(errMsg), key);
        }
        return null;
    }

    public PartETag uploadPart(File file, String key, String uploadId,
                               int partNum, byte[] md5Hash) throws IOException {
        InputStream inputStream = new ResettableFileInputStream(file);
        return uploadPart(inputStream, key, uploadId, partNum, file.length(), md5Hash);
    }

    public PartETag uploadPart(
            InputStream inputStream,
            String key, String uploadId, int partNum, long partSize, byte[] md5Hash) throws IOException {
        return uploadPart(inputStream, key, uploadId, partNum, partSize, md5Hash, null);
    }

    @Override
    public PartETag uploadPartCopy(String uploadId, String srcKey, String destKey,
                                   int partNum, long firstByte, long lastByte) throws IOException {
        LOG.debug("Execute a part copy from the source key [{}] to the dest key [{}]. " +
                        "upload id: {}, part number: {}, firstByte: {}, lastByte: {}.",
                srcKey, destKey, uploadId, partNum, firstByte, lastByte);
        try {
            CopyPartRequest copyPartRequest = new CopyPartRequest();
            copyPartRequest.setSourceBucketName(this.bucketName);
            copyPartRequest.setDestinationBucketName(this.bucketName);
            copyPartRequest.setSourceEndpointBuilder(this.cosClient.getClientConfig().getEndpointBuilder());
            copyPartRequest.setUploadId(uploadId);
            copyPartRequest.setSourceKey(srcKey);
            copyPartRequest.setDestinationKey(destKey);
            copyPartRequest.setPartNumber(partNum);
            copyPartRequest.setFirstByte(firstByte);
            copyPartRequest.setLastByte(lastByte);
            CopyPartResult copyPartResult = (CopyPartResult) this.callCOSClientWithRetry(copyPartRequest);
            return copyPartResult.getPartETag();
        } catch (Exception e) {
            String exceptionMessage = String.format(
                    "Copy the object part [%d-%d] from the srcKey[%s] to the destKey[%s] failed. " +
                            "upload id: %s, part number: %d, exception: %s.",
                    firstByte, lastByte, srcKey, destKey, uploadId, partNum, e);
            handleException(new Exception(exceptionMessage), srcKey);
        }

        return null;
    }

    public void abortMultipartUpload(String key, String uploadId) throws IOException {
        LOG.info("Ready to doAbort the multipart upload. cos key: {}, upload id: {}.", key, uploadId);

        try {
            AbortMultipartUploadRequest abortMultipartUploadRequest =
                    new AbortMultipartUploadRequest(
                            bucketName, key, uploadId);
            this.callCOSClientWithRetry(abortMultipartUploadRequest);
        } catch (Exception e) {
            String errMsg = String.format("Aborting the multipart upload failed. cos key: %s, upload id: %s. " +
                    "exception: %s.", key, uploadId, e);
            handleException(new Exception(errMsg), key);
        }
    }

    /**
     * get cos upload Id
     *
     * @param key cos key
     * @return uploadId
     * @throws IOException when fail to get the MultipartManager Upload ID.
     */
    public String getUploadId(String key) throws IOException {
        if (null == key || key.length() == 0) {
            return "";
        }

        ObjectMetadata objectMetadata = new ObjectMetadata();
        if (crc32cEnabled) {
            objectMetadata.setHeader(Constants.CRC32C_REQ_HEADER, Constants.CRC32C_REQ_HEADER_VAL);
        }

        InitiateMultipartUploadRequest initiateMultipartUploadRequest =
                new InitiateMultipartUploadRequest(bucketName, key);
        if (null != this.storageClass) {
            initiateMultipartUploadRequest.setStorageClass(this.storageClass);
        }
        initiateMultipartUploadRequest.setObjectMetadata(objectMetadata);
        this.setEncryptionMetadata(initiateMultipartUploadRequest, objectMetadata);
        //客户端加密先用partsize代替datasize,后面再做替换
        if(clientEncryptionEnabled){
            initiateMultipartUploadRequest.setDataSizePartSize(this.partSize, this.partSize);
        }
        try {
            InitiateMultipartUploadResult initiateMultipartUploadResult =
                    (InitiateMultipartUploadResult) this.callCOSClientWithRetry(initiateMultipartUploadRequest);
            return initiateMultipartUploadResult.getUploadId();
        } catch (Exception e) {
            String errMsg =
                    String.format("Get the upload id failed, cos key: %s, " + "exception: %s", key, e);
            handleException(new Exception(errMsg), key);
        }

        return null;
    }

    /**
     * complete cos mpu, sometimes return null complete mpu result
     *
     * @param key          cos key
     * @param uploadId     upload id
     * @param partETagList each part etag list
     * @return result
     * @throws IOException when fail to complete the multipart upload.
     */
    public CompleteMultipartUploadResult completeMultipartUpload(
            String key, String uploadId, List partETagList) throws IOException {
        Collections.sort(partETagList, new Comparator() {
            @Override
            public int compare(PartETag o1, PartETag o2) {
                return o1.getPartNumber() - o2.getPartNumber();
            }
        });
        try {
            ObjectMetadata objectMetadata = new ObjectMetadata();
            if (crc32cEnabled) {
                objectMetadata.setHeader(Constants.CRC32C_REQ_HEADER, Constants.CRC32C_REQ_HEADER_VAL);
            }
            CompleteMultipartUploadRequest completeMultipartUploadRequest =
                    new CompleteMultipartUploadRequest(bucketName, key, uploadId,
                            partETagList);
            completeMultipartUploadRequest.setObjectMetadata(objectMetadata);
            return (CompleteMultipartUploadResult) this.callCOSClientWithRetry(completeMultipartUploadRequest);
        } catch (CosServiceException cse) {
            // when first calling with 503 access time out, next retry will 409.
            int statusCode = cse.getStatusCode();
            if (statusCode == 409) {
                // check file whether exist
                FileMetadata fileMetadata = this.queryObjectMetadata(key);
                if (null == fileMetadata) {
                    // if file not exist through exception
                    handleException(cse, key);
                }
                LOG.warn("Upload the cos key [{}] complete mpu concurrently", key);
            } else {
                // other exception
                String errMsg = String.format("Complete the multipart upload failed. " +
                        "cos service exception, cos key: %s, upload id: %s, " +
                        "exception: %s", key, uploadId, cse.toString());
                handleException(new Exception(errMsg), key);
            }
        } catch (Exception e) {
            String errMsg = String.format("Complete the multipart upload failed. " +
                    "cos key: %s, upload id: %s, " +
                    "exception: %s", key, uploadId, e.toString());
            handleException(new Exception(errMsg), key);
        }
        return null;
    }

    private FileMetadata queryObjectMetadata(String key) throws IOException {
        return queryObjectMetadata(key, null);
    }

    private FileMetadata queryObjectMetadata(String key,
                                             CosNResultInfo info) throws IOException {
        LOG.debug("Query Object metadata. cos key: {}.", key);
        GetObjectMetadataRequest getObjectMetadataRequest =
                new GetObjectMetadataRequest(bucketName, key);

        this.setEncryptionMetadata(getObjectMetadataRequest, new ObjectMetadata());
        try {
            ObjectMetadata objectMetadata =
                    (ObjectMetadata) callCOSClientWithRetry(getObjectMetadataRequest);
            long mtime = 0;
            long fileSize = 0;
            if (objectMetadata.getLastModified() != null) {
                mtime = objectMetadata.getLastModified().getTime();
            }
            fileSize = objectMetadata.getContentLength();
            if(clientEncryptionEnabled) {
                if (objectMetadata.getUserMetadata().containsKey(CSE_SINGLE_UPLOAD_FILE_SIZE)) {
                    fileSize = Long.parseLong(
                            objectMetadata.getUserMetadata().get(CSE_SINGLE_UPLOAD_FILE_SIZE));
                } else if (objectMetadata.getUserMetadata().containsKey(CSE_MPU_FILE_SIZE)) {
                    fileSize = Long.parseLong(
                            objectMetadata.getUserMetadata().get(CSE_MPU_FILE_SIZE));
                }
            }
            String ETag = objectMetadata.getETag();
            String crc64ecm = objectMetadata.getCrc64Ecma();
            String crc32cm = (String) objectMetadata.getRawMetadataValue(Constants.CRC32C_RESP_HEADER);
            String versionId = objectMetadata.getVersionId();
            Map userMetadata = null;
            if (objectMetadata.getUserMetadata() != null) {
                userMetadata = new HashMap<>();
                for (Map.Entry userMetadataEntry : objectMetadata.getUserMetadata().entrySet()) {
                    if (userMetadataEntry.getKey().startsWith(ensureValidAttributeName(XATTR_PREFIX))) {
                        String xAttrJsonStr = new String(Base64.decode(userMetadataEntry.getValue()),
                                StandardCharsets.UTF_8);
                        CosNXAttr cosNXAttr;
                        try {
                            cosNXAttr = Jackson.fromJsonString(xAttrJsonStr, CosNXAttr.class);
                        } catch (CosClientException e) {
                            LOG.warn("Parse the xAttr failed. name: {}, XAttJsonStr: {}.",
                                    userMetadataEntry.getKey(), xAttrJsonStr);
                            continue;               // Skip
                        }

                        if (null != cosNXAttr) {
                            userMetadata.put(cosNXAttr.getName(),
                                    cosNXAttr.getValue().getBytes(CosNFileSystem.METADATA_ENCODING));
                        }
                    }
                    //metadata of client side encryption
                    if(userMetadataEntry.getKey().startsWith(CLIENT_SIDE_ENCRYPTION_PREFIX)
                            || userMetadataEntry.getKey().startsWith(COS_TAG_LEN_PREFIX)) {
                        userMetadata.put(userMetadataEntry.getKey(), userMetadataEntry.getValue().getBytes(CosNFileSystem.METADATA_ENCODING));
                    }
                }
            }
            boolean isFile = true;
            if (isPosixBucket) {
                if (objectMetadata.isFileModeDir() || key.equals(CosNFileSystem.PATH_DELIMITER)) {
                    isFile = false;
                }
            } else {
                isFile = !key.endsWith(CosNFileSystem.PATH_DELIMITER);
            }
            FileMetadata fileMetadata =
                    new FileMetadata(key, fileSize, mtime, isFile,
                            ETag, crc64ecm, crc32cm, versionId,
                            objectMetadata.getStorageClass(), userMetadata);
            // record the last request result info
            if (info != null) {
                info.setRequestID(objectMetadata.getRequestId());
            }
            LOG.debug("Retrieve the file metadata. cos key: {}, ETag:{}, length:{}, crc64ecm: {}.", key,
                    objectMetadata.getETag(), objectMetadata.getContentLength(), objectMetadata.getCrc64Ecma());
            return fileMetadata;
        } catch (CosServiceException e) {
            if (info != null) {
                info.setRequestID(e.getRequestId());
            }
            if (e.getStatusCode() != 404) {
                String errorMsg =
                        String.format("Retrieve the file metadata file failure. " +
                                "cos key: %s, exception: %s", key, e);
                handleException(new Exception(errorMsg), key);
            }
        }
        return null;
    }

    @Override
    public FileMetadata retrieveMetadata(String key) throws IOException {
        return retrieveMetadata(key, null, true);
    }

    // this method only used in getFileStatus to get the head request result info
    @Override
    public FileMetadata retrieveMetadata(String key,
                                         CosNResultInfo info, boolean checkFile) throws IOException {
        if (key.endsWith(CosNFileSystem.PATH_DELIMITER)) {
            key = key.substring(0, key.length() - 1);
        }

        if (!key.isEmpty() && checkFile) {
            FileMetadata fileMetadata = queryObjectMetadata(key, info);
            if (fileMetadata != null) {
                return fileMetadata;
            }
        }
        // judge if the key is directory
        key = key + CosNFileSystem.PATH_DELIMITER;
        return queryObjectMetadata(key, info);
    }

    @Override
    public CosNSymlinkMetadata retrieveSymlinkMetadata(String symlink) throws IOException {
        return this.retrieveSymlinkMetadata(symlink, null);
    }

    @Override
    public CosNSymlinkMetadata retrieveSymlinkMetadata(String symlink, CosNResultInfo info) throws IOException {
        LOG.debug("Get the symlink [{}]'s metadata.", symlink);
        try {
            GetSymlinkRequest getSymlinkRequest = new GetSymlinkRequest(
                this.bucketName, symlink, null);
            GetSymlinkResult getSymlinkResult = (GetSymlinkResult) callCOSClientWithRetry(getSymlinkRequest);
            if (null != info) {
                info.setRequestID(getSymlinkResult.getRequestId());
            }
            return new CosNSymlinkMetadata(symlink, getSymlinkResult.getContentLength(),
                getSymlinkResult.getLastModified(), false,
                getSymlinkResult.getETag(), null, null,
                getSymlinkResult.getVersionId(), StorageClass.Standard.toString(), getSymlinkResult.getTarget());
        } catch (CosServiceException cosServiceException) {
            if (null != info) {
                info.setRequestID(cosServiceException.getRequestId());
            }
            if (cosServiceException.getStatusCode() == 400 && cosServiceException.getErrorCode()
                .compareToIgnoreCase("NotSymlink") == 0) {
                LOG.debug("The key [{}] is not a symlink.", symlink);
                return null;
            }
            if (cosServiceException.getStatusCode() != 404) {
                String errMsg = String.format("Retrieve the symlink metadata failed. symlink: %s, exception: %s.",
                    symlink, cosServiceException);
                handleException(new Exception(errMsg), symlink);
            }
        }
        return null;
    }

    @Override
    public byte[] retrieveAttribute(String key, String attribute) throws IOException {
        LOG.debug("Get the extended attribute. cos key: {}, attribute: {}.", key, attribute);

        FileMetadata fileMetadata = retrieveMetadata(key);
        if (null != fileMetadata) {
            if (null != fileMetadata.getUserAttributes()) {
                return fileMetadata.getUserAttributes().get(attribute);
            }
        }

        return null;
    }

    @Override
    public void storeDirAttribute(String key, String attribute, byte[] value) throws IOException {
        LOG.debug("Store a attribute to the specified directory. cos key: {}, attribute: {}, value: {}.",
                key, attribute, new String(value, CosNFileSystem.METADATA_ENCODING));
        if (!key.endsWith(CosNFileSystem.PATH_DELIMITER)) {
            key = key + CosNFileSystem.PATH_DELIMITER;
        }
        storeAttribute(key, attribute, value, false);
    }

    @Override
    public void storeFileAttribute(String key, String attribute, byte[] value) throws IOException {
        LOG.debug("Store a attribute to the specified file. cos key: {}, attribute: {}, value: {}.",
                key, attribute, new String(value, CosNFileSystem.METADATA_ENCODING));
        storeAttribute(key, attribute, value, false);
    }

    @Override
    public void removeDirAttribute(String key, String attribute) throws IOException {
        LOG.debug("Remove the attribute from the specified directory. cos key: {}, attribute: {}.",
                key, attribute);
        if (!key.endsWith(CosNFileSystem.PATH_DELIMITER)) {
            key = key + CosNFileSystem.PATH_DELIMITER;
        }
        storeAttribute(key, attribute, null, true);
    }

    @Override
    public void removeFileAttribute(String key, String attribute) throws IOException {
        LOG.debug("Remove the attribute from the specified file. cos key: {}, attribute: {}.",
                key, attribute);
        storeAttribute(key, attribute, null, true);
    }

    private void storeAttribute(String key, String attribute, byte[] value, boolean deleted) throws IOException {
        if (deleted) {
            LOG.debug("Delete the extended attribute. cos key: {}, attribute: {}.", key, attribute);
        }

        if (null != value && !deleted) {
            LOG.debug("Store the extended attribute. cos key: {}, attribute: {}, value: {}.",
                    key, attribute, new String(value, StandardCharsets.UTF_8));
        }

        if (null == value && !deleted) {
            throw new IOException("The attribute value to be set can not be null.");
        }

        GetObjectMetadataRequest getObjectMetadataRequest = new GetObjectMetadataRequest(bucketName, key);
        this.setEncryptionMetadata(getObjectMetadataRequest, new ObjectMetadata());
        ObjectMetadata objectMetadata = null;
        try {
            objectMetadata = (ObjectMetadata) callCOSClientWithRetry(getObjectMetadataRequest);
        } catch (CosServiceException e) {
            if (e.getStatusCode() != 404) {
                String errorMessage = String.format("Retrieve the file metadata failed. " +
                        "cos key: %s, exception: %s.", key, e);
                handleException(new Exception(errorMessage), key);
            }
        }

        if (null != objectMetadata) {
            Map userMetadata = objectMetadata.getUserMetadata();
            if (deleted) {
                if (null != userMetadata) {
                    userMetadata.remove(ensureValidAttributeName(attribute));
                } else {
                    return;
                }
            } else {
                if (null == userMetadata) {
                    userMetadata = new HashMap<>();
                }
                CosNXAttr cosNXAttr = new CosNXAttr();
                cosNXAttr.setName(attribute);
                cosNXAttr.setValue(new String(value, CosNFileSystem.METADATA_ENCODING));
                String xAttrJsonStr = Jackson.toJsonString(cosNXAttr);
                userMetadata.put(ensureValidAttributeName(XATTR_PREFIX + attribute),
                        Base64.encodeAsString(xAttrJsonStr.getBytes(StandardCharsets.UTF_8)));
            }
            objectMetadata.setUserMetadata(userMetadata);

            // 构造原地copy请求来设置用户自定义属性
            if (crc32cEnabled) {
                objectMetadata.setHeader(Constants.CRC32C_REQ_HEADER, Constants.CRC32C_REQ_HEADER_VAL);
            }

            CopyObjectRequest copyObjectRequest = new CopyObjectRequest(bucketName, key, bucketName, key);
            if (null != objectMetadata.getStorageClass()) {
                copyObjectRequest.setStorageClass(objectMetadata.getStorageClass());
            }
            copyObjectRequest.setNewObjectMetadata(objectMetadata);
            copyObjectRequest.setRedirectLocation("Replaced");
            this.setEncryptionMetadata(copyObjectRequest, objectMetadata);
            copyObjectRequest.setSourceEndpointBuilder(
                    this.cosClient.getClientConfig().getEndpointBuilder());

            try {
                callCOSClientWithRetry(copyObjectRequest);
            } catch (Exception e) {
                String errMsg = String.format("Failed to modify the user-defined attributes. " +
                        "cos key: %s, attribute: %s, exception: %s.", key, attribute, e);
                handleException(new Exception(errMsg), key);
            }
        }
    }

    /**
     * retrieve cos key
     *
     * @param key The key is the object name that is being retrieved from the
     *            cos bucket.
     * @return This method returns null if the key is not found.
     * @throws IOException Retrieve the specified cos key failed.
     */
    @Override
    public InputStream retrieve(String key) throws IOException {
        LOG.debug("Retrieve the key: {}.", key);
        GetObjectRequest getObjectRequest =
                new GetObjectRequest(this.bucketName, key);
        if (this.trafficLimit >= 0) {
            getObjectRequest.setTrafficLimit(this.trafficLimit);
        }
        this.setEncryptionMetadata(getObjectRequest, new ObjectMetadata());

        try {
            COSObject cosObject =
                    (COSObject) callCOSClientWithRetry(getObjectRequest);
            return cosObject.getObjectContent();
        } catch (Exception e) {
            String errMsg = String.format("Retrieving the cos key [%s] occurs an exception: %s", key, e);
            handleException(new Exception(errMsg), key);
        }

        return null; // never will get here
    }

    /**
     * @param key The key is the object name that is being retrieved from the
     *            cos bucket.
     * @return This method returns null if the key is not found.
     * @throws IOException Retrieve the specified cos key with a specified byte range start failed.
     */

    @Override
    public InputStream retrieve(String key, long byteRangeStart) throws IOException {
        try {
            LOG.debug("Retrieve the cos key: {}, byte range start: {}.", key, byteRangeStart);
            long fileSize = getFileLength(key);
            long byteRangeEnd = fileSize - 1;
            GetObjectRequest getObjectRequest =
                    new GetObjectRequest(this.bucketName, key);
            if (this.trafficLimit >= 0) {
                getObjectRequest.setTrafficLimit(this.trafficLimit);
            }
            this.setEncryptionMetadata(getObjectRequest, new ObjectMetadata());

            if (byteRangeEnd >= byteRangeStart) {
                getObjectRequest.setRange(byteRangeStart, fileSize - 1);
            }
            COSObject cosObject =
                    (COSObject) callCOSClientWithRetry(getObjectRequest);
            return cosObject.getObjectContent();
        } catch (Exception e) {
            String errMsg =
                    String.format("Retrieving key [%s] with byteRangeStart [%d] " +
                                    "occurs an exception: %s.",
                            key, byteRangeStart, e);
            handleException(new Exception(errMsg), key);
            return null; // never will get here
        }
    }

    @Override
    public InputStream retrieveBlock(String key, long byteRangeStart,
                                     long byteRangeEnd) throws IOException {
        LOG.debug("Retrieve the cos key: {}, byte range start: {}, byte range end: {}.", key, byteRangeStart,
                byteRangeEnd);
        try {
            GetObjectRequest request = new GetObjectRequest(this.bucketName,
                    key);
            request.setRange(byteRangeStart, byteRangeEnd);
            if (this.trafficLimit >= 0) {
                request.setTrafficLimit(this.trafficLimit);
            }
            this.setEncryptionMetadata(request, new ObjectMetadata());
            COSObject cosObject =
                    (COSObject) this.callCOSClientWithRetry(request);
            return cosObject.getObjectContent();
        } catch (CosServiceException e) {
            String errMsg =
                    String.format("Retrieving the key %s with the byteRangeStart [%d] " +
                                    "occurs an exception: %s.",
                            key, byteRangeStart, e);
            handleException(new Exception(errMsg), key);
        } catch (CosClientException e) {
            String errMsg =
                    String.format("Retrieving key %s with the byteRangeStart [%d] and the byteRangeEnd [%d] " +
                            "occurs an exception: %s.", key, byteRangeStart, byteRangeEnd, e);
            handleException(new Exception(errMsg), key);
        }

        return null;
    }

    public ObjectMetadata getClientSideEncryptionHeader(FileMetadata sourceFileMetadata) {
        Map userClientEncryptionMetadata = new HashMap<>();
        for (Map.Entry userMetadataEntry : sourceFileMetadata.getUserAttributes().entrySet()) {
            if((userMetadataEntry.getKey().startsWith(CLIENT_SIDE_ENCRYPTION_PREFIX))
                    || userMetadataEntry.getKey().startsWith(COS_TAG_LEN_PREFIX)){
                userClientEncryptionMetadata.put(userMetadataEntry.getKey(), new String(userMetadataEntry.getValue()));
            }
        }
        ObjectMetadata newObjectMetadata = new ObjectMetadata();
        if(! userClientEncryptionMetadata.isEmpty()) {
            newObjectMetadata.setUserMetadata(userClientEncryptionMetadata);
        }
        return  newObjectMetadata;
    }

    @Override
    public boolean retrieveBlock(String key, long byteRangeStart,
                                 long blockSize,
                                 String localBlockPath) throws IOException {
        long fileSize = getFileLength(key);
        long byteRangeEnd = 0;
        try {
            GetObjectRequest request = new GetObjectRequest(this.bucketName,
                    key);
            if (this.trafficLimit >= 0) {
                request.setTrafficLimit(this.trafficLimit);
            }
            this.setEncryptionMetadata(request, new ObjectMetadata());
            if (fileSize > 0) {
                byteRangeEnd = Math.min(fileSize - 1,
                        byteRangeStart + blockSize - 1);
                request.setRange(byteRangeStart, byteRangeEnd);
            }
            cosClient.getObject(request, new File(localBlockPath));
            return true;
        } catch (Exception e) {
            String errMsg =
                    String.format("Retrieving block key [%s] with range [%d - %d] " +
                                    "occurs an exception: %s",
                            key, byteRangeStart, byteRangeEnd, e.getMessage());
            handleException(new Exception(errMsg), key);
            return false; // never will get here
        }
    }


    @Override
    public CosNPartialListing list(String prefix, int maxListingLength) throws IOException {
        return list(prefix, maxListingLength, null);
    }

    @Override
    public CosNPartialListing list(String prefix, int maxListingLength, CosNResultInfo info) throws IOException {
        return list(prefix, maxListingLength, null, false, info);
    }

    @Override
    public CosNPartialListing list(String prefix, int maxListingLength,
                                   String priorLastKey,
                                   boolean recurse) throws IOException {
        return list(prefix, maxListingLength, priorLastKey, recurse, null);
    }

    @Override
    public CosNPartialListing list(String prefix, int maxListingLength,
                                   String priorLastKey,
                                   boolean recurse, CosNResultInfo info) throws IOException {
        return list(prefix, recurse ? null : CosNFileSystem.PATH_DELIMITER, maxListingLength, priorLastKey, info);
    }

    /**
     * list objects
     *
     * @param prefix           prefix
     * @param delimiter        delimiter
     * @param maxListingLength max no. of entries
     * @param priorLastKey     last key in any previous search
     * @return a list of matches
     * @throws IOException on any reported failure
     */

    private CosNPartialListing list(String prefix, String delimiter,
                                    int maxListingLength,
                                    String priorLastKey, CosNResultInfo info) throws IOException {
        LOG.debug("List the cos key prefix: {}, max listing length: {}, delimiter: {}, prior last key: {}.",
                prefix,
                delimiter, maxListingLength, priorLastKey);
        if (!prefix.startsWith(CosNFileSystem.PATH_DELIMITER)) {
            prefix += CosNFileSystem.PATH_DELIMITER;
        }
        ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
        listObjectsRequest.setBucketName(bucketName);
        listObjectsRequest.setPrefix(prefix);
        listObjectsRequest.setDelimiter(delimiter);
        listObjectsRequest.setMarker(priorLastKey);
        listObjectsRequest.setMaxKeys(maxListingLength);
        ObjectListing objectListing = null;
        try {
            objectListing =
                    (ObjectListing) callCOSClientWithRetry(listObjectsRequest);
        } catch (Exception e) {
            String errMsg = String.format("prefix: %s, delimiter: %s, maxListingLength: %d, "
                            + "priorLastKey: %s. list occur a exception: %s",
                    prefix, (delimiter == null) ? "" : delimiter, maxListingLength, priorLastKey, e);
            LOG.error(errMsg);
            handleException(new Exception(errMsg), prefix);
        }
        if (null == objectListing) {
            String errMessage = String.format("List objects failed for the prefix: %s, delimiter: %s, "
                            + "maxListingLength:%d, priorLastKey: %s.", prefix, (delimiter == null) ? "" : delimiter,
                    maxListingLength, priorLastKey);
            handleException(new Exception(errMessage), prefix);
        }

        ArrayList fileMetadataArray =
                new ArrayList<>();
        ArrayList commonPrefixArray =
                new ArrayList<>();
        List summaries = objectListing.getObjectSummaries();
        boolean isKeySamePrefix = false;
        for (COSObjectSummary cosObjectSummary : summaries) {
            String filePath = cosObjectSummary.getKey();
            if (!filePath.startsWith(CosNFileSystem.PATH_DELIMITER)) {
                filePath = CosNFileSystem.PATH_DELIMITER + filePath;
            }
            if (filePath.equals(prefix)) {
                isKeySamePrefix = true;
                continue;
            }
            long mtime = 0;
            if (cosObjectSummary.getLastModified() != null) {
                mtime = cosObjectSummary.getLastModified().getTime();
            }
            long fileLen = cosObjectSummary.getSize();
            String fileEtag = cosObjectSummary.getETag();
            if (cosObjectSummary.getKey().endsWith(CosNFileSystem.PATH_DELIMITER) && cosObjectSummary.getSize() == 0) {
                fileMetadataArray.add(new FileMetadata(filePath, fileLen, mtime, false,
                        fileEtag, null, null, null,
                        cosObjectSummary.getStorageClass()));
            } else {
                fileMetadataArray.add(new FileMetadata(filePath, fileLen, mtime,
                        true, fileEtag, null, null, null,
                        cosObjectSummary.getStorageClass()));
            }
        }
        List commonPrefixes = objectListing.getCommonPrefixes();
        for (String commonPrefix : commonPrefixes) {
            if (!commonPrefix.startsWith(CosNFileSystem.PATH_DELIMITER)) {
                commonPrefix = CosNFileSystem.PATH_DELIMITER + commonPrefix;
            }
            commonPrefixArray.add(new FileMetadata(commonPrefix, 0, 0, false));
        }

        FileMetadata[] fileMetadata = new FileMetadata[fileMetadataArray.size()];
        for (int i = 0; i < fileMetadataArray.size(); ++i) {
            fileMetadata[i] = fileMetadataArray.get(i);
        }
        FileMetadata[] commonPrefixMetaData =
                new FileMetadata[commonPrefixArray.size()];
        for (int i = 0; i < commonPrefixArray.size(); ++i) {
            commonPrefixMetaData[i] = commonPrefixArray.get(i);
        }

        // 如果truncated为false, 则表明已经遍历完
        if (!objectListing.isTruncated()) {
            CosNPartialListing ret = new CosNPartialListing(null, fileMetadata, commonPrefixMetaData);
            if (info != null) {
                info.setRequestID(objectListing.getRequestId());
                info.setKeySameToPrefix(isKeySamePrefix);
            }
            return ret;
        } else {
            CosNPartialListing ret = new CosNPartialListing(objectListing.getNextMarker(),
                    fileMetadata, commonPrefixMetaData);
            if (info != null) {
                info.setRequestID(objectListing.getRequestId());
                info.setKeySameToPrefix(isKeySamePrefix);
            }
            return ret;
        }
    }

    @Override
    public void delete(String key) throws IOException {
        LOG.debug("Delete the cos key: {} from bucket: {}.", key, this.bucketName);
        try {
            DeleteObjectRequest deleteObjectRequest =
                    new DeleteObjectRequest(bucketName, key);
            callCOSClientWithRetry(deleteObjectRequest);
        } catch (Exception e) {
            String errMsg = String.format("Deleting the cos key [%s] occurs an exception: " +
                    "%s", key, e);
            handleException(new Exception(errMsg), key);
        }
    }

    /**
     * delete recursive only used on posix bucket to delete dir recursive
     *
     * @param key cos key
     * @throws IOException e
     */
    @Override
    public void deleteRecursive(String key) throws IOException {
        LOG.debug("Delete the cos key recursive: {} from bucket: {}.", key, this.bucketName);
        try {
            DeleteObjectRequest deleteObjectRequest =
                    new DeleteObjectRequest(bucketName, key);
            deleteObjectRequest.setRecursive(true);
            callCOSClientWithRetry(deleteObjectRequest);
        } catch (Exception e) {
            String errMsg = String.format("Deleting the cos key recursive [%s] occurs an exception: " +
                    "%s", key, e);
            handleException(new Exception(errMsg), key);
        }
    }

    @Override
    public void copy(String srcKey, String dstKey) throws IOException {
        try {
            FileMetadata sourceFileMetadata = this.retrieveMetadata(srcKey);
            ObjectMetadata objectMetadata = getClientSideEncryptionHeader(sourceFileMetadata);
            if (crc32cEnabled) {
                objectMetadata.setHeader(Constants.CRC32C_REQ_HEADER, Constants.CRC32C_REQ_HEADER_VAL);
            }

            CopyObjectRequest copyObjectRequest =
                    new CopyObjectRequest(bucketName, srcKey, bucketName, dstKey);
            // 如果 sourceFileMetadata 为 null,则有可能这个文件是个软链接,但是也兼容copy
            if (null != sourceFileMetadata && null != sourceFileMetadata.getStorageClass()) {
                copyObjectRequest.setStorageClass(sourceFileMetadata.getStorageClass());
            }
            copyObjectRequest.setNewObjectMetadata(objectMetadata);
            this.setEncryptionMetadata(copyObjectRequest, objectMetadata);
            copyObjectRequest.setSourceEndpointBuilder(this.cosClient.getClientConfig().getEndpointBuilder());
            callCOSClientWithRetry(copyObjectRequest);
        } catch (Exception e) {
            String errMsg = String.format(
                    "Copy the object failed, src cos key: %s, dst cos key: %s, " +
                            "exception: %s", srcKey, dstKey, e);
            handleException(new Exception(errMsg), srcKey);
        }
    }

    /**
     * rename operation
     *
     * @param srcKey src cos key
     * @param dstKey dst cos key
     * @throws IOException when fail to rename the resource key to the dest key.
     */
    @Override
    public void rename(String srcKey, String dstKey) throws IOException {
        if (!isPosixBucket) {
            normalBucketRename(srcKey, dstKey);
        } else {
            posixBucketRename(srcKey, dstKey);
        }
    }

    @Override
    public void createSymlink(String symLink, String targetKey) throws IOException {
        LOG.debug("Create a symlink [{}] for the target object key [{}].", symLink, targetKey);
        try {
            PutSymlinkRequest putSymlinkRequest = new PutSymlinkRequest(this.bucketName, symLink, targetKey);
            PutSymlinkResult putSymlinkResult = (PutSymlinkResult) callCOSClientWithRetry(putSymlinkRequest);
        } catch (Exception e) {
            String errMsg = String.format(
                "Create the symlink failed. symlink: %s, target cos key: %s, exception: %s.",
                symLink, targetKey, e);
            handleException(new Exception(errMsg), symLink);
        }
    }

    @Override
    public String getSymlink(String symlink) throws IOException {
        LOG.debug("Get the symlink [{}].", symlink);
        try {
            GetSymlinkRequest getSymlinkRequest = new GetSymlinkRequest(
                this.bucketName, symlink, null);
            GetSymlinkResult getSymlinkResult = (GetSymlinkResult) callCOSClientWithRetry(getSymlinkRequest);
            return getSymlinkResult.getTarget();
        } catch (CosServiceException cosServiceException) {
            if (cosServiceException.getStatusCode() == 400 &&
                cosServiceException.getErrorCode().compareToIgnoreCase("NotSymlink") == 0) {
                LOG.debug("The key [{}] is not a symlink.", symlink);
                return null;
            }

            if (cosServiceException.getStatusCode() != 404) {
                String errMsg = String.format(
                    "Get the symlink failed. symlink: %s, exception: %s.", symlink, cosServiceException);
                handleException(new Exception(errMsg), symlink);
            }
        }
        return null;
    }

    public void normalBucketRename(String srcKey, String dstKey) throws IOException {
        LOG.debug("Rename normal bucket key, the source cos key [{}] to the dest cos key [{}].", srcKey, dstKey);
        try {
            FileMetadata sourceFileMetadata = this.retrieveMetadata(srcKey);
            ObjectMetadata objectMetadata = getClientSideEncryptionHeader(sourceFileMetadata);
            if (crc32cEnabled) {
                objectMetadata.setHeader(Constants.CRC32C_REQ_HEADER, Constants.CRC32C_REQ_HEADER_VAL);
            }
            CopyObjectRequest copyObjectRequest =
                    new CopyObjectRequest(bucketName, srcKey, bucketName, dstKey);
            // get the storage class of the source file
            if (null != sourceFileMetadata.getStorageClass()) {
                copyObjectRequest.setStorageClass(sourceFileMetadata.getStorageClass());
            }
            copyObjectRequest.setNewObjectMetadata(objectMetadata);
            this.setEncryptionMetadata(copyObjectRequest, objectMetadata);
            copyObjectRequest.setSourceEndpointBuilder(this.cosClient.getClientConfig().getEndpointBuilder());
            callCOSClientWithRetry(copyObjectRequest);
            DeleteObjectRequest deleteObjectRequest =
                    new DeleteObjectRequest(bucketName, srcKey);
            callCOSClientWithRetry(deleteObjectRequest);
        } catch (Exception e) {
            String errMsg = String.format(
                    "Rename object failed, normal bucket, source cos key: %s, dest cos key: %s, " +
                            "exception: %s", srcKey, dstKey, e);
            handleException(new Exception(errMsg), srcKey);
        }
    }

    public void posixBucketRename(String srcKey, String dstKey) throws IOException {
        LOG.debug("Rename posix bucket key, the source cos key [{}] to the dest cos key [{}].", srcKey, dstKey);
        try {
            RenameRequest renameRequest = new RenameRequest(bucketName, srcKey, dstKey);
            callCOSClientWithRetry(renameRequest);
        } catch (Exception e) {
            String errMsg = String.format(
                    "Rename object failed, posix bucket, source cos key: %s, dest cos key: %s, " +
                            "exception: %s", srcKey, dstKey, e);
            handleException(new Exception(errMsg), srcKey);
        }
    }

    @Override
    public void purge(String prefix) throws IOException {
        throw new IOException("purge not supported");
    }

    @Override
    public void dump() throws IOException {
        throw new IOException("dump not supported");
    }

    @Override
    public void close() {
        if (null != this.cosClient) {
            this.cosClient.shutdown();
        }

        if (null != this.rangerCredentialsClient) {
            this.rangerCredentialsClient.close();
        }

        this.rangerCredentialsClient = null;
        this.cosClient = null;
    }

    private void preClose() {
        if (null != this.cosClient) {
            this.cosClient.shutdown();
        }

        this.cosClient = null;
    }

    @Override
    public void setPosixBucket(boolean isPosixBucket) {
        this.isPosixBucket = isPosixBucket;
    }

    // process Exception and print detail
    private void handleException(Exception e, String key) throws IOException {
        String cosPath = "cosn://" + bucketName + key;
        String exceptInfo = String.format("%s : %s", cosPath, e.toString());
        throw new IOException(exceptInfo);
    }

    @Override
    public long getFileLength(String key) throws IOException {
        GetObjectMetadataRequest getObjectMetadataRequest =
                new GetObjectMetadataRequest(bucketName, key);
        this.setEncryptionMetadata(getObjectMetadataRequest, new ObjectMetadata());
        try {
            ObjectMetadata objectMetadata =
                    (ObjectMetadata) callCOSClientWithRetry(getObjectMetadataRequest);
            return objectMetadata.getContentLength();
        } catch (Exception e) {
            String errMsg = String.format("Getting the file length occurs an exception, " +
                            "cos key: %s, exception: %s",
                    key, e.getMessage());
            handleException(new Exception(errMsg), key);
            return 0; // never will get here
        }
    }

    @Override
    public CosNPartListing listParts(String key, String uploadId) throws IOException {
        LOG.debug("List parts key: {}, uploadId: {}", key, uploadId);
        ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, key, uploadId);
        PartListing partListing = null;
        List partSummaries = new LinkedList<>();
        do {
           try {
               partListing = (PartListing) callCOSClientWithRetry(listPartsRequest);
           } catch (Exception e) {
               String errMsg = String.format("list parts occurs an exception, " +
                               "cos key: %s, upload id: %s, exception: %s",
                       key, uploadId, e.getMessage());
               LOG.error(errMsg);
               handleException(new Exception(errMsg), key);
               return null;
           }
           partSummaries.addAll(partListing.getParts());
           listPartsRequest.setPartNumberMarker(partListing.getNextPartNumberMarker());
        } while (partListing.isTruncated());
        return new CosNPartListing(partSummaries);
    }

    @Override
    public boolean isPosixBucket() {
        return this.isPosixBucket;
    }

    private PartETag isPartExist(CosNPartListing partListing, int partNum, long partSize) {
        PartETag ret = null;
        if (null == partListing) {
            return null;
        }
        for (PartSummary partSummary : partListing.getPartSummaries()){
            // for now only check number and size.
            if (partSummary.getPartNumber() == partNum && partSummary.getSize() == partSize) {
                ret = new PartETag(partSummary.getPartNumber(), partSummary.getETag());
                break;
            }
        }
        return ret;
    }

    private  void callCOSClientWithSSEKMS(X request, SSECOSKeyManagementParams managementParams) {
        try {
            if (request instanceof PutObjectRequest) {
                ((PutObjectRequest) request).setSSECOSKeyManagementParams(managementParams);
            } else if (request instanceof CopyObjectRequest) {
                ((CopyObjectRequest) request).setSSECOSKeyManagementParams(managementParams);
            } else if (request instanceof InitiateMultipartUploadRequest) {
                ((InitiateMultipartUploadRequest) request).setSSECOSKeyManagementParams(managementParams);
            }
        } catch (Exception e) {
            String errMsg =
                    String.format("callCOSClientWithSSEKMS failed:" +
                            " %s", e);
            LOG.error(errMsg);
        }
    }

    private  void callCOSClientWithSSECOS(X request, ObjectMetadata objectMetadata) {
        try {
            objectMetadata.setServerSideEncryption(SSEAlgorithm.AES256.getAlgorithm());
            if (request instanceof PutObjectRequest) {
                ((PutObjectRequest) request).setMetadata(objectMetadata);
            } else if (request instanceof UploadPartRequest) {
                ((UploadPartRequest) request).setObjectMetadata(objectMetadata);
            } else if (request instanceof CopyObjectRequest) {
                ((CopyObjectRequest) request).setNewObjectMetadata(objectMetadata);
            } else if (request instanceof InitiateMultipartUploadRequest) {
                ((InitiateMultipartUploadRequest) request).setObjectMetadata(objectMetadata);
            }
        } catch (Exception e) {
            String errMsg = String.format("callCOSClientWithSSECOS failed: %s", e);
            LOG.error(errMsg);
        }
    }

    private  void callCOSClientWithSSEC(X request, SSECustomerKey sseKey) {
        try {
            if (request instanceof PutObjectRequest) {
                ((PutObjectRequest) request).setSSECustomerKey(sseKey);
            } else if (request instanceof UploadPartRequest) {
                ((UploadPartRequest) request).setSSECustomerKey(sseKey);
            } else if (request instanceof GetObjectMetadataRequest) {
                ((GetObjectMetadataRequest) request).setSSECustomerKey(sseKey);
            } else if (request instanceof CopyObjectRequest) {
                ((CopyObjectRequest) request).setDestinationSSECustomerKey(sseKey);
                ((CopyObjectRequest) request).setSourceSSECustomerKey(sseKey);
            } else if (request instanceof GetObjectRequest) {
                ((GetObjectRequest) request).setSSECustomerKey(sseKey);
            } else if (request instanceof InitiateMultipartUploadRequest) {
                ((InitiateMultipartUploadRequest) request).setSSECustomerKey(sseKey);
            }
        } catch (Exception e) {
            String errMsg = String.format("callCOSClientWithSSEC failed: %s", e);
            LOG.error(errMsg);
        }
    }

    private  void setEncryptionMetadata(X request, ObjectMetadata objectMetadata) {
        switch (encryptionSecrets.getEncryptionMethod()) {
            case SSE_C:
                callCOSClientWithSSEC(request, new SSECustomerKey(encryptionSecrets.getEncryptionKey()));
                break;
            case SSE_COS:
                callCOSClientWithSSECOS(request, objectMetadata);
                break;
            case SSE_KMS:
                String key = encryptionSecrets.getEncryptionKey();
                String context = Base64.encodeAsString(encryptionSecrets.getEncryptionContext().getBytes());
                SSECOSKeyManagementParams ssecosKeyManagementParams = new SSECOSKeyManagementParams(key, context);
                callCOSClientWithSSEKMS(request, ssecosKeyManagementParams);
                break;
            case NONE:
            default:
                break;
        }
    }

    private void checkEncryptionMethod(ClientConfig config,
                                       CosNEncryptionMethods cosSSE, String sseKey) throws IOException {
        int sseKeyLen = StringUtils.isNullOrEmpty(sseKey) ? 0 : sseKey.length();

        String description = "Encryption key:";
        if (sseKey == null) {
            description += "null ";
        } else {
            switch (sseKeyLen) {
                case 0:
                    description += " empty";
                    break;
                case 1:
                    description += " of length 1";
                    break;
                default:
                    description = description + " of length " + sseKeyLen + " ending with "
                            + sseKey.charAt(sseKeyLen - 1);
            }
        }

        switch (cosSSE) {
            case SSE_C:
                LOG.debug("Using SSE_C with {}", description);
                config.setHttpProtocol(HttpProtocol.https);
                if (sseKeyLen == 0) {
                    throw new IOException("missing encryption key for SSE_C ");
                } else if (!sseKey.matches(CosNConfigKeys.BASE64_PATTERN)) {
                    throw new IOException("encryption key need to Base64 encoding for SSE_C ");
                }
                break;
            case SSE_COS:
                if (sseKeyLen != 0) {
                    LOG.debug("Using SSE_COS");
                    throw new IOException("SSE_COS to encryption with key error: "
                            + " (" + description + ")");
                }
                break;
            case SSE_KMS:
                // sseKeyLen can be empty which auto used by cos server
                System.setProperty(SkipMd5CheckStrategy.DISABLE_PUT_OBJECT_MD5_VALIDATION_PROPERTY, "true");
                config.setHttpProtocol(HttpProtocol.https);
            case NONE:
            default:
                LOG.debug("Data is unencrypted");
                break;
        }
    }

    // posix bucket mkdir if the middle part exist will return the 500 error,
    // and the rename if the dst exist will return the 500 status too,
    // which make the related 5** retry useless. mo improve the resp info to filter.
    private  Object callCOSClientWithRetry(X request) throws CosServiceException, IOException {
        String sdkMethod = "";
        int retryIndex = 1;
        int l5ErrorCodeRetryIndex = 1;
        while (true) {
            try {
                if (request instanceof PutObjectRequest) {
                    sdkMethod = "putObject";
                    if (((PutObjectRequest) request).getInputStream().markSupported()) {
                        ((PutObjectRequest) request).getInputStream()
                                .mark((int) ((PutObjectRequest) request).getMetadata().getContentLength());
                    }
                    return this.cosClient.putObject((PutObjectRequest) request);
                } else if (request instanceof UploadPartRequest) {
                    sdkMethod = "uploadPart";
                    if (((UploadPartRequest) request).getInputStream().markSupported()) {
                        ((UploadPartRequest) request).getInputStream()
                                .mark((int) ((UploadPartRequest) request).getPartSize());
                    }
                    return this.cosClient.uploadPart((UploadPartRequest) request);
                } else if (request instanceof CopyPartRequest) {
                    sdkMethod = "copyPartRequest";
                    return this.cosClient.copyPart((CopyPartRequest) request);
                } else if (request instanceof HeadBucketRequest) { // use for checking bucket type
                    sdkMethod = "headBucket";
                    return this.cosClient.headBucket((HeadBucketRequest) request);
                } else if (request instanceof RenameRequest) {
                    sdkMethod = "rename";
                    this.cosClient.rename((RenameRequest) request);
                    return new Object();
                } else if (request instanceof GetObjectMetadataRequest) {
                    sdkMethod = "queryObjectMeta";
                    return this.cosClient.getObjectMetadata((GetObjectMetadataRequest) request);
                } else if (request instanceof DeleteObjectRequest) {
                    sdkMethod = "deleteObject";
                    this.cosClient.deleteObject((DeleteObjectRequest) request);
                    return new Object();
                } else if (request instanceof CopyObjectRequest) {
                    sdkMethod = "copyFile";
                    return this.cosClient.copyObject((CopyObjectRequest) request);
                } else if (request instanceof GetObjectRequest) {
                    sdkMethod = "getObject";
                    return this.cosClient.getObject((GetObjectRequest) request);
                } else if (request instanceof ListObjectsRequest) {
                    sdkMethod = "listObjects";
                    return this.cosClient.listObjects((ListObjectsRequest) request);
                } else if (request instanceof InitiateMultipartUploadRequest) {
                    sdkMethod = "initiateMultipartUpload";
                    return this.cosClient.initiateMultipartUpload((InitiateMultipartUploadRequest) request);
                } else if (request instanceof CompleteMultipartUploadRequest) {
                    sdkMethod = "completeMultipartUpload";
                    return this.cosClient.completeMultipartUpload((CompleteMultipartUploadRequest) request);
                } else if (request instanceof AbortMultipartUploadRequest) {
                    sdkMethod = "abortMultipartUpload";
                    this.cosClient.abortMultipartUpload((AbortMultipartUploadRequest) request);
                    return new Object();
                } else if (request instanceof PutSymlinkRequest) {
                    sdkMethod = "putSymlink";
                    return this.cosClient.putSymlink((PutSymlinkRequest) request);
                } else if (request instanceof GetSymlinkRequest) {
                    sdkMethod = "getSymlink";
                    return this.cosClient.getSymlink((GetSymlinkRequest) request);
                } else if (request instanceof ListPartsRequest) {
                    sdkMethod = "listParts";
                    return this.cosClient.listParts((ListPartsRequest) request);
                } else {
                    throw new IOException("no such method");
                }
            } catch (ResponseNotCompleteException nce) {
                if (this.completeMPUCheckEnabled && request instanceof CompleteMultipartUploadRequest) {
                    String key = ((CompleteMultipartUploadRequest) request).getKey();
                    FileMetadata fileMetadata = this.queryObjectMetadata(key);
                    if (null == fileMetadata) {
                        // if file not exist must throw the exception.
                        handleException(nce, key);
                    }
                    LOG.warn("Complete mpu resp not complete key [{}]", key);
                    // todo: some other double check after cgi unified the ctime of mpu.
                    return new CompleteMultipartUploadResult();
                } else {
                    throw new IOException(nce);
                }
            } catch (CosServiceException cse) {
                String errMsg = String.format("all cos sdk failed, retryIndex: [%d / %d], "
                        + "call method: %s, exception: %s", retryIndex, this.maxRetryTimes, sdkMethod, cse);
                int statusCode = cse.getStatusCode();
                String errorCode = cse.getErrorCode();
                LOG.debug("fail to retry statusCode {}, errorCode {}", statusCode, errorCode);
                // 对5xx错误进行重试
                if (request instanceof CopyObjectRequest && hasErrorCode(statusCode, errorCode)) {
                    if (retryIndex <= this.maxRetryTimes) {
                        LOG.info(errMsg, cse);
                        ++retryIndex;
                    } else {
                        LOG.error(errMsg, cse);
                        throw new IOException(errMsg);
                    }
                } else if (request instanceof CompleteMultipartUploadRequest && hasErrorCode(statusCode, errorCode)) {
                    // complete mpu error code might be in body when status code is 200
                    // double check to head object only works in big data job case which key is not same.
                    String key = ((CompleteMultipartUploadRequest) request).getKey();
                    FileMetadata fileMetadata = this.queryObjectMetadata(key);
                    if (null != fileMetadata) {
                        // if file exist direct return.
                        LOG.info("complete mpu error in body, error code {}, but key {} already exist, length {}",
                                errorCode, key, fileMetadata.getLength());
                        return new CompleteMultipartUploadResult();
                    }
                    // here same like the copy request not setting the interval sleep for now
                    if (retryIndex <= this.maxRetryTimes) {
                        LOG.info(errMsg, cse);
                        ++retryIndex;
                    } else {
                        LOG.error(errMsg, cse);
                        throw new IOException(errMsg);
                    }
                } else if (statusCode / 100 == 5) {
                    if (retryIndex <= this.maxRetryTimes) {

                        if (statusCode == 503) {
                            if (useL5Id) {
                                if (l5ErrorCodeRetryIndex >= this.l5UpdateMaxRetryTimes) {
                                    // L5上报,进行重试
                                    l5EndpointResolver.handle(-1, 0);
                                    l5ErrorCodeRetryIndex = 1;
                                } else {
                                    l5ErrorCodeRetryIndex = l5ErrorCodeRetryIndex + 1;
                                }
                            }
                        }

                        LOG.info(errMsg, cse);
                        long sleepLeast = retryIndex * 300L;
                        long sleepBound = retryIndex * 500L;
                        try {
                            if (request instanceof PutObjectRequest) {
                                LOG.info("Try to reset the put object request input stream.");
                                if (((PutObjectRequest) request).getInputStream().markSupported()) {
                                    ((PutObjectRequest) request).getInputStream().reset();
                                } else {
                                    LOG.error("The put object request input stream can not be reset, so it can not be" +
                                            " retried.");
                                    throw cse;
                                }
                            }

                            if (request instanceof UploadPartRequest) {
                                LOG.info("Try to reset the upload part request input stream.");
                                if (((UploadPartRequest) request).getInputStream().markSupported()) {
                                    ((UploadPartRequest) request).getInputStream().reset();
                                } else {
                                    LOG.error("The upload part request input stream can not be reset, so it can not " +
                                            "be retried" +
                                            ".");
                                    throw cse;
                                }
                            }

                            // mpu might occur 503 access time out but already completed,
                            // if direct retry may occur 403 not found the upload id.
                            if (request instanceof CompleteMultipartUploadRequest && statusCode == 503) {
                                String key = ((CompleteMultipartUploadRequest) request).getKey();
                                FileMetadata fileMetadata = this.queryObjectMetadata(key);
                                if (null != fileMetadata) {
                                    // if file exist direct return.
                                    LOG.info("complete mpu error might access time out, " +
                                                    "but key {} already exist, length {}",
                                            key, fileMetadata.getLength());
                                    return new CompleteMultipartUploadResult();
                                }
                            }

                            Thread.sleep(
                                    ThreadLocalRandom.current().nextLong(sleepLeast, sleepBound));
                            ++retryIndex;
                        } catch (InterruptedException e) {
                            throw new IOException(e.toString());
                        }
                    } else {
                        LOG.error(errMsg, cse);
                        throw new IOException(errMsg);
                    }
                } else {
                    throw cse;
                }
            } catch (Exception e) {
                throw new IOException(e);
            }
        }
    }

    private static String ensureValidAttributeName(String attributeName) {
        return attributeName.replace('.', '-').toLowerCase();
    }

    private boolean hasErrorCode(int statusCode, String errCode) {
        return statusCode / 100 == 2 && errCode != null && !errCode.isEmpty();
    }

    public COSClient getCOSClient() {
        return this.cosClient;
    }

    public RangerCredentialsClient getRangerCredentialsClient() {
        return this.rangerCredentialsClient;
    }

    private String getPluginVersionInfo() {
        Properties versionProperties = new Properties();
        InputStream inputStream = null;
        String versionStr = "unknown";
        final String versionFile = "hadoopCosPluginVersionInfo.properties";
        try {
            inputStream = this.getClass().getClassLoader().getResourceAsStream(versionFile);
            if (inputStream != null) {
                versionProperties.load(inputStream);
                versionStr = versionProperties.getProperty("plugin_version");
            } else {
                LOG.error("load versionInfo properties failed, propName: {} ", versionFile);
            }
        } catch (IOException e) {
            LOG.error("load versionInfo properties exception, propName: {} ", versionFile);
        } finally {
            IOUtils.closeQuietly(inputStream, LOG);
        }
        return versionStr;
    }

    //for client side encryption,copy tmp file to expected file,set real file size in user metadata.
    public void ModifyDataSize(String key, long fileSize) throws IOException {
        try {
            GetObjectMetadataRequest getObjectMetadataRequest = new GetObjectMetadataRequest(bucketName, key);
            ObjectMetadata objectMetadata = (ObjectMetadata) callCOSClientWithRetry(getObjectMetadataRequest);

            Map objectMetadata_tmp= objectMetadata.getUserMetadata();
            objectMetadata_tmp.put(CSE_MPU_FILE_SIZE, Long.toString(fileSize));
            objectMetadata.setUserMetadata(objectMetadata_tmp);

            CopyObjectRequest copyObjectRequest =
                    new CopyObjectRequest(bucketName, key, bucketName, key.substring(0, key.length() - CSE_MPU_KEY_SUFFIX.length()));
            if (null != objectMetadata.getStorageClass()) {
                copyObjectRequest.setStorageClass(objectMetadata.getStorageClass());
            }
            copyObjectRequest.setNewObjectMetadata(objectMetadata);
            this.setEncryptionMetadata(copyObjectRequest, objectMetadata);
            copyObjectRequest.setSourceEndpointBuilder(this.cosClient.getClientConfig().getEndpointBuilder());
            callCOSClientWithRetry(copyObjectRequest);
        } catch (Exception e) {
            String errMsg = String.format(
                    "Modify the object datasize failed, cos key: %s, " +
                            "exception: %s", key, e);
            handleException(new Exception(errMsg), key);
        } finally {
            try {
                delete(key);
            } catch (IOException e){
                String errMsg = String.format(
                        "delete CSE temporary file failed, cos key: %s, " +
                                "exception: %s", key, e);
                handleException(new Exception(errMsg), key);
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy