/*******************************************************************************
* 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 org.apache.felix.gogo.commands.Argument;
import org.apache.felix.gogo.commands.Command;
import org.apache.felix.gogo.commands.Option;
import org.cloudifysource.dsl.internal.CloudifyConstants;
import org.cloudifysource.dsl.rest.request.SetServiceInstancesRequest;
import org.cloudifysource.dsl.rest.response.ServiceDescription;
import org.cloudifysource.restclient.RestClient;
import org.cloudifysource.shell.Constants;
import org.cloudifysource.shell.ShellUtils;
import org.cloudifysource.shell.exceptions.CLIException;
import org.cloudifysource.shell.exceptions.CLIStatusException;
import org.cloudifysource.shell.installer.CLIEventsDisplayer;
import org.cloudifysource.shell.rest.RestAdminFacade;
import org.cloudifysource.shell.rest.RestLifecycleEventsLatch;
import org.cloudifysource.shell.rest.inspect.service.SetInstancesScaledownInstallationProcessInspector;
import org.cloudifysource.shell.rest.inspect.service.SetInstancesScaleupInstallationProcessInspector;
import org.fusesource.jansi.Ansi.Color;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/************
* Manually sets the number of instances for a specific service.
*
* @author barakme
*
*/
@Command(scope = "cloudify", name = "set-instances", description = "Sets the number of services of an elastic service")
public class SetInstances extends AdminAwareCommand implements NewRestClientCommand {
private static final int DEFAULT_TIMEOUT_MINUTES = 10;
@Argument(index = 0, name = "service-name", required = true, description = "the service to scale")
private String serviceName;
@Argument(index = 1, name = "count", required = true, description = "the target number of instances")
private int count;
@Option(required = false, name = "-timeout",
description = "number of minutes to wait for instances. Default is set to 10 minutes")
protected int timeout = DEFAULT_TIMEOUT_MINUTES;
// NOTE: This flag has been disabled as manual scaling is not supported with location aware services in Cloudify
// 2.2.
// This issue will be revisited in the future.
// @Option(required = false, name = "-location-aware", description =
// "When true re-starts failed machines in the same cloud location. Default is set to false.")
private boolean locationAware = false;
private static final String TIMEOUT_ERROR_MESSAGE = "The operation timed out. "
+ "Try to increase the timeout using the -timeout flag";
private final CLIEventsDisplayer displayer = new CLIEventsDisplayer();
@Override
protected Object doExecute() throws Exception {
String applicationName = this.getCurrentApplicationName();
if (applicationName == null) {
applicationName = CloudifyConstants.DEFAULT_APPLICATION_NAME;
}
final int initialNumberOfInstances = adminFacade.getInstanceList(applicationName, serviceName).size();
if (initialNumberOfInstances == count) {
return getFormattedMessage("num_instances_already_met", count);
}
final Map<String, String> response =
adminFacade.setInstances(applicationName, serviceName, count, isLocationAware(), timeout);
final String pollingID = response.get(CloudifyConstants.LIFECYCLE_EVENT_CONTAINER_ID);
final RestLifecycleEventsLatch lifecycleEventsPollingLatch =
this.adminFacade.getLifecycleEventsPollingLatch(pollingID, TIMEOUT_ERROR_MESSAGE);
boolean isDone = false;
boolean continuous = false;
while (!isDone) {
try {
if (!continuous) {
lifecycleEventsPollingLatch.waitForLifecycleEvents(timeout, TimeUnit.MINUTES);
} else {
lifecycleEventsPollingLatch.continueWaitForLifecycleEvents(timeout, TimeUnit.MINUTES);
}
isDone = true;
} catch (final TimeoutException e) {
if (!(Boolean) session.get(Constants.INTERACTIVE_MODE)) {
throw e;
}
final boolean continueInstallation = promptWouldYouLikeToContinueQuestion();
if (!continueInstallation) {
throw new CLIStatusException(e, "application_installation_timed_out_on_client",
applicationName);
}
continuous = continueInstallation;
}
}
return getFormattedMessage("set_instances_completed_successfully", Color.GREEN, serviceName, count);
}
private boolean promptWouldYouLikeToContinueQuestion() throws IOException {
return ShellUtils.promptUser(session, "would_you_like_to_continue_polling_on_instance_lifecycle_events");
}
public boolean isLocationAware() {
return locationAware;
}
public void setLocationAware(final boolean locationAware) {
this.locationAware = locationAware;
}
@Override
public Object doExecuteNewRestClient() throws Exception {
final RestClient newRestClient = ((RestAdminFacade) getRestAdminFacade()).getNewRestClient();
final String applicationName = resolveApplicationName();
final ServiceDescription serviceDescription = newRestClient.getServiceDescription(applicationName, serviceName);
final String deploymentId = serviceDescription.getDeploymentId();
final int initialNumberOfInstances = serviceDescription.getPlannedInstances();
if (initialNumberOfInstances == count) {
return getFormattedMessage("num_instances_already_met", count);
}
final int lastEventIndex = newRestClient.getLastEvent(deploymentId).getIndex();
final SetServiceInstancesRequest request = new SetServiceInstancesRequest();
request.setCount(count);
request.setLocationAware(false);
request.setTimeout(this.timeout);
// REST API call to server
newRestClient.setServiceInstances(applicationName, serviceName, request);
if (count > initialNumberOfInstances) {
return waitForScaleOut(deploymentId, count, lastEventIndex,
initialNumberOfInstances);
}
return waitForScaleIn(deploymentId, count, lastEventIndex, initialNumberOfInstances);
}
private String resolveApplicationName() {
String applicationName = this.getCurrentApplicationName();
if (applicationName == null) {
applicationName = CloudifyConstants.DEFAULT_APPLICATION_NAME;
}
return applicationName;
}
private String waitForScaleOut(final String deploymentID, final int plannedNumberOfInstnaces,
final int lastEventIndex, final int currentNumberOfInstances) throws InterruptedException,
CLIException, IOException {
final SetInstancesScaleupInstallationProcessInspector inspector =
new SetInstancesScaleupInstallationProcessInspector(
((RestAdminFacade) adminFacade).getNewRestClient(),
deploymentID,
verbose,
serviceName,
plannedNumberOfInstnaces,
getCurrentApplicationName(),
lastEventIndex,
currentNumberOfInstances);
int actualTimeout = this.timeout;
boolean isDone = false;
displayer.printEvent("installing_service", serviceName, plannedNumberOfInstnaces);
displayer.printEvent("waiting_for_lifecycle_of_service", serviceName);
while (!isDone) {
try {
inspector.waitForLifeCycleToEnd(actualTimeout);
isDone = true;
} catch (final TimeoutException e) {
// if non interactive, throw exception
if (!(Boolean) session.get(Constants.INTERACTIVE_MODE)) {
throw new CLIException(e.getMessage(), e);
}
// ask the user whether to continue viewing the installation or to stop
displayer.printEvent("");
final boolean continueViewing = promptWouldYouLikeToContinueQuestion();
if (continueViewing) {
// prolong the polling timeouts
actualTimeout = DEFAULT_TIMEOUT_MINUTES;
} else {
throw new CLIStatusException(e,
"service_installation_timed_out_on_client",
serviceName);
}
}
}
// drop one line before printing the last message
displayer.printEvent("");
return getFormattedMessage("service_install_ended", Color.GREEN, serviceName);
}
private String waitForScaleIn(
final String deploymentID,
final int plannedNumberOfInstnaces,
final int lastEventIndex,
final int currentNumberOfInstances)
throws InterruptedException, CLIException, IOException {
final SetInstancesScaledownInstallationProcessInspector inspector =
new SetInstancesScaledownInstallationProcessInspector(
((RestAdminFacade) adminFacade).getNewRestClient(),
deploymentID,
verbose,
serviceName,
plannedNumberOfInstnaces,
getCurrentApplicationName(),
lastEventIndex,
currentNumberOfInstances);
int actualTimeout = this.timeout;
boolean isDone = false;
displayer.printEvent("installing_service", serviceName, plannedNumberOfInstnaces);
displayer.printEvent("waiting_for_lifecycle_of_service", serviceName);
while (!isDone) {
try {
inspector.waitForLifeCycleToEnd(actualTimeout);
isDone = true;
} catch (final TimeoutException e) {
// if non interactive, throw exception
if (!(Boolean) session.get(Constants.INTERACTIVE_MODE)) {
throw new CLIException(e.getMessage(), e);
}
// ask the user whether to continue viewing the installation or to stop
displayer.printEvent("");
final boolean continueViewing = promptWouldYouLikeToContinueQuestion();
if (continueViewing) {
// prolong the polling timeouts
actualTimeout = DEFAULT_TIMEOUT_MINUTES;
} else {
throw new CLIStatusException(e,
"service_installation_timed_out_on_client",
serviceName);
}
}
}
// drop one line before printing the last message
displayer.printEvent("");
return getFormattedMessage("service_install_ended", Color.GREEN, serviceName);
}
}