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

com.cisco.oss.foundation.cluster.masterslave.consul.ConsulMastershipElector Maven / Gradle / Ivy

Go to download

This project is a multi data center supported master slave cluster manager library

The newest version!
package com.cisco.oss.foundation.cluster.masterslave.consul;

import com.cisco.oss.foundation.cluster.masterslave.MastershipElector;
import com.cisco.oss.foundation.cluster.utils.MasterSlaveConfigurationUtil;
import com.cisco.oss.foundation.configuration.CcpConstants;
import com.cisco.oss.foundation.configuration.ConfigurationFactory;
import com.cisco.oss.foundation.http.HttpClient;
import com.cisco.oss.foundation.http.HttpMethod;
import com.cisco.oss.foundation.http.HttpRequest;
import com.cisco.oss.foundation.http.HttpResponse;
import com.cisco.oss.foundation.http.apache.ApacheHttpClientFactory;
import com.google.common.io.BaseEncoding;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.Option;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

/**
 * Consul implementation for logic for electing new masters.
 * Created by Yair Ogen (yaogen) on 15/02/2016.
 */
public class ConsulMastershipElector implements MastershipElector {

    public static final String ACTIVE_DATACENTER = "primaryDatacenter";
    private static final Logger LOGGER = LoggerFactory.getLogger(ConsulMastershipElector.class);
    protected HttpClient consulClient;
    protected String activeVersionKey = "";
    protected String sessionId = "";
    protected String mastershipKey = "";
    protected String jobName;
    protected Thread sessionTTlThread;
    //    protected String checkId;
    protected int ttlUpdateTime;
    protected int ttlPeriod;
    protected Configuration conf = Configuration.defaultConfiguration();
    protected Configuration nullableConf = conf.addOptions(Option.DEFAULT_PATH_LEAF_TO_NULL);

