Issue 29: alestic run support

git-svn-id: http://jclouds.googlecode.com/svn/trunk@2555 3d8758e0-26b5-11de-8745-db77d3ebf521
This commit is contained in:
adrian.f.cole 2009-12-31 02:35:23 +00:00
parent cc0dbf6e1c
commit f27950205a
6 changed files with 208 additions and 67 deletions

View File

@ -88,8 +88,8 @@ public class RunInstancesOptions extends BaseEC2RequestOptions {
/** /**
* MIME, Base64-encoded user data. * MIME, Base64-encoded user data.
*/ */
public RunInstancesOptions withUserData(String info) { public RunInstancesOptions withUserData(String data) {
formParameters.put("UserData", checkNotNull(info, "info")); formParameters.put("UserData", checkNotNull(data, "data"));
return this; return this;
} }

View File

@ -122,4 +122,89 @@ public interface InstanceAsyncClient {
@EndpointParam(parser = RegionToEndpoint.class) Region region, @EndpointParam(parser = RegionToEndpoint.class) Region region,
@BinderParam(BindInstanceIdsToIndexedFormParams.class) String... instanceIds); @BinderParam(BindInstanceIdsToIndexedFormParams.class) String... instanceIds);
/**
* @see AMIClient#getUserDataForInstanceInRegion
*/
@POST
@Path("/")
@FormParams(keys = { ACTION, "Attribute" }, values = { "DescribeInstanceAttribute", "userData" })
Future<String> getUserDataForInstanceInRegion(
@EndpointParam(parser = RegionToEndpoint.class) Region region,
@BinderParam(BindInstanceIdsToIndexedFormParams.class) String... instanceIds);
/**
* @see AMIClient#getRootDeviceNameForInstanceInRegion
*/
@POST
@Path("/")
@FormParams(keys = { ACTION, "Attribute" }, values = { "DescribeInstanceAttribute",
"rootDeviceName" })
Future<String> getRootDeviceNameForInstanceInRegion(
@EndpointParam(parser = RegionToEndpoint.class) Region region,
@BinderParam(BindInstanceIdsToIndexedFormParams.class) String... instanceIds);
/**
* @see AMIClient#getRamdiskForInstanceInRegion
*/
@POST
@Path("/")
@FormParams(keys = { ACTION, "Attribute" }, values = { "DescribeInstanceAttribute", "ramdisk" })
Future<String> getRamdiskForInstanceInRegion(
@EndpointParam(parser = RegionToEndpoint.class) Region region,
@BinderParam(BindInstanceIdsToIndexedFormParams.class) String... instanceIds);
/**
* @see AMIClient#getDisableApiTerminationForInstanceInRegion
*/
@POST
@Path("/")
@FormParams(keys = { ACTION, "Attribute" }, values = { "DescribeInstanceAttribute",
"disableApiTermination" })
Future<String> getDisableApiTerminationForInstanceInRegion(
@EndpointParam(parser = RegionToEndpoint.class) Region region,
@BinderParam(BindInstanceIdsToIndexedFormParams.class) String... instanceIds);
/**
* @see AMIClient#getKernelForInstanceInRegion
*/
@POST
@Path("/")
@FormParams(keys = { ACTION, "Attribute" }, values = { "DescribeInstanceAttribute", "kernel" })
Future<String> getKernelForInstanceInRegion(
@EndpointParam(parser = RegionToEndpoint.class) Region region,
@BinderParam(BindInstanceIdsToIndexedFormParams.class) String... instanceIds);
/**
* @see AMIClient#getInstanceTypeForInstanceInRegion
*/
@POST
@Path("/")
@FormParams(keys = { ACTION, "Attribute" }, values = { "DescribeInstanceAttribute",
"instanceType" })
Future<String> getInstanceTypeForInstanceInRegion(
@EndpointParam(parser = RegionToEndpoint.class) Region region,
@BinderParam(BindInstanceIdsToIndexedFormParams.class) String... instanceIds);
/**
* @see AMIClient#getInstanceInitiatedShutdownBehaviorForInstanceInRegion
*/
@POST
@Path("/")
@FormParams(keys = { ACTION, "Attribute" }, values = { "DescribeInstanceAttribute",
"instanceInitiatedShutdownBehavior" })
Future<String> getInstanceInitiatedShutdownBehaviorForInstanceInRegion(
@EndpointParam(parser = RegionToEndpoint.class) Region region,
@BinderParam(BindInstanceIdsToIndexedFormParams.class) String... instanceIds);
/**
* @see AMIClient#getBlockDeviceMappingForInstanceInRegion
*/
@POST
@Path("/")
@FormParams(keys = { ACTION, "Attribute" }, values = { "DescribeInstanceAttribute",
"blockDeviceMapping" })
Future<String> getBlockDeviceMappingForInstanceInRegion(
@EndpointParam(parser = RegionToEndpoint.class) Region region,
@BinderParam(BindInstanceIdsToIndexedFormParams.class) String... instanceIds);
} }

View File

@ -225,4 +225,22 @@ public interface InstanceClient {
* /> * />
*/ */
Set<InstanceStateChange> startInstancesInRegion(Region region, String... instanceIds); Set<InstanceStateChange> startInstancesInRegion(Region region, String... instanceIds);
String getUserDataForInstanceInRegion(Region region, String... instanceIds);
String getRootDeviceNameForInstanceInRegion(Region region, String... instanceIds);
String getRamdiskForInstanceInRegion(Region region, String... instanceIds);
String getDisableApiTerminationForInstanceInRegion(Region region, String... instanceIds);
String getKernelForInstanceInRegion(Region region, String... instanceIds);
String getInstanceTypeForInstanceInRegion(Region region, String... instanceIds);
String getInstanceInitiatedShutdownBehaviorForInstanceInRegion(Region region,
String... instanceIds);
String getBlockDeviceMappingForInstanceInRegion(Region region, String... instanceIds);
} }

View File

@ -25,14 +25,16 @@ package org.jclouds.aws.ec2;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.aws.ec2.options.RunInstancesOptions.Builder.asType; import static org.jclouds.aws.ec2.options.RunInstancesOptions.Builder.asType;
import static org.jclouds.scriptbuilder.domain.Statements.exec;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNotNull;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.Set;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
@ -45,14 +47,18 @@ import org.jclouds.aws.ec2.domain.PublicIpInstanceIdPair;
import org.jclouds.aws.ec2.domain.Region; import org.jclouds.aws.ec2.domain.Region;
import org.jclouds.aws.ec2.domain.Reservation; import org.jclouds.aws.ec2.domain.Reservation;
import org.jclouds.aws.ec2.domain.RunningInstance; import org.jclouds.aws.ec2.domain.RunningInstance;
import org.jclouds.aws.ec2.predicates.InstanceStateRunning;
import org.jclouds.encryption.internal.Base64;
import org.jclouds.http.HttpResponseException; import org.jclouds.http.HttpResponseException;
import org.jclouds.logging.log4j.config.Log4JLoggingModule; import org.jclouds.logging.log4j.config.Log4JLoggingModule;
import org.jclouds.predicates.RetryablePredicate; import org.jclouds.predicates.RetryablePredicate;
import org.jclouds.predicates.SocketOpen; import org.jclouds.predicates.SocketOpen;
import org.jclouds.scriptbuilder.ScriptBuilder;
import org.jclouds.scriptbuilder.domain.OsFamily;
import org.jclouds.ssh.ExecResponse;
import org.jclouds.ssh.SshClient; import org.jclouds.ssh.SshClient;
import org.jclouds.ssh.SshException; import org.jclouds.ssh.SshException;
import org.jclouds.ssh.jsch.config.JschSshClientModule; import org.jclouds.ssh.jsch.config.JschSshClientModule;
import org.jclouds.util.Utils;
import org.testng.annotations.AfterTest; import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeGroups; import org.testng.annotations.BeforeGroups;
import org.testng.annotations.Test; import org.testng.annotations.Test;
@ -63,7 +69,8 @@ import com.google.inject.Injector;
/** /**
* Follows the book Cloud Application Architectures ISBN: 978-0-596-15636-7 * Follows the book Cloud Application Architectures ISBN: 978-0-596-15636-7
* <p/> * <p/>
* * adds in functionality to boot a lamp instance: http://alestic.com/2009/06/ec2-user-data-scripts
* <p/>
* Generally disabled, as it incurs higher fees. * Generally disabled, as it incurs higher fees.
* *
* @author Adrian Cole * @author Adrian Cole
@ -73,13 +80,14 @@ public class CloudApplicationArchitecturesEC2ClientLiveTest {
private EC2Client client; private EC2Client client;
protected SshClient.Factory sshFactory; protected SshClient.Factory sshFactory;
private String serverPrefix = System.getProperty("user.name") + ".ec2"; private String instancePrefix = System.getProperty("user.name") + ".ec2";
private KeyPair keyPair; private KeyPair keyPair;
private String securityGroupName; private String securityGroupName;
private String serverId; private String instanceId;
private InetAddress address; private InetAddress address;
private RetryablePredicate<InetSocketAddress> socketTester; private RetryablePredicate<InetSocketAddress> socketTester;
private RetryablePredicate<RunningInstance> runningTester;
@BeforeGroups(groups = { "live" }) @BeforeGroups(groups = { "live" })
public void setupClient() throws InterruptedException, ExecutionException, TimeoutException { public void setupClient() throws InterruptedException, ExecutionException, TimeoutException {
@ -89,15 +97,16 @@ public class CloudApplicationArchitecturesEC2ClientLiveTest {
.withModules(new Log4JLoggingModule(), new JschSshClientModule()).buildInjector(); .withModules(new Log4JLoggingModule(), new JschSshClientModule()).buildInjector();
client = injector.getInstance(EC2Client.class); client = injector.getInstance(EC2Client.class);
sshFactory = injector.getInstance(SshClient.Factory.class); sshFactory = injector.getInstance(SshClient.Factory.class);
SocketOpen socketOpen = injector.getInstance(SocketOpen.class); runningTester = new RetryablePredicate<RunningInstance>(new InstanceStateRunning(client
socketTester = new RetryablePredicate<InetSocketAddress>(socketOpen, 60, 1, TimeUnit.SECONDS); .getInstanceServices()), 180, 5, TimeUnit.SECONDS);
injector.injectMembers(socketOpen); // add logger socketTester = new RetryablePredicate<InetSocketAddress>(new SocketOpen(), 180, 1,
TimeUnit.SECONDS);
} }
@Test(enabled = false) @Test(enabled = false)
void testCreateSecurityGroupIngressCidr() throws InterruptedException, ExecutionException, void testCreateSecurityGroupIngressCidr() throws InterruptedException, ExecutionException,
TimeoutException { TimeoutException {
securityGroupName = serverPrefix + "ingress"; securityGroupName = instancePrefix + "ingress";
try { try {
client.getSecurityGroupServices().deleteSecurityGroupInRegion(Region.DEFAULT, client.getSecurityGroupServices().deleteSecurityGroupInRegion(Region.DEFAULT,
@ -115,7 +124,7 @@ public class CloudApplicationArchitecturesEC2ClientLiveTest {
@Test(enabled = false) @Test(enabled = false)
void testCreateKeyPair() throws InterruptedException, ExecutionException, TimeoutException { void testCreateKeyPair() throws InterruptedException, ExecutionException, TimeoutException {
String keyName = serverPrefix + "1"; String keyName = instancePrefix + "1";
try { try {
client.getKeyPairServices().deleteKeyPairInRegion(Region.DEFAULT, keyName); client.getKeyPairServices().deleteKeyPairInRegion(Region.DEFAULT, keyName);
} catch (Exception e) { } catch (Exception e) {
@ -133,32 +142,42 @@ public class CloudApplicationArchitecturesEC2ClientLiveTest {
@Test(enabled = false, dependsOnMethods = { "testCreateKeyPair", @Test(enabled = false, dependsOnMethods = { "testCreateKeyPair",
"testCreateSecurityGroupIngressCidr" }) "testCreateSecurityGroupIngressCidr" })
public void testCreateRunningInstance() throws Exception { public void testCreateRunningInstance() throws Exception {
RunningInstance server = null; RunningInstance instance = null;
while (server == null) { while (instance == null) {
try { try {
String script = new ScriptBuilder() // lamp install script
.addStatement(exec("runurl run.alestic.com/apt/upgrade"))//
.addStatement(exec("runurl run.alestic.com/install/lamp"))//
.build(OsFamily.UNIX);
// userData must be base 64 encoded
String encodedScript = Base64.encodeBytes(script.getBytes());
System.out.printf("%d: running instance%n", System.currentTimeMillis()); System.out.printf("%d: running instance%n", System.currentTimeMillis());
Reservation reservation = client.getInstanceServices().runInstancesInRegion( Reservation reservation = client.getInstanceServices().runInstancesInRegion(
Region.DEFAULT, null, // allow ec2 to chose an availability zone Region.DEFAULT, null, // allow ec2 to chose an availability zone
"ami-7e28ca17", // the ami I want "ami-ccf615a5", // alestic ami allows auto-invoke of user data scripts
1, // minimum instances 1, // minimum instances
1, // maximum instances 1, // maximum instances
asType(InstanceType.M1_SMALL) // smallest instance size asType(InstanceType.M1_SMALL) // smallest instance size
.withKeyName(keyPair.getKeyName()) // key I created above .withKeyName(keyPair.getKeyName()) // key I created above
.withSecurityGroup(securityGroupName)); // group I created above .withSecurityGroup(securityGroupName) // group I created above
server = Iterables.getOnlyElement(reservation.getRunningInstances()); .withUserData(encodedScript)); // script to run as root
instance = Iterables.getOnlyElement(reservation.getRunningInstances());
} catch (HttpResponseException htpe) { } catch (HttpResponseException htpe) {
if (htpe.getResponse().getStatusCode() == 400) if (htpe.getResponse().getStatusCode() == 400)
continue; continue;
throw htpe; throw htpe;
} }
} }
assertNotNull(server.getId()); assertNotNull(instance.getId());
serverId = server.getId(); instanceId = instance.getId();
assertEquals(server.getInstanceState(), InstanceState.PENDING); assertEquals(instance.getInstanceState(), InstanceState.PENDING);
server = blockUntilRunningInstanceActive(serverId); instance = blockUntilWeCanSshIntoInstance(instance);
sshPing(instance);
sshPing(server); System.out.printf("%d: %s ssh connection made%n", System.currentTimeMillis(), instanceId);
System.out.printf("%d: %s ssh connection made%n", System.currentTimeMillis(), serverId);
} }
@ -175,16 +194,16 @@ public class CloudApplicationArchitecturesEC2ClientLiveTest {
assert compare.getInstanceId() == null; assert compare.getInstanceId() == null;
client.getElasticIPAddressServices().associateAddressInRegion(Region.DEFAULT, address, client.getElasticIPAddressServices().associateAddressInRegion(Region.DEFAULT, address,
serverId); instanceId);
compare = Iterables.getLast(client.getElasticIPAddressServices().describeAddressesInRegion( compare = Iterables.getLast(client.getElasticIPAddressServices().describeAddressesInRegion(
Region.DEFAULT, address)); Region.DEFAULT, address));
assertEquals(compare.getPublicIp(), address); assertEquals(compare.getPublicIp(), address);
assertEquals(compare.getInstanceId(), serverId); assertEquals(compare.getInstanceId(), instanceId);
Reservation reservation = Iterables.getOnlyElement(client.getInstanceServices() Reservation reservation = Iterables.getOnlyElement(client.getInstanceServices()
.describeInstancesInRegion(Region.DEFAULT, serverId)); .describeInstancesInRegion(Region.DEFAULT, instanceId));
assertNotNull(Iterables.getOnlyElement(reservation.getRunningInstances()).getIpAddress()); assertNotNull(Iterables.getOnlyElement(reservation.getRunningInstances()).getIpAddress());
assertFalse(Iterables.getOnlyElement(reservation.getRunningInstances()).getIpAddress() assertFalse(Iterables.getOnlyElement(reservation.getRunningInstances()).getIpAddress()
@ -201,14 +220,47 @@ public class CloudApplicationArchitecturesEC2ClientLiveTest {
assert compare.getInstanceId() == null; assert compare.getInstanceId() == null;
reservation = Iterables.getOnlyElement(client.getInstanceServices() reservation = Iterables.getOnlyElement(client.getInstanceServices()
.describeInstancesInRegion(Region.DEFAULT, serverId)); .describeInstancesInRegion(Region.DEFAULT, instanceId));
// assert reservation.getRunningInstances().last().getIpAddress() == null; TODO // assert reservation.getRunningInstances().last().getIpAddress() == null; TODO
} }
private RunningInstance blockUntilWeCanSshIntoInstance(RunningInstance instance)
throws UnknownHostException {
System.out.printf("%d: %s awaiting instance to run %n", System.currentTimeMillis(), instance
.getId());
assert runningTester.apply(instance);
// search my account for the instance I just created
Set<Reservation> reservations = client.getInstanceServices().describeInstancesInRegion(
instance.getRegion(), instance.getId()); // last parameter (ids) narrows the search
instance = Iterables.getOnlyElement(Iterables.getOnlyElement(reservations)
.getRunningInstances());
System.out.printf("%d: %s awaiting ssh service to start%n", System.currentTimeMillis(),
instance.getIpAddress());
assert socketTester.apply(new InetSocketAddress(instance.getIpAddress(), 22));
System.out.printf("%d: %s ssh service started%n", System.currentTimeMillis(), instance
.getDnsName());
sshPing(instance);
System.out.printf("%d: %s ssh connection made%n", System.currentTimeMillis(), instance
.getId());
System.out.printf("%d: %s awaiting http service to start%n", System.currentTimeMillis(),
instance.getIpAddress());
assert socketTester.apply(new InetSocketAddress(instance.getIpAddress(), 80));
System.out.printf("%d: %s http service started%n", System.currentTimeMillis(), instance
.getDnsName());
return instance;
}
/** /**
* this tests "personality" as the file looked up was sent during server creation * this tests "personality" as the file looked up was sent during instance creation
*
* @throws UnknownHostException
*/ */
private void sshPing(RunningInstance newDetails) throws IOException { private void sshPing(RunningInstance newDetails) throws UnknownHostException {
try { try {
doCheckKey(newDetails); doCheckKey(newDetails);
} catch (SshException e) {// try twice in case there is a network timeout } catch (SshException e) {// try twice in case there is a network timeout
@ -220,46 +272,29 @@ public class CloudApplicationArchitecturesEC2ClientLiveTest {
} }
} }
private void doCheckKey(RunningInstance newDetails) throws IOException { private void doCheckKey(RunningInstance newDetails) throws UnknownHostException {
doCheckKey(InetAddress.getByName(newDetails.getDnsName())); doCheckKey(newDetails.getIpAddress());
} }
private void doCheckKey(InetAddress address) throws IOException { private void doCheckKey(InetAddress address) {
SshClient connection = sshFactory.create(new InetSocketAddress(address, 22), "root", keyPair SshClient ssh = sshFactory.create(new InetSocketAddress(address, 22), "root", keyPair
.getKeyMaterial().getBytes()); .getKeyMaterial().getBytes());
try { try {
connection.connect(); ssh.connect();
InputStream etcPasswd = connection.get("/etc/passwd"); ExecResponse hello = ssh.exec("echo hello");
Utils.toStringAndClose(etcPasswd); assertEquals(hello.getOutput().trim(), "hello");
} finally { } finally {
if (connection != null) if (ssh != null)
connection.disconnect(); ssh.disconnect();
} }
} }
private RunningInstance blockUntilRunningInstanceActive(String serverId)
throws InterruptedException, ExecutionException, TimeoutException {
RunningInstance currentDetails = null;
for (currentDetails = getRunningInstance(serverId); currentDetails.getInstanceState() != InstanceState.RUNNING; currentDetails = getRunningInstance(serverId)) {
System.out.printf("%s blocking on status active: currently: %s%n", currentDetails.getId(),
currentDetails.getInstanceState());
Thread.sleep(5 * 1000);
}
System.out.printf("%d: %s awaiting ssh service to start%n", System.currentTimeMillis(),
currentDetails.getDnsName());
assert socketTester.apply(new InetSocketAddress(currentDetails.getDnsName(), 22));
System.out.printf("%d: %s ssh service started%n", System.currentTimeMillis(), currentDetails
.getDnsName());
return currentDetails;
}
@AfterTest @AfterTest
void cleanup() throws InterruptedException, ExecutionException, TimeoutException { void cleanup() throws InterruptedException, ExecutionException, TimeoutException {
if (address != null) if (address != null)
client.getElasticIPAddressServices().releaseAddressInRegion(Region.DEFAULT, address); client.getElasticIPAddressServices().releaseAddressInRegion(Region.DEFAULT, address);
if (serverId != null) if (instanceId != null)
client.getInstanceServices().terminateInstancesInRegion(Region.DEFAULT, serverId); client.getInstanceServices().terminateInstancesInRegion(Region.DEFAULT, instanceId);
if (keyPair != null) if (keyPair != null)
client.getKeyPairServices().deleteKeyPairInRegion(Region.DEFAULT, keyPair.getKeyName()); client.getKeyPairServices().deleteKeyPairInRegion(Region.DEFAULT, keyPair.getKeyName());
if (securityGroupName != null) if (securityGroupName != null)
@ -267,11 +302,4 @@ public class CloudApplicationArchitecturesEC2ClientLiveTest {
securityGroupName); securityGroupName);
} }
private RunningInstance getRunningInstance(String serverId) throws InterruptedException,
ExecutionException, TimeoutException {
return Iterables.getOnlyElement(Iterables.getOnlyElement(
client.getInstanceServices().describeInstancesInRegion(Region.DEFAULT, serverId))
.getRunningInstances());
}
} }

View File

@ -404,7 +404,6 @@ public class EBSBootEC2ClientLiveTest {
client.getInstanceServices().startInstancesInRegion(ebsInstance.getRegion(), client.getInstanceServices().startInstancesInRegion(ebsInstance.getRegion(),
ebsInstance.getId()); ebsInstance.getId());
ebsInstance = blockUntilWeCanSshIntoInstance(ebsInstance); ebsInstance = blockUntilWeCanSshIntoInstance(ebsInstance);
} }
private void verifyImage() { private void verifyImage() {

View File

@ -92,8 +92,19 @@ public class Statements {
return KILL; return KILL;
} }
/**
* statement can have multiple newlines, note you should use {@code {lf} } to be portable
*
* @see ShellToken
*/
public static Statement interpret(String portableStatement) { public static Statement interpret(String portableStatement) {
return new InterpretableStatement(portableStatement); return new InterpretableStatement(portableStatement);
} }
/**
* interprets and adds a newline to the statement
*/
public static Statement exec(String portableStatement) {
return interpret(portableStatement+"{lf}");
}
} }