blob: 8ebb35238b8f730c92c5dbe18848bee9109fafd4 [file] [log] [blame]
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you 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.jclouds.compute.callables;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Date;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.annotation.Resource;
import org.jclouds.Constants;
import org.jclouds.compute.domain.ExecResponse;
import org.jclouds.compute.predicates.ScriptStatusReturnsZero;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.logging.Logger;
import org.jclouds.predicates.RetryablePredicate;
import org.jclouds.scriptbuilder.InitBuilder;
import org.jclouds.ssh.SshClient;
import com.google.common.base.Predicate;
import com.google.common.base.Throwables;
import com.google.common.util.concurrent.AbstractFuture;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.name.Named;
/**
* A future that works in tandem with a task that was invoked by {@link InitBuilder}
*
* @author Adrian Cole
*/
public class BlockUntilInitScriptStatusIsZeroThenReturnOutput extends AbstractFuture<ExecResponse> {
public static interface Factory {
BlockUntilInitScriptStatusIsZeroThenReturnOutput create(SudoAwareInitManager commandRunner);
}
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
private final ExecutorService userThreads;
private final SudoAwareInitManager commandRunner;
private final RetryablePredicate<String> notRunningAnymore;
private boolean shouldCancel;
@Inject
public BlockUntilInitScriptStatusIsZeroThenReturnOutput(
@Named(Constants.PROPERTY_USER_THREADS) ExecutorService userThreads,
ComputeServiceConstants.InitStatusProperties properties,
final ScriptStatusReturnsZero stateRunning, @Assisted final SudoAwareInitManager commandRunner) {
long retryMaxWait = TimeUnit.DAYS.toMillis(365); // arbitrarily high value, but Long.MAX_VALUE doesn't work!
long retryInitialPeriod = properties.initStatusInitialPeriod;
long retryMaxPeriod = properties.initStatusMaxPeriod;
this.commandRunner = checkNotNull(commandRunner, "commandRunner");
this.userThreads = checkNotNull(userThreads, "userThreads");
this.notRunningAnymore = new RetryablePredicate<String>(new Predicate<String>() {
@Override
public boolean apply(String arg0) {
return commandRunner.runAction(arg0).getOutput().trim().equals("");
}
}, retryMaxWait, retryInitialPeriod, retryMaxPeriod, TimeUnit.MILLISECONDS) {
/**
* make sure we stop the retry loop if someone cancelled the future, this keeps threads
* from being consumed on dead tasks
*/
@Override
protected boolean atOrAfter(Date end) {
if (shouldCancel)
Throwables.propagate(new TimeoutException("cancelled"));
return super.atOrAfter(end);
}
};
}
/**
* in case login credentials or user changes at runtime.
*/
public void setSshClient(SshClient client) {
}
/**
* Submits a thread that will either set the result of the future or the exception that took
* place
*/
public BlockUntilInitScriptStatusIsZeroThenReturnOutput init() {
userThreads.submit(new Runnable() {
@Override
public void run() {
try {
boolean complete = notRunningAnymore.apply("status");
String stdout = commandRunner.runAction("tail").getOutput();
String stderr = commandRunner.runAction("tailerr").getOutput();
// TODO make ScriptBuilder save exit status on nuhup
logger.debug("<< complete(%s) status(%s)", commandRunner.getStatement().getInstanceName(), complete);
set(new ExecResponse(stdout, stderr, complete && !shouldCancel ? 0 : -1));
} catch (Exception e) {
setException(e);
}
}
});
return this;
}
@Override
protected void interruptTask() {
logger.debug("<< cancelled(%s)", commandRunner.getStatement().getInstanceName());
commandRunner.refreshAndRunAction("stop");
shouldCancel = true;
super.interruptTask();
}
@Override
public String toString() {
return String.format("running task[%s]", commandRunner);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((commandRunner == null) ? 0 : commandRunner.hashCode());
result = prime * result + ((logger == null) ? 0 : logger.hashCode());
result = prime * result + ((notRunningAnymore == null) ? 0 : notRunningAnymore.hashCode());
result = prime * result + (shouldCancel ? 1231 : 1237);
result = prime * result + ((userThreads == null) ? 0 : userThreads.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
BlockUntilInitScriptStatusIsZeroThenReturnOutput other = (BlockUntilInitScriptStatusIsZeroThenReturnOutput) obj;
if (commandRunner == null) {
if (other.commandRunner != null)
return false;
} else if (!commandRunner.equals(other.commandRunner))
return false;
if (logger == null) {
if (other.logger != null)
return false;
} else if (!logger.equals(other.logger))
return false;
if (notRunningAnymore == null) {
if (other.notRunningAnymore != null)
return false;
} else if (!notRunningAnymore.equals(other.notRunningAnymore))
return false;
if (shouldCancel != other.shouldCancel)
return false;
if (userThreads == null) {
if (other.userThreads != null)
return false;
} else if (!userThreads.equals(other.userThreads))
return false;
return true;
}
@Override
public ExecResponse get(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException,
ExecutionException {
try {
return super.get(timeout, unit);
} catch (TimeoutException e) {
throw new ScriptStillRunningException(timeout, unit, this);
}
}
}