    @Override
    public void init(String id, String jobName) {
        this.mastershipKey = "master-slave/" + MasterSlaveConfigurationUtil.COMPONENT_NAME + "/" + jobName;
        this.jobName = jobName;
        this.activeVersionKey = getActiveVersionKey();

        int numberOfInitAttempts = ConfigurationFactory.getConfiguration().getInt("consulClient.numberOfInitAttempts", 3);
        boolean success = false;

        ttlPeriod = MasterSlaveConfigurationUtil.getMasterSlaveLeaseTime(jobName);
        if (ttlPeriod < 10) {
            LOGGER.error("Lease time must be at least 10 seconds. For {} the value is: {}. Exiting the application", jobName, ttlPeriod);
            System.exit(-1);
        }
        ttlUpdateTime = ttlPeriod / 3;

        for (int attemptNumber = 1; attemptNumber <= numberOfInitAttempts && !success; attemptNumber++) {
            try {
                initConsul();
                success = true;
            } catch (Exception e) {
                if (attemptNumber != numberOfInitAttempts) {
                    LOGGER.warn("problem initializing consul elector for: {}. Attempt: {}. Error is: {}", jobName, attemptNumber, e);
                } else {
                    LOGGER.error("problem initializing consul elector for: {}. Attempt: {} Failed. EXITING the application. Error is: {}", jobName, attemptNumber, e);
                    System.exit(-1);
                }
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e1) {
                    //ignore
                }
            }
        }
    }

    private void initConsul() {

        if (consulClient == null) {
            consulClient = ApacheHttpClientFactory.createHttpClient("consulClient");
        }

        createSession();
    }

    private void closeSession() {

        HttpRequest destroySession = HttpRequest.newBuilder()
                .httpMethod(HttpMethod.PUT)
                .uri("/v1/session/destroy/" + sessionId)
                .build();

        execute(destroySession, false, "destroy session");
    }

    private synchronized void createSession() {

        String body = "{\n" +
                "  \"Name\": \"" + MasterSlaveConfigurationUtil.INSTANCE_ID + "\",\n" +
                "\"TTL\": \"" + ttlPeriod + "s\"" +
                "}";

        HttpRequest createSession = HttpRequest.newBuilder()
                .httpMethod(HttpMethod.PUT)
                .uri("/v1/session/create")
                .entity(body)
                .build();

        HttpResponse response = execute(createSession, true, "create session");

        String jsonId = response.getResponseAsString();
        this.sessionId = JsonPath.parse(jsonId).read("$.ID");
        LOGGER.info("new Session Id is: {}", sessionId);

        if (sessionTTlThread == null) {
            startSessionHeartbeatThread();
        }
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            //ignore
        }
    }

    private void startSessionHeartbeatThread() {
        sessionTTlThread = new Thread(() -> {
            while (true) {
                try {
                    HttpRequest renewSession = HttpRequest.newBuilder()
                            .httpMethod(HttpMethod.PUT)
                            .uri("/v1/session/renew/" + sessionId)
                            .silentLogging()
                            .build();

                    HttpResponse response = consulClient.execute(renewSession);
                    if (!response.isSuccess()) {
                        String renewSessionResponse = response.getResponseAsString();
                        LOGGER.warn("failed to pass check. got response: {}, error response: {}", response.getStatus(), renewSessionResponse);
                        if(renewSessionResponse.contains("not found")){
                            createSession();
                        }
                    }
                } catch (Exception e) {
                    LOGGER.warn("problem in heartbeat: {}", e.toString());
                } finally {
                    try {
                        TimeUnit.SECONDS.sleep(ttlUpdateTime);
                    } catch (InterruptedException e) {
                        //ignore
                    }
                }
            }
        }, jobName + "-SessionRenewThread");
        sessionTTlThread.setDaemon(true);
        sessionTTlThread.start();
        sessionTTlThread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                LOGGER.error("Uncaught Exception for Job: {} in thread: {}. Exception is: {}", jobName, t.getName(), e);
            }
        });
    }


    @Override
    public boolean isReady() {
        HttpRequest ping = HttpRequest.newBuilder()
                .httpMethod(HttpMethod.GET)
                .uri("/v1/agent/self")
                .silentLogging()
                .build();


        HttpResponse response = execute(ping, false, "ping agent");

        return response.isSuccess();


    }


    public boolean isActiveKeyValue(String key, String currentValue) {

        HttpRequest getActiveKey = HttpRequest.newBuilder()
                .httpMethod(HttpMethod.GET)
                .uri("/v1/kv/" + key)
                .silentLogging()
                .build();

        boolean isActive = true;
        String consulValue = "";

        HttpResponse response = consulClient.execute(getActiveKey);
        if (!response.isSuccess()) {
            LOGGER.debug("failed to get value from KV store for key: {}. got response: {}, error response: {}", key, response.getStatus(), response.getResponseAsString());
        } else {
            String jsonKeyValue = response.getResponseAsString();
            String valueInBase64 = JsonPath.parse(jsonKeyValue).read("$.[0].Value");

            if (StringUtils.isNotEmpty(valueInBase64)) {
                consulValue = new String(BaseEncoding.base64().decode(valueInBase64));
                isActive = currentValue.equals(consulValue);
            }
        }

        LOGGER.debug("isActive: {}, env value: {}, consul key: {}, consul value: {}", isActive, currentValue, key, consulValue);

        return isActive;
    }

    @Override
    public boolean isActiveVersion(String currentVersion) {

        return isActiveKeyValue(activeVersionKey, currentVersion);
    }

    @Override
    public boolean isActiveDataCenter(String currentDataCenter) {

        return isActiveKeyValue(ACTIVE_DATACENTER, currentDataCenter);

    }

    @Override
    public boolean isMaster() {
        boolean lockAcquired = false;

        HttpRequest getSession = HttpRequest.newBuilder()
                .httpMethod(HttpMethod.GET)
                .uri("/v1/kv/" + mastershipKey)
                .silentLogging()
                .build();

        HttpResponse getSessionResponse = consulClient.execute(getSession);
        if (getSessionResponse.isSuccess()) {
            String sessionOwner = JsonPath.using(nullableConf).parse(getSessionResponse.getResponseAsString()).read("$.[0].Session");
            if (StringUtils.isNoneBlank(sessionOwner)) {
                LOGGER.trace("Lock is by session: {}", sessionOwner);
                return sessionId.equals(sessionOwner);
            }
        }


        HttpRequest acquireLock = HttpRequest.newBuilder()
                .httpMethod(HttpMethod.PUT)
                .uri("/v1/kv/" + mastershipKey)
                .queryParams("acquire", sessionId)
                .silentLogging()
                .build();

        HttpResponse response = consulClient.execute(acquireLock);
        String responseAsString = response.getResponseAsString();
        String lockResponseReason = "";
        if (!response.isSuccess()) {
            LOGGER.error("failed to acquire lock for key: {}. got response: {}, error response: {}", mastershipKey, response.getStatus(), responseAsString);

            if (responseAsString.contains("invalid session") || responseAsString.contains("Invalid session")) {
                closeSession();
                createSession();

                acquireLock = HttpRequest.newBuilder()
                        .httpMethod(HttpMethod.PUT)
                        .uri("/v1/kv/" + mastershipKey)
                        .queryParams("acquire", sessionId)
                        .silentLogging()
                        .build();

                HttpResponse acquireLockRetryResponse = execute(acquireLock, false, "acquire lock");
                if (acquireLockRetryResponse.isSuccess()) {
                    lockAcquired = Boolean.valueOf(acquireLockRetryResponse.getResponseAsString());
                    lockResponseReason = "Successful response from consul with response for acquire lock: " + lockAcquired;
                }else{
                    lockResponseReason = acquireLockRetryResponse.getResponseAsString();
                }
            }
        } else {
            lockAcquired = Boolean.valueOf(responseAsString);
            lockResponseReason = "Successful response from consul with response for acquire lock: " + lockAcquired;
        }

        LOGGER.debug("lock acquired: {}. reason: {}", lockAcquired, lockResponseReason);
        return lockAcquired;
    }

    @Override
    public void close() {
        if (consulClient != null) {
            releaseLock();
            closeSession();
        }
    }

    private void releaseLock() {
        LOGGER.trace("going to release lock");
        HttpRequest releaseLock = HttpRequest.newBuilder()
                .httpMethod(HttpMethod.PUT)
                .uri("/v1/kv/" + mastershipKey)
                .queryParams("release", sessionId)
                .build();

        HttpResponse response = execute(releaseLock, false, "release lock");
        LOGGER.trace("release lock response: " + response.getStatus());

    }

    private HttpResponse execute(HttpRequest request, boolean throwOnError, String opName) {
        HttpResponse response = consulClient.execute(request);
        String responseAsString = response.getResponseAsString();
        if (!response.isSuccess()) {
            LOGGER.error("failed to " + opName + ". got response: {}, error response: {}", response.getStatus(), responseAsString);
            if (throwOnError) {
                throw new ConsulException("failed to " + opName + ". status: " + response.getStatus());
            }
        }

        return response;
    }

    protected String getActiveVersionKey() {
        return MasterSlaveConfigurationUtil.COMPONENT_NAME + "-version";
    }

    @Override
    public String getActiveVersion() {
        return System.getenv(CcpConstants.ARTIFACT_VERSION);
    }

    @Override
    public void cleanupMaster() {
        try {
            releaseLock();
        } catch (Exception e) {
            LOGGER.error("instance is turning into slave but can't release lock. error: {}", e);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy