/*******************************************************************************
* 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.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.commons.io.FileUtils;
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.internal.CloudifyMessageKeys;
import org.cloudifysource.dsl.rest.response.ControllerDetails;
import org.cloudifysource.dsl.rest.response.ShutdownManagementResponse;
import org.cloudifysource.dsl.utils.ServiceUtils;
import org.cloudifysource.restclient.RestClient;
import org.cloudifysource.shell.ConditionLatch;
import org.cloudifysource.shell.Constants;
import org.cloudifysource.shell.GigaShellMain;
import org.cloudifysource.shell.exceptions.CLIException;
import org.cloudifysource.shell.installer.CLIEventsDisplayer;
import org.cloudifysource.shell.rest.RestAdminFacade;
import org.codehaus.jackson.map.ObjectMapper;
/**
* @author rafi, barakm
* @since 2.5.0
*
* Shuts down the managers of the current cloud.
*/
@Command(scope = "cloudify", name = "shutdown-managers", description = "Shuts down the Cloudify manager processes, "
+ "leaving the hosts up for maintenance. "
+ "Use bootstrap-cloud -use-existing to restart.")
public class ShutdownManagers extends AbstractGSCommand implements NewRestClientCommand {
private static final long POLLING_INTERVAL_MILLI_SECONDS = 1000;
@Option(required = false, name = "-file",
description = "path to file where controller information will be saved. "
+ " Can be used to re-bootstrap a cloud.")
private File existingManagersFile;
@Option(required = false, name = "-timeout", description = "Minutes to wait for shutdown to complete.")
private int timeout = 2;
private final CLIEventsDisplayer displayer = new CLIEventsDisplayer();
/**
* Shuts down the local cloud, and waits until shutdown is complete or until the timeout is reached.
*
* @return command return message.
* @throws Exception
* if command failed.
*/
@Override
protected Object doExecute() throws Exception {
validateManagersFilePath();
if (this.adminFacade == null) {
adminFacade = getRestAdminFacade();
}
if (adminFacade.isConnected()) {
adminFacade.verifyCloudAdmin();
} else {
throw new CLIException(getFormattedMessage(CloudifyErrorMessages.REST_NOT_CONNECTED.getName()));
}
final List<ControllerDetails> managers = adminFacade.shutdownManagers();
final String managerIPs = getManagerIPs(managers);
logger.info(getFormattedMessage(CloudifyMessageKeys.SHUTDOWN_MANAGERS_INITIATED.getName(), managerIPs));
writeManagersToFile(managers);
waitForShutdown(managers, ((RestAdminFacade) adminFacade).getUrl().getPort());
return getFormattedMessage(CloudifyMessageKeys.SHUTDOWN_MANAGERS_SUCCESS.getName());
}
private void waitForShutdown(final List<ControllerDetails> managers, final int port)
throws CLIException, InterruptedException, TimeoutException {
logger.fine("[waitForShutdown] - waiting for shutdown of all manaement machines [total "
+ managers.size() + " managers]");
final Set<ControllerDetails> managersStillUp = new HashSet<ControllerDetails>();
managersStillUp.addAll(managers);
final ConditionLatch conditionLatch =
new ConditionLatch()
.verbose(verbose)
.pollingInterval(POLLING_INTERVAL_MILLI_SECONDS, TimeUnit.MILLISECONDS)
.timeout(timeout, TimeUnit.MINUTES)
.timeoutErrorMessage(CloudifyErrorMessages.SHUTDOWN_MANAGERS_TIMEOUT.getName());
conditionLatch.waitFor(new ConditionLatch.Predicate() {
@Override
public boolean isDone() throws CLIException, InterruptedException {
final Iterator<ControllerDetails> iterator = managersStillUp.iterator();
while (iterator.hasNext()) {
final ControllerDetails manager = iterator.next();
final String host =
manager.isBootstrapToPublicIp() ? manager.getPublicIp() : manager.getPrivateIp();
if (ServiceUtils.isPortFree(host, port)) {
iterator.remove();
displayer.printEvent(getFormattedMessage(
CloudifyErrorMessages.MANAGEMENT_SERVERS_MANAGER_DOWN.getName(), host));
if (managersStillUp.isEmpty()) {
logger.fine("all ports are free, disconnecting");
disconnect();
return true;
}
logger.fine("manager [" + host + "] port is free, "
+ managersStillUp.size() + " more to check");
} else {
logger.fine("manager [" + host + "] port is not free");
displayer.printNoChange();
}
}
return false;
}
});
}
private void writeManagersToFile(final List<ControllerDetails> managers) throws IOException {
logger.fine("[writeManagersToFile] - writing managers to file [" + existingManagersFile + "]");
if (this.existingManagersFile != null) {
final ObjectMapper mapper = new ObjectMapper();
final String managersAsString = mapper.writeValueAsString(managers);
FileUtils.writeStringToFile(existingManagersFile, managersAsString);
}
}
private String getManagerIPs(final List<ControllerDetails> managers) {
List<String> ips = new ArrayList<String>(managers.size());
for (final ControllerDetails managerDetails : managers) {
if (managerDetails.isBootstrapToPublicIp()) {
ips.add(managerDetails.getPublicIp());
} else {
ips.add(managerDetails.getPrivateIp());
}
}
return ips.toString();
}
private void validateManagersFilePath() {
if (existingManagersFile != null) {
if (existingManagersFile.exists() && !existingManagersFile.isFile()) {
throw new IllegalArgumentException("Expected " + existingManagersFile + " to be a file");
}
}
}
private void disconnect() {
try {
adminFacade.disconnect();
} catch (final CLIException e) {
// ignore
}
session.put(Constants.ACTIVE_APP, CloudifyConstants.DEFAULT_APPLICATION_NAME);
GigaShellMain.getInstance().setCurrentApplicationName(CloudifyConstants.DEFAULT_APPLICATION_NAME);
}
public File getExistingManagersFile() {
return existingManagersFile;
}
public void setExistingManagersFile(final File existingManagersFile) {
this.existingManagersFile = existingManagersFile;
}
public int getTimeout() {
return timeout;
}
public void setTimeout(final int timeout) {
this.timeout = timeout;
}
@Override
public Object doExecuteNewRestClient() throws Exception {
validateManagersFilePath();
adminFacade = getRestAdminFacade();
if (adminFacade.isConnected()) {
adminFacade.verifyCloudAdmin();
} else {
throw new CLIException(getFormattedMessage(CloudifyErrorMessages.REST_NOT_CONNECTED.getName()));
}
final RestAdminFacade rest = (RestAdminFacade) this.adminFacade;
final RestClient newRestClient = rest.getNewRestClient();
final ShutdownManagementResponse shutdownManagementResponse = newRestClient.shutdownManagers();
final List<ControllerDetails> managers = Arrays.asList(shutdownManagementResponse.getControllers());
final String managerIPs = getManagerIPs(managers);
logger.info(getFormattedMessage(CloudifyMessageKeys.SHUTDOWN_MANAGERS_INITIATED.getName(), managerIPs));
writeManagersToFile(managers);
waitForShutdown(managers, rest.getUrl().getPort());
return getFormattedMessage(CloudifyMessageKeys.SHUTDOWN_MANAGERS_SUCCESS.getName());
}
}