added aws demo that resizes volume size for EBS-backed instances

it includes main classes and Java launcher in /resize-ebs-java,
as well as JRuby launcher for the same Java classes
This commit is contained in:
Alex Yarmula 2010-02-17 00:31:36 -08:00
parent 3e8381e3f3
commit f83dce9469
9 changed files with 704 additions and 0 deletions

View File

@ -0,0 +1,54 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.jclouds</groupId>
<artifactId>jclouds-aws-demos-project</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>jclouds-aws-demo-ebsresize-ruby</artifactId>
<groupId>org.jclouds</groupId>
<name>JRuby client for demo to change volume size for EBS-based instances</name>
<description>JRuby is used to run jclouds-aws-demo-ebsresize demo</description>
<repositories>
<repository>
<id>jclouds</id>
<url>http://jclouds.googlecode.com/svn/repo</url>
</repository>
<repository>
<id>jclouds-snapshot.repository</id>
<url>http://jclouds.rimuhosting.com/maven2/snapshots</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.jclouds</groupId>
<artifactId>jclouds-aws-demo-ebsresize</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jruby-maven-plugin</artifactId>
<version>1.0-beta-4</version>
<configuration>
<script>src/main/scripts/launcher.rb</script>
</configuration>
<dependencies>
<dependency>
<groupId>org.jclouds</groupId>
<artifactId>jclouds-aws-demo-ebsresize</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,37 @@
class EbsVolumesJRuby
def initialize access_key_id, secret_key
@access_key_id = access_key_id
@secret_key = secret_key
end
def resize instance_id, region, new_size
@manager = org.jclouds.tools.ebsresize.InstanceVolumeManager.new(@access_key_id, @secret_key)
ebs_api = @manager.get_ebs_api
instance_api = @manager.get_instance_api
api = @manager.get_api
@instance = instance_api.get_instance_by_id_and_region instance_id, region
instance_api.stop_instance @instance
volume = ebs_api.get_root_volume_for_instance @instance
ebs_api.detach_volume_from_stopped_instance volume, @instance
new_volume = ebs_api.clone_volume_with_new_size volume, new_size
ebs_api.attach_volume_to_stopped_instance new_volume, @instance
instance_api.start_instance @instance
api.get_elastic_block_store_services().delete_volume_in_region @instance.get_region(), volume.get_id()
end
def run_remote_resize_commands credentials, key_pair
@manager.run_remote_resize_commands @instance, credentials, key_pair
end
end

View File

@ -0,0 +1,20 @@
require "src/main/scripts/ebs_volumes_j_ruby.rb"
instance_id = "AMAZON_INSTANCE_ID (i-xxxxxx)"
region = org.jclouds.aws.domain.Region::US_EAST_1
new_size = 6
remote_login = "ubuntu"
remote_password = ""
path_to_key = ""
ebs_volumes = EbsVolumesJRuby.new("YOUR_ACCESS_KEY_ID", "YOUR_SECRET_KEY")
ebs_volumes.resize instance_id, region, new_size
key_pair = org.jclouds.util.Utils::toStringAndClose(
java.io.FileInputStream.new(path_to_key))
credentials = org.jclouds.domain.Credentials.new(remote_login, remote_password)
java.lang.Thread::sleep(25000);
ebs_volumes.run_remote_resize_commands credentials, key_pair

View File

@ -0,0 +1,46 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.jclouds</groupId>
<artifactId>jclouds-aws-demos-project</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>jclouds-aws-demo-ebsresize</artifactId>
<groupId>org.jclouds</groupId>
<name>jclouds EC2 example, to change volume size for EBS-based instances</name>
<description>For a given running instance, resizes the volume (with several minutes of downtime)</description>
<repositories>
<repository>
<id>jclouds</id>
<url>http://jclouds.googlecode.com/svn/repo</url>
</repository>
<repository>
<id>jclouds-snapshot.repository</id>
<url>http://jclouds.rimuhosting.com/maven2/snapshots</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.jclouds</groupId>
<artifactId>jclouds-jsch</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,59 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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.
* ====================================================================
*/
import org.jclouds.aws.domain.Region;
import org.jclouds.domain.Credentials;
import org.jclouds.tools.ebsresize.InstanceVolumeManager;
import org.jclouds.util.Utils;
import java.io.*;
/**
* Launcher for EBS resize demo. This is the Java version of what
* can be written in Ruby (see <jclouds>/aws/demos/resize-ebs/jruby-client)
*
* @author Oleksiy Yarmula
*/
public class EbsImages {
String accessKeyId = "YOUR_ACCESS_KEY_ID";
String secretKey = "YOUR_SECRET_KEY";
String instanceId = "AMAZON_INSTANCE_ID (i-xxxxxx)";
Region region = Region.US_EAST_1;
int newSize = 6;
String pathToKeyPair = "/Users/alex/miscdev/aws/security/alexpair.pem";
String remoteLogin = "ubuntu";
String remotePassword = "";
public static void main(String[] args) throws Exception {
new EbsImages().launch();
}
public void launch() throws Exception {
InstanceVolumeManager manager = new InstanceVolumeManager(accessKeyId, secretKey);
String privateKey =
Utils.toStringAndClose(new FileInputStream(pathToKeyPair));
manager.resizeVolume(instanceId, region, new Credentials(remoteLogin, remotePassword),
privateKey, newSize);
}
}

View File

@ -0,0 +1,143 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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.jclouds.tools.ebsresize;
import org.jclouds.aws.domain.Region;
import org.jclouds.aws.ec2.EC2AsyncClient;
import org.jclouds.aws.ec2.EC2Client;
import org.jclouds.aws.ec2.domain.RunningInstance;
import org.jclouds.aws.ec2.domain.Volume;
import org.jclouds.compute.ComputeServiceContext;
import org.jclouds.compute.ComputeServiceContextFactory;
import org.jclouds.compute.domain.ComputeMetadata;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.internal.NodeMetadataImpl;
import org.jclouds.domain.Credentials;
import org.jclouds.rest.RestContext;
import org.jclouds.tools.ebsresize.facade.ElasticBlockStoreFacade;
import org.jclouds.tools.ebsresize.facade.InstanceFacade;
import org.jclouds.tools.ebsresize.util.SshExecutor;
import java.io.IOException;
import java.util.Map;
/**
* Launches a sequence of commands to change the size of
* EBS volume.
*
* This results in several minutes of downtime of the instance.
* More details available at:
* <a href="http://alestic.com/2010/02/ec2-resize-running-ebs-root" />
*
*
* @author Oleksiy Yarmula
*/
public class InstanceVolumeManager {
private final ComputeServiceContext context;
@SuppressWarnings({"FieldCanBeLocal"})
private final RestContext<EC2AsyncClient, EC2Client> ec2context;
private final EC2Client api;
private final ElasticBlockStoreFacade ebsApi;
private final InstanceFacade instanceApi;
public InstanceVolumeManager(String accessKeyId, String secretKey) {
try {
context = new ComputeServiceContextFactory()
.createContext("ec2", accessKeyId, secretKey);
} catch(IOException e) { throw new RuntimeException(e); }
ec2context = context.getProviderSpecificContext();
api = ec2context.getApi();
ebsApi = new ElasticBlockStoreFacade(api.getElasticBlockStoreServices());
instanceApi = new InstanceFacade(api.getInstanceServices());
}
public void resizeVolume(String instanceId, Region region,
Credentials instanceCredentials, String pathToKeyPair, int newSize) {
RunningInstance instance = instanceApi.getInstanceByIdAndRegion(instanceId, region);
instanceApi.stopInstance(instance);
Volume volume = ebsApi.getRootVolumeForInstance(instance);
ebsApi.detachVolumeFromStoppedInstance(volume, instance);
Volume newVolume = ebsApi.cloneVolumeWithNewSize(volume, newSize);
ebsApi.attachVolumeToStoppedInstance(newVolume, instance);
instanceApi.startInstance(instance);
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);
}
public void runRemoteResizeCommands(RunningInstance instance, Credentials instanceCredentials,
String keyPair) {
Map<String, ? extends ComputeMetadata> nodes = context.getComputeService().getNodes();
//if don't set it here, nodeMetadata.getCredentials = null
NodeMetadata nodeMetadata = addCredentials((NodeMetadata) nodes.get(instance.getId()),
instanceCredentials);
SshExecutor sshExecutor = new SshExecutor(nodeMetadata, instanceCredentials,
keyPair, instance);
sshExecutor.connect();
sshExecutor.execute("sudo resize2fs " + instance.getRootDeviceName());
}
private NodeMetadata addCredentials(NodeMetadata nodeMetadata, Credentials credentials) {
return new
NodeMetadataImpl(nodeMetadata.getId(), nodeMetadata.getName(),
nodeMetadata.getLocationId(),
nodeMetadata.getUri(),
nodeMetadata.getUserMetadata(), nodeMetadata.getTag(),
nodeMetadata.getState(), nodeMetadata.getPublicAddresses(),
nodeMetadata.getPrivateAddresses(), nodeMetadata.getExtra(),
credentials);
}
public ElasticBlockStoreFacade getEbsApi() {
return ebsApi;
}
public InstanceFacade getInstanceApi() {
return instanceApi;
}
public EC2Client getApi() {
return api;
}
}

View File

@ -0,0 +1,150 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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.jclouds.tools.ebsresize.facade;
import static com.google.common.base.Preconditions.*;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import org.jclouds.aws.ec2.domain.*;
import org.jclouds.aws.ec2.options.CreateSnapshotOptions;
import org.jclouds.aws.ec2.options.DetachVolumeOptions;
import org.jclouds.aws.ec2.predicates.SnapshotCompleted;
import org.jclouds.aws.ec2.predicates.VolumeAttached;
import org.jclouds.aws.ec2.predicates.VolumeAvailable;
import org.jclouds.aws.ec2.services.ElasticBlockStoreClient;
import org.jclouds.predicates.RetryablePredicate;
import java.util.concurrent.TimeUnit;
/**
* Aggregates several methods of jClouds' EC2 functionality
* to work with elastic block store.
*
* @author Oleksiy Yarmula
*/
public class ElasticBlockStoreFacade {
final private ElasticBlockStoreClient elasticBlockStoreServices;
final private Predicate<Volume> volumeAvailable;
final private Predicate<Attachment> volumeAttached;
final private Predicate<Snapshot> snapshotCompleted;
public ElasticBlockStoreFacade(ElasticBlockStoreClient elasticBlockStoreServices) {
this.elasticBlockStoreServices = elasticBlockStoreServices;
this.volumeAvailable =
new RetryablePredicate<Volume>(new VolumeAvailable(elasticBlockStoreServices),
600, 10, TimeUnit.SECONDS);
this.volumeAttached =
new RetryablePredicate<Attachment>(new VolumeAttached(elasticBlockStoreServices),
600, 10, TimeUnit.SECONDS);
this.snapshotCompleted = new RetryablePredicate<Snapshot>(new SnapshotCompleted(
elasticBlockStoreServices), 600, 10, TimeUnit.SECONDS);
}
/**
* Returns the root volume for instance with EBS type of root device.
*
* @param runningInstance
* instance of EBS type that has volume(s) attached
* @return root device volume
*/
public Volume getRootVolumeForInstance(RunningInstance runningInstance) {
checkArgument(runningInstance.getRootDeviceType() == RootDeviceType.EBS,
"Only storage for instances with EBS type of root device can be resized.");
String rootDevice = checkNotNull(runningInstance.getRootDeviceName());
runningInstance.getRootDeviceType();
//get volume id
String volumeId = null;
for(String ebsVolumeId : runningInstance.getEbsBlockDevices().keySet()) {
if(! rootDevice.equals(ebsVolumeId)) continue;
RunningInstance.EbsBlockDevice device = runningInstance.getEbsBlockDevices().get(ebsVolumeId);
volumeId = checkNotNull(device.getVolumeId(), "Device's volume id must not be null.");
}
//return volume by volume id
return Iterables.getOnlyElement(elasticBlockStoreServices.
describeVolumesInRegion(runningInstance.getRegion(), volumeId));
}
/**
* Detaches volume from an instance.
*
* This method blocks until the operations are fully completed.
* @param volume
* volume to detach
* @param stoppedInstance
* instance to which the volume is currently attached
*/
public void detachVolumeFromStoppedInstance(Volume volume, RunningInstance stoppedInstance) {
elasticBlockStoreServices.detachVolumeInRegion(stoppedInstance.getRegion(), volume.getId(), false,
DetachVolumeOptions.Builder.fromInstance(stoppedInstance.getId()));
assertTrue(volumeAvailable.apply(volume));
}
/**
* Makes a 'copy' of current volume with different size.
* Behind the scenes, if creates a snapshot of current volume,
* and then creates a new volume with given size from the snapshot.
*
* This method blocks until the operations are fully completed.
*
* @param volume volume to be cloned
* @param newSize size of new volume
* @return newly created volume
*/
public Volume cloneVolumeWithNewSize(Volume volume, int newSize) {
Snapshot createdSnapshot =
elasticBlockStoreServices.createSnapshotInRegion(volume.getRegion(), volume.getId(),
CreateSnapshotOptions.Builder.withDescription("snapshot to test extending volumes"));
assertTrue(snapshotCompleted.apply(createdSnapshot));
Volume newVolume = elasticBlockStoreServices.createVolumeFromSnapshotInAvailabilityZone(
volume.getAvailabilityZone(), newSize,
createdSnapshot.getId());
assertTrue(volumeAvailable.apply(newVolume));
return newVolume;
}
/**
* Attaches volume to a (stopped) instance.
*
* This method blocks until the operations are fully completed.
* @param volume
* volume to attach
* @param instance
* instance to which the volume is to be attached, must be in
* a 'stopped' state
*/
public void attachVolumeToStoppedInstance(Volume volume, RunningInstance instance) {
Attachment volumeAttachment = elasticBlockStoreServices.
attachVolumeInRegion(instance.getRegion(), volume.getId(), instance.getId(),
instance.getRootDeviceName());
assertTrue(volumeAttached.apply(volumeAttachment));
}
public void assertTrue(boolean value) {
if(!value) throw new RuntimeException("Found false, expected true");
}
}

View File

@ -0,0 +1,107 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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.jclouds.tools.ebsresize.facade;
import static com.google.common.base.Preconditions.*;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import org.jclouds.aws.domain.Region;
import org.jclouds.aws.ec2.domain.Reservation;
import org.jclouds.aws.ec2.domain.RunningInstance;
import org.jclouds.aws.ec2.predicates.InstanceStateRunning;
import org.jclouds.aws.ec2.predicates.InstanceStateStopped;
import org.jclouds.aws.ec2.services.InstanceClient;
import org.jclouds.predicates.RetryablePredicate;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Aggregates several methods of jClouds' EC2 functionality
* to work with instance client (instance store). These
* features are specific to instances with EBS root device.
*
* @author Oleksiy Yarmula
*/
public class InstanceFacade {
final private InstanceClient instanceServices;
final private Predicate<RunningInstance> instanceRunning;
final private Predicate<RunningInstance> instanceStopped;
public InstanceFacade(InstanceClient instanceServices) {
this.instanceServices = instanceServices;
this.instanceRunning =
new RetryablePredicate<RunningInstance>(new InstanceStateRunning(instanceServices),
600, 10, TimeUnit.SECONDS);
this.instanceStopped =
new RetryablePredicate<RunningInstance>(new InstanceStateStopped(instanceServices),
600, 10, TimeUnit.SECONDS);
}
/**
* Starts an instance, given that it has an EBS volume attached.
* This command is only available for EC2 instances with
* EBS root device.
*
* This method blocks until the operations are fully completed.
*
* @param instance instance to start
* @see #stopInstance(org.jclouds.aws.ec2.domain.RunningInstance)
*/
public void startInstance(RunningInstance instance) {
instanceServices.startInstancesInRegion(instance.getRegion(), instance.getId());
assertTrue(instanceRunning.apply(instance));
}
/**
* Stops an instance.
* This command is only available for EC2 instances with
* EBS root device.
*
* This method blocks until the operations are fully completed.
*
* @param instance instance to stop
* @see #startInstance(org.jclouds.aws.ec2.domain.RunningInstance)
*/
public void stopInstance(RunningInstance instance) {
instanceServices.stopInstancesInRegion(instance.getRegion(), false, instance.getId());
assertTrue(instanceStopped.apply(instance));
}
/**
* Given an instance id and its {@link Region}, returns a {@link RunningInstance}.
*
* @param instanceId id of instance
* @param region region of the instance
* @return instance, corresponding to instanceId and region
*/
public RunningInstance getInstanceByIdAndRegion(String instanceId, Region region) {
Set<Reservation> reservations = instanceServices.describeInstancesInRegion(region, instanceId);
Reservation reservation = checkNotNull(Iterables.getOnlyElement(reservations));
return Iterables.getOnlyElement(reservation);
}
public void assertTrue(boolean value) {
if(!value) throw new RuntimeException("Found false, expected true");
}
}

View File

@ -0,0 +1,88 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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.jclouds.tools.ebsresize.util;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import org.jclouds.aws.ec2.domain.RunningInstance;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.predicates.RunScriptRunning;
import org.jclouds.compute.util.ComputeUtils;
import org.jclouds.domain.Credentials;
import org.jclouds.logging.Logger;
import org.jclouds.predicates.RetryablePredicate;
import org.jclouds.ssh.SshClient;
import org.jclouds.ssh.jsch.JschSshClient;
import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;
/**
* @author Oleksiy Yarmula
*/
public class SshExecutor {
private final Predicate<SshClient> runScriptRunning =
new RetryablePredicate<SshClient>(Predicates.not(new RunScriptRunning()),
600, 3, TimeUnit.SECONDS);
private final NodeMetadata nodeMetadata;
private final Credentials instanceCredentials;
private final String keyPair;
private final RunningInstance instance;
private final SshClient sshClient;
private final ComputeUtils utils;
public SshExecutor(NodeMetadata nodeMetadata,
Credentials instanceCredentials,
String keyPair,
RunningInstance instance) {
this.nodeMetadata = nodeMetadata;
this.instanceCredentials = instanceCredentials;
this.keyPair = keyPair;
this.instance = instance;
this.sshClient =
new JschSshClient(new InetSocketAddress(instance.getDnsName(), 22), 60000,
instanceCredentials.account, keyPair.getBytes());
this.utils = new ComputeUtils(null, runScriptRunning, null);
}
public void connect() {
sshClient.connect();
}
public void execute(String command) {
ComputeUtils.RunScriptOnNode script = utils.runScriptOnNode(nodeMetadata,
"basicscript.sh", command.getBytes());
script.setConnection(sshClient, Logger.CONSOLE);
try {
script.call();
} catch(Exception e) {
throw new RuntimeException(e);
}
}
}