package water.deploy; import java.io.*; import java.util.*; import org.apache.commons.codec.binary.Base64; import water.H2O; import water.persist.PersistS3; import water.util.Log; import water.util.Utils; import com.amazonaws.AmazonServiceException; import com.amazonaws.auth.PropertiesCredentials; import com.amazonaws.services.ec2.AmazonEC2Client; import com.amazonaws.services.ec2.model.*; /** * Manages EC2 instances. * <br> * Note: This class is intended for debug and experimentation purposes only, please refer to the * documentation to run H2O on AWS. */ public class EC2 { private static final String USER = System.getProperty("user.name"); private static final String NAME = USER + "-H2O-Cloud"; public int boxes; public String region = "us-east-1"; //public String image = "ami-3565305c"; // Amazon Linux, x64, Instance-Store, US East N. Virginia //public String image = "ami-dfbfe4b6"; // Amazon Linux, x64, HVM, Instance-Store, US East N. Virginia //public String image = "ami-e1357b88"; // Ubuntu Raring 13.04 amd64 public String image = "ami-09614460"; // Ubuntu Raring 13.04 amd64 HVM //public String type = "m1.xlarge"; public String type = "cc2.8xlarge"; // HPC public String securityGroup = "ssh"; // "default"; public boolean confirm = true; //@formatter:off static String cloudConfig = "" + // TODO try Amazon AMI "Enhanced Networking" l("#cloud-config") + l("users:") + l(" - name: " + USER) + l(" sudo: ALL=(ALL) NOPASSWD:ALL") + l(" ssh-authorized-keys:") + l(" - " + pubKey()) + l(" shell: /bin/bash") + l("") + l("runcmd:") + l(" - iptables -I INPUT -p tcp --dport 22 -j DROP") + // l(" - echo 'fs.file-max = 524288' > /etc/sysctl.d/increase-max-fd.conf") + // l(" - sysctl -w fs.file-max=524288") + // l(" - echo '* soft nofile 524288' > /etc/security/limits.d/increase-max-fd-soft.conf") + // l(" - echo '* hard nofile 524288' > /etc/security/limits.d/increase-max-fd-hard.conf") + // l(" - apt-get update") + l(" - apt-get -y install openjdk-7-jdk") + // l(" - apt-get -y install openvpn") + l(" - iptables -D INPUT 1") + l(""); static String l(String line) { return line + "\n"; } //@formatter:on /** * Create or terminate EC2 instances. Uses their Name tag to find existing ones. */ public Cloud resize() throws Exception { AmazonEC2Client ec2 = new AmazonEC2Client(new PersistS3.H2OAWSCredentialsProviderChain()); ec2.setEndpoint("ec2." + region + ".amazonaws.com"); DescribeInstancesResult describeInstancesResult = ec2.describeInstances(); List<Reservation> reservations = describeInstancesResult.getReservations(); List<Instance> instances = new ArrayList<Instance>(); for( Reservation reservation : reservations ) { for( Instance instance : reservation.getInstances() ) { String ip = ip(instance); if( ip != null ) { String name = null; if( instance.getTags().size() > 0 ) name = instance.getTags().get(0).getValue(); if( NAME.equals(name) ) instances.add(instance); } } } System.out.println("Found " + instances.size() + " EC2 instances for user " + USER); if( instances.size() > boxes ) { for( int i = 0; i < instances.size() - boxes; i++ ) { // TODO terminate? } } else if( instances.size() < boxes ) { int launchCount = boxes - instances.size(); System.out.println("Creating " + launchCount + " EC2 instances."); if( confirm ) { System.out.println("Please confirm [y/n]"); String s = Utils.readConsole(); if( s == null || !s.equalsIgnoreCase("y") ) throw new Exception("Aborted"); } CreatePlacementGroupRequest group = new CreatePlacementGroupRequest(); group.withGroupName(USER); group.withStrategy(PlacementStrategy.Cluster); try { ec2.createPlacementGroup(group); } catch( AmazonServiceException ex ) { if( !"InvalidPlacementGroup.Duplicate".equals(ex.getErrorCode()) ) throw ex; } RunInstancesRequest run = new RunInstancesRequest(); run.withInstanceType(type); run.withImageId(image); run.withMinCount(launchCount).withMaxCount(launchCount); run.withSecurityGroupIds(securityGroup); Placement placement = new Placement(); placement.setGroupName(USER); run.withPlacement(placement); BlockDeviceMapping map = new BlockDeviceMapping(); map.setDeviceName("/dev/sdb"); map.setVirtualName("ephemeral0"); run.withBlockDeviceMappings(map); run.withUserData(new String(Base64.encodeBase64(cloudConfig.getBytes()))); RunInstancesResult runRes = ec2.runInstances(run); ArrayList<String> ids = new ArrayList<String>(); for( Instance instance : runRes.getReservation().getInstances() ) ids.add(instance.getInstanceId()); List<Instance> created = wait(ec2, ids); System.out.println("Created " + created.size() + " EC2 instances."); instances.addAll(created); } String[] pub = new String[boxes]; String[] prv = new String[boxes]; for( int i = 0; i < boxes; i++ ) { pub[i] = instances.get(i).getPublicIpAddress(); prv[i] = instances.get(i).getPrivateIpAddress(); } System.out.println("EC2 public IPs: " + Utils.join(' ', pub)); System.out.println("EC2 private IPs: " + Utils.join(' ', prv)); Cloud cloud = new Cloud(); cloud.publicIPs.addAll(Arrays.asList(pub)); cloud.privateIPs.addAll(Arrays.asList(prv)); return cloud; } private static String pubKey() { BufferedReader r = null; try { String pub = System.getProperty("user.home") + "/.ssh/id_rsa.pub"; r = new BufferedReader(new FileReader(new File(pub))); return r.readLine(); } catch( IOException e ) { throw Log.errRTExcept(e); } finally { if( r != null ) try { r.close(); } catch( IOException e ) { throw Log.errRTExcept(e); } } } private List<Instance> wait(AmazonEC2Client ec2, List<String> ids) { System.out.println("Establishing ssh connections, make sure security group '" // + securityGroup + "' allows incoming TCP 22."); boolean tagsDone = false; for( ;; ) { try { if( !tagsDone ) { CreateTagsRequest createTagsRequest = new CreateTagsRequest(); createTagsRequest.withResources(ids).withTags(new Tag("Name", NAME)); ec2.createTags(createTagsRequest); tagsDone = true; } DescribeInstancesRequest request = new DescribeInstancesRequest(); request.withInstanceIds(ids); DescribeInstancesResult result = ec2.describeInstances(request); List<Reservation> reservations = result.getReservations(); List<Instance> instances = new ArrayList<Instance>(); for( Reservation reservation : reservations ) for( Instance instance : reservation.getInstances() ) if( ip(instance) != null ) instances.add(instance); if( instances.size() == ids.size() ) { // Try to connect to SSH port on each box if( canConnect(instances) ) return instances; } } catch( AmazonServiceException xe ) { // Ignore and retry } try { Thread.sleep(500); } catch( InterruptedException e ) { throw Log.errRTExcept(e); } } } private static String ip(Instance instance) { String ip = instance.getPublicIpAddress(); if( ip != null && ip.length() != 0 ) if( instance.getState().getName().equals("running") ) return ip; return null; } private static boolean canConnect(List<Instance> instances) { for( Instance instance : instances ) { try { String ssh = "ssh -q" + Host.SSH_OPTS + " " + instance.getPublicIpAddress(); Process p = Runtime.getRuntime().exec(ssh + " exit"); if( p.waitFor() != 0 ) return false; } catch( Exception e ) { return false; } finally { } } return true; } }