com.jivesoftware.os.upena.uba.service.Nanny.java Source code

Java tutorial

Introduction

Here is the source code for com.jivesoftware.os.upena.uba.service.Nanny.java

Source

/*
 * Copyright 2013 Jive Software, 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 com.jivesoftware.os.upena.uba.service;

import com.google.common.base.Throwables;
import com.google.common.cache.Cache;
import com.jivesoftware.os.jive.utils.shell.utils.Curl;
import com.jivesoftware.os.mlogger.core.MetricLogger;
import com.jivesoftware.os.mlogger.core.MetricLoggerFactory;
import com.jivesoftware.os.routing.bird.shared.InstanceDescriptor;
import com.jivesoftware.os.routing.bird.shared.RSAKeyPairGenerator;
import com.jivesoftware.os.uba.shared.NannyReport;
import com.jivesoftware.os.uba.shared.PasswordStore;
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.security.UnrecoverableKeyException;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

public class Nanny {

    private static final MetricLogger LOG = MetricLoggerFactory.getLogger();

    private final PasswordStore passwordStore;
    private final UpenaClient upenaClient;
    private final RepositoryProvider repositoryProvider;
    private final InstancePath instancePath;
    private final DeployableValidator deployableValidator;
    private final DeployLog deployLog;
    private final HealthLog healthLog;
    private final DeployableScriptInvoker invokeScript;
    private final AtomicBoolean redeploy;
    private final AtomicBoolean destroyed;
    private final AtomicReference<InstanceDescriptor> instanceDescriptor;
    private final LinkedBlockingQueue<Runnable> linkedBlockingQueue;
    private final ThreadPoolExecutor threadPoolExecutor;
    private final AtomicLong lastRestart = new AtomicLong(System.currentTimeMillis());
    private final AtomicLong restartAtTimestamp = new AtomicLong(-1);
    private final AtomicLong startupTimestamp = new AtomicLong(-1);
    private final UbaLog ubaLog;
    private final Cache<String, Boolean> haveRunConfigExtractionCache;
    private final AtomicReference<String> status = new AtomicReference<>("");
    final AtomicLong lastStartupId = new AtomicLong(-1);
    final AtomicLong startupId = new AtomicLong(0);
    final AtomicLong unexpectedRestartTimestamp = new AtomicLong(-1);

    public Nanny(PasswordStore passwordStore, UpenaClient upenaClient, RepositoryProvider repositoryProvider,
            InstanceDescriptor instanceDescriptor, InstancePath instancePath,
            DeployableValidator deployableValidator, DeployLog deployLog, HealthLog healthLog,
            DeployableScriptInvoker invokeScript, UbaLog ubaLog,
            Cache<String, Boolean> haveRunConfigExtractionCache) {

        this.passwordStore = passwordStore;
        this.upenaClient = upenaClient;
        this.repositoryProvider = repositoryProvider;
        this.instanceDescriptor = new AtomicReference<>(instanceDescriptor);
        this.instancePath = instancePath;
        this.deployableValidator = deployableValidator;
        this.deployLog = deployLog;
        this.healthLog = healthLog;
        this.invokeScript = invokeScript;
        this.ubaLog = ubaLog;
        linkedBlockingQueue = new LinkedBlockingQueue<>(10);
        threadPoolExecutor = new ThreadPoolExecutor(1, 1, 1000, TimeUnit.MILLISECONDS, linkedBlockingQueue);
        boolean exists = instancePath.deployLog().exists();
        LOG.info("Stats script for {} exists == {}", instanceDescriptor, exists);
        redeploy = new AtomicBoolean(!exists);
        destroyed = new AtomicBoolean(false);
        this.haveRunConfigExtractionCache = haveRunConfigExtractionCache;
    }

    synchronized public boolean ensureCerts(InstanceDescriptor id) throws Exception {
        boolean sslEnabled = false;
        for (Map.Entry<String, InstanceDescriptor.InstanceDescriptorPort> entry : id.ports.entrySet()) {
            if (entry.getValue().sslEnabled) {
                sslEnabled = true;
                break;
            }
        }
        boolean generated = false;
        if (sslEnabled) {
            SelfSigningCertGenerator generator = new SelfSigningCertGenerator();
            String password = passwordStore.password(id.instanceKey);
            File certFile = instancePath.certs("sslKeystore");

            boolean generateSSL = false;
            try {
                if (!certFile.exists() || !generator.validate(id.instanceKey, password, certFile)) {
                    generateSSL = true;
                }
            } catch (IOException x) {
                if (Throwables.getRootCause(x) instanceof UnrecoverableKeyException) {
                    LOG.warn(
                            "Looks like password changed so existing certs will be replaced with regenerate certs.");
                    generateSSL = true;
                } else {
                    throw x;
                }
            }

            if (generateSSL) {
                FileUtils.deleteQuietly(certFile);
                generator.create(id.instanceKey, password, certFile);
                generated = true;
            }
        }

        File oauthKeystoreFile = instancePath.certs("oauthKeystore");
        File oauthPublicKeyFile = instancePath.certs("oauthPublicKey");
        String password = passwordStore.password(id.instanceKey);
        RSAKeyPairGenerator generator = new RSAKeyPairGenerator();

        boolean generateOauth = false;
        try {
            if (id.publicKey == null || !id.publicKey.equals(
                    generator.getPublicKey(id.instanceKey, password, oauthKeystoreFile, oauthPublicKeyFile))) {
                generateOauth = true;
            }
        } catch (IOException x) {
            if (Throwables.getRootCause(x) instanceof UnrecoverableKeyException) {
                LOG.warn(
                        "Looks like password changed so existing oauth certs will be replaced with regenerate certs.");
                generateOauth = true;
            } else {
                throw x;
            }
        }
        if (generateOauth) {
            FileUtils.deleteQuietly(oauthKeystoreFile);
            FileUtils.deleteQuietly(oauthPublicKeyFile);
            generator.create(id.instanceKey, password, oauthKeystoreFile, oauthPublicKeyFile);
            upenaClient.updateKeyPair(id.instanceKey,
                    generator.getPublicKey(id.instanceKey, password, oauthKeystoreFile, oauthPublicKeyFile));
        }
        return generated;
    }

    public String getStatus() {
        return status.get();
    }

    public long getUnexpectedRestartTimestamp() {
        return unexpectedRestartTimestamp.get();
    }

    public InstanceDescriptor getInstanceDescriptor() {
        return instanceDescriptor.get();
    }

    public long getStartTimeMillis() {
        return startupTimestamp.longValue();
    }

    synchronized public void setInstanceDescriptor(UbaCoordinate ubaCoordinate, InstanceDescriptor id)
            throws Exception {
        InstanceDescriptor got = instanceDescriptor.get();
        if (got != null && !got.equals(id)) {
            instancePath.writeInstanceDescriptor(ubaCoordinate, id);
            ensureCerts(id);
            startupId.incrementAndGet();
            unexpectedRestartTimestamp.set(-1);
            redeploy.set(true);
            LOG.info("Instance changed from " + got + " to " + id);
        } else if (!instancePath.script("status").exists()) {
            startupId.incrementAndGet();
            unexpectedRestartTimestamp.set(-1);
            redeploy.set(true);
            LOG.info("Missing status script from " + got + " to " + id);
        }
        if (!redeploy.get()) {
            LOG.debug("Service:" + instancePath.toHumanReadableName() + " has NOT changed.");
        } else {
            instanceDescriptor.set(id);
        }
        if (id.restartTimestampGMTMillis > lastRestart.get()) {
            restartAtTimestamp.set(id.restartTimestampGMTMillis);
        }
    }

    public void forceRestart() {
        restartAtTimestamp.set(System.currentTimeMillis());
    }

    public DeployLog getDeployLog() {
        return deployLog;
    }

    public HealthLog getHealthLog() {
        return healthLog;
    }

    public NannyReport report() {
        return new NannyReport(deployLog.getState(), instanceDescriptor.get(), deployLog.commitedLog());
    }

    synchronized public String nanny(UbaCoordinate ubaCoordinate) throws InterruptedException, ExecutionException {

        long now = System.currentTimeMillis();
        if (restartAtTimestamp.get() > 0 && restartAtTimestamp.get() < now) {
            status.set("Restarting");
            deployLog.log("Nanny", "restart triggered by timestamp. " + this, null);
            if (kill()) {
                lastRestart.set(now);
                restartAtTimestamp.set(-1);
            }
        }

        try {
            if (destroyed.get()) {
                status.set("Destroying");
                deployLog.log("Nanny", "tried to check a service that has been destroyed. " + this, null);
                return deployLog.getState();
            }
            if (linkedBlockingQueue.size() == 0) {
                try {
                    if (redeploy.get()) {
                        status.set("Redeploy");
                        NannyDestroyCallable destroyTask = new NannyDestroyCallable(instanceDescriptor.get(),
                                instancePath, deployLog, healthLog, invokeScript, ubaLog);

                        deployLog.log("Nanny", "destroying in preperation to redeploy. " + this, null);
                        Future<Boolean> destroyFuture = threadPoolExecutor.submit(destroyTask);
                        status.set("Destroying");
                        if (destroyFuture.get()) {

                            NannyDeployCallable deployTask = new NannyDeployCallable(repositoryProvider,
                                    ubaCoordinate, instanceDescriptor.get(), instancePath, deployLog, healthLog,
                                    deployableValidator, invokeScript, ubaLog);
                            deployLog.log("Nanny", "redeploying. " + this, null);
                            Future<Boolean> deployedFuture = threadPoolExecutor.submit(deployTask);
                            try {
                                status.set("Deploying");
                                if (deployedFuture.get()) {
                                    try {
                                        try (FileWriter writer = new FileWriter(instancePath.deployLog())) {
                                            for (String line : deployLog.peek()) {
                                                writer.write(line);
                                            }
                                        }
                                        redeploy.set(false);
                                        status.set("Redeployed");
                                        deployLog.log("Nanny", "successfully redeployed. " + this, null);
                                    } catch (Exception x) {
                                        status.set("Failed redeployed");
                                        deployLog.log("Nanny", "failed to redeployed. " + this, x);
                                    }
                                }
                            } catch (ExecutionException ee) {
                                status.set("Unexpected state");
                                deployLog.log("Nanny", "encountered an unexpected condition. " + this, ee);
                            }
                        }
                    }

                    NannyStatusCallable nannyTask = new NannyStatusCallable(this, status, startupTimestamp,
                            instanceDescriptor.get(), instancePath, deployLog, healthLog, invokeScript, ubaLog,
                            haveRunConfigExtractionCache);
                    if (nannyTask.callable()) {
                        threadPoolExecutor.submit(nannyTask);
                    } else {
                        deployLog.log("Nanny", "skipped status check. " + this, null);
                    }

                } catch (InterruptedException | ExecutionException x) {
                    deployLog.log("Nanny", "is already running. " + this, x);
                }
                return deployLog.getState();
            } else {
                return deployLog.getState();
            }
        } finally {
            deployLog.commit();
        }
    }

    synchronized Boolean destroy() throws InterruptedException, ExecutionException {
        destroyed.set(true);
        startupId.incrementAndGet();
        unexpectedRestartTimestamp.set(-1);
        NannyDestroyCallable nannyTask = new NannyDestroyCallable(instanceDescriptor.get(), instancePath, deployLog,
                healthLog, invokeScript, ubaLog);
        Future<Boolean> waitForDestory = threadPoolExecutor.submit(nannyTask);
        Boolean result = waitForDestory.get();
        if (result) {
            nannyTask.wipeoutFiles();
        }
        return result;

    }

    synchronized Boolean kill() throws InterruptedException, ExecutionException {
        status.set("Kill");
        startupId.incrementAndGet();
        unexpectedRestartTimestamp.set(-1);
        NannyDestroyCallable nannyTask = new NannyDestroyCallable(instanceDescriptor.get(), instancePath, deployLog,
                healthLog, invokeScript, ubaLog);
        Future<Boolean> waitForDestory = threadPoolExecutor.submit(nannyTask);
        status.set("Killing");
        Boolean result = waitForDestory.get();
        if (result) {
            status.set("Killed " + result);
        } else {
            status.set("Failed to kill");
        }
        return result;

    }

    String invalidateRouting(String tenantId) {
        try {
            LOG.info("invalidating tenant routing for tenatId:" + tenantId + " on " + this);
            StringBuilder curl = new StringBuilder();
            curl.append("localhost:");
            curl.append(instanceDescriptor.get().ports.get("manage"));
            curl.append("/tenant/routing/invalidate?");
            curl.append("tenantId=").append(tenantId).append('&');
            curl.append("connectToServiceId=*").append('&');
            curl.append("portName=*");

            String response = Curl.create().curl(curl.toString());
            LOG.info(response);
            return response;
        } catch (IOException x) {
            LOG.warn("failed to invalidate tenant routing for tenantId:" + tenantId + " on " + this);
            return "failed to invalidate tenant routing for tenantId:" + tenantId + " on " + this;
        }
    }

    public void stop() {
        threadPoolExecutor.shutdownNow();
    }

    @Override
    public String toString() {
        return "Nanny{" + "instancePath=" + instancePath + ", instanceDescriptor=" + instanceDescriptor + '}';
    }

    public static String idToHtml(InstanceDescriptor id) {
        StringBuilder sb = new StringBuilder();
        sb.append("<ul>");
        sb.append("<li>").append(id.datacenter).append(":").append(id.clusterName).append(":").append(id.publicHost)
                .append("</li>");
        sb.append("<li>").append(id.serviceName).append(":").append(id.releaseGroupName).append(":")
                .append(id.instanceName).append("</li>");
        sb.append("</ul>");
        return sb.toString();
    }
}