/*******************************************************************************
* 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.rest.inspect;
import org.cloudifysource.dsl.rest.response.DeploymentEvent;
import org.cloudifysource.dsl.rest.response.DeploymentEvents;
import org.cloudifysource.restclient.RestClient;
import org.cloudifysource.restclient.exceptions.RestClientException;
import org.cloudifysource.shell.ConditionLatch;
import org.cloudifysource.shell.exceptions.CLIException;
import org.cloudifysource.shell.installer.CLIEventsDisplayer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;
/**
* Created with IntelliJ IDEA. User: elip Date: 5/29/13 Time: 1:50 PM <br>
* </br>
*
* Provides functionality for inspecting the installation process of services/application.
*
*/
public abstract class InstallationProcessInspector {
protected Logger logger = Logger.getLogger(InstallationProcessInspector.class.getName());
private static final int POLLING_INTERVAL_MILLI_SECONDS = 500;
protected static final int RESOURCE_NOT_FOUND_EXCEPTION_CODE = 404;
protected RestClient restClient;
private final boolean verbose;
protected final String deploymentId;
protected final String applicationName;
protected final Map<String, Integer> plannedNumberOfInstancesPerService;
protected final Map<String, Integer> currentRunningInstancesPerService;
private int lastEventIndex = 0;
private final CLIEventsDisplayer displayer = new CLIEventsDisplayer();
public InstallationProcessInspector(final RestClient restClient,
final String deploymentId,
final String applicationName,
final boolean verbose,
final Map<String, Integer> plannedNumberOfInstancesPerService,
final Map<String, Integer> currentRunningInstancesPerService) {
this.restClient = restClient;
this.deploymentId = deploymentId;
this.applicationName = applicationName;
this.verbose = verbose;
this.plannedNumberOfInstancesPerService = plannedNumberOfInstancesPerService;
this.currentRunningInstancesPerService = currentRunningInstancesPerService;
}
/**
* Waits until the application/service lifecycle ends. As long as the installation continues, it will print out the
* most recent events not yet printed.
*
* @param timeout
* the timeout.
* @throws InterruptedException
* Thrown in case the thread was interrupted.
* @throws CLIException
* Thrown in case an error happened while trying to retrieve events.
* @throws TimeoutException
* Thrown in case the timeout is reached.
*/
public void waitForLifeCycleToEnd(final long timeout) throws InterruptedException, CLIException, TimeoutException {
ConditionLatch conditionLatch = createConditionLatch(timeout);
conditionLatch.waitFor(new ConditionLatch.Predicate() {
@Override
public boolean isDone() throws CLIException, InterruptedException {
try {
printInstalledInstances();
boolean ended = lifeCycleEnded();
List<String> latestEvents = getLatestEvents();
if (!latestEvents.isEmpty()) {
displayer.printEvents(latestEvents);
} else {
if (!ended) {
displayer.printNoChange();
}
}
if (ended) {
printInstalledInstances();
}
return ended;
} catch (final RestClientException e) {
throw new CLIException(e.getMessage(), e, e.getVerbose());
}
}
private void printInstalledInstances() throws RestClientException {
for (Map.Entry<String, Integer> entry : plannedNumberOfInstancesPerService.entrySet()) {
int runningInstances = getNumberOfRunningInstances(entry.getKey());
if (runningInstances > currentRunningInstancesPerService.get(entry.getKey())) {
// a new instance is now running
displayer.printEvent("succesfully_installed_instances", runningInstances,
entry.getValue(), entry.getKey());
currentRunningInstancesPerService.put(entry.getKey(), runningInstances);
}
}
}
});
}
/**
* Determines whether or not the life cycle for this installation has ended.
*
* @return true if the service/application are fully running.
* @throws RestClientException
* Thrown in case an error happened during a rest call.
* @throws CLIException
* Thrown in case the CLI determined that some sort error happened.
*/
public abstract boolean lifeCycleEnded() throws RestClientException, CLIException;
/**
* Query the number of running instances for a particular service.
*
* @param serviceName
* The service name.
* @return how many instances are in running state.
* @throws RestClientException
* Thrown in case an error happened during a rest call.
*/
public abstract int getNumberOfRunningInstances(final String serviceName) throws RestClientException;
/**
*
* @return the error message presented upon timeout.
*/
public abstract String getTimeoutErrorMessage();
/**
* Gets the latest events of this deployment id. Events are sorted by event index.
*
* @return A list of events. If this is the first time events are requested, all events are retrieved. Otherwise,
* only new events (that were not reported earlier) are retrieved.
* @throws RestClientException
* Indicates a failure to get events from the server.
*/
public List<String> getLatestEvents() throws RestClientException {
List<String> eventsStrings = new ArrayList<String>();
DeploymentEvents events = restClient.getDeploymentEvents(deploymentId, lastEventIndex + 1, -1);
if (events == null || events.getEvents().isEmpty()) {
return eventsStrings;
}
for (DeploymentEvent event : events.getEvents()) {
eventsStrings.add(event.getDescription());
}
lastEventIndex = events.getEvents().get(events.getEvents().size() - 1).getIndex();
return eventsStrings;
}
/**
* Creates a {@link ConditionLatch} object with the given timeout (in minutes), using a polling interval of 500 ms.
*
* @param timeout
* Timeout, in minutes.
* @return a configured {@link ConditionLatch} object
*/
public ConditionLatch createConditionLatch(final long timeout) {
return new ConditionLatch()
.verbose(verbose)
.pollingInterval(POLLING_INTERVAL_MILLI_SECONDS, TimeUnit.MILLISECONDS)
.timeout(timeout, TimeUnit.MINUTES)
.timeoutErrorMessage(getTimeoutErrorMessage());
}
public void setLastEventIndex(final int eventIndex) {
this.lastEventIndex = eventIndex;
}
}