org.apache.hadoop.chukwa.datacollection.sender.ChukwaHttpSender.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.chukwa.datacollection.sender.ChukwaHttpSender.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.hadoop.chukwa.datacollection.sender;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpMethodRetryHandler;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.hadoop.chukwa.Chunk;
import org.apache.hadoop.chukwa.datacollection.adaptor.Adaptor;
import org.apache.hadoop.io.DataOutputBuffer;
import org.apache.log4j.Logger;

/**
 * Encapsulates all of the http setup and connection details needed for
 * chunks to be delivered to a collector.
 * <p>
 * On error, tries the list of available collectors, pauses for a minute, and then repeats.
 * </p>
 * <p> Will wait forever for collectors to come up. </p>
 */
public class ChukwaHttpSender implements ChukwaSender {
    static final int MAX_RETRIES_PER_COLLECTOR = 4; //fast retries, in http client
    static final int SENDER_RETRIES = 3;
    static final int WAIT_FOR_COLLECTOR_REBOOT = 20 * 1000;
    //FIXME: this should really correspond to the timer in RetryListOfCollectors

    static Logger log = Logger.getLogger(ChukwaHttpSender.class);
    static HttpClient client = null;
    static MultiThreadedHttpConnectionManager connectionManager = null;
    static String currCollector = null;

    protected Iterator<String> collectors;

    static {
        connectionManager = new MultiThreadedHttpConnectionManager();
        client = new HttpClient(connectionManager);
        connectionManager.closeIdleConnections(1000);
    }

    public static class CommitListEntry {
        public Adaptor adaptor;
        public long uuid;

        public CommitListEntry(Adaptor a, long uuid) {
            adaptor = a;
            this.uuid = uuid;
        }
    }

    //FIXME: probably we're better off with an EventListRequestEntity
    static class BuffersRequestEntity implements RequestEntity {
        List<DataOutputBuffer> buffers;

        public BuffersRequestEntity(List<DataOutputBuffer> buf) {
            buffers = buf;
        }

        public long getContentLength() {
            long len = 4;//first we send post length, then buffers
            for (DataOutputBuffer b : buffers)
                len += b.getLength();
            return len;
        }

        public String getContentType() {
            return "application/octet-stream";
        }

        public boolean isRepeatable() {
            return true;
        }

        public void writeRequest(OutputStream out) throws IOException {
            DataOutputStream dos = new DataOutputStream(out);
            dos.writeInt(buffers.size());
            for (DataOutputBuffer b : buffers)
                dos.write(b.getData(), 0, b.getLength());
        }
    }

    public ChukwaHttpSender() {
        //setup default collector
        ArrayList<String> tmp = new ArrayList<String>();
        this.collectors = tmp.iterator();
        currCollector = "http://localhost:8080";
        log.info("added a single collector to collector list in ConnectorClient constructor, it's hasNext is now: "
                + collectors.hasNext());

    }

    /**
     * Set up a single connector for this client to send {@link Chunk}s to
     * @param collector the url of the collector
     */
    public void setCollectors(String collector) {
    }

    /**
     * Set up a list of connectors for this client to send {@link Chunk}s to
     * @param collectors
     */
    public void setCollectors(Iterator<String> collectors) {
        this.collectors = collectors;
        //setup a new destination from our list of collectors if one hasn't been set up
        if (currCollector == null) {
            if (collectors.hasNext()) {
                currCollector = collectors.next();
            } else
                log.error("No collectors to try in send(), not even trying to do doPost()");
        }
    }

    /**
     * grab all of the chunks currently in the chunkQueue, stores a copy of them locally, calculates
     * their size, sets them up 
     * @return array of chunk id's which were ACKed by collector
     */
    public List<CommitListEntry> send(List<Chunk> toSend) throws InterruptedException, IOException {
        List<DataOutputBuffer> serializedEvents = new ArrayList<DataOutputBuffer>();
        List<CommitListEntry> commitResults = new ArrayList<CommitListEntry>();

        log.info("collected " + toSend.size() + " chunks");

        //Serialize each chunk in turn into it's own DataOutputBuffer and add that buffer to serializedEvents  
        for (Chunk c : toSend) {
            DataOutputBuffer b = new DataOutputBuffer(c.getSerializedSizeEstimate());
            try {
                c.write(b);
            } catch (IOException err) {
                log.error("serialization threw IOException", err);
            }
            serializedEvents.add(b);
            //store a CLE for this chunk which we will use to ack this chunk to the caller of send()
            //(e.g. the agent will use the list of CLE's for checkpointing)
            commitResults.add(new CommitListEntry(c.getInitiator(), c.getSeqID()));
        }
        toSend.clear();

        //collect all serialized chunks into a single buffer to send
        RequestEntity postData = new BuffersRequestEntity(serializedEvents);

        int retries = SENDER_RETRIES;
        while (currCollector != null) {
            //need to pick a destination here
            PostMethod method = new PostMethod();
            try {
                doPost(method, postData, currCollector);

                retries = SENDER_RETRIES; //reset count on success
                //if no exception was thrown from doPost, ACK that these chunks were sent
                return commitResults;
            } catch (Throwable e) {
                log.error("Http post exception", e);
                log.info("Checking list of collectors to see if another collector has been specified for rollover");
                if (collectors.hasNext()) {
                    currCollector = collectors.next();
                    log.info("Found a new collector to roll over to, retrying HTTP Post to collector "
                            + currCollector);
                } else {
                    if (retries > 0) {
                        log.warn("No more collectors to try rolling over to; waiting " + WAIT_FOR_COLLECTOR_REBOOT
                                + " ms (" + retries + "retries left)");
                        Thread.sleep(WAIT_FOR_COLLECTOR_REBOOT);
                        retries--;
                    } else {
                        log.error("No more collectors to try rolling over to; aborting");
                        throw new IOException("no collectors");
                    }
                }
            } finally {
                // be sure the connection is released back to the connection manager
                method.releaseConnection();
            }
        } //end retry loop
        return new ArrayList<CommitListEntry>();
    }

    /**
     * Handles the HTTP post. Throws HttpException on failure
     */
    @SuppressWarnings("deprecation")
    private void doPost(PostMethod method, RequestEntity data, String dest) throws IOException, HttpException {

        HttpMethodParams pars = method.getParams();
        pars.setParameter(HttpMethodParams.RETRY_HANDLER, (Object) new HttpMethodRetryHandler() {
            public boolean retryMethod(HttpMethod m, IOException e, int exec) {
                return !(e instanceof java.net.ConnectException) && (exec < MAX_RETRIES_PER_COLLECTOR);
            }
        });
        method.setParams(pars);
        method.setPath(dest);

        //send it across the network
        method.setRequestEntity(data);

        log.info("HTTP post to " + dest + " length = " + data.getContentLength());
        // Send POST request

        client.setTimeout(8000);
        int statusCode = client.executeMethod(method);

        if (statusCode != HttpStatus.SC_OK) {
            log.error("HTTP post response statusCode: " + statusCode + ", statusLine: " + method.getStatusLine());
            //do something aggressive here
            throw new HttpException("got back a failure from server");
        }
        //implicitly "else"
        log.info(
                "got success back from the remote collector; response length " + method.getResponseContentLength());

        //FIXME: should parse acks here
        InputStream rstream = null;

        // Get the response body
        byte[] resp_buf = method.getResponseBody();
        rstream = new ByteArrayInputStream(resp_buf);
        BufferedReader br = new BufferedReader(new InputStreamReader(rstream));
        String line;
        while ((line = br.readLine()) != null) {
            System.out.println("response: " + line);
        }
    }

    public static void main(String[] argv) throws InterruptedException {
        //HttpConnectorClient cc = new HttpConnectorClient();
        //do something smarter than to hide record headaches, like force them to create and add records to a chunk
        //cc.addChunk("test-source", "test-streamName", "test-application", "test-dataType", new byte[]{1,2,3,4,5});
    }
}