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

com.sitewhere.microservice.configuration.ConfigurableMicroservice Maven / Gradle / Ivy

There is a newer version: 3.0.13
Show newest version
/*
 * Copyright (c) SiteWhere, LLC. All rights reserved. http://www.sitewhere.com
 *
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.txt file.
 */
package com.sitewhere.microservice.configuration;

import java.util.concurrent.CountDownLatch;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.inject.CreationException;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.sitewhere.microservice.Microservice;
import com.sitewhere.microservice.MicroserviceUtils;
import com.sitewhere.microservice.configuration.model.instance.InstanceConfiguration;
import com.sitewhere.microservice.lifecycle.CompositeLifecycleStep;
import com.sitewhere.microservice.lifecycle.LifecycleProgressContext;
import com.sitewhere.microservice.lifecycle.LifecycleProgressMonitor;
import com.sitewhere.microservice.scripting.KubernetesScriptManagement;
import com.sitewhere.microservice.util.MarshalUtils;
import com.sitewhere.spi.SiteWhereException;
import com.sitewhere.spi.microservice.IFunctionIdentifier;
import com.sitewhere.spi.microservice.IMicroserviceConfiguration;
import com.sitewhere.spi.microservice.configuration.IConfigurableMicroservice;
import com.sitewhere.spi.microservice.configuration.IInstanceConfigurationListener;
import com.sitewhere.spi.microservice.configuration.IInstanceConfigurationMonitor;
import com.sitewhere.spi.microservice.configuration.IInstanceModule;
import com.sitewhere.spi.microservice.configuration.IInstanceSpecUpdates;
import com.sitewhere.spi.microservice.configuration.IInstanceStatusUpdates;
import com.sitewhere.spi.microservice.configuration.IMicroserviceConfigurationListener;
import com.sitewhere.spi.microservice.configuration.IMicroserviceConfigurationMonitor;
import com.sitewhere.spi.microservice.configuration.IMicroserviceModule;
import com.sitewhere.spi.microservice.lifecycle.ICompositeLifecycleStep;
import com.sitewhere.spi.microservice.lifecycle.ILifecycleProgressMonitor;
import com.sitewhere.spi.microservice.lifecycle.LifecycleStatus;
import com.sitewhere.spi.microservice.scripting.IScriptManagement;

import io.fabric8.kubernetes.client.informers.SharedInformerFactory;
import io.sitewhere.k8s.crd.instance.SiteWhereInstance;
import io.sitewhere.k8s.crd.microservice.SiteWhereMicroservice;

/**
 * Base class for microservices that monitor the configuration folder for
 * updates.
 */
public abstract class ConfigurableMicroservice
	extends Microservice
	implements IConfigurableMicroservice, IInstanceConfigurationListener, IMicroserviceConfigurationListener {

    /** Instance configuration monitor */
    private IInstanceConfigurationMonitor instanceMonitor;

    /** Microservice configuration monitor */
    private IMicroserviceConfigurationMonitor microserviceMonitor;

    /** Script management implementation */
    private IScriptManagement scriptManagement;

    /** Latest instance resource */
    private SiteWhereInstance lastInstanceResource;

    /** Latest microservice resource */
    private SiteWhereMicroservice lastMicroserviceResource;

    /** Instance configuration */
    private InstanceConfiguration instanceConfiguration;

    /** Active configuration */
    private C microserviceConfiguration;

    /** Active microservice configuration module */
    private IMicroserviceModule microserviceConfigurationModule;

    /** Active instance configuration module */
    private IInstanceModule instanceConfigurationModule;

    /** Guice injector containing configured components */
    private Injector injector;

    /** Latch for configuration availability */
    private CountDownLatch configurationAvailable = new CountDownLatch(1);

    /*
     * @see com.sitewhere.spi.microservice.configuration.IConfigurableMicroservice#
     * getLastInstanceResource()
     */
    @Override
    public SiteWhereInstance getLastInstanceResource() {
	return this.lastInstanceResource;
    }

    /*
     * @see com.sitewhere.spi.microservice.configuration.IConfigurableMicroservice#
     * getLastMicroserviceResource()
     */
    @Override
    public SiteWhereMicroservice getLastMicroserviceResource() {
	return this.lastMicroserviceResource;
    }

    /*
     * @see com.sitewhere.spi.microservice.configuration.IConfigurableMicroservice#
     * getMicroserviceConfiguration()
     */
    @Override
    public C getMicroserviceConfiguration() {
	return this.microserviceConfiguration;
    }

    /*
     * @see com.sitewhere.spi.microservice.IMicroservice#
     * getMicroserviceConfigurationModule()
     */
    @Override
    public IMicroserviceModule getMicroserviceConfigurationModule() {
	return this.microserviceConfigurationModule;
    }

    /*
     * @see
     * com.sitewhere.spi.microservice.IMicroservice#getInstanceConfigurationModule()
     */
    @Override
    public IInstanceModule getInstanceConfigurationModule() {
	return this.instanceConfigurationModule;
    }

    /*
     * @see com.sitewhere.spi.microservice.configuration.IConfigurableMicroservice#
     * getInjector()
     */
    @Override
    public Injector getInjector() {
	return this.injector;
    }

    /*
     * @see
     * com.sitewhere.spi.microservice.configuration.IInstanceConfigurationListener#
     * onInstanceAdded(io.sitewhere.k8s.crd.instance.SiteWhereInstance)
     */
    @Override
    public void onInstanceAdded(SiteWhereInstance instance) {
	// Reflect configuration updated if not null.
	InstanceSpecUpdates specUpdates = new InstanceSpecUpdates();
	specUpdates.setConfigurationUpdated(instance.getSpec().getConfiguration() != null);

	// No status updates.
	InstanceStatusUpdates statusUpdates = new InstanceStatusUpdates();

	onInstanceUpdated(instance, specUpdates, statusUpdates);
    }

    /*
     * @see
     * com.sitewhere.spi.microservice.configuration.IInstanceConfigurationListener#
     * onInstanceUpdated(io.sitewhere.k8s.crd.instance.SiteWhereInstance)
     */
    @Override
    public void onInstanceUpdated(SiteWhereInstance instance, IInstanceSpecUpdates specUpdates,
	    IInstanceStatusUpdates statusUpdates) {
	// Save resource reference.
	this.lastInstanceResource = instance;

	// Skip partially configured instance.
	if (instance.getSpec().getConfiguration() == null) {
	    getLogger().info("Skipping instance configuration which has not yet been bootstrapped.");
	    return;
	}

	// Only interested in configuration updates.
	if (!specUpdates.isConfigurationUpdated()) {
	    getLogger().info("Skipping instance update where configuration was not updated.");
	    return;
	}

	// Save updated resource and parse configuration.
	try {
	    this.instanceConfiguration = MarshalUtils.unmarshalJsonNode(instance.getSpec().getConfiguration(),
		    InstanceConfiguration.class);
	    this.instanceConfigurationModule = new InstanceModule(getInstanceConfiguration());
	} catch (JsonProcessingException e) {
	    getLogger().error(String.format("Invalid instance configuration (%s). Content is: \n\n%s\n", e.getMessage(),
		    instance.getSpec().getConfiguration()));
	    return;
	}

	// Handle updated configuration.
	onConfigurationUpdated();
    }

    /*
     * @see
     * com.sitewhere.spi.microservice.configuration.IInstanceConfigurationListener#
     * onInstanceDeleted(io.sitewhere.k8s.crd.instance.SiteWhereInstance)
     */
    @Override
    public void onInstanceDeleted(SiteWhereInstance instance) {
	this.lastInstanceResource = null;
	onConfigurationDeleted();
    }

    /*
     * @see com.sitewhere.spi.microservice.configuration.
     * IMicroserviceConfigurationListener#onMicroserviceAdded(io.sitewhere.k8s.crd.
     * microservice.SiteWhereMicroservice)
     */
    @Override
    public void onMicroserviceAdded(SiteWhereMicroservice microservice) {
	onMicroserviceUpdated(microservice);
    }

    /*
     * @see com.sitewhere.spi.microservice.configuration.
     * IMicroserviceConfigurationListener#onMicroserviceUpdated(io.sitewhere.k8s.crd
     * .microservice.SiteWhereMicroservice)
     */
    @Override
    public void onMicroserviceUpdated(SiteWhereMicroservice microservice) {
	// Only process updates for the functional area of this microservice.
	if (!getIdentifier().getPath().equals(MicroserviceUtils.getFunctionalArea(microservice))) {
	    return;
	}

	boolean wasConfigured = getLastMicroserviceResource() != null
		&& getLastMicroserviceResource().getSpec().getConfiguration() != null;
	boolean configUpdated = wasConfigured && !getLastMicroserviceResource().getSpec().getConfiguration()
		.equals(microservice.getSpec().getConfiguration());

	getLogger().info(String.format("Handling microservice resource update. configured=%s updated=%s",
		String.valueOf(wasConfigured), String.valueOf(configUpdated)));

	// Save updated resource and parse configuration.
	this.lastMicroserviceResource = microservice;
	try {
	    C configuration = MarshalUtils.unmarshalJsonNode(microservice.getSpec().getConfiguration(),
		    getConfigurationClass());
	    this.microserviceConfiguration = configuration;
	    this.microserviceConfigurationModule = createConfigurationModule();
	} catch (JsonProcessingException e) {
	    getLogger().error(String.format("Invalid microservice configuration (%s). Content is: \n\n%s\n",
		    e.getMessage(), microservice.getSpec().getConfiguration()));
	}

	// If configuration was not updated, skip context restart.
	if (wasConfigured && !configUpdated) {
	    return;
	}

	// Handle updated configuration.
	if (!wasConfigured || configUpdated) {
	    onConfigurationUpdated();
	}
    }

    /*
     * @see com.sitewhere.spi.microservice.configuration.
     * IMicroserviceConfigurationListener#onMicroserviceDeleted(io.sitewhere.k8s.crd
     * .microservice.SiteWhereMicroservice)
     */
    @Override
    public void onMicroserviceDeleted(SiteWhereMicroservice microservice) {
	this.lastMicroserviceResource = null;
	onConfigurationDeleted();
    }

    /**
     * Called when configuration is added or updated.
     */
    protected void onConfigurationUpdated() {
	if (getInstanceConfigurationModule() == null) {
	    getLogger().info("Waiting for instance configuration to be loaded before starting.");
	    return;
	}
	if (getMicroserviceConfigurationModule() == null) {
	    getLogger().info("Waiting for microservice configuration to be loaded before starting.");
	    return;
	}

	// Create Guice injector with the instance and microservice bindings.
	try {
	    this.injector = Guice.createInjector(getInstanceConfigurationModule(),
		    getMicroserviceConfigurationModule());
	} catch (CreationException e) {
	    getLogger().error("Guice configuration module failed to initialize.", e);
	}

	getMicroserviceOperationsService().execute(new Runnable() {

	    @Override
	    public void run() {
		try {
		    if (getLifecycleStatus() == LifecycleStatus.Stopped) {
			getLogger().info("Initializing and starting microservice configuration...");
			initializeAndStart();
		    } else {
			getLogger().info("Detected configuration update. Restarting configuration...");
			restartConfiguration();
		    }
		    getConfigurationAvailable().countDown();
		} catch (SiteWhereException e) {
		    getLogger().error("Unable to restart microservice.", e);
		}
	    }
	});
    }

    /**
     * Called when configuration for instance or microservice has been deleted.
     */
    protected void onConfigurationDeleted() {
	getMicroserviceOperationsService().execute(new Runnable() {

	    @Override
	    public void run() {
		try {
		    stopAndTerminate();
		} catch (SiteWhereException e) {
		    getLogger().error("Unable to stop microservice.", e);
		}
	    }
	});
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.sitewhere.server.lifecycle.LifecycleComponent#initialize(com.
     * sitewhere.spi.server.lifecycle.ILifecycleProgressMonitor)
     */
    @Override
    public void initialize(ILifecycleProgressMonitor monitor) throws SiteWhereException {
	super.initialize(monitor);

	// Create script management support.
	this.scriptManagement = new KubernetesScriptManagement();

	// Wait for instance/microservice config available.
	waitForConfigurationReady();

	// Organizes steps for initializing microservice.
	ICompositeLifecycleStep initialize = new CompositeLifecycleStep("Initialize " + getName());

	// Initialize script management.
	initialize.addInitializeStep(this, getScriptManagement(), true);

	// Start script management.
	initialize.addStartStep(this, getScriptManagement(), true);

	// Execute initialization steps.
	initialize.execute(monitor);
    }

    /*
     * @see
     * com.sitewhere.microservice.Microservice#createKubernetesResourceControllers(
     * io.fabric8.kubernetes.client.informers.SharedInformerFactory)
     */
    @Override
    public void createKubernetesResourceControllers(SharedInformerFactory informers) throws SiteWhereException {
	super.createKubernetesResourceControllers(informers);

	// Add shared informer for instance configuration monitoring.
	this.instanceMonitor = new InstanceConfigurationMonitor(getKubernetesClient(), informers);
	getInstanceConfigurationMonitor().getListeners().add(this);
	getInstanceConfigurationMonitor().start();

	// Add shared informer for microservice configuration monitoring.
	this.microserviceMonitor = new MicroserviceConfigurationMonitor(getKubernetesClient(), informers);
	getMicroserviceConfigurationMonitor().getListeners().add(this);
	getMicroserviceConfigurationMonitor().start();
    }

    /*
     * @see com.sitewhere.spi.microservice.configuration.IConfigurableMicroservice#
     * getInstanceConfiguration()
     */
    @Override
    public InstanceConfiguration getInstanceConfiguration() {
	return this.instanceConfiguration;
    }

    /*
     * @see com.sitewhere.spi.microservice.configuration.IConfigurableMicroservice#
     * configurationInitialize(com.sitewhere.microservice.configuration.model.
     * instance.InstanceConfiguration,
     * com.sitewhere.spi.microservice.IMicroserviceConfiguration,
     * com.sitewhere.spi.microservice.lifecycle.ILifecycleProgressMonitor)
     */
    @Override
    public void configurationInitialize(InstanceConfiguration instance, C microservice,
	    ILifecycleProgressMonitor monitor) throws SiteWhereException {
	// if (local != null) {
	// initializeDiscoverableBeans(local).execute(monitor);
	// }
    }

    /*
     * @see com.sitewhere.spi.microservice.configuration.IConfigurableMicroservice#
     * configurationStart(com.sitewhere.microservice.configuration.model.instance.
     * InstanceConfiguration,
     * com.sitewhere.spi.microservice.IMicroserviceConfiguration,
     * com.sitewhere.spi.microservice.lifecycle.ILifecycleProgressMonitor)
     */
    @Override
    public void configurationStart(InstanceConfiguration instance, C microservice, ILifecycleProgressMonitor monitor)
	    throws SiteWhereException {
	// if (local != null) {
	// startDiscoverableBeans(local).execute(monitor);
	// }
    }

    /*
     * @see com.sitewhere.spi.microservice.configuration.IConfigurableMicroservice#
     * configurationStop(com.sitewhere.microservice.configuration.model.instance.
     * InstanceConfiguration,
     * com.sitewhere.spi.microservice.IMicroserviceConfiguration,
     * com.sitewhere.spi.microservice.lifecycle.ILifecycleProgressMonitor)
     */
    @Override
    public void configurationStop(InstanceConfiguration instance, C microservice, ILifecycleProgressMonitor monitor)
	    throws SiteWhereException {
	// if (local != null) {
	// stopDiscoverableBeans(local).execute(monitor);
	// }
    }

    /*
     * @see com.sitewhere.spi.microservice.configuration.IConfigurableMicroservice#
     * configurationTerminate(com.sitewhere.microservice.configuration.model.
     * instance.InstanceConfiguration,
     * com.sitewhere.spi.microservice.IMicroserviceConfiguration,
     * com.sitewhere.spi.microservice.lifecycle.ILifecycleProgressMonitor)
     */
    @Override
    public void configurationTerminate(InstanceConfiguration instance, C microservice,
	    ILifecycleProgressMonitor monitor) throws SiteWhereException {
	// if (local != null) {
	// terminateDiscoverableBeans(local).execute(monitor);
	// }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.sitewhere.server.lifecycle.LifecycleComponent#terminate(com.sitewhere
     * .spi.server.lifecycle.ILifecycleProgressMonitor)
     */
    @Override
    public void terminate(ILifecycleProgressMonitor monitor) throws SiteWhereException {
	// Stop and terminate the configuration components.
	getLogger().info("Shutting down configurable microservice components...");
	stopConfiguration();
	terminateConfiguration();

	super.terminate(monitor);

	// Organizes steps for stopping microservice.
	ICompositeLifecycleStep stop = new CompositeLifecycleStep("Stop " + getName());

	// Stop script management.
	stop.addStopStep(this, getScriptManagement());

	// Terminate script management.
	stop.addTerminateStep(this, getScriptManagement());

	// Execute shutdown steps.
	stop.execute(monitor);
    }

    /*
     * @see com.sitewhere.spi.microservice.configuration.IConfigurableMicroservice#
     * initializeConfiguration()
     */
    @Override
    public void initializeConfiguration() throws SiteWhereException {
	try {
	    // Load microservice configuration.
	    SiteWhereInstance instance = getLastInstanceResource();
	    if (instance == null || instance.getSpec() == null || instance.getSpec().getConfiguration() == null) {
		throw new SiteWhereException("Global instance configuration not set. Unable to start microservice.");
	    }

	    // Load instance YAML configuration as JSON.
	    try {
		JsonNode instanceJson = instance.getSpec().getConfiguration();
		InstanceConfiguration instanceConfiguration = MarshalUtils.unmarshalJsonNode(instanceJson,
			InstanceConfiguration.class);
		if (getLogger().isDebugEnabled()) {
		    getLogger().debug(String.format("\nINSTANCE CONFIG:\n\n%s\n",
			    MarshalUtils.marshalJsonAsPrettyString(instanceConfiguration)));
		}
		this.instanceConfiguration = instanceConfiguration;
	    } catch (JsonProcessingException e) {
		throw new SiteWhereException(String.format("Invalid instance configuration (%s). Content is: \n\n%s\n",
			e.getMessage(), instance.getSpec().getConfiguration()));
	    }

	    ILifecycleProgressMonitor monitor = new LifecycleProgressMonitor(
		    new LifecycleProgressContext(1, "Initialize microservice configuration."), getMicroservice());
	    long start = System.currentTimeMillis();
	    getLogger().info("Initializing from updated configuration...");
	    configurationInitialize(getInstanceConfiguration(), getMicroserviceConfiguration(), monitor);
	    if (getLifecycleStatus() == LifecycleStatus.LifecycleError) {
		throw getMicroservice().getLifecycleError();
	    }
	    getLogger().info("Microservice configuration '" + getMicroservice().getName() + "' initialized in "
		    + (System.currentTimeMillis() - start) + "ms.");
	} catch (Throwable t) {
	    getLogger().error("Unable to initialize microservice configuration '" + getMicroservice().getName() + "'.",
		    t);
	    throw t;
	}
    }

    /*
     * @see com.sitewhere.spi.microservice.configuration.IConfigurableMicroservice#
     * startConfiguration()
     */
    @Override
    public void startConfiguration() throws SiteWhereException {
	try {
	    // Start microservice.
	    ILifecycleProgressMonitor monitor = new LifecycleProgressMonitor(
		    new LifecycleProgressContext(1, "Start microservice configuration."), getMicroservice());
	    long start = System.currentTimeMillis();
	    configurationStart(getInstanceConfiguration(), getMicroserviceConfiguration(), monitor);
	    if (getMicroservice().getLifecycleStatus() == LifecycleStatus.LifecycleError) {
		throw getMicroservice().getLifecycleError();
	    }
	    getLogger().info("Microservice configuration '" + getMicroservice().getName() + "' started in "
		    + (System.currentTimeMillis() - start) + "ms.");
	} catch (Throwable t) {
	    getLogger().error("Unable to start microservice configuration '" + getMicroservice().getName() + "'.", t);
	    throw t;
	}
    }

    /*
     * @see com.sitewhere.spi.microservice.configuration.IConfigurableMicroservice#
     * stopConfiguration()
     */
    @Override
    public void stopConfiguration() throws SiteWhereException {
	try {
	    // Stop microservice.
	    if (getLifecycleStatus() != LifecycleStatus.Stopped) {
		ILifecycleProgressMonitor monitor = new LifecycleProgressMonitor(
			new LifecycleProgressContext(1, "Stop microservice configuration."), getMicroservice());
		long start = System.currentTimeMillis();
		configurationStop(getInstanceConfiguration(), getMicroserviceConfiguration(), monitor);
		if (getLifecycleStatus() == LifecycleStatus.LifecycleError) {
		    throw getMicroservice().getLifecycleError();
		}
		getMicroservice().getLogger().info("Microservice configuration '" + getMicroservice().getName()
			+ "' stopped in " + (System.currentTimeMillis() - start) + "ms.");
	    }
	} catch (Throwable t) {
	    getLogger().error("Unable to stop microservice configuration '" + getMicroservice().getName() + "'.", t);
	    throw t;
	}
    }

    /*
     * @see com.sitewhere.spi.microservice.configuration.IConfigurableMicroservice#
     * terminateConfiguration()
     */
    @Override
    public void terminateConfiguration() throws SiteWhereException {
	try {
	    // Terminate microservice.
	    if (getLifecycleStatus() != LifecycleStatus.Terminated) {
		ILifecycleProgressMonitor monitor = new LifecycleProgressMonitor(
			new LifecycleProgressContext(1, "Terminate microservice configuration."), this);
		long start = System.currentTimeMillis();
		configurationTerminate(getInstanceConfiguration(), getMicroserviceConfiguration(), monitor);
		if (getLifecycleStatus() == LifecycleStatus.LifecycleError) {
		    throw getMicroservice().getLifecycleError();
		}
		getLogger().info("Microservice configuration '" + getName() + "' terminated in "
			+ (System.currentTimeMillis() - start) + "ms.");
	    }
	} catch (Throwable t) {
	    getLogger().error("Unable to terminate microservice configuration '" + getName() + "'.", t);
	    throw t;
	}
    }

    /**
     * Initialize and start the microservice components.
     * 
     * @throws SiteWhereException
     */
    protected void initializeAndStart() throws SiteWhereException {
	initializeConfiguration();
	startConfiguration();
    }

    /**
     * Stop and terminate the microservice components.
     * 
     * @throws SiteWhereException
     */
    protected void stopAndTerminate() throws SiteWhereException {
	stopConfiguration();
	terminateConfiguration();
    }

    /*
     * @see com.sitewhere.spi.microservice.configuration.IConfigurableMicroservice#
     * restartConfiguration()
     */
    @Override
    public void restartConfiguration() throws SiteWhereException {
	stopAndTerminate();
	initializeAndStart();
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.sitewhere.microservice.spi.configuration.IConfigurableMicroservice#
     * waitForConfigurationReady()
     */
    @Override
    public void waitForConfigurationReady() throws SiteWhereException {
	if (getConfigurationAvailable().getCount() == 0) {
	    return;
	}
	try {
	    getLogger().info("Waiting for configuration to be loaded...");
	    getConfigurationAvailable().await();
	} catch (InterruptedException e) {
	    throw new SiteWhereException("Interrupted while waiting for instance configuration to become available.");
	}
    }

    /*
     * @see com.sitewhere.spi.microservice.configuration.IConfigurableMicroservice#
     * getInstanceConfigurationMonitor()
     */
    @Override
    public IInstanceConfigurationMonitor getInstanceConfigurationMonitor() {
	return instanceMonitor;
    }

    /*
     * @see com.sitewhere.spi.microservice.configuration.IConfigurableMicroservice#
     * getMicroserviceConfigurationMonitor()
     */
    @Override
    public IMicroserviceConfigurationMonitor getMicroserviceConfigurationMonitor() {
	return microserviceMonitor;
    }

    /*
     * @see com.sitewhere.spi.microservice.configuration.IConfigurableMicroservice#
     * getScriptManagement()
     */
    @Override
    public IScriptManagement getScriptManagement() {
	return scriptManagement;
    }

    protected CountDownLatch getConfigurationAvailable() {
	return configurationAvailable;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy