Added SocketOpen retryable predicate for connecting to ssh

Some refactoring, including use of guava's checkState and more precise method parameters
This commit is contained in:
Alex Yarmula 2010-02-17 10:40:35 -08:00
parent a46542ee91
commit 543af26e92
5 changed files with 46 additions and 28 deletions

View File

@ -30,7 +30,7 @@
<artifactId>jclouds-aws-demo-ebsresize</artifactId> <artifactId>jclouds-aws-demo-ebsresize</artifactId>
<groupId>org.jclouds</groupId> <groupId>org.jclouds</groupId>
<name>jclouds EC2 example, to change volume size for EBS-based instances</name> <name>jclouds EC2 demo, to change volume size for EBS-based instances</name>
<description>For a given running instance, resizes the volume (with several minutes of downtime)</description> <description>For a given running instance, resizes the volume (with several minutes of downtime)</description>
<repositories> <repositories>

View File

@ -19,6 +19,10 @@
package org.jclouds.tools.ebsresize; package org.jclouds.tools.ebsresize;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import org.jclouds.aws.domain.Region; import org.jclouds.aws.domain.Region;
import org.jclouds.aws.ec2.EC2AsyncClient; import org.jclouds.aws.ec2.EC2AsyncClient;
import org.jclouds.aws.ec2.EC2Client; import org.jclouds.aws.ec2.EC2Client;
@ -30,13 +34,17 @@ import org.jclouds.compute.domain.ComputeMetadata;
import org.jclouds.compute.domain.NodeMetadata; import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.internal.NodeMetadataImpl; import org.jclouds.compute.domain.internal.NodeMetadataImpl;
import org.jclouds.domain.Credentials; import org.jclouds.domain.Credentials;
import org.jclouds.predicates.RetryablePredicate;
import org.jclouds.predicates.SocketOpen;
import org.jclouds.rest.RestContext; import org.jclouds.rest.RestContext;
import org.jclouds.tools.ebsresize.facade.ElasticBlockStoreFacade; import org.jclouds.tools.ebsresize.facade.ElasticBlockStoreFacade;
import org.jclouds.tools.ebsresize.facade.InstanceFacade; import org.jclouds.tools.ebsresize.facade.InstanceFacade;
import org.jclouds.tools.ebsresize.util.SshExecutor; import org.jclouds.tools.ebsresize.util.SshExecutor;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit;
/** /**
* Launches a sequence of commands to change the size of * Launches a sequence of commands to change the size of
@ -59,6 +67,8 @@ public class InstanceVolumeManager {
private final EC2Client api; private final EC2Client api;
private final ElasticBlockStoreFacade ebsApi; private final ElasticBlockStoreFacade ebsApi;
private final InstanceFacade instanceApi; private final InstanceFacade instanceApi;
private final Predicate<InetSocketAddress> socketOpen =
new RetryablePredicate<InetSocketAddress>(new SocketOpen(), 180, 5, TimeUnit.SECONDS);
public InstanceVolumeManager(String accessKeyId, String secretKey) { public InstanceVolumeManager(String accessKeyId, String secretKey) {
@ -92,14 +102,6 @@ public class InstanceVolumeManager {
api.getElasticBlockStoreServices().deleteVolumeInRegion(instance.getRegion(), volume.getId()); api.getElasticBlockStoreServices().deleteVolumeInRegion(instance.getRegion(), volume.getId());
//TODO: how to know that an instance is available for SSH for certain?
// sometimes 20 seconds is enough after it started, sometimes it isn't
try {
Thread.sleep(20000);
} catch(InterruptedException e) {
e.printStackTrace();
}
runRemoteResizeCommands(instance, instanceCredentials, pathToKeyPair); runRemoteResizeCommands(instance, instanceCredentials, pathToKeyPair);
} }
@ -112,8 +114,14 @@ public class InstanceVolumeManager {
NodeMetadata nodeMetadata = addCredentials((NodeMetadata) nodes.get(instance.getId()), NodeMetadata nodeMetadata = addCredentials((NodeMetadata) nodes.get(instance.getId()),
instanceCredentials); instanceCredentials);
InetSocketAddress socket =
new InetSocketAddress(Iterables.getLast(nodeMetadata.getPublicAddresses()), 22);
SshExecutor sshExecutor = new SshExecutor(nodeMetadata, instanceCredentials, SshExecutor sshExecutor = new SshExecutor(nodeMetadata, instanceCredentials,
keyPair, instance); keyPair, socket);
waitForSocket(socket);
sshExecutor.connect(); sshExecutor.connect();
sshExecutor.execute("sudo resize2fs " + instance.getRootDeviceName()); sshExecutor.execute("sudo resize2fs " + instance.getRootDeviceName());
} }
@ -129,6 +137,11 @@ public class InstanceVolumeManager {
credentials); credentials);
} }
public void waitForSocket(InetSocketAddress socket) {
checkState(socketOpen.apply(socket),
/*or throw*/ "Couldn't connect to instance");
}
public ElasticBlockStoreFacade getEbsApi() { public ElasticBlockStoreFacade getEbsApi() {
return ebsApi; return ebsApi;
} }

View File

@ -99,7 +99,8 @@ public class ElasticBlockStoreFacade {
public void detachVolumeFromStoppedInstance(Volume volume, RunningInstance stoppedInstance) { public void detachVolumeFromStoppedInstance(Volume volume, RunningInstance stoppedInstance) {
elasticBlockStoreServices.detachVolumeInRegion(stoppedInstance.getRegion(), volume.getId(), false, elasticBlockStoreServices.detachVolumeInRegion(stoppedInstance.getRegion(), volume.getId(), false,
DetachVolumeOptions.Builder.fromInstance(stoppedInstance.getId())); DetachVolumeOptions.Builder.fromInstance(stoppedInstance.getId()));
assertTrue(volumeAvailable.apply(volume)); checkState(volumeAvailable.apply(volume),
/*or throw*/ "Couldn't detach the volume from instance");
} }
/** /**
@ -117,12 +118,14 @@ public class ElasticBlockStoreFacade {
Snapshot createdSnapshot = Snapshot createdSnapshot =
elasticBlockStoreServices.createSnapshotInRegion(volume.getRegion(), volume.getId(), elasticBlockStoreServices.createSnapshotInRegion(volume.getRegion(), volume.getId(),
CreateSnapshotOptions.Builder.withDescription("snapshot to test extending volumes")); CreateSnapshotOptions.Builder.withDescription("snapshot to test extending volumes"));
assertTrue(snapshotCompleted.apply(createdSnapshot)); checkState(snapshotCompleted.apply(createdSnapshot),
/*or throw*/ "Couldn't create a snapshot");
Volume newVolume = elasticBlockStoreServices.createVolumeFromSnapshotInAvailabilityZone( Volume newVolume = elasticBlockStoreServices.createVolumeFromSnapshotInAvailabilityZone(
volume.getAvailabilityZone(), newSize, volume.getAvailabilityZone(), newSize,
createdSnapshot.getId()); createdSnapshot.getId());
assertTrue(volumeAvailable.apply(newVolume)); checkState(volumeAvailable.apply(newVolume),
/*or throw*/ "Couldn't create a volume from the snapshot");
return newVolume; return newVolume;
} }
@ -141,10 +144,7 @@ public class ElasticBlockStoreFacade {
Attachment volumeAttachment = elasticBlockStoreServices. Attachment volumeAttachment = elasticBlockStoreServices.
attachVolumeInRegion(instance.getRegion(), volume.getId(), instance.getId(), attachVolumeInRegion(instance.getRegion(), volume.getId(), instance.getId(),
instance.getRootDeviceName()); instance.getRootDeviceName());
assertTrue(volumeAttached.apply(volumeAttachment)); checkState(volumeAttached.apply(volumeAttachment),
} /*or throw*/ "Couldn't attach volume back to the instance");
public void assertTrue(boolean value) {
if(!value) throw new RuntimeException("Found false, expected true");
} }
} }

View File

@ -69,7 +69,8 @@ public class InstanceFacade {
*/ */
public void startInstance(RunningInstance instance) { public void startInstance(RunningInstance instance) {
instanceServices.startInstancesInRegion(instance.getRegion(), instance.getId()); instanceServices.startInstancesInRegion(instance.getRegion(), instance.getId());
assertTrue(instanceRunning.apply(instance)); checkState(instanceRunning.apply(instance),
/*or throw*/ "Couldn't start the instance");
} }
/** /**
@ -84,7 +85,8 @@ public class InstanceFacade {
*/ */
public void stopInstance(RunningInstance instance) { public void stopInstance(RunningInstance instance) {
instanceServices.stopInstancesInRegion(instance.getRegion(), false, instance.getId()); instanceServices.stopInstancesInRegion(instance.getRegion(), false, instance.getId());
assertTrue(instanceStopped.apply(instance)); checkState(instanceStopped.apply(instance),
/*or throw*/ "Couldn't stop the instance");
} }
/** /**
@ -99,9 +101,5 @@ public class InstanceFacade {
Reservation reservation = checkNotNull(Iterables.getOnlyElement(reservations)); Reservation reservation = checkNotNull(Iterables.getOnlyElement(reservations));
return Iterables.getOnlyElement(reservation); return Iterables.getOnlyElement(reservation);
} }
public void assertTrue(boolean value) {
if(!value) throw new RuntimeException("Found false, expected true");
}
} }

View File

@ -35,6 +35,15 @@ import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
* Allows remote command execution on instance.
*
* This is based on {@link org.jclouds.ssh.SshClient} and
* {@link org.jclouds.compute.util.ComputeUtils}, with
* some convenience methods.
*
* @see org.jclouds.ssh.SshClient
* @see org.jclouds.compute.util.ComputeUtils
*
* @author Oleksiy Yarmula * @author Oleksiy Yarmula
*/ */
public class SshExecutor { public class SshExecutor {
@ -46,21 +55,19 @@ public class SshExecutor {
private final NodeMetadata nodeMetadata; private final NodeMetadata nodeMetadata;
private final Credentials instanceCredentials; private final Credentials instanceCredentials;
private final String keyPair; private final String keyPair;
private final RunningInstance instance;
private final SshClient sshClient; private final SshClient sshClient;
private final ComputeUtils utils; private final ComputeUtils utils;
public SshExecutor(NodeMetadata nodeMetadata, public SshExecutor(NodeMetadata nodeMetadata,
Credentials instanceCredentials, Credentials instanceCredentials,
String keyPair, String keyPair,
RunningInstance instance) { InetSocketAddress socket) {
this.nodeMetadata = nodeMetadata; this.nodeMetadata = nodeMetadata;
this.instanceCredentials = instanceCredentials; this.instanceCredentials = instanceCredentials;
this.keyPair = keyPair; this.keyPair = keyPair;
this.instance = instance;
this.sshClient = this.sshClient =
new JschSshClient(new InetSocketAddress(instance.getDnsName(), 22), 60000, new JschSshClient(socket, 60000,
instanceCredentials.account, keyPair.getBytes()); instanceCredentials.account, keyPair.getBytes());
this.utils = new ComputeUtils(null, runScriptRunning, null); this.utils = new ComputeUtils(null, runScriptRunning, null);