com.mastfrog.giulius.settings.etcd.MetaEtcdClient.java Source code

Java tutorial

Introduction

Here is the source code for com.mastfrog.giulius.settings.etcd.MetaEtcdClient.java

Source

/*
 * The MIT License
 *
 * Copyright 2014 Tim Boudreau.
 *
 * 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.mastfrog.giulius.settings.etcd;

import com.google.common.collect.ImmutableList;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.justinsb.etcd.EtcdClient;
import com.justinsb.etcd.EtcdClientException;
import com.justinsb.etcd.EtcdResult;
import static com.mastfrog.giulius.settings.etcd.EtcdModule.DEFAULT_FAIL_WINDOW;
import static com.mastfrog.giulius.settings.etcd.EtcdModule.DEFAULT_MAX_FAILS_TO_DISABLE;
import static com.mastfrog.giulius.settings.etcd.EtcdModule.DEFAULT_MAX_RETRIES;
import static com.mastfrog.giulius.settings.etcd.EtcdModule.SETTINGS_KEY_FAIL_WINDOW;
import static com.mastfrog.giulius.settings.etcd.EtcdModule.SETTINGS_KEY_MAX_FAILS_TO_DISABLE;
import static com.mastfrog.giulius.settings.etcd.EtcdModule.SETTINGS_KEY_MAX_RETRIES;
import com.mastfrog.settings.Settings;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.joda.time.DateTimeUtils;
import org.joda.time.Duration;

/**
 * A wrapper for a cluster of etcd clients to try as failovers.
 *
 * @author Tim Boudreau
 */
public class MetaEtcdClient implements Provider<EtcdClient> {

    private final List<Entry> entries;
    private final int maxRetries;

    @Inject
    public MetaEtcdClient(Settings settings, EtcdClient... clients) {
        if (clients == null || clients.length == 0) {
            throw new IllegalArgumentException("No clients: " + clients);
        }
        maxRetries = settings.getInt(SETTINGS_KEY_MAX_RETRIES, DEFAULT_MAX_RETRIES);
        Duration failWindow = Duration
                .standardSeconds(settings.getInt(SETTINGS_KEY_FAIL_WINDOW, DEFAULT_FAIL_WINDOW));
        List<Entry> e = new ArrayList<>();
        int maxFails = settings.getInt(SETTINGS_KEY_MAX_FAILS_TO_DISABLE, DEFAULT_MAX_FAILS_TO_DISABLE);

        for (EtcdClient client : clients) {
            e.add(new Entry(client, failWindow, maxFails));
        }
        entries = ImmutableList.copyOf(e);
    }

    private Entry client(boolean unavailableOk) {
        List<Entry> ens = entries;
        if (unavailableOk) {
            ens = new LinkedList<Entry>(ens);
            Collections.shuffle(ens);
        }
        for (Entry e : entries) {
            if (e.available || unavailableOk) {
                return e;
            }
        }
        return null;
    }

    private EtcdResult tryClients(EtcdTask task) throws EtcdClientException {
        for (int i = 0; i < maxRetries; i++) {
            Entry e = client(false);
            if (e == null) {
                e = client(true);
            }
            try {
                final Entry e1 = e;
                return e1.run(task);
            } catch (Exception ex) {
                if (i == maxRetries - 1) {
                    throw ex;
                }
            }
        }
        throw new AssertionError("Should not get here");
    }

    public EtcdResult get(final String key) throws EtcdClientException {
        EtcdTask task = new EtcdTask() {

            @Override
            public EtcdResult run(EtcdClient client) throws EtcdClientException {
                return client.get(key);
            }
        };
        return tryClients(task);
    }

    public EtcdResult delete(final String key) throws EtcdClientException {
        EtcdTask task = new EtcdTask() {

            @Override
            public EtcdResult run(EtcdClient client) throws EtcdClientException {
                return client.delete(key);
            }
        };
        return tryClients(task);
    }

    public EtcdResult set(final String key, final String value) throws EtcdClientException {
        EtcdTask task = new EtcdTask() {

            @Override
            public EtcdResult run(EtcdClient client) throws EtcdClientException {
                return client.set(key, value);
            }

        };
        return tryClients(task);
    }

    public EtcdResult set(final String key, final String value, final int ttl) throws EtcdClientException {
        EtcdTask task = new EtcdTask() {

            @Override
            public EtcdResult run(EtcdClient client) throws EtcdClientException {
                return client.set(key, value, ttl);
            }

        };
        return tryClients(task);
    }

    public EtcdResult listChildren(final String key) throws EtcdClientException {
        EtcdTask task = new EtcdTask() {

            @Override
            public EtcdResult run(EtcdClient client) throws EtcdClientException {
                return client.listChildren(key);
            }

        };
        return tryClients(task);
    }

    @Override
    public EtcdClient get() {
        Entry e = client(false);
        if (e == null) {
            e = client(true);
        }
        return e.get();
    }

    private static final class Entry {

        private final EtcdClient client;
        private final AtomicLong lastFailed = new AtomicLong();
        private final AtomicInteger failCount = new AtomicInteger();
        private volatile boolean available;
        private final int maxFails;
        private final Duration failWindow;

        public Entry(EtcdClient client, Duration failWindow, int maxFails) {
            this.client = client;
            this.failWindow = failWindow;
            this.maxFails = maxFails;
        }

        EtcdClient get() {
            return client;
        }

        EtcdResult run(EtcdTask task) throws EtcdClientException {
            try {
                EtcdResult res = task.run(client);
                onSuccess();
                return res;
            } catch (EtcdClientException ex) {
                onFail(ex);
                throw ex;
            }
        }

        boolean inFailWindow() {
            Duration timeSinceLastFail = new Duration(DateTimeUtils.currentTimeMillis() - lastFailed.get());
            boolean result = timeSinceLastFail.isShorterThan(failWindow);
            if (!result) {
                failCount.set(0);
            }
            return result;
        }

        void onSuccess() {
            failCount.set(0);
            available = true;
        }

        void onFail(EtcdClientException ex) {
            boolean inFailWindow = inFailWindow();
            System.out.println("onFail in window " + inFailWindow);
            lastFailed.set(DateTimeUtils.currentTimeMillis());
            if (failCount.incrementAndGet() > maxFails && inFailWindow) {
                System.out.println("Set available to false");
                available = false;
            } else {
                System.out.println("setAvailable to true");
                available = true;
            }
        }

        public boolean isAvailable() {
            return available || !inFailWindow();
        }
    }

    interface EtcdTask {

        EtcdResult run(EtcdClient client) throws EtcdClientException;
    }
}