← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~mbp/launchpad/ec2-region into lp:launchpad

 

Martin Pool has proposed merging lp:~mbp/launchpad/ec2-region into lp:launchpad with lp:~mbp/launchpad/ec2-tmpfs as a prerequisite.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~mbp/launchpad/ec2-region/+merge/82373

This adds a --region option to ec2, so that if you live in the asia-pacific or europe you can get lower latency, or if the us-east-1 zone is on fire you can work somewhere else.

Needs more testing.

ec2 is a bit hacky.  I don't know if this makes it too much worse.
-- 
https://code.launchpad.net/~mbp/launchpad/ec2-region/+merge/82373
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~mbp/launchpad/ec2-region into lp:launchpad.
=== modified file 'lib/devscripts/ec2test/builtins.py'
--- lib/devscripts/ec2test/builtins.py	2011-11-16 09:49:32 +0000
+++ lib/devscripts/ec2test/builtins.py	2011-11-16 09:49:33 +0000
@@ -129,6 +129,14 @@
           "and --file."))
 
 
+region_option = Option(
+    'region',
+    type=str,
+    help=("Name of the AWS region in which to run the instance.  "
+        "Must be the same as the region holding the image file. "
+        "For example, 'us-west-1'."))
+
+
 def filename_type(filename):
     """An option validator for filenames.
 
@@ -502,6 +510,7 @@
         postmortem_option,
         debug_option,
         include_download_cache_changes_option,
+        region_option,
         ListOption(
             'demo', type=str,
             help="Allow this netmask to connect to the instance."),
@@ -570,6 +579,7 @@
         instance_type_option,
         postmortem_option,
         debug_option,
+        region_option,
         ListOption(
             'extra-update-image-command', type=str,
             help=('Run this command (with an ssh agent) on the image before '
@@ -586,6 +596,7 @@
 
     def run(self, ami_name, machine=None, instance_type='m1.large',
             debug=False, postmortem=False, extra_update_image_command=None,
+            region=None,
             public=False):
         set_trace_if(debug)
 
@@ -599,17 +610,14 @@
         for variable in ['LANG', 'LC_ALL', 'LC_TIME']:
             os.environ.pop(variable, None)
 
-        credentials = EC2Credentials.load_from_file()
-
         session_name = EC2SessionName.make(EC2TestRunner.name)
         instance = EC2Instance.make(
             session_name, instance_type, machine,
-            credentials=credentials)
-        instance.check_bundling_prerequisites(
-            ami_name, credentials)
+            region=region)
+        instance.check_bundling_prerequisites(ami_name)
         instance.set_up_and_run(
             postmortem, True, self.update_image, instance,
-            extra_update_image_command, ami_name, credentials, public)
+            extra_update_image_command, ami_name, instance._credentials, public)
 
     def update_image(self, instance, extra_update_image_command, ami_name,
                      credentials, public):
@@ -669,9 +677,13 @@
     The first in the list is the default image.
     """
 
-    def run(self):
-        credentials = EC2Credentials.load_from_file()
+    takes_options = [
+        region_option,
+        ]
+
+    def run(self, region=None):
         session_name = EC2SessionName.make(EC2TestRunner.name)
+        credentials = EC2Credentials.load_from_file(region_name=region)
         account = credentials.connect(session_name)
         format = "%5s  %-12s  %-12s  %-12s %s\n"
         self.outf.write(
@@ -698,6 +710,7 @@
     aliases = ["ls"]
 
     takes_options = [
+        region_option,
         Option('show-urls',
                help="Include more information about each instance"),
         Option('all', short_name='a',
@@ -766,9 +779,9 @@
             ': '.join((state, str(num)))
             for (state, num) in sorted(list(by_state.items())))
 
-    def run(self, show_urls=False, all=False):
-        credentials = EC2Credentials.load_from_file()
+    def run(self, show_urls=False, all=False, region=None):
         session_name = EC2SessionName.make(EC2TestRunner.name)
+        credentials = EC2Credentials.load_from_file(region_name=region)
         account = credentials.connect(session_name)
         instances = list(self.iter_instances(account))
         if len(instances) == 0:

=== modified file 'lib/devscripts/ec2test/credentials.py'
--- lib/devscripts/ec2test/credentials.py	2011-05-27 19:57:02 +0000
+++ lib/devscripts/ec2test/credentials.py	2011-11-16 09:49:33 +0000
@@ -12,6 +12,7 @@
 import os
 
 import boto
+import boto.ec2
 from bzrlib.errors import BzrCommandError
 from devscripts.ec2test.account import EC2Account
 
@@ -32,12 +33,13 @@
 
     DEFAULT_CREDENTIALS_FILE = '~/.ec2/aws_id'
 
-    def __init__(self, identifier, secret):
+    def __init__(self, identifier, secret, region_name):
         self.identifier = identifier
         self.secret = secret
+        self.region_name = region_name or 'us-east-1'
 
     @classmethod
-    def load_from_file(cls, filename=None):
+    def load_from_file(cls, filename=None, region_name=None):
         """Load the EC2 credentials from 'filename'."""
         if filename is None:
             filename = os.path.expanduser(cls.DEFAULT_CREDENTIALS_FILE)
@@ -50,15 +52,18 @@
             secret = aws_file.readline().strip()
         finally:
             aws_file.close()
-        return cls(identifier, secret)
+        return cls(identifier, secret, region_name)
 
     def connect(self, name):
         """Connect to EC2 with these credentials.
 
-        :param name: ???
+        :param name: Arbitrary local name for the object.
         :return: An `EC2Account` connected to EC2 with these credentials.
         """
-        conn = boto.connect_ec2(self.identifier, self.secret)
+        conn = boto.ec2.connect_to_region(
+            self.region_name,
+            aws_access_key_id=self.identifier,
+            aws_secret_access_key=self.secret)
         return EC2Account(name, conn)
 
     def connect_s3(self):

=== modified file 'lib/devscripts/ec2test/instance.py'
--- lib/devscripts/ec2test/instance.py	2011-11-16 09:49:32 +0000
+++ lib/devscripts/ec2test/instance.py	2011-11-16 09:49:33 +0000
@@ -85,8 +85,6 @@
 cat >> /etc/apt/sources.list << EOF
 deb http://ppa.launchpad.net/launchpad/ubuntu $DISTRIB_CODENAME main
 deb http://ppa.launchpad.net/bzr/ubuntu $DISTRIB_CODENAME main
-deb http://us.ec2.archive.ubuntu.com/ubuntu/ $DISTRIB_CODENAME multiverse
-deb-src http://us.ec2.archive.ubuntu.com/ubuntu/ $DISTRIB_CODENAME main
 EOF
 
 export DEBIAN_FRONTEND=noninteractive
@@ -113,7 +111,7 @@
 apt-key adv --recv-keys --keyserver pool.sks-keyservers.net cbede690576d1e4e813f6bb3ebaf723d37b19b80
 
 aptitude update
-aptitude -y install language-pack-en   # Do this first so later things don't complain about locales
+LANG=C aptitude -y install language-pack-en   # Do this first so later things don't complain about locales
 aptitude -y full-upgrade
 
 # This next part is cribbed from rocketfuel-setup
@@ -210,7 +208,7 @@
 
     @classmethod
     def make(cls, name, instance_type, machine_id, demo_networks=None,
-             credentials=None):
+             credentials=None, region=None):
         """Construct an `EC2Instance`.
 
         :param name: The name to use for the key pair and security group for
@@ -225,6 +223,7 @@
         :param demo_networks: A list of networks to add to the security group
             to allow access to the instance.
         :param credentials: An `EC2Credentials` object.
+        :param region: A string region name eg 'us-east-1'.
         """
         # This import breaks in the test environment.  Do it here so
         # that unit tests (which don't use this factory) can still
@@ -246,7 +245,7 @@
         user_key = get_user_key()
 
         if credentials is None:
-            credentials = EC2Credentials.load_from_file()
+            credentials = EC2Credentials.load_from_file(region_name=region)
 
         # Make the EC2 connection.
         account = credentials.connect(name)
@@ -274,12 +273,14 @@
             raise BzrCommandError(
                 'you must have set your launchpad login in bzr.')
 
-        return EC2Instance(
+        instance = EC2Instance(
             name, image, instance_type, demo_networks, account,
-            from_scratch, user_key, login)
+            from_scratch, user_key, login, region)
+        instance._credentials = credentials
+        return instance
 
     def __init__(self, name, image, instance_type, demo_networks, account,
-                 from_scratch, user_key, launchpad_login):
+                 from_scratch, user_key, launchpad_login, region):
         self._name = name
         self._image = image
         self._account = account
@@ -289,6 +290,7 @@
         self._from_scratch = from_scratch
         self._user_key = user_key
         self._launchpad_login = launchpad_login
+        self._region = region
 
     def log(self, msg):
         """Log a message on stdout, flushing afterwards."""
@@ -363,7 +365,7 @@
                 ssh.connect(self.hostname, **connect_args)
             except (socket.error, paramiko.AuthenticationException, EOFError), e:
                 self.log('.')
-                if getattr(e, 'errno') == errno.ECONNREFUSED:
+                if getattr(e, 'errno', None) == errno.ECONNREFUSED:
                     # Pretty normal if the machine has started but sshd isn't
                     # up yet.  Don't make a fuss.
                     time.sleep(1)
@@ -531,13 +533,9 @@
                 '%r must match a single %s file' % (pattern, file_kind))
         return matches[0]
 
-    def check_bundling_prerequisites(self, name, credentials):
+    def check_bundling_prerequisites(self, name):
         """Check, as best we can, that all the files we need to bundle exist.
         """
-        if subprocess.call(['which', 'ec2-register']):
-            raise BzrCommandError(
-                '`ec2-register` command not found.  '
-                'Try `sudo apt-get install ec2-api-tools`.')
         local_ec2_dir = os.path.expanduser('~/.ec2')
         if not os.path.exists(local_ec2_dir):
             raise BzrCommandError(
@@ -555,7 +553,8 @@
         # The bucket `name` needs to exist and be accessible. We create it
         # here to reserve the name. If the bucket already exists and conforms
         # to the above requirements, this is a no-op.
-        credentials.connect_s3().create_bucket(name)
+        self._credentials.connect_s3().create_bucket(
+            name, location=self._credentials.region_name)
 
     def bundle(self, name, credentials):
         """Bundle, upload and register the instance as a new AMI.
@@ -610,21 +609,20 @@
         mfilename = os.path.basename(manifest)
         manifest_path = os.path.join(name, mfilename)
 
-        env = os.environ.copy()
-        if 'JAVA_HOME' not in os.environ:
-            env['JAVA_HOME'] = '/usr/lib/jvm/default-java'
         now = datetime.strftime(datetime.utcnow(), "%Y-%m-%d %H:%M:%S UTC")
-        description = "Created %s" % now
-        cmd = [
-            'ec2-register',
-            '--private-key=%s' % self.local_pk,
-            '--cert=%s' % self.local_cert,
-            '--name=%s' % (name,),
-            '--description=%s' % description,
-            manifest_path,
-            ]
-        self.log("Executing command: %s" % ' '.join(cmd))
-        subprocess.check_call(cmd, env=env)
+        description = "Created %s by %r on %s" % (
+            now,
+            os.environ.get('EMAIL'),
+            socket.gethostname())
+
+        self.log('registering image: ')
+        credentials.connect('bundle').register_image(
+            name=name,
+            description=description,
+            image_location=manifest_path,
+            )
+        self.log('ok\n')
+
 
 
 class EC2InstanceConnection: