/*******************************************************************************
* Copyright (c) 2011 GigaSpaces Technologies Ltd. All rights reserved
*
* 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 org.cloudifysource.rest.util;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.cloudifysource.domain.Service;
import org.cloudifysource.domain.cloud.Cloud;
import org.cloudifysource.dsl.internal.CloudifyConstants;
import org.cloudifysource.dsl.internal.DSLApplicationCompilationResult;
import org.cloudifysource.dsl.internal.DSLReader;
import org.cloudifysource.dsl.internal.DSLUtils;
import org.cloudifysource.dsl.internal.packaging.FileAppender;
import org.cloudifysource.dsl.internal.packaging.Packager;
import org.cloudifysource.dsl.utils.ServiceUtils;
import org.cloudifysource.rest.controllers.ServiceController;
import com.j_spaces.kernel.Environment;
/**********
* A Runnable implementation that executes the deployment logic of an application.
*
* @author adaml, barakme
* @since 2.0
*/
public class ApplicationInstallerRunnable implements Runnable {
private static final int SERVICE_INSTANCE_STARTUP_TIMEOUT_MINUTES = 60;
private static final java.util.logging.Logger logger = java.util.logging.Logger
.getLogger(ApplicationInstallerRunnable.class.getName());
private final ServiceController controller;
private final DSLApplicationCompilationResult result;
private final String applicationName;
private final File overridesFile;
private final String authGroups;
private final List<Service> services;
private final Cloud cloud;
private final boolean selfHealing;
private final File cloudOverrides;
private UUID pollingTaskId;
private final boolean debugAll;
private final String debugModeString;
private final String debugEvents;
/**************
* Constructor.
*
* @param controller
* installation requests are delegated to this controller.
* @param result
* the application compilation result.
* @param applicationName
* the application name.
* @param overridesFile
* Application overrides file.
* @param authGroups
* Security authorization groups for this application.
* @param services
* the list of services.
* @param cloud
* the cloud configuration object.
* @param selfHealing
* true if self healing is enabled for all services in this application, false if it is disabled for
* them.
* @param cloudOverrides
* cloud configuration overrides for all services in this application.
* @param debugAll
* @param debugModeString
* @param debugEvents
*/
public ApplicationInstallerRunnable(
final ServiceController controller,
final DSLApplicationCompilationResult result,
final String applicationName,
final File overridesFile,
final String authGroups,
final List<Service> services,
final Cloud cloud,
final boolean selfHealing,
final File cloudOverrides,
final boolean debugAll, final String debugEvents, final String debugModeString) {
super();
this.controller = controller;
this.result = result;
this.applicationName = applicationName;
this.overridesFile = overridesFile;
this.services = services;
this.cloud = cloud;
this.selfHealing = selfHealing;
this.authGroups = authGroups;
this.cloudOverrides = cloudOverrides;
this.debugAll = debugAll;
this.debugModeString = debugModeString;
this.debugEvents = debugEvents;
}
@Override
public void run() {
final File appDir = result.getApplicationDir();
logger.fine("Installing application " + applicationName + " with the following services: " + services);
final boolean asyncInstallPossible = isAsyncInstallPossibleForApplication();
logger.info("Async install setting is " + asyncInstallPossible);
try {
installServices(appDir, applicationName, authGroups, asyncInstallPossible, cloud, cloudOverrides);
FileUtils.deleteDirectory(appDir);
} catch (final IOException e) {
e.printStackTrace();
}
}
private void installServices(
final File appDir,
final String applicationName,
final String authGroups,
final boolean async,
final Cloud cloud,
final File cloudOverrides)
throws IOException {
logger.info("Installing services for application: " + applicationName + ". Async install: " + async + ". Number of services: " + this.services.size());
for (final Service service : services) {
logger.info("Installing service: " + service.getName() + " for application: " + applicationName);
service.getCustomProperties().put("usmJarPath",
Environment.getHomeDirectory() + "/lib/platform/usm");
final Properties contextProperties = createServiceContextProperties(
service, applicationName, async, cloud);
final String serviceName = service.getName();
final String absolutePUName = ServiceUtils.getAbsolutePUName(
applicationName, serviceName);
final File serviceDirectory = new File(appDir, serviceName);
// scan for service cloud configuration file
final File serviceCloudConfiguration = new File(serviceDirectory,
CloudifyConstants.SERVICE_CLOUD_CONFIGURATION_FILE_NAME);
byte[] serviceCloudConfigurationContents = null;
if (serviceCloudConfiguration.exists()) {
serviceCloudConfigurationContents = FileUtils
.readFileToByteArray(serviceCloudConfiguration);
FileUtils.forceDelete(serviceCloudConfiguration);
}
boolean found = false;
try {
// this will actually create an empty props file.
final FileAppender appender = new FileAppender("finalPropsFile.properties");
final LinkedHashMap<File, String> filesToAppend = new LinkedHashMap<File, String>();
// first add the application properties file. least important overrides.
// lookup application properties file
final File applicationPropertiesFile =
DSLReader.findDefaultDSLFileIfExists(DSLUtils.APPLICATION_PROPERTIES_FILE_NAME, appDir);
filesToAppend.put(applicationPropertiesFile, "Application Properties File");
// add the service properties file, second level overrides.
// lookup service properties file
final String propertiesFileName = DSLUtils.getPropertiesFileName(serviceDirectory,
DSLUtils.SERVICE_DSL_FILE_NAME_SUFFIX);
final File servicePropertiesFile = new File(serviceDirectory, propertiesFileName);
filesToAppend.put(servicePropertiesFile, "Service Properties File");
// lookup overrides file
File actualOverridesFile = overridesFile;
if (actualOverridesFile == null) {
// when using the CLI, the application overrides file is inside the directory
actualOverridesFile =
DSLReader.findDefaultDSLFileIfExists(DSLUtils.APPLICATION_OVERRIDES_FILE_NAME, appDir);
}
// add the overrides file given in the command or via REST, most important overrides.
filesToAppend.put(actualOverridesFile, "Overrides Properties File");
/*
* name the merged properties file as the original properties file. this will allow all properties to be
* available by anyone who parses the default properties file. (like Lifecycle scripts)
*/
appender.appendAll(servicePropertiesFile, filesToAppend);
// Pack the folder and name it absolutePuName
final File packedFile = Packager.pack(service, serviceDirectory, absolutePUName, null);
result.getApplicationFile().delete();
packedFile.deleteOnExit();
// Deployment will be done using the service's absolute PU name.
logger.info("Deploying PU: " + absolutePUName + ". File: "
+ packedFile + ". Properties: " + contextProperties);
final String templateName = service.getCompute() == null ? null
: service.getCompute().getTemplate();
controller.deployElasticProcessingUnit(
absolutePUName,
applicationName,
authGroups,
serviceName,
packedFile,
contextProperties,
templateName,
true,
0,
TimeUnit.SECONDS,
serviceCloudConfigurationContents,
selfHealing,
null /* service overrides file */,
cloudOverrides);
try {
FileUtils.deleteDirectory(packedFile.getParentFile());
} catch (final IOException ioe) {
// sometimes this delete fails. Not sure why. Maybe deploy
// is async?
logger.warning("Failed to delete temporary directory: "
+ packedFile.getParentFile());
}
if (!async) {
logger.info("Waiting for instance of service: " + serviceName + " of application: " + applicationName);
final boolean instanceFound = controller
.waitForServiceInstance(applicationName,
serviceName,
SERVICE_INSTANCE_STARTUP_TIMEOUT_MINUTES,
TimeUnit.MINUTES);
if (!instanceFound) {
throw new TimeoutException(
"Service "
+ serviceName
+ " of application "
+ applicationName
+ " was installed, but no instance of the service has started after "
+ SERVICE_INSTANCE_STARTUP_TIMEOUT_MINUTES
+ " minutes.");
}
logger.info("Found instance of: " + serviceName);
}
found = true;
logger.fine("service " + service + " deployed.");
} catch (final Exception e) {
logger.log(
Level.SEVERE,
"Failed to install service: "
+ serviceName
+ " of application: "
+ applicationName
+ ". Application installation will halt. "
+ "Some services may already have started, and should be shutdown manually. Error was: "
+ e.getMessage(), e);
this.controller.handleDeploymentException(e, this.pollingTaskId);
return;
}
if (!found) {
logger.severe("Failed to find an instance of service: "
+ serviceName
+ " while installing application "
+ applicationName
+ ". Application installation will stop. Some services may have been installed!");
return;
}
}
}
/**
*
* @return true if all services have Lifecycle events.
*/
public boolean isAsyncInstallPossibleForApplication() {
// check if all services are USM
for (final Service service : this.services) {
if (service.getLifecycle() == null) {
return false;
}
}
return true;
}
/**
* Sets the polling id for this deployment task.
*
* @param taskPollingId
* polling task id
*/
public void setTaskPollingId(final UUID taskPollingId) {
this.pollingTaskId = taskPollingId;
}
private Properties createServiceContextProperties(final Service service,
final String applicationName, final boolean async, final Cloud cloud) {
final Properties contextProperties = new Properties();
if (service.getDependsOn() != null) {
String serviceNames = service.getDependsOn().toString();
serviceNames = serviceNames.substring(1, serviceNames.length() - 1);
if (serviceNames.equals("")) {
contextProperties.setProperty(
CloudifyConstants.CONTEXT_PROPERTY_DEPENDS_ON, "[]");
} else {
final String[] splitServiceNames = serviceNames.split(",");
final List<String> absoluteServiceNames = new ArrayList<String>();
for (final String name : splitServiceNames) {
absoluteServiceNames.add(ServiceUtils.getAbsolutePUName(
applicationName, name.trim()));
}
contextProperties.setProperty(
CloudifyConstants.CONTEXT_PROPERTY_DEPENDS_ON,
Arrays.toString(absoluteServiceNames.toArray()));
}
}
if (service.getType() != null) {
contextProperties.setProperty(
CloudifyConstants.CONTEXT_PROPERTY_SERVICE_TYPE,
service.getType());
}
if (service.getIcon() != null) {
contextProperties.setProperty(
CloudifyConstants.CONTEXT_PROPERTY_SERVICE_ICON,
CloudifyConstants.SERVICE_EXTERNAL_FOLDER
+ service.getIcon());
}
if (service.getNetwork() != null) {
contextProperties
.setProperty(
CloudifyConstants.CONTEXT_PROPERTY_NETWORK_PROTOCOL_DESCRIPTION,
service.getNetwork().getProtocolDescription());
}
contextProperties.setProperty(
CloudifyConstants.CONTEXT_PROPERTY_ASYNC_INSTALL,
Boolean.toString(async));
if (cloud != null) {
contextProperties.setProperty(
CloudifyConstants.CONTEXT_PROPERTY_CLOUD_NAME,
cloud.getName());
}
contextProperties.setProperty(
CloudifyConstants.CONTEXT_PROPERTY_ELASTIC,
Boolean.toString(service.isElastic()));
if (debugAll) {
contextProperties.setProperty(CloudifyConstants.CONTEXT_PROPERTY_DEBUG_ALL, Boolean.TRUE.toString());
contextProperties.setProperty(CloudifyConstants.CONTEXT_PROPERTY_DEBUG_MODE, debugModeString);
} else if (StringUtils.isNotBlank(debugEvents)) {
contextProperties.setProperty(CloudifyConstants.CONTEXT_PROPERTY_DEBUG_EVENTS, debugEvents);
contextProperties.setProperty(CloudifyConstants.CONTEXT_PROPERTY_DEBUG_MODE, debugModeString);
}
return contextProperties;
}
}