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

io.mantisrx.client.SinkClientImpl Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2019 Netflix, Inc.
 *
 * Licensed 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 io.mantisrx.client;

import com.netflix.spectator.api.BasicTag;
import io.mantisrx.common.metrics.Gauge;
import io.mantisrx.common.metrics.Metrics;
import io.mantisrx.common.metrics.spectator.MetricGroupId;
import io.mantisrx.server.master.client.MasterClientWrapper;
import io.reactivex.mantis.remote.observable.EndpointChange;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import lombok.ToString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
import rx.Observer;
import rx.functions.Action1;
import rx.functions.Func1;


public class SinkClientImpl implements SinkClient {

    private static final Logger logger = LoggerFactory.getLogger(SinkClientImpl.class);
    final String jobId;
    final SinkConnectionFunc sinkConnectionFunc;
    final JobSinkLocator jobSinkLocator;
    final private AtomicBoolean nowClosed = new AtomicBoolean(false);
    final private SinkConnections sinkConnections = new SinkConnections<>();
    private final String sinkGuageName = "SinkConnections";
    private final String expectedSinksGaugeName = "ExpectedSinkConnections";
    private final String sinkReceivingDataGaugeName = "sinkRecvngData";
    private final String clientNotConnectedToAllSourcesGaugeName = "clientNotConnectedToAllSources";
    private final Gauge sinkGauge;
    private final Gauge expectedSinksGauge;
    private final Gauge sinkReceivingDataGauge;
    private final Gauge clientNotConnectedToAllSourcesGauge;
    private final AtomicInteger numSinkWorkers = new AtomicInteger();
    private final AtomicInteger numSinkRunningWorkers = new AtomicInteger();
    private final Observer sinkConnectionsStatusObserver;
    private final long dataRecvTimeoutSecs;
    private final Metrics metrics;
    private final boolean disablePingFiltering;

    SinkClientImpl(String jobId, SinkConnectionFunc sinkConnectionFunc, JobSinkLocator jobSinkLocator,
                   Observable numSinkWorkersObservable,
                   Observer sinkConnectionsStatusObserver, long dataRecvTimeoutSecs) {
        this(jobId, sinkConnectionFunc, jobSinkLocator, numSinkWorkersObservable, sinkConnectionsStatusObserver,
                dataRecvTimeoutSecs, false);

    }

    SinkClientImpl(String jobId, SinkConnectionFunc sinkConnectionFunc, JobSinkLocator jobSinkLocator,
                   Observable numSinkWorkersObservable,
                   Observer sinkConnectionsStatusObserver, long dataRecvTimeoutSecs,
                   boolean disablePingFiltering) {
        this.jobId = jobId;
        this.sinkConnectionFunc = sinkConnectionFunc;
        this.jobSinkLocator = jobSinkLocator;
        BasicTag jobIdTag = new BasicTag("jobId", Optional.ofNullable(jobId).orElse("NullJobId"));
        MetricGroupId metricGroupId = new MetricGroupId(SinkClientImpl.class.getCanonicalName(), jobIdTag);
        this.metrics = new Metrics.Builder()
                .id(metricGroupId)
                .addGauge(sinkGuageName)
                .addGauge(expectedSinksGaugeName)
                .addGauge(sinkReceivingDataGaugeName)
                .addGauge(clientNotConnectedToAllSourcesGaugeName)
                .build();
        sinkGauge = metrics.getGauge(sinkGuageName);
        expectedSinksGauge = metrics.getGauge(expectedSinksGaugeName);
        sinkReceivingDataGauge = metrics.getGauge(sinkReceivingDataGaugeName);
        clientNotConnectedToAllSourcesGauge = metrics.getGauge(clientNotConnectedToAllSourcesGaugeName);
        numSinkWorkersObservable
                .doOnNext((jobSinkNumWorkers) -> {
                    numSinkWorkers.set(jobSinkNumWorkers.getNumSinkWorkers());
                    numSinkRunningWorkers.set(jobSinkNumWorkers.getNumSinkRunningWorkers());

                })
                .takeWhile((integer) -> !nowClosed.get())
                .subscribe();
        this.sinkConnectionsStatusObserver = sinkConnectionsStatusObserver;
        this.dataRecvTimeoutSecs = dataRecvTimeoutSecs;
        this.disablePingFiltering = disablePingFiltering;
    }

    private String toSinkName(String host, int port) {
        return host + "-" + port;
    }

    @Override
    public boolean hasError() {
        return false;
    }

    @Override
    public String getError() {
        return null;
    }

    @Override
    public Observable> getResults() {
        return getPartitionedResults(-1, 0);
    }

    @Override
    public Observable> getPartitionedResults(final int forIndex, final int totalPartitions) {
        return internalGetResults(forIndex, totalPartitions);
        //        return Observable
        //                .create(new Observable.OnSubscribe>() {
        //                    @Override
        //                    public void call(final Subscriber subscriber) {
        //                        internalGetResults(forIndex, totalPartitions).subscribe(subscriber);
        //                    }
        //                })
        //                .subscribeOn(Schedulers.io());
    }

    private  Observable> internalGetResults(int forIndex, int totalPartitions) {
        return jobSinkLocator
                .locatePartitionedSinkForJob(jobId, forIndex, totalPartitions)
                .map(new Func1>() {
                    @Override
                    public Observable call(EndpointChange endpointChange) {
                        if (nowClosed.get())
                            return Observable.empty();
                        if (endpointChange.getType() == EndpointChange.Type.complete) {
                            return handleEndpointClose(endpointChange);
                        } else {
                            return handleEndpointConnect(endpointChange);
                        }
                    }
                })
                .doOnUnsubscribe(() -> {
                    try {
                        logger.warn("Closing connections to sink of job {}", jobId);
                        closeAllConnections();
                    } catch (Exception e) {
                        logger.warn("Error closing all connections to sink of job {}", jobId, e);
                    }
                })
                .share()
                //                .lift(new Observable.Operator, Observable>() {
                //                    @Override
                //                    public Subscriber> call(Subscriber> subscriber) {
                //                        subscriber.add(Subscriptions.create(new Action0() {
                //                            @Override
                //                            public void call() {
                //                                try {
                //                                    logger.warn("Closing connections to sink of job " + jobId);
                //                                    closeAllConnections();
                //                                } catch (Exception e) {
                //                                    throw new RuntimeException(e);
                //                                }
                //                            }
                //                        }));
                //                        return subscriber;
                //                    }
                //                })
                //                .share()
                //                .lift(new DropOperator>("client_partition_share"))
                ;
    }

    private  Observable handleEndpointConnect(EndpointChange endpoint) {
        logger.info("Opening connection to sink at " + endpoint.toString());
        final String unwrappedHost = MasterClientWrapper.getUnwrappedHost(endpoint.getEndpoint().getHost());

        SinkConnection sinkConnection = sinkConnectionFunc.call(unwrappedHost, endpoint.getEndpoint().getPort(),
                new Action1() {
                    @Override
                    public void call(Boolean flag) {
                        updateSinkConx(flag);
                    }
                },
                new Action1() {
                    @Override
                    public void call(Boolean flag) {
                        updateSinkDataReceivingStatus(flag);
                    }
                },
                dataRecvTimeoutSecs,
                this.disablePingFiltering
        );
        if (nowClosed.get()) {// check if closed before adding
            try {
                sinkConnection.close();
            } catch (Exception e) {
                logger.warn("Error closing sink connection " + sinkConnection.getName() + " - " + e.getMessage(), e);
            }
            return Observable.empty();
        }
        sinkConnections.put(toSinkName(unwrappedHost, endpoint.getEndpoint().getPort()), sinkConnection);
        if (nowClosed.get()) {
            try {
                sinkConnection.close();
                sinkConnections.remove(toSinkName(unwrappedHost, endpoint.getEndpoint().getPort()));
                return Observable.empty();
            } catch (Exception e) {
                logger.warn("Error closing sink connection - " + e.getMessage());
            }
        }
        return ((SinkConnection) sinkConnection).call()
                .takeWhile(e -> !nowClosed.get())
                ;
    }

    private void updateSinkDataReceivingStatus(Boolean flag) {
        if (flag)
            sinkReceivingDataGauge.increment();
        else
            sinkReceivingDataGauge.decrement();
        expectedSinksGauge.set(numSinkWorkers.get());
        if (expectedSinksGauge.value() != sinkReceivingDataGauge.value()) {
            this.clientNotConnectedToAllSourcesGauge.set(1);
        } else {
            this.clientNotConnectedToAllSourcesGauge.set(0);
        }
        if (sinkConnectionsStatusObserver != null) {
            synchronized (sinkConnectionsStatusObserver) {
                sinkConnectionsStatusObserver.onNext(new SinkConnectionsStatus(sinkReceivingDataGauge.value(), sinkGauge.value(), numSinkWorkers.get(), numSinkRunningWorkers.get()));
            }
        }
    }

    private void updateSinkConx(Boolean flag) {
        if (flag)
            sinkGauge.increment();
        else
            sinkGauge.decrement();
        expectedSinksGauge.set(numSinkWorkers.get());
        if (expectedSinksGauge.value() != sinkReceivingDataGauge.value()) {
            this.clientNotConnectedToAllSourcesGauge.set(1);
        } else {
            this.clientNotConnectedToAllSourcesGauge.set(0);
        }
        if (sinkConnectionsStatusObserver != null) {
            synchronized (sinkConnectionsStatusObserver) {
                sinkConnectionsStatusObserver.onNext(new SinkConnectionsStatus(sinkReceivingDataGauge.value(), sinkGauge.value(), numSinkWorkers.get(), numSinkRunningWorkers.get()));
            }
        }
    }

    private  Observable handleEndpointClose(EndpointChange endpoint) {
        logger.info("Closed connection to sink at " + endpoint.toString());
        final String unwrappedHost = MasterClientWrapper.getUnwrappedHost(endpoint.getEndpoint().getHost());
        final SinkConnection removed = (SinkConnection) sinkConnections.remove(toSinkName(unwrappedHost, endpoint.getEndpoint().getPort()));
        if (removed != null) {
            try {
                removed.close();
            } catch (Exception e) {
                // shouldn't happen
                logger.error("Unexpected exception on closing sinkConnection: " + e.getMessage(), e);
            }
        } else {
            logger.error("SinkConnections does not contain endpoint to be removed. host: {}, sinkConnections: {}",
                    unwrappedHost, sinkConnections);
        }
        return Observable.empty();
    }

    private void closeAllConnections() throws Exception {
        nowClosed.set(true);
        sinkConnections.closeOut((SinkConnection tSinkConnection) -> {
            try {
                tSinkConnection.close();
            } catch (Exception e) {
                logger.warn("Error closing sink connection " + tSinkConnection.getName() +
                        " - " + e.getMessage(), e);
            }
        });
    }

    @ToString
    class SinkConnections {

        final private Map> sinkConnections = new HashMap<>();
        private boolean isClosed = false;

        private void put(String key, SinkConnection val) {
            synchronized (sinkConnections) {
                if (isClosed)
                    return;
                sinkConnections.put(key, val);
            }
        }

        private SinkConnection remove(String key) {
            synchronized (sinkConnections) {
                return sinkConnections.remove(key);
            }
        }

        private void closeOut(Action1> onClose) {
            synchronized (sinkConnections) {
                isClosed = true;
            }
            for (SinkConnection sinkConnection : sinkConnections.values()) {
                logger.info("Closing " + sinkConnection.getName());
                onClose.call(sinkConnection);
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy