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

com.sitewhere.microservice.multitenant.MicroserviceTenantEngine Maven / Gradle / Ivy

The newest version!
/**
 * Copyright © 2014-2021 The SiteWhere Authors
 *
 * 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.sitewhere.microservice.multitenant;

import java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.base.CaseFormat;
import com.google.inject.CreationException;
import com.google.inject.Injector;
import com.sitewhere.microservice.configuration.model.instance.persistence.PersistenceConfigurations;
import com.sitewhere.microservice.lifecycle.CompositeLifecycleStep;
import com.sitewhere.microservice.lifecycle.TenantEngineLifecycleComponent;
import com.sitewhere.microservice.scripting.Binding;
import com.sitewhere.microservice.scripting.ScriptManager;
import com.sitewhere.microservice.util.MarshalUtils;
import com.sitewhere.spi.SiteWhereException;
import com.sitewhere.spi.microservice.IFunctionIdentifier;
import com.sitewhere.spi.microservice.configuration.IConfigurableMicroservice;
import com.sitewhere.spi.microservice.lifecycle.ICompositeLifecycleStep;
import com.sitewhere.spi.microservice.lifecycle.ILifecycleProgressMonitor;
import com.sitewhere.spi.microservice.multitenant.IMicroserviceTenantEngine;
import com.sitewhere.spi.microservice.multitenant.ITenantEngineBootstrapper;
import com.sitewhere.spi.microservice.multitenant.ITenantEngineConfiguration;
import com.sitewhere.spi.microservice.multitenant.ITenantEngineSpecUpdateOperation;
import com.sitewhere.spi.microservice.multitenant.ITenantEngineStatusUpdateOperation;
import com.sitewhere.spi.microservice.scripting.IScriptManager;

import io.sitewhere.k8s.crd.ResourceLabels;
import io.sitewhere.k8s.crd.common.BootstrapState;
import io.sitewhere.k8s.crd.tenant.SiteWhereTenant;
import io.sitewhere.k8s.crd.tenant.configuration.TenantConfigurationTemplate;
import io.sitewhere.k8s.crd.tenant.dataset.TenantDatasetTemplate;
import io.sitewhere.k8s.crd.tenant.engine.SiteWhereTenantEngine;
import io.sitewhere.k8s.crd.tenant.engine.SiteWhereTenantEngineList;
import io.sitewhere.k8s.crd.tenant.engine.configuration.TenantEngineConfigurationTemplate;
import io.sitewhere.k8s.crd.tenant.engine.dataset.TenantEngineDatasetTemplate;

/**
 * Component within microservice which runs for a specific tenant. Each tenant
 * engine has its own configuration and data model.
 * 
 * @param 
 */
public abstract class MicroserviceTenantEngine
	extends TenantEngineLifecycleComponent implements IMicroserviceTenantEngine {

    /** Number of milliseconds to wait between dataset bootstrap checks */
    private static final int DATASET_BOOTSTRAP_CHECK_INTERVAL = 5000;

    /** Tenant resource */
    private SiteWhereTenant tenantResource;

    /** Tenant engine resource */
    private SiteWhereTenantEngine tenantEngineResource;

    /** Active configuration */
    private T activeConfiguration;

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

    /** Script manager */
    private IScriptManager scriptManager = new ScriptManager();

    /** Tenant engine bootstrapper */
    private TenantEngineBootstrapper tenantEngineBootstrapper = new TenantEngineBootstrapper();

    public MicroserviceTenantEngine(SiteWhereTenantEngine tenantEngineResource) {
	this.tenantEngineResource = tenantEngineResource;
    }

    /*
     * @see
     * com.sitewhere.spi.microservice.multitenant.IMicroserviceTenantEngine#getName(
     * )
     */
    @Override
    public String getName() {
	return getTenantEngineResource().getMetadata().getName();
    }

    /*
     * @see com.sitewhere.spi.microservice.multitenant.IMicroserviceTenantEngine#
     * getTenantResource()
     */
    @Override
    public SiteWhereTenant getTenantResource() {
	return tenantResource;
    }

    /*
     * @see com.sitewhere.spi.microservice.multitenant.IMicroserviceTenantEngine#
     * getTenantEngineResource()
     */
    @Override
    public SiteWhereTenantEngine getTenantEngineResource() {
	return tenantEngineResource;
    }

    /*
     * @see com.sitewhere.spi.microservice.multitenant.IMicroserviceTenantEngine#
     * getActiveConfiguration()
     */
    @Override
    public T getActiveConfiguration() {
	return activeConfiguration;
    }

    /*
     * @see com.sitewhere.microservice.lifecycle.TenantEngineLifecycleComponent#
     * getTenantEngine()
     */
    @Override
    public IMicroserviceTenantEngine getTenantEngine() {
	return this;
    }

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

	// Refresh active configuration from k8s resource.
	refreshConfiguration();

	// Load tenant engine components from Guice injector.
	loadEngineComponents();

	// Create step that will initialize components.
	ICompositeLifecycleStep init = new CompositeLifecycleStep("Initialize tenant engine " + getName());

	// Initialize script manager.
	init.addInitializeStep(this, getScriptManager(), true);

	// Initialize engine bootstrapper.
	init.addInitializeStep(this, getTenantEngineBootstrapper(), true);

	// Execute initialization steps.
	init.execute(monitor);

	// Allow subclass to execute initialization logic.
	tenantInitialize(monitor);
    }

    /**
     * Resolve the tenant resource references by tenant engine label.
     * 
     * @throws SiteWhereException
     */
    protected void resolveTenantResource() throws SiteWhereException {
	String tenantToken = getTenantEngineResource().getMetadata().getLabels()
		.get(ResourceLabels.LABEL_SITEWHERE_TENANT);
	if (tenantToken == null) {
	    throw new SiteWhereException("Tenant engine does not have a tenant label. Unable to resolve.");
	}
	String namespace = getMicroservice().getInstanceSettings().getKubernetesNamespace();
	SiteWhereTenant tenant = getMicroservice().getSiteWhereKubernetesClient().getTenants().inNamespace(namespace)
		.withName(tenantToken).get();
	if (tenant == null) {
	    throw new SiteWhereException(String.format(
		    "Tenant engine label references a tenant '%s' which does not exist in namespace '%s'.", tenantToken,
		    namespace));
	}
	this.tenantResource = tenant;
    }

    /*
     * @see com.sitewhere.spi.microservice.multitenant.IMicroserviceTenantEngine#
     * loadTenantEngineResource()
     */
    @Override
    public SiteWhereTenantEngine loadTenantEngineResource() throws SiteWhereException {
	String engineName = getTenantEngineResource().getMetadata().getName();
	String namespace = getMicroservice().getInstanceSettings().getKubernetesNamespace();
	SiteWhereTenantEngine found = getMicroservice().getSiteWhereKubernetesClient().getTenantEngines()
		.inNamespace(namespace).withName(engineName).get();
	if (found == null) {
	    throw new SiteWhereException(String.format("No tenant engine resource found with name '%s'.", engineName));
	}
	return found;
    }

    /*
     * @see com.sitewhere.spi.microservice.multitenant.IMicroserviceTenantEngine#
     * updateTenantEngineResource(io.sitewhere.k8s.crd.tenant.engine.
     * SiteWhereTenantEngine)
     */
    @Override
    public SiteWhereTenantEngine updateTenantEngineResource(SiteWhereTenantEngine engine) throws SiteWhereException {
	String engineName = getTenantEngineResource().getMetadata().getName();
	String namespace = getMicroservice().getInstanceSettings().getKubernetesNamespace();
	return getMicroservice().getSiteWhereKubernetesClient().getTenantEngines().inNamespace(namespace)
		.withName(engineName).createOrReplace(engine);
    }

    /*
     * @see com.sitewhere.spi.microservice.multitenant.IMicroserviceTenantEngine#
     * updateTenantEngineStatus(io.sitewhere.k8s.crd.tenant.engine.
     * SiteWhereTenantEngine)
     */
    @Override
    public SiteWhereTenantEngine updateTenantEngineStatus(SiteWhereTenantEngine engine) throws SiteWhereException {
	try {
	    return getMicroservice().getSiteWhereKubernetesClient().getTenantEngines()
		    .withName(engine.getMetadata().getName()).updateStatus(engine);
	} catch (Throwable e) {
	    throw new SiteWhereException("Unhandled exception updating tenant engine status.", e);
	}
    }

    /*
     * @see com.sitewhere.spi.microservice.multitenant.IMicroserviceTenantEngine#
     * executeTenantEngineSpecUpdate(com.sitewhere.spi.microservice.multitenant.
     * ITenantEngineSpecUpdateOperation)
     */
    @Override
    public SiteWhereTenantEngine executeTenantEngineSpecUpdate(ITenantEngineSpecUpdateOperation operation)
	    throws SiteWhereException {
	return operation.execute(this);
    }

    /*
     * @see com.sitewhere.spi.microservice.multitenant.IMicroserviceTenantEngine#
     * executeTenantEngineStatusUpdate(com.sitewhere.spi.microservice.multitenant.
     * ITenantEngineStatusUpdateOperation)
     */
    @Override
    public SiteWhereTenantEngine executeTenantEngineStatusUpdate(ITenantEngineStatusUpdateOperation operation)
	    throws SiteWhereException {
	return operation.execute(this);
    }

    /**
     * Parse tenant engine configuraion into expected configuration type.
     * 
     * @return
     * @throws SiteWhereException
     */
    protected T parseConfiguration() throws SiteWhereException {
	try {
	    JsonNode configuration = getTenantEngineResource().getSpec().getConfiguration();
	    return MarshalUtils.unmarshalJsonNode(configuration, getConfigurationClass());
	} catch (JsonProcessingException e) {
	    throw new SiteWhereException("Unable to parse tenant engine configuration.", e);
	} catch (Throwable t) {
	    throw new SiteWhereException("Unhandled exception parsing tenant engine configuration.", t);
	}
    }

    /**
     * Refresh active configuration from k8s resource.
     * 
     * @throws SiteWhereException
     */
    protected void refreshConfiguration() throws SiteWhereException {
	this.activeConfiguration = parseConfiguration();
	getLogger().info(String.format("Tenant engine will use configuration: \n%s\n\n",
		MarshalUtils.marshalJsonAsPrettyString(getActiveConfiguration())));
	try {
	    // Inherit existing bindings from the microsevice injector.
	    this.injector = ((IConfigurableMicroservice) getMicroservice()).getInjector()
		    .createChildInjector(createConfigurationModule());
	    PersistenceConfigurations configs = getInjector().getInstance(PersistenceConfigurations.class);
	    if (configs == null) {
		getLogger().debug("Did not find persistence configurations.");
	    } else {
		getLogger().debug(String.format("Found persistence configs:\n%s\n\n",
			MarshalUtils.marshalJsonAsPrettyString(configs)));
	    }
	} catch (CreationException e) {
	    throw new SiteWhereException("Guice configuration module failed to initialize.", e);
	}
    }

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

	// Create step that will start components.
	ICompositeLifecycleStep start = new CompositeLifecycleStep("Start tenant engine " + getName());

	// Start tenant script manager.
	start.addStartStep(this, getScriptManager(), true);

	// Execute startup steps.
	start.execute(monitor);

	// Allow subclass to execute startup logic.
	tenantStart(monitor);

	// Execute bootstrap in background
	startNestedComponent(getTenantEngineBootstrapper(), monitor, true);
    }

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

	// Stop the engine bootstrapper.
	stopNestedComponent(getTenantEngineBootstrapper(), monitor);

	// Allow subclass to execute shutdown logic.
	tenantStop(monitor);

	// Create step that will stop components.
	ICompositeLifecycleStep stop = new CompositeLifecycleStep("Stop tenant engine " + getName());

	// Stop tenant script manager.
	stop.addStopStep(this, getScriptManager());

	// Execute shutdown steps.
	stop.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 {
	// Create step that will terminate components.
	ICompositeLifecycleStep stop = new CompositeLifecycleStep("Terminate tenant engine " + getName());

	// Terminate tenant script manager.
	stop.addTerminateStep(this, getScriptManager());

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

    /*
     * @see com.sitewhere.spi.microservice.multitenant.IMicroserviceTenantEngine#
     * loadEngineComponents()
     */
    @Override
    public void loadEngineComponents() throws SiteWhereException {
    }

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

    /*
     * @see com.sitewhere.spi.microservice.multitenant.IMicroserviceTenantEngine#
     * getConfigurationTemplate()
     */
    @Override
    public TenantEngineConfigurationTemplate getConfigurationTemplate() throws SiteWhereException {
	String tctName = getTenantResource().getSpec().getConfigurationTemplate();
	if (tctName == null) {
	    throw new SiteWhereException("No tenant configuration template specified.");
	}
	TenantConfigurationTemplate tct = getMicroservice().getSiteWhereKubernetesClient()
		.getTenantConfigurationTemplates().withName(tctName).get();
	if (tct == null) {
	    throw new SiteWhereException(String.format("No tenant configuration template found for '%s'.", tctName));
	}
	String functionName = CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL,
		getMicroservice().getIdentifier().getPath());
	String tectName = tct.getSpec().getTenantEngineTemplates().get(functionName);
	if (tectName == null) {
	    throw new SiteWhereException(
		    String.format("No tenant engine configuration template listed for '%s'.", tectName));
	}
	TenantEngineConfigurationTemplate tect = getMicroservice().getSiteWhereKubernetesClient()
		.getTenantEngineConfigurationTemplates().withName(tectName).get();
	if (tect == null) {
	    throw new SiteWhereException(
		    String.format("No tenant engine configuration template found for '%s'.", tectName));
	}
	return tect;
    }

    /*
     * @see com.sitewhere.spi.microservice.multitenant.IMicroserviceTenantEngine#
     * getDatasetTemplate()
     */
    @Override
    public TenantEngineDatasetTemplate getDatasetTemplate() throws SiteWhereException {
	String tdtName = getTenantResource().getSpec().getDatasetTemplate();
	if (tdtName == null) {
	    throw new SiteWhereException("No tenant dataset template specified.");
	}
	TenantDatasetTemplate tdt = getMicroservice().getSiteWhereKubernetesClient().getTenantDatasetTemplates()
		.withName(tdtName).get();
	if (tdt == null) {
	    throw new SiteWhereException(String.format("No tenant dataset template found for '%s'.", tdtName));
	}
	String functionName = CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL,
		getMicroservice().getIdentifier().getPath());
	String tedtName = tdt.getSpec().getTenantEngineTemplates().get(functionName);
	if (tedtName == null) {
	    // Not all tenant engines use datasets to bootstrap their data.
	    return null;
	}
	TenantEngineDatasetTemplate tedt = getMicroservice().getSiteWhereKubernetesClient()
		.getTenantEngineDatasetTemplates().withName(tedtName).get();
	if (tedt == null) {
	    throw new SiteWhereException(String.format("No tenant engine dataset template found for '%s'.", tedtName));
	}
	return tedt;
    }

    /*
     * @see com.sitewhere.spi.microservice.multitenant.IMicroserviceTenantEngine#
     * hasExistingDataset()
     */
    @Override
    public boolean hasExistingDataset() throws SiteWhereException {
	return false;
    }

    /*
     * @see com.sitewhere.spi.microservice.multitenant.IMicroserviceTenantEngine#
     * getTenantEngineBootstrapper()
     */
    @Override
    public ITenantEngineBootstrapper getTenantEngineBootstrapper() throws SiteWhereException {
	return tenantEngineBootstrapper;
    }

    /*
     * @see com.sitewhere.spi.microservice.multitenant.IMicroserviceTenantEngine#
     * getTenantBootstrapPrerequisites()
     */
    @Override
    public IFunctionIdentifier[] getTenantBootstrapPrerequisites() {
	return new IFunctionIdentifier[0];
    }

    /*
     * @see com.sitewhere.spi.microservice.multitenant.IMicroserviceTenantEngine#
     * setDatasetBootstrapBindings(com.sitewhere.microservice.scripting.Binding)
     */
    @Override
    public void setDatasetBootstrapBindings(Binding binding) throws SiteWhereException {
    }

    /*
     * @see com.sitewhere.spi.microservice.multitenant.IMicroserviceTenantEngine#
     * waitForTenantDatasetBootstrapped(com.sitewhere.spi.microservice.
     * IFunctionIdentifier)
     */
    @Override
    public void waitForTenantDatasetBootstrapped(IFunctionIdentifier identifier) throws SiteWhereException {
	while (true) {
	    Map labels = new HashMap<>();
	    labels.put(ResourceLabels.LABEL_SITEWHERE_TENANT, getTenantResource().getMetadata().getName());
	    labels.put(ResourceLabels.LABEL_SITEWHERE_FUNCTIONAL_AREA, identifier.getPath());
	    SiteWhereTenantEngineList matches = getMicroservice().getSiteWhereKubernetesClient().getTenantEngines()
		    .inNamespace(getTenantResource().getMetadata().getNamespace()).withLabels(labels).list();
	    SiteWhereTenantEngine match = null;
	    if (matches.getItems().size() == 1) {
		match = matches.getItems().get(0);
	    } else if (matches.getItems().size() > 1) {
		throw new SiteWhereException(String.format("Multiple tenant engines found for dependent dataset '%s'",
			identifier.getPath()));
	    }
	    if (match != null) {
		BootstrapState state = match.getStatus() != null ? match.getStatus().getBootstrapState() : null;
		if (state != null && state == BootstrapState.Bootstrapped) {
		    getLogger().info(String.format("Dataset for '%s' has been bootstrapped.", identifier.getPath()));
		    return;
		}
		getLogger()
			.info(String.format("Waiting for dataset for '%s' to become available. Current state is '%s'.",
				identifier.getPath(), state));
	    } else {
		getLogger()
			.info(String.format("Waiting for dataset for '%s' to become available.", identifier.getPath()));
	    }

	    try {
		Thread.sleep(DATASET_BOOTSTRAP_CHECK_INTERVAL);
	    } catch (InterruptedException e) {
		getLogger().info("Interrupted while waiting for dataset to become available.");
	    }
	}
    }

    /*
     * @see com.sitewhere.spi.microservice.multitenant.IMicroserviceTenantEngine#
     * onTenantBootstrapComplete()
     */
    @Override
    public void onTenantBootstrapComplete() {
    }

    /*
     * @see com.sitewhere.spi.microservice.multitenant.IMicroserviceTenantEngine#
     * getScriptManager()
     */
    @Override
    public IScriptManager getScriptManager() {
	return scriptManager;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy