/*******************************************************************************
* 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.shell.commands;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang.StringUtils;
import org.apache.felix.gogo.commands.Command;
import org.apache.felix.gogo.commands.Option;
import org.cloudifysource.dsl.internal.CloudifyConstants;
import org.cloudifysource.dsl.internal.CloudifyErrorMessages;
import org.cloudifysource.dsl.utils.ServiceUtils;
import org.cloudifysource.shell.AdminFacade;
import org.cloudifysource.shell.CloudifyLicenseVerifier;
import org.cloudifysource.shell.Constants;
import org.cloudifysource.shell.KeystoreFileVerifier;
import org.cloudifysource.shell.ShellUtils;
import org.cloudifysource.shell.exceptions.CLIStatusException;
import org.cloudifysource.shell.installer.CLILocalhostBootstrapperListener;
import org.cloudifysource.shell.installer.LocalhostGridAgentBootstrapper;
import org.fusesource.jansi.Ansi.Color;
import com.j_spaces.kernel.Environment;
/**
* @author rafi, barakm
* @since 2.0.0
*
* Starts Cloudify Agent without any zone, and the Cloudify management
* processes on local machine. These processes are isolated from Cloudify
* processes running on other machines.
*
* Optional arguments: lookup-groups - A unique name that is used to
* group together Cloudify components (default: localcloud). nic-address
* - The IP address of the local host network card. Specify when local
* machine has more than one network adapter, and a specific network card
* should be used for network communication. user - The username for a
* secure connection to the rest server pwd - The password for a secure
* connection to the rest server timeout - The number of minutes to wait
* until the operation is completed (default: 5).
*
* Command syntax: bootstrap-localcloud [-lookup-groups lookup-groups]
* [-nic-address nic-address] [-user username] [-password password]
* [-timeout timeout]
*/
@Command(scope = "cloudify", name = "bootstrap-localcloud", description = "Starts Cloudify Agent without any zone,"
+ " and the Cloudify management processes on local machine. These processes are isolated from Cloudify "
+ "processes running on other machines.")
public class BootstrapLocalCloud extends AbstractGSCommand {
private static final int DEFAULT_PROGRESS_INTERVAL = 2;
private static final int DEFAULT_TIMEOUT = 5;
private static final String PATH_SEPARATOR = System.getProperty("file.separator");
// JSHOMEDIR is not set yet
private static final String CLOUDIFY_HOME = Environment.getHomeDirectory();
private static final String DEFAULT_SECURITY_FOLDER =
CLOUDIFY_HOME + PATH_SEPARATOR + "config" + PATH_SEPARATOR + "security";
private static final String DEFAULT_SECURITY_FILE_PATH =
DEFAULT_SECURITY_FOLDER + PATH_SEPARATOR + "spring-security.xml";
@Option(required = false, name = "-lookup-groups", description = "A unique name that is used to group together"
+ " Cloudify components. The default localcloud lookup group is '"
+ LocalhostGridAgentBootstrapper.LOCALCLOUD_LOOKUPGROUP
+ "'. Override in order to start multiple local clouds on the local machine.")
private String lookupGroups;
@Option(required = false,
description = "if set, bootstrap will not wait for webui service to start.", name = "-no-wait-for-webui")
private boolean noWaitForWebui = false;
@Option(required = false, description = "Server security mode (on/off)", name = "-secured")
private boolean secured;
@Option(required = false, description = "Path to a custom spring security configuration file",
name = "-security-file")
private String securityFilePath;
@Option(required = false, description = "The username when connecting to a secure admin server", name = "-user")
private String username;
@Option(required = false, description = "The password when connecting to a secure admin server", name = "-password")
private String password;
@Option(required = false, description = "The path to the keystore used for SSL connections", name = "-keystore")
private String keystoreFilePath;
@Option(required = false, description = "The password to the keystore", name = "-keystore-password")
private String keystorePassword;
@Option(required = false, name = "-nic-address", description = "The ip address of the local host network card. "
+ "Specify when local machine has more than one network adapter, and a specific network card should be "
+ "used for network communication.")
private String nicAddress = "127.0.0.1";
@Option(required = false, description = "The number of minutes to wait until the operation is done.",
name = "-timeout")
private int timeoutInMinutes = DEFAULT_TIMEOUT;
private String securityProfile = CloudifyConstants.SPRING_PROFILE_NON_SECURE;
/**
* {@inheritDoc}
*/
@Override
protected Object doExecute()
throws Exception {
new CloudifyLicenseVerifier().verifyLicense();
// first check java home is correctly configured
final String javaHome = System.getenv("JAVA_HOME");
if (javaHome == null || javaHome.trim().length() == 0) {
return messages.getString("missing_java_home");
}
final boolean javaHomeValid = isJavaHomeValid(javaHome);
if (!javaHomeValid) {
return getFormattedMessage("incorrect_java_home", Color.RED, javaHome);
}
setSecurityMode();
final LocalhostGridAgentBootstrapper installer = new LocalhostGridAgentBootstrapper();
installer.setVerbose(verbose);
installer.setLookupGroups(lookupGroups);
installer.setNicAddress(nicAddress);
installer.setProgressInSeconds(DEFAULT_PROGRESS_INTERVAL);
installer.setWaitForWebui(!noWaitForWebui);
installer.addListener(new CLILocalhostBootstrapperListener());
installer.setAdminFacade((AdminFacade) session.get(Constants.ADMIN_FACADE));
installer.startLocalCloudOnLocalhostAndWait(securityProfile, securityFilePath, username, password,
keystoreFilePath, keystorePassword, timeoutInMinutes, TimeUnit.MINUTES);
return messages.getString("local_cloud_started");
}
private boolean isJavaHomeValid(final String javaHome) {
final File javaHomeDir = new File(javaHome);
if (!javaHomeDir.exists() || !javaHomeDir.isDirectory()) {
return false;
}
final File binDir = new File(javaHomeDir, "bin");
if (!binDir.exists() || !binDir.isDirectory()) {
return false;
}
final File[] javacCandidates = binDir.listFiles(new FileFilter() {
@Override
public boolean accept(final File pathname) {
if (!pathname.isFile()) {
return false;
}
if (ServiceUtils.isWindows()) {
return pathname.getName().equals("javac.exe");
} else {
return pathname.getName().equals("javac");
}
}
});
return javacCandidates.length > 0;
}
public String getNicAddress() {
return nicAddress;
}
public void setNicAddress(final String nicAddress) {
this.nicAddress = nicAddress;
}
public int getTimeoutInMinutes() {
return timeoutInMinutes;
}
public void setTimeoutInMinutes(final int timeoutInMinutes) {
this.timeoutInMinutes = timeoutInMinutes;
}
private void setSecurityMode() throws CLIStatusException, IOException {
if (secured) {
// enable security
if (keystoreFilePath == null || keystorePassword == null || securityFilePath == null) {
throw new IllegalArgumentException(ShellUtils.getFormattedMessage(
CloudifyErrorMessages.SECURED_BOOTSTRAP_IS_PARTIAL.getName()));
}
securityProfile = CloudifyConstants.SPRING_PROFILE_SECURE;
logger.info(getFormattedMessage(CloudifyErrorMessages.SETTING_SERVER_SECURITY_PROFILE.getName(),
CloudifyConstants.SPRING_PROFILE_SECURE));
} else {
// disable security
logger.info(getFormattedMessage(CloudifyErrorMessages.SETTING_SERVER_SECURITY_PROFILE.getName(),
CloudifyConstants.SPRING_PROFILE_NON_SECURE));
securityProfile = CloudifyConstants.SPRING_PROFILE_NON_SECURE;
}
if (securityProfile.equalsIgnoreCase(CloudifyConstants.SPRING_PROFILE_NON_SECURE)) {
if (StringUtils.isNotBlank(username)) {
throw new IllegalArgumentException("'-user' is only valid when '-secured' is set");
}
if (StringUtils.isNotBlank(password)) {
throw new IllegalArgumentException("'-password' is only valid when '-secured' is set");
}
if (StringUtils.isNotBlank(securityFilePath)) {
throw new IllegalArgumentException("'-securityfile' is only valid when '-secured' is set");
}
if (StringUtils.isNotBlank(keystoreFilePath)) {
throw new IllegalArgumentException("'-keystore' is only valid when '-secured' is set");
}
if (StringUtils.isNotBlank(keystorePassword)) {
throw new IllegalArgumentException("'-keystore-password' is only valid when '-secured' is set");
}
} else {
if (StringUtils.isBlank(securityFilePath)) {
throw new IllegalArgumentException("Security file is missing or empty");
}
if (StringUtils.isNotBlank(username) && StringUtils.isBlank(password)) {
throw new IllegalArgumentException("Password is missing or empty");
}
if (StringUtils.isBlank(username) && StringUtils.isNotBlank(password)) {
throw new IllegalArgumentException("Username is missing or empty");
}
if (StringUtils.isBlank(keystoreFilePath)) {
throw new IllegalArgumentException("Keystore is missing or empty");
}
if (StringUtils.isBlank(keystorePassword)) {
throw new IllegalArgumentException("Keystore password is missing or empty");
}
if (StringUtils.isNotBlank(keystoreFilePath)) {
validateKeystoreFile(keystorePassword, new File(keystoreFilePath));
}
}
validateSecurityVars();
}
private void validateSecurityVars() throws IOException {
// handle the configuration file
if (!ShellUtils.isSecureConnection(securityProfile)) {
securityFilePath = new File(DEFAULT_SECURITY_FILE_PATH).getCanonicalPath();
} else {
if (StringUtils.isNotBlank(securityFilePath)) {
final File securityFile = new File(securityFilePath);
if (!securityFile.isFile()) {
throw new IOException("Security configuration file not found: " + securityFilePath);
}
securityFilePath = securityFile.getCanonicalPath();
} else {
throw new IllegalArgumentException("-security-file is missing or empty");
}
// handle the keystore file
if (StringUtils.isNotBlank(keystoreFilePath)) {
final File keystoreFile = new File(keystoreFilePath);
if (!keystoreFile.isFile()) {
throw new IOException("Keystore file not found: " + keystoreFilePath);
}
keystoreFilePath = keystoreFile.getCanonicalPath();
} else {
throw new IllegalArgumentException("-keystore is missing or empty: ");
}
}
}
private void validateKeystoreFile(final String password, final File keystoreFile) throws CLIStatusException {
new KeystoreFileVerifier().verifyKeystoreFile(keystoreFile, password);
}
}