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

com.uber.tchannel.hyperbahn.api.HyperbahnClient Maven / Gradle / Ivy

There is a newer version: 0.8.30
Show newest version
/*
 * Copyright (c) 2015 Uber Technologies, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.uber.tchannel.hyperbahn.api;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.uber.tchannel.api.SubChannel;
import com.uber.tchannel.api.TChannel;
import com.uber.tchannel.api.TFuture;
import com.uber.tchannel.api.handlers.TFutureCallback;
import com.uber.tchannel.channels.Connection;
import com.uber.tchannel.hyperbahn.messages.AdvertiseRequest;
import com.uber.tchannel.hyperbahn.messages.AdvertiseResponse;
import com.uber.tchannel.messages.JsonRequest;
import com.uber.tchannel.messages.JsonResponse;
import java.io.FileInputStream;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Type;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;

public final class HyperbahnClient {
    private static final Logger logger = LoggerFactory.getLogger(HyperbahnClient.class);

    private static final String HYPERBAHN_SERVICE_NAME = "hyperbahn";
    private static final String HYPERBAHN_ADVERTISE_ENDPOINT = "ad";
    public AtomicBoolean destroyed = new AtomicBoolean(false); // FIXME why is it public and non-final???

    private final String service;
    private final TChannel tchannel;
    private final SubChannel hyperbahnChannel;
    private final List routers;
    private final long advertiseTimeout;
    private final long advertiseInterval;

    private final Timer advertiseTimer = new Timer(true);

    private static final long REQUEST_TIMEOUT = 1000;

    private HyperbahnClient(Builder builder) {
        this.service = builder.service;
        this.tchannel = builder.channel;
        this.routers = builder.routers;
        this.advertiseTimeout = builder.advertiseTimeout;
        this.advertiseInterval = builder.advertiseInterval;
        this.hyperbahnChannel = makeClientChannel(HYPERBAHN_SERVICE_NAME);
    }

    public SubChannel makeClientChannel(String service) {
        return tchannel.makeSubChannel(service, Connection.Direction.IN)
            .setPeers(routers);
    }

    /**
     * Starts advertising on Hyperbahn at a fixed interval.
     *
     * @return a future that resolves to the response of the first advertise request
     */
    public TFuture> advertise() {

        final AdvertiseRequest advertiseRequest = new AdvertiseRequest();
        advertiseRequest.addService(service, 0);

        // TODO: options for hard fail, retries etc.
        final JsonRequest request = new JsonRequest.Builder(
            HYPERBAHN_SERVICE_NAME,
            HYPERBAHN_ADVERTISE_ENDPOINT
        )
            .setBody(advertiseRequest)
            .setTimeout(REQUEST_TIMEOUT)
            .setRetryLimit(4)
            .build();

        final TFuture> future = hyperbahnChannel.send(request);
        future.addCallback(new TFutureCallback>() {
            @Override
            public void onResponse(JsonResponse response) {
                if (response.isError()) {
                    logger.error("Failed to advertise to Hyperbahn: {} - {}",
                        response.getError().getErrorType(),
                        response.getError().getMessage());
                }

                if (destroyed.get()) {
                    return;
                }

                scheduleAdvertise();
            }
        });

        return future;
    }

    private void scheduleAdvertise() {
        if (destroyed.get()) {
            return;
        }

        advertiseTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                advertise();
            }
        }, advertiseInterval);
    }

    public void stopAdvertising() {
        advertiseTimer.cancel();
    }

    public void shutdown() {
        if (!destroyed.compareAndSet(false, true)) {
            return;
        }

        logger.info("Shutting down HyperbahnClient and TChannel.");
        this.stopAdvertising();
        this.tchannel.shutdown();
        this.routers.clear();
        logger.info("HyperbahnClient shutdown complete.");
    }

    private static final Type LIST_OF_STRINGS = new TypeToken>(){}.getType();

    public static class Builder {

        private final @NotNull String service;
        private final @NotNull TChannel channel;

        private List routers;

        private long advertiseTimeout = 5000;
        private long advertiseInterval = 60 * 1000;

        public Builder(@NotNull String service, @NotNull TChannel channel) {
            if (service == null) {
                throw new NullPointerException("`service` cannot be null");
            }

            if (channel == null) {
                throw new NullPointerException("`channel` cannot be null");
            }

            this.service = service;
            this.channel = channel;
        }

        public Builder setAdvertiseTimeout(long advertiseTimeout) {
            this.advertiseTimeout = advertiseTimeout;
            return this;
        }

        public Builder setAdvertiseInterval(long advertiseInterval) {
            this.advertiseInterval = advertiseInterval;
            return this;
        }

        public Builder setRouters(List routers) {
            this.routers = routers;
            return this;
        }

        public Builder setRouterFile(String routerFile) throws IOException {
            this.routers = loadRouters(routerFile);
            return this;
        }

        private static @NotNull List loadRouters(String hostsFilePath) throws IOException {

            List hostPorts;
            try (Reader reader = new InputStreamReader(new FileInputStream(hostsFilePath), StandardCharsets.UTF_8)) {
                hostPorts = new Gson().fromJson(reader, LIST_OF_STRINGS);
            }

            List routers = new ArrayList<>();
            for (String hostPort : hostPorts) {
                if (hostPort == null) {
                    continue;
                }
                String[] hostPortPair = hostPort.split(Pattern.quote(":"));
                String host = hostPortPair[0];
                int port = Integer.parseInt(hostPortPair[1]);
                InetSocketAddress router = new InetSocketAddress(host, port);
                routers.add(router);
            }

            return routers;

        }

        public HyperbahnClient build() {
            return new HyperbahnClient(this);
        }

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy