package alien4cloud.orchestrators.services;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import javax.annotation.Resource;
import javax.inject.Inject;
import org.springframework.stereotype.Component;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import alien4cloud.dao.IGenericSearchDAO;
import alien4cloud.dao.model.GetMultipleDataResult;
import alien4cloud.deployment.DeploymentService;
import alien4cloud.exception.AlreadyExistException;
import alien4cloud.model.common.Usage;
import alien4cloud.model.deployment.Deployment;
import alien4cloud.model.orchestrators.Orchestrator;
import alien4cloud.model.orchestrators.OrchestratorConfiguration;
import alien4cloud.model.orchestrators.OrchestratorState;
import alien4cloud.orchestrators.locations.services.LocationService;
import alien4cloud.orchestrators.locations.services.PluginArchiveIndexer;
import alien4cloud.orchestrators.plugin.ILocationAutoConfigurer;
import alien4cloud.orchestrators.plugin.IOrchestratorPlugin;
import alien4cloud.orchestrators.plugin.IOrchestratorPluginFactory;
import alien4cloud.paas.OrchestratorPluginService;
import alien4cloud.paas.exception.PluginConfigurationException;
import alien4cloud.utils.MapUtil;
import lombok.extern.slf4j.Slf4j;
/**
* Service to manage state of an orchestrator
*/
@Slf4j
@Component
public class OrchestratorStateService {
@Resource(name = "alien-es-dao")
private IGenericSearchDAO alienDAO;
@Inject
private OrchestratorConfigurationService orchestratorConfigurationService;
@Inject
private OrchestratorPluginService orchestratorPluginService;
@Inject
private DeploymentService deploymentService;
@Inject
private OrchestratorService orchestratorService;
@Inject
private LocationService locationService;
@Inject
private PluginArchiveIndexer archiveIndexer;
/**
* Unload all orchestrators from JVM memory, it's typically to refresh/reload code
*/
public void unloadAllOrchestrators() {
List<Orchestrator> enabledOrchestratorList = orchestratorService.getAllEnabledOrchestrators();
if (enabledOrchestratorList != null && !enabledOrchestratorList.isEmpty()) {
log.info("Unloading orchestrators");
for (final Orchestrator orchestrator : enabledOrchestratorList) {
// un-register the orchestrator.
IOrchestratorPlugin orchestratorInstance = orchestratorPluginService.unregister(orchestrator.getId());
if (orchestratorInstance != null) {
IOrchestratorPluginFactory orchestratorFactory = orchestratorService.getPluginFactory(orchestrator);
orchestratorFactory.destroy(orchestratorInstance);
}
}
log.info("{} Orchestrators Unloaded", enabledOrchestratorList.size());
}
}
/**
* Initialize all orchestrator that have a non-disabled state.
*/
public ListenableFuture<?> initialize() {
return initialize(null);
}
/**
* Initialize all orchestrator that have a non-disabled state.
* Note: Each orchestrator initialization is down in it's own thread so it doesn't impact application startup or other orchestrator connection.
*
* @param callback the callback to be executed when initialize finish
*/
public ListenableFuture<?> initialize(FutureCallback callback) {
ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
try {
List<ListenableFuture<?>> futures = new ArrayList<>();
// get all the orchestrator that are not disabled
final List<Orchestrator> enabledOrchestratorList = orchestratorService.getAllEnabledOrchestrators();
if (enabledOrchestratorList == null || enabledOrchestratorList.isEmpty()) {
return Futures.immediateFuture(null);
}
log.info("Initializing orchestrators");
for (final Orchestrator orchestrator : enabledOrchestratorList) {
// error in initialization and timeouts should not impact startup time of Alien 4 cloud and other PaaS Providers.
ListenableFuture<?> future = executorService.submit(new Runnable() {
@Override
public void run() {
try {
load(orchestrator);
} catch (AlreadyExistException e) {
log.info("Orchestrator was already loaded at initialization for {}.", orchestrator.getId());
} catch (Exception e) {
// we have to catch everything as we don't know what a plugin can do here and cannot interrupt startup.
// Any orchestrator that failed to load will be considered as DISABLED as the registration didn't occurred
log.error("Unexpected error in plugin", e);
orchestrator.setState(OrchestratorState.DISABLED);
alienDAO.save(orchestrator);
}
}
});
futures.add(future);
}
ListenableFuture<?> combinedFuture = Futures.allAsList(futures);
if (callback != null) {
Futures.addCallback(combinedFuture, callback);
}
Futures.addCallback(combinedFuture, new FutureCallback<Object>() {
@Override
public void onSuccess(Object result) {
log.info("{} Orchestrators loaded", enabledOrchestratorList.size());
}
@Override
public void onFailure(Throwable t) {
log.error("Unable to load orchestrators", t);
}
});
return combinedFuture;
} finally {
executorService.shutdown();
}
}
/**
* Enable an orchestrator.
*
* @param orchestrator The orchestrator to enable.
*/
public synchronized void enable(Orchestrator orchestrator) throws PluginConfigurationException {
if (orchestrator.getState().equals(OrchestratorState.DISABLED)) {
load(orchestrator);
} else {
log.debug("Request to enable ignored: orchestrator {} (id: {}) is already enabled", orchestrator.getName(), orchestrator.getId());
throw new AlreadyExistException("Orchestrator {} is already instanciated.");
}
}
/**
* Load and connect the given orchestrator.
*
* @param orchestrator the orchestrator to load and connect.
*/
private void load(Orchestrator orchestrator) throws PluginConfigurationException {
log.info("Loading and connecting orchestrator {} (id: {})", orchestrator.getName(), orchestrator.getId());
// check that the orchestrator is not already loaded.
if (orchestratorPluginService.get(orchestrator.getId()) != null) {
throw new AlreadyExistException("Plugin is already loaded.");
}
// switch the state to connecting
orchestrator.setState(OrchestratorState.CONNECTING);
alienDAO.save(orchestrator);
// TODO move below in a thread to perform plugin loading and connection asynchronously
IOrchestratorPluginFactory orchestratorFactory = orchestratorService.getPluginFactory(orchestrator);
IOrchestratorPlugin<Object> orchestratorInstance = orchestratorFactory.newInstance();
// index the archive in alien catalog
archiveIndexer.indexOrchestratorArchives(orchestratorFactory, orchestratorInstance);
// Set the configuration for the provider
OrchestratorConfiguration orchestratorConfiguration = orchestratorConfigurationService.getConfigurationOrFail(orchestrator.getId());
try {
Object configuration = orchestratorConfigurationService.configurationAsValidObject(orchestrator.getId(),
orchestratorConfiguration.getConfiguration());
orchestratorInstance.setConfiguration(configuration);
} catch (IOException e) {
throw new PluginConfigurationException("Failed convert configuration json in object.", e);
}
// connect the orchestrator
orchestratorInstance.init(deploymentService.getCloudActiveDeploymentContexts(orchestrator.getId()));
// register the orchestrator instance to be polled for updates
orchestratorPluginService.register(orchestrator.getId(), orchestratorInstance);
orchestrator.setState(OrchestratorState.CONNECTED);
alienDAO.save(orchestrator);
if (orchestratorInstance instanceof ILocationAutoConfigurer) {
// trigger locations auto-configurations
locationService.autoConfigure(orchestrator, (ILocationAutoConfigurer) orchestratorInstance);
}
}
/**
* Disable an orchestrator.
*
* @param orchestrator The orchestrator to disable.
* @param force If true the orchestrator is disabled even if some deployments are currently running.
*/
public synchronized List<Usage> disable(Orchestrator orchestrator, boolean force) {
if (!force) {
// If there is at least one active deployment.
GetMultipleDataResult<Deployment> result = alienDAO.buildQuery(Deployment.class)
.setFilters(MapUtil.newHashMap(new String[] { "orchestratorId", "endDate" },
new String[][] { new String[] { orchestrator.getId() }, new String[] { null } }))
.prepareSearch().setFieldSort("_timestamp", true).search(0, 1);
// TODO place a lock to avoid deployments during the disabling of the orchestrator.
if (result.getData().length > 0) {
List<Usage> usages = generateDeploymentUsages(result.getData());
return usages;
}
}
try {
// unregister the orchestrator.
IOrchestratorPlugin orchestratorInstance = orchestratorPluginService.unregister(orchestrator.getId());
if (orchestratorInstance != null) {
IOrchestratorPluginFactory orchestratorFactory = orchestratorService.getPluginFactory(orchestrator);
orchestratorFactory.destroy(orchestratorInstance);
}
} catch (Exception e) {
log.info("Unable to destroy orchestrator, it may not be created yet", e);
} finally {
// Mark the orchestrator as disabled
orchestrator.setState(OrchestratorState.DISABLED);
alienDAO.save(orchestrator);
}
return null;
}
private List<Usage> generateDeploymentUsages(Deployment[] data) {
List<Usage> usages = Lists.newArrayList();
for (Deployment deployment : data) {
usages.add(new Usage(deployment.getSourceName(), deployment.getSourceType().getSourceType().getSimpleName(), deployment.getSourceId(), null));
}
return usages;
}
}