Package com.netflix.suro.input.remotefile

Source Code of com.netflix.suro.input.remotefile.S3Consumer

package com.netflix.suro.input.remotefile;

import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSSessionCredentials;
import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.netflix.servo.monitor.DynamicCounter;
import com.netflix.servo.monitor.MonitorConfig;
import com.netflix.suro.input.RecordParser;
import com.netflix.suro.input.SuroInput;
import com.netflix.suro.message.MessageContainer;
import com.netflix.suro.routing.MessageRouter;
import com.netflix.suro.sink.notice.Notice;
import com.netflix.suro.sink.remotefile.AWSSessionCredentialsAdapter;
import com.netflix.util.Pair;
import org.apache.commons.io.FileUtils;
import org.jets3t.service.Jets3tProperties;
import org.jets3t.service.S3ServiceException;
import org.jets3t.service.impl.rest.httpclient.RestS3Service;
import org.jets3t.service.model.S3Object;
import org.jets3t.service.security.AWSCredentials;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.zip.GZIPInputStream;

public class S3Consumer implements SuroInput {
    public static final String TYPE = "s3";

    private static Logger log = LoggerFactory.getLogger(S3Consumer.class);

    private final String id;
    private final String s3Endpoint;
    private final long timeout;
    private final int concurrentDownload;
    private final Notice<String> notice;
    private final RecordParser recordParser;
    private final String downloadPath;
    private AWSCredentialsProvider credentialsProvider;
    private RestS3Service s3Service;
    private volatile boolean running = false;
    private ExecutorService executor;
    private Future<?> runner = null;

    private final MessageRouter router;
    private final ObjectMapper jsonMapper;

    @JsonCreator
    public S3Consumer(
            @JsonProperty("id") String id,
            @JsonProperty("s3Endpoint") String s3Endpoint,
            @JsonProperty("notice") Notice notice,
            @JsonProperty("recvTimeout") long timeout,
            @JsonProperty("concurrentDownload") int concurrentDownload,
            @JsonProperty("downloadPath") String downloadPath,
            @JsonProperty("recordParser") RecordParser recordParser,
            @JacksonInject AWSCredentialsProvider credentialProvider,
            @JacksonInject MessageRouter router,
            @JacksonInject ObjectMapper jsonMapper,
            @JacksonInject RestS3Service s3Service
    ) {
        this.id = id;
        this.s3Endpoint = s3Endpoint == null ? "s3.amazonaws.com" : s3Endpoint;
        this.notice = notice;
        this.timeout = timeout == 0 ? 1000 : timeout;
        this.concurrentDownload = concurrentDownload == 0 ? 5 : concurrentDownload;
        this.recordParser = recordParser;
        this.downloadPath = downloadPath == null ? "/logs/suro-s3consumer/" + id : downloadPath;

        this.credentialsProvider = credentialProvider;
        this.router = router;
        this.jsonMapper = jsonMapper;

        this.s3Service = s3Service;

        Preconditions.checkNotNull(notice, "notice is needed");
        Preconditions.checkNotNull(recordParser, "recordParser is needed");
    }

    @Override
    public String getId() {
        return id;
    }

    @Override
    public void start() throws Exception {
        try {
            if (s3Service == null) {
                Jets3tProperties properties = new Jets3tProperties();
                properties.setProperty("s3service.s3-endpoint", s3Endpoint);
                if (credentialsProvider.getCredentials() instanceof AWSSessionCredentials) {
                    s3Service = new RestS3Service(
                            new AWSSessionCredentialsAdapter(credentialsProvider),
                            null, null, properties);
                } else {
                    s3Service = new RestS3Service(
                            new AWSCredentials(
                                    credentialsProvider.getCredentials().getAWSAccessKeyId(),
                                    credentialsProvider.getCredentials().getAWSSecretKey()),
                            null, null, properties);
                }
            }

            executor = new ThreadPoolExecutor(
                    concurrentDownload + 1,
                    concurrentDownload + 1,
                    0, TimeUnit.MILLISECONDS,
                    new ArrayBlockingQueue<Runnable>(concurrentDownload) {
                        @Override
                        public boolean offer(Runnable runnable) {
                            try {
                                put(runnable); // not to reject the task, slowing down
                            } catch (InterruptedException e) {
                                // do nothing
                            }
                            return true;
                        }
                    },
                    new ThreadFactoryBuilder().setDaemon(true).setNameFormat("S3Consumer-%d").build());
        } catch (S3ServiceException e) {
            throw new RuntimeException(e);
        }

        notice.init();

        running = true;
        runner = executor.submit(new Runnable() {
            @Override
            public void run() {
                while (running) {
                    try {
                        long pause = pausedTime.get();
                        if (pause > 0) {
                            Thread.sleep(pause);
                            pausedTime.addAndGet(-pause);
                        }
                        Pair<String, String> msg = notice.peek();
                        if (msg != null) {
                            executor.submit(createDownloadRunnable(msg));
                        } else {
                            Thread.sleep(timeout);
                        }
                    } catch (Exception e) {
                        log.error("Exception on receiving messages from Notice", e);
                    }
                }
            }
        });
    }

    @Override
    public void shutdown() {
        try {
            log.info("shutting down S3Consumer now");

            running = false;
            try {
                runner.get();
            } catch (InterruptedException e) {
                // do nothing
            } catch (ExecutionException e) {
                log.error("Exception on stopping the task", e);
            }

            executor.shutdown();
            while (true) {
                if (!executor.awaitTermination(timeout * 5, TimeUnit.MILLISECONDS)) {
                    log.warn("downloading jobs were not terminated gracefully, retry again...");
                } else {
                    break;
                }
            }

            s3Service.shutdown();
        } catch (Exception e) {
            log.error("Exception on shutting down s3Service: " + e.getMessage(), e);
        }
    }

    private AtomicLong pausedTime = new AtomicLong(0);

    @Override
    public void setPause(long ms) {
        pausedTime.addAndGet(ms);
    }

    public static TypeReference<Map<String, Object>> typeReference = new TypeReference<Map<String, Object>>() {};
    private static final int retryCount = 5;
    private static final int sleepOnS3Exception = 5000;

    private Runnable createDownloadRunnable(final Pair<String, String> msg) {
        Map<String, Object> msgMap = null;

        try {
            msgMap = parseMessage(msg);
        } catch (Exception e) {
            log.error("Invalid message: " + e.getMessage(), e);
            return createEmptyRunnable(msg);
        }

        String s3Bucket = null;
        List<String> s3ObjectKey = null;
        try {
            s3Bucket = (String) msgMap.get("s3Bucket");
            s3ObjectKey = (List<String>) msgMap.get("s3ObjectKey");
            if (s3Bucket == null || s3ObjectKey == null) {
                throw new NullPointerException("s3Bucket or s3ObjectKey is null");
            }
        } catch (Exception e) {
            log.error("Invalid message: " + e.getMessage(), e);
            return createEmptyRunnable(msg);
        }

        final String s3BucketClone = s3Bucket;
        final List<String> s3ObjectKeyClone = s3ObjectKey;

        return new Runnable() {
            @Override
            public void run() {
                List<String> downloadedFiles = new ArrayList<String>();

                for (String path : s3ObjectKeyClone) {
                    boolean success = false;
                    String localFileName = path.replace("/", "");
                    for (int i = 0; i < retryCount; ++i) {
                        try {
                            S3Object object = s3Service.getObject(s3BucketClone, path);
                            FileUtils.copyInputStreamToFile(object.getDataInputStream(),
                                    new File(downloadPath, localFileName));
                            success = true;
                            log.info(path + " downloaded successfully");
                            break;
                        } catch (Exception e) {
                            log.error("Exception on downloading and processing file: " + e.getMessage(), e);
                            DynamicCounter.increment(
                                    MonitorConfig.builder("s3Exception").withTag("consumerId", id).build());
                            try {
                                Thread.sleep(sleepOnS3Exception);
                            } catch (InterruptedException e1) {
                                // do nothing
                            }
                        }
                    }
                    if (success) {
                        downloadedFiles.add(localFileName);
                    }
                }

                for (String path : downloadedFiles) {
                    try {
                        BufferedReader br = new BufferedReader(
                                new InputStreamReader(
                                        createInputStream(path)));
                        String data = null;
                        while ((data = br.readLine()) != null) {
                            try {
                                if (data.trim().length() > 0) {
                                    for (MessageContainer msg : recordParser.parse(data)) {
                                        router.process(S3Consumer.this, msg);
                                    }
                                }
                            } catch (Exception e) {
                                log.error("Exception on parsing and processing: " + e.getMessage(), e);
                            }
                        }
                        br.close();
                        deleteFile(path);
                    } catch (Exception e) {
                        log.error("Exception on processing downloaded file: " + e.getMessage(), e);
                        DynamicCounter.increment(
                                MonitorConfig.builder("processingException").withTag("consumerId", id).build()
                        );
                    }
                }

                notice.remove(msg.first());
            }
        };
    }

    private void deleteFile(String path) {
        File f = new File(downloadPath, path);
        while (f.exists()) {
            f.delete();
        }
    }

    @VisibleForTesting
    protected Map<String, Object> parseMessage(Pair<String, String> msg) throws IOException {
        Map<String, Object> msgContainer = jsonMapper.readValue(msg.second(), typeReference);
        if (!(msgContainer.get("Message") instanceof Map)) {
            return jsonMapper.readValue(msgContainer.get("Message").toString(), typeReference);
        } else {
            return (Map<String, Object>) msgContainer.get("Message");
        }
    }

    private InputStream createInputStream(String path) throws IOException {
        if (path.endsWith(".gz")) {
            return new GZIPInputStream(
                    new FileInputStream(new File(downloadPath, path)));
        } else {
            return new FileInputStream(new File(downloadPath, path));
        }
    }

    private Runnable createEmptyRunnable(final Pair<String, String> msg) {
        return new Runnable() {

            @Override
            public void run() {
                log.error("invalid msg: " + msg.second());
            }
        };
    }

    @Override
    public boolean equals(Object o) {
        if (o instanceof S3Consumer) {
            S3Consumer kafkaConsumer = (S3Consumer) o;
            return kafkaConsumer.id.equals(id);
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        return (getId()).hashCode();
    }
}
TOP

Related Classes of com.netflix.suro.input.remotefile.S3Consumer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.