packages feed

remote-0.1: examples/kmeans/awsgo

#!/usr/bin/env python

import sys
import time
import os
import math
import optparse

import boto
import boto.ec2
import subprocess

packages=""

script_header="""#!/bin/bash
append()
{
   file="$1"
   shift
   $@ | tee -a "$file"
}
suappend()
{
   file="$1"
   shift
   $@ | sudo tee -a "$file"
}
try()
{
   log="$HOME/$1"
   shift
   msg="$1"
   shift
   (echo "------ `date`: $@") >> $log
   ($@ 2>&1) | tee -a "$log" | tail -1 > tmp-log
   ret="${PIPESTATUS[0]}"
   if [ 0 != "$ret" ]
   then
     echo 1>&2 "Failed during $msg: `cat tmp-log`"
     rm tmp-log
     return 1
   fi
   rm tmp-log
   return 0
}
try1()
{
   if ! try "$@"
   then 
     echo 1>&2 "Retrying..."
     sleep 10
     if ! try "$@"
     then    
        echo 1>&2 "Retrying again..."
        sleep 20
        if ! try "$@"
        then
           echo 1>&2 "Giving up."
           exit 1
        fi
     fi 
   fi
   return 0
}
"""
exec_args=["-cfgRoundtripTimeout=20000000","+RTS","-K20000000"]
local_files = ["KMeans.hs","MakeData.hs","KMeansCommon.hs"]
node_config_data = {"apts":"ghc cabal-install git-core libghc6-binary-dev libghc6-mtl-dev libghc6-network-dev libghc6-stm-dev"}
node_config_script = script_header+"""
cd
try cfg.log "update apt repository" sudo apt-get -q -y update
try cfg.log "install apt packages" sudo apt-get -q -y install %(apts)s
try1 cfg.log "cabalupdate" cabal update
try1 cfg.log "cabalify" cabal install pureMD5 utf8-string directory crypto-api tagged data-default cereal semigroups
try cfg.log "retrieve git repository" git clone git://github.com/jepst/CloudHaskell.git
cd CloudHaskell
try cfg.log "configure local repository" cabal configure
try1 cfg.log "build local repository" cabal build
try1 cfg.log "install local repoistory" cabal install
cd
try cfg.log "compile executable" ghc -O3 --make KMeans.hs
""" % node_config_data

def writefile(fname,content):
    with open(fname,"w") as f:
        f.write(content)

def err(s):
    print s
    sys.exit(1)

def waitforallup(instances):
    while True:
        pending = False
        running = False
        for instance in instances:
            if instance.state == u'pending':
                pending = True
                instance.update()
            elif instance.state == u'running':
                running = True
            else:
                print("Encountered unexpected instance state: "+instance.state) # and terminate remaining instances?
        if pending:
            time.sleep(3)
        else:
            break 

ssh_opts=["-o","BatchMode=yes","-o","ServerAliveInterval=240","-o","StrictHostKeyChecking=no"]

def allinstances_xfer(keyfile,username,instances,thefile):
    processes = []
    for i in instances:
       cmd=["scp","-r","-q","-i",keyfile]+ssh_opts + thefile + [username+"@"+i.public_dns_name+":"]
       while True:
          subproc=subprocess.Popen(cmd,stderr=subprocess.STDOUT)
          result = subproc.wait()
          if result == 0:
             break
          else:
             print "Problem transfering",thefile,"to",i.public_dns_name," and retrying"
             time.sleep(60)

def allinstances_recv(keyfile,username,instances,thefile):
    processes = []
    for i in instances:
       cmd=["scp","-r","-q","-i",keyfile]+ssh_opts+ [ username+"@"+i.public_dns_name+":"+thefile,thefile]
       subproc=subprocess.Popen(cmd,stderr=subprocess.STDOUT)
       processes.append(subproc)
    for (i,p) in zip(instances,processes):
        result = p.wait()
        if result != 0:
            err("Error with remote receiving on instance "+i.id+" at "+i.public_dns_name)

def allinstances_runscript(keyfile,username,instances,scriptmaker):
    processes = []
    counter=0
    for i in instances:
       text = scriptmaker(i)
       fname="tmp-"+str(counter) # use random nm here
       writefile(fname,text)
       cmd1=["scp","-r","-q","-i",keyfile]+ssh_opts+[ fname ,username+"@"+i.public_dns_name+":"]
       cmd2=["ssh","-q","-i",keyfile]+ssh_opts+["-l",username,"-o","StrictHostKeyChecking=no", i.public_dns_name,"bash",fname]
       cmd = ["bash","-c",(" ".join(cmd1))+" ; "+(" ".join(cmd2))]
       subproc=subprocess.Popen(cmd,stderr=subprocess.STDOUT)
       processes.append(subproc)
       counter=counter+1
       time.sleep(5)
    for (i,p) in zip(instances,processes):
        result = p.wait()
        if result != 0:
            err("Error during remote script execution on instance "+i.id+" at "+i.public_dns_name)
    for i in range(counter):
        os.remove("tmp-"+str(i))


def allinstances_exec(keyfile,username,instances,thecmd):
    processes = []
    for i in instances:
       cmd=["ssh","-i",keyfile,"-l",username]+ssh_opts+[ i.public_dns_name]+thecmd
#       print " ".join(cmd)
       subproc=subprocess.Popen(cmd,stderr=subprocess.STDOUT)
       processes.append(subproc)
    for (i,p) in zip(instances,processes):
        result = p.wait()
        if result != 0:
            err("Error "+str(result)+" with remote process on instance "+i.id+" at "+i.public_dns_name)

def allinstances_spawn(keyfile,username,instances,thecmd):
    processes = []
    for i in instances:
       cmd=["ssh","-n","-q","-i",keyfile,"-l",username]+ssh_opts+[ i.public_dns_name]+thecmd
       subproc=subprocess.Popen(cmd,stderr=subprocess.STDOUT)
       processes.append(subproc)
       time.sleep(5)

def hostscript(instance):
    hostsfile = "try cfg.log 'setup hostname' suappend /etc/hosts echo 127.0.1.1 `hostname`"
    return hostsfile

def main():
    parser = optparse.OptionParser()
    parser.add_option("-q", "--datamultiple",help="Data multiplier (default 1)",dest="data_multiple",default=1,type="int")
    parser.add_option("-r", "--region", help="Region (default eu-west-1)", dest="region", default="eu-west-1")
    parser.add_option("-i", "--image", help="Virtual machine image (default ami-311f2b45)",dest="ami",default="ami-311f2b45")
# other useful ubuntu instances at
# and here http://uec-images.ubuntu.com/releases/10.04/release/
# and http://aws.amazon.com/ec2/instance-types/
    parser.add_option("-m", "--nmapers", help="Number of mapper nodes (default 10)",dest="mapper_nodes",type="int",default=10)
    parser.add_option("-n", "--nreducers", help="Number of reducer nodes (default 3)",dest="reducer_nodes",type="int",default=3)
    parser.add_option("-p", "--nodesperhost",help="Number of nodes to start on each host (default 4)",dest="nodes_per_host",type="int",default=4)
    parser.add_option("-k", "--keypair",help="Keypair for starting EC2 instances (default aws) -- will look for corresponding file in ~/.ssh/",dest="keypair",default="aws")
    parser.add_option("-s", "--securitygroup",help="Name of security group for launching instances (default default)",dest="securitygroup",default="default")
    parser.add_option("-t","--instancetype",help="Name of instance type for launching instances (default m1.small)",dest="instancetype",default="m1.small")
    parser.add_option("-l","--login-user",help="Username for connecting to instances (default ubuntu)",dest="username",default="ubuntu")
    (options, args) = parser.parse_args()

    if os.sep in options.keypair:
        s = os.path.split(options.keypair)[-1]
        keypair = s.split(".pem")[0]
        keypairfile = os.path.expanduser(options.keypair)
        if not os.path.exists(keypairfile):
            err("Specified keypair file "+keypairfile+" doesn't exist")
    else:
        keypairbase = options.keypair
        attempts = [os.path.join("~",".ssh,",keypairbase),
                    os.path.join("~",".ssh,",keypairbase+".pem"),
                    os.path.join("~",".ec2",keypairbase),
                    os.path.join("~",".ec2",keypairbase+".pem") ]
        for attempt in attempts:
            path = os.path.expanduser(attempt)
            if os.path.exists(path):
                keypair = options.keypair
                keypairfile = path
                break
        else:
            err("Could not find a keyfile matching \""+keypairbase+"\" in ~/.ssh or ~/.ec2") 

    try:
        for r in boto.ec2.regions():
            if r.name == options.region:
                region = r
                break
        else:
            err("Region %s not found." % options.region)
    except AttributeError:
       err("Can't connect to AWS. Set AWS_SECRET_ACCESS_KEY and AWS_ACCESS_KEY_ID environment variables to the values visible under your Security Credentials screen.")
    
    ec2 = boto.connect_ec2(region=region)

    num_nodes = 1 + options.mapper_nodes + options.reducer_nodes
    numhosts = int (math.ceil (float(num_nodes) / options.nodes_per_host))

    print "Starting "+str(numhosts)+" instances of image "+options.ami+"."
    image = ec2.get_all_images(image_ids=[options.ami])[0]
    reservation = image.run(numhosts,numhosts,key_name=keypair,instance_type=options.instancetype,security_groups=[options.securitygroup])
    waitforallup(reservation.instances)
    time.sleep(30)

    print "Configuring instances."
    allinstances_xfer(keypairfile,options.username,reservation.instances,local_files)
    allinstances_runscript(keypairfile,options.username,reservation.instances,lambda a: node_config_script + hostscript(a) + "\n")

    config_file="cfgKnownHosts "+(" ".join([i.private_dns_name for i in reservation.instances]))
    writefile("config",config_file)
    allinstances_xfer(keypairfile,options.username,reservation.instances,["config"])

    print "Transfering data."
    mappers=[]
    reducers=[]
    master=[]
    insts = reservation.instances * options.nodes_per_host
    master.append(insts.pop(0))
    for i in range(options.mapper_nodes):
        mappers.append(insts.pop(0))
    for i in range(options.reducer_nodes):
        reducers.append(insts.pop(0))
    allinstances_xfer(keypairfile,options.username,reservation.instances,["kmeans-points","kmeans-clusters"])

    print "Starting worker nodes."
    allinstances_spawn(keypairfile,options.username,mappers,["./KMeans","-cfgRole=MAPPER"]+exec_args)
    allinstances_spawn(keypairfile,options.username,reducers,["./KMeans","-cfgRole=REDUCER"]+exec_args)
    time.sleep(30)

    print "Starting master node on "+master[0].public_dns_name+"."
    starttime=time.time()
    allinstances_exec(keypairfile,options.username,master,["./KMeans","-cfgRole=MASTER"]+exec_args)
    endtime=time.time()
#    print "Completed computation in "+str(endtime-starttime)+" seconds."

#    print "Retrieving results."
#    allinstances_recv(keypairfile,options.username,master,"kmeans-converged")

    print "Shutting down instances."
    for i in reservation.instances:
        i.stop()

def safe_main():
    try:
        main()
    except boto.exception.EC2ResponseError  as a:
        err("Error from EC2 service: "+a.error_message+": "+a.reason)

if __name__ == "__main__":
    safe_main()