Merge lp:~mwhudson/launchpad/ami-from-scratch into lp:launchpad

Proposed by Michael Hudson-Doyle
Status: Merged
Merged at revision: not available
Proposed branch: lp:~mwhudson/launchpad/ami-from-scratch
Merge into: lp:launchpad
Diff against target: 878 lines
5 files modified
lib/devscripts/ec2test/builtins.py (+23/-17)
lib/devscripts/ec2test/instance.py (+252/-121)
lib/devscripts/ec2test/sshconfig.py (+0/-95)
lib/devscripts/ec2test/testrunner.py (+15/-23)
utilities/update-sourcecode (+4/-2)
To merge this branch: bzr merge lp:~mwhudson/launchpad/ami-from-scratch
Reviewer Review Type Date Requested Status
Jonathan Lange (community) Approve
Review via email: mp+12331@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

Hi jml,

I hit back with an unsolicited ec2 branch :-)

This one started with thinking about preparing an image for public consumption. I don't really know what files are lurking in unexpected places on the ec2test instances we currently use, so I started to think about building images from a blank hardy image such as those on alestic.com. Eventually, I came up with this approach, which hides all the setup of an instance in the EC2Instance object, and gives a way of saying that an image will come up 'blank' and needs completely setting up. It also changes to assuming a sudo account exists on a pre set-up image rather than creating a user on each run.

As a side effect, you could say 'ec2 test -m based-on:<id of some blank karmic ami>' to run all the tests in a karmic image, which is nice.

I made a couple of images with --public and got wgrant to test them (successfully). I shouldn't make the image public until this branch is about ready to land as while the trunk ec2 script works with the new image, it's rather slow (lots and lots of stuff gets rsynced off devpad).

Because I spent so long going back and forth on ways to do this, a fair bit of drive by clean up happened en route. I hope it doesn't muddle things too much.

Cheers,
mwh

Revision history for this message
Jonathan Lange (jml) wrote :
Download full text (37.4 KiB)

On Thu, Sep 24, 2009 at 8:30 AM, Michael Hudson
<email address hidden> wrote:
> Michael Hudson has proposed merging lp:~mwhudson/launchpad/ami-from-scratch into lp:launchpad/devel.
>
>    Requested reviews:
>    Jonathan Lange (jml)
>
>
> Hi jml,
>
> I hit back with an unsolicited ec2 branch :-)
>

Wuu.

> This one started with thinking about preparing an image for public consumption.  I don't really know what files are lurking in unexpected places on the ec2test instances we currently use, so I started to think about building images from a blank hardy image such as those on alestic.com.  Eventually, I came up with this approach, which hides all the setup of an instance in the EC2Instance object, and gives a way of saying that an image will come up 'blank' and needs completely setting up.  It also changes to assuming a sudo account exists on a pre set-up image rather than creating a user on each run.
>

By "pre set-up image", you mean these "blank" images, right?

> As a side effect, you could say 'ec2 test -m based-on:<id of some blank karmic ami>' to run all the tests in a karmic image, which is nice.
>

That *is* nice.

> I made a couple of images with --public and got wgrant to test them (successfully).  I shouldn't make the image public until this branch is about ready to land as while the trunk ec2 script works with the new image, it's rather slow (lots and lots of stuff gets rsynced off devpad).
>

Why will the public image be slow while trunk ec2 works with the new
image. I feel like I'm missing a couple of steps.

> Because I spent so long going back and forth on ways to do this, a fair bit of drive by clean up happened en route.  I hope it doesn't muddle things too much.
>

I'm sure I'll appreciate the drive-by cleanup.

...

OK, I'm done reviewing the branch. I've got a lot of questions,
because there's a lot I don't know. Overall the branch looks good, the
code seems much more solid, despite the fact that you're adding
functionality.

I would, however, like to see your answers before the branch lands.

> Cheers,
> mwh
> --
> https://code.launchpad.net/~mwhudson/launchpad/ami-from-scratch/+merge/12331
> You are requested to review the proposed merge of lp:~mwhudson/launchpad/ami-from-scratch into lp:launchpad/devel.
>
> === modified file 'lib/devscripts/ec2test/builtins.py'
> --- lib/devscripts/ec2test/builtins.py  2009-09-18 05:57:43 +0000
> +++ lib/devscripts/ec2test/builtins.py  2009-09-24 07:30:35 +0000
> @@ -95,6 +95,7 @@
>           'changes in your download cache, you must explicitly choose to '
>           'include or ignore the changes.'))
>
> +
>  postmortem_option = Option(
>     'postmortem', short_name='p',
>     help=('Drop to interactive prompt after the test and before shutting '
> @@ -308,7 +309,7 @@
>         postmortem = True
>         shutdown = True
>         instance.set_up_and_run(
> -            postmortem, shutdown, self.run_server, runner,
> +            postmortem, shutdown, self.run_server, runner, instance,
>             demo_network_string)
>

"instance" is a parameter of set_up_and_run? That's a bit weird. I wonder...

So I still don't know the motivation for this change, but I wonder if
set_u...

Revision history for this message
Jonathan Lange (jml) wrote :

As per previous comment.

review: Needs Information
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :
Download full text (40.1 KiB)

Jonathan Lange wrote:
> On Thu, Sep 24, 2009 at 8:30 AM, Michael Hudson
> <email address hidden> wrote:
>> Michael Hudson has proposed merging lp:~mwhudson/launchpad/ami-from-scratch into lp:launchpad/devel.
>>
>> Requested reviews:
>> Jonathan Lange (jml)
>>
>>
>> Hi jml,
>>
>> I hit back with an unsolicited ec2 branch :-)
>>
>
> Wuu.
>
>> This one started with thinking about preparing an image for public consumption. I don't really know what files are lurking in unexpected places on the ec2test instances we currently use, so I started to think about building images from a blank hardy image such as those on alestic.com. Eventually, I came up with this approach, which hides all the setup of an instance in the EC2Instance object, and gives a way of saying that an image will come up 'blank' and needs completely setting up. It also changes to assuming a sudo account exists on a pre set-up image rather than creating a user on each run.
>>
>
> By "pre set-up image", you mean these "blank" images, right?

No, I mean an image that has been "prepared", as in, "not from scratch".

>> As a side effect, you could say 'ec2 test -m based-on:<id of some blank karmic ami>' to run all the tests in a karmic image, which is nice.
>>
>
> That *is* nice.
>
>> I made a couple of images with --public and got wgrant to test them (successfully). I shouldn't make the image public until this branch is about ready to land as while the trunk ec2 script works with the new image, it's rather slow (lots and lots of stuff gets rsynced off devpad).
>>
>
> Why will the public image be slow while trunk ec2 works with the new
> image. I feel like I'm missing a couple of steps.

Mostly because there is a lot of crap in
/code/rocketfuel-built/sourcecode that's no longer specified in
sourcedeps.conf and so the rsync that the trunk ec2 test takes ages.

>> Because I spent so long going back and forth on ways to do this, a fair bit of drive by clean up happened en route. I hope it doesn't muddle things too much.
>>
>
> I'm sure I'll appreciate the drive-by cleanup.
>
> ...
>
> OK, I'm done reviewing the branch. I've got a lot of questions,
> because there's a lot I don't know. Overall the branch looks good, the
> code seems much more solid, despite the fact that you're adding
> functionality.

Yeah, I'm happy with the changes overall.

> I would, however, like to see your answers before the branch lands.

OK.

>> Cheers,
>> mwh
>> --
>> https://code.launchpad.net/~mwhudson/launchpad/ami-from-scratch/+merge/12331
>> You are requested to review the proposed merge of lp:~mwhudson/launchpad/ami-from-scratch into lp:launchpad/devel.
>>
>> === modified file 'lib/devscripts/ec2test/builtins.py'
>> --- lib/devscripts/ec2test/builtins.py 2009-09-18 05:57:43 +0000
>> +++ lib/devscripts/ec2test/builtins.py 2009-09-24 07:30:35 +0000
>> @@ -95,6 +95,7 @@
>> 'changes in your download cache, you must explicitly choose to '
>> 'include or ignore the changes.'))
>>
>> +
>> postmortem_option = Option(
>> 'postmortem', short_name='p',
>> help=('Drop to interactive prompt after the test and before shutting '
>> @@ -308,7 +309,7 @@
>> po...

Revision history for this message
Jonathan Lange (jml) wrote :

Thanks. Can you send me the interdiff of your changes when they're done?

One day, Launchpad will do this for us...

jml

review: Approve
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

Michael Hudson wrote:

>>> user_connection.run_with_ssh_agent(
>>> 'bzr pull -d /var/launchpad/download-cache lp:lp-source-dependencies')
>>> + user_connection.run_with_ssh_agent(
>>> + "/var/launchpad/test/utilities/update-sourcecode "
>>> + "/var/launchpad/sourcecode")
>>> + if public:
>>> + user_connection.perform(
>>> + 'rm -rf /var/launchpad/sourcecode/shipit '
>>> + '/var/launchpad/sourcecode/canonical-identity-provider')
>>> + user_connection.perform(
>>> + 'rm -rf .ssh/known_hosts .bazaar .bzr.log')
>> This duplicates knowledge from update-sourcecode. Could we perhaps
>> instead change update-sourcecode to have an option to not even try to
>> get the private branches, and then remove this removal logic?
>>
>> Actually, this would be a worthwhile change even for day-to-day use of
>> update-sourcecode.
>
> Hm, yeah, that would be more reliable. I'll change that.

I did this in a separate branch, and got Tim to review it. So this
change has happened, but mostly isn't in the interdiff I'm about to
attach to this mail.

Cheers,
mwh

1=== modified file 'lib/devscripts/ec2test/builtins.py'
2--- lib/devscripts/ec2test/builtins.py 2009-09-24 07:13:28 +0000
3+++ lib/devscripts/ec2test/builtins.py 2009-09-25 03:14:09 +0000
4@@ -402,16 +402,16 @@
5 # trunk contains a update-sourcecode script that works in a non-built
6 # tree, do this. This should be changed before landing.
7 user_connection.run_with_ssh_agent(
8- 'bzr pull -d /var/launchpad/test lp:~mwhudson/launchpad/no-more-devpad-ssh')
9+ 'bzr pull -d /var/launchpad/test lp:~mwhudson/launchpad/ami-from-scratch')
10 user_connection.run_with_ssh_agent(
11 'bzr pull -d /var/launchpad/download-cache lp:lp-source-dependencies')
12+ if public:
13+ update_sourcecode_options = '--public-only'
14+ else:
15+ update_sourcecode_options = ''
16 user_connection.run_with_ssh_agent(
17 "/var/launchpad/test/utilities/update-sourcecode "
18- "/var/launchpad/sourcecode")
19- if public:
20- user_connection.perform(
21- 'rm -rf /var/launchpad/sourcecode/shipit '
22- '/var/launchpad/sourcecode/canonical-identity-provider')
23+ "/var/launchpad/sourcecode" + update_sourcecode_options)
24 user_connection.perform(
25 'rm -rf .ssh/known_hosts .bazaar .bzr.log')
26 user_connection.close()
27
28=== modified file 'lib/devscripts/ec2test/instance.py'
29--- lib/devscripts/ec2test/instance.py 2009-09-24 07:12:14 +0000
30+++ lib/devscripts/ec2test/instance.py 2009-09-25 03:09:22 +0000
31@@ -60,6 +60,9 @@
32 # root and another that should be run as the 'ec2test' user.
33
34 from_scratch_root = """
35+# From 'help set':
36+# -x Print commands and their arguments as they are executed.
37+# -e Exit immediately if a command exits with a non-zero status.
38 set -xe
39
40 sed -ie 's/main universe/main universe multiverse/' /etc/apt/sources.list
41@@ -74,7 +77,7 @@
42
43 # This next part is cribbed from rocketfuel-setup
44 dev_host() {
45- sed -i 's/^127.0.0.88.*$/&\ ${hostname}/' /etc/hosts
46+ sed -i \"s/^127.0.0.88.*$/&\ ${hostname}/\" /etc/hosts
47 }
48
49 echo 'Adding development hosts on local machine'
50@@ -142,6 +145,9 @@
51
52
53 from_scratch_ec2test = """
54+# From 'help set':
55+# -x Print commands and their arguments as they are executed.
56+# -e Exit immediately if a command exits with a non-zero status.
57 set -xe
58
59 bzr launchpad-login %(launchpad-login)s
60@@ -178,7 +184,7 @@
61
62 # We call this here so that it has a chance to complain before the
63 # instance is started (which can take some time).
64- get_user_key()
65+ user_key = get_user_key()
66
67 if credentials is None:
68 credentials = EC2Credentials.load_from_file()
69@@ -213,10 +219,10 @@
70
71 return EC2Instance(
72 name, image, instance_type, demo_networks, account, vals,
73- from_scratch)
74+ from_scratch, user_key)
75
76 def __init__(self, name, image, instance_type, demo_networks, account,
77- vals, from_scratch):
78+ vals, from_scratch, user_key):
79 self._name = name
80 self._image = image
81 self._account = account
82@@ -225,6 +231,7 @@
83 self._boto_instance = None
84 self._vals = vals
85 self._from_scratch = from_scratch
86+ self._user_key = user_key
87
88 def log(self, msg):
89 """Log a message on stdout, flushing afterwards."""
90@@ -306,13 +313,42 @@
91 return EC2InstanceConnection(self, username, ssh)
92
93 def _upload_local_key(self, conn, remote_filename):
94- """ """
95- user_key = get_user_key()
96+ """Upload a key from the local user's agent to `remote_filename`.
97+
98+ The key will be uploaded in a format suitable for
99+ ~/.ssh/authorized_keys.
100+ """
101 authorized_keys_file = conn.sftp.open(remote_filename, 'w')
102 authorized_keys_file.write(
103- "%s %s\n" % (user_key.get_name(), user_key.get_base64()))
104+ "%s %s\n" % (self._user_key.get_name(), self._user_key.get_base64()))
105 authorized_keys_file.close()
106
107+ def _ensure_ec2test_user_has_keys(self, connection=None):
108+ """Make sure that we can connect over ssh as the 'ec2test' user.
109+
110+ We add both the key that was used to start the instance (so
111+ _connect('ec2test') works and a key from the locally running ssh agent
112+ (so EC2InstanceConnection.run_with_ssh_agent works).
113+ """
114+ if not self._ec2test_user_has_keys:
115+ if connection is None:
116+ connection = self._connect('root')
117+ our_connection = True
118+ else:
119+ our_connection = False
120+ self._upload_local_key(connection, 'local_key')
121+ connection.perform(
122+ 'cat /root/.ssh/authorized_keys local_key '
123+ '> /home/ec2test/.ssh/authorized_keys && rm local_key')
124+ connection.perform('chown -R ec2test:ec2test /home/ec2test/')
125+ connection.perform('chmod 644 /home/ec2test/.ssh/*')
126+ if our_connection:
127+ connection.close()
128+ self.log(
129+ 'You can now use ssh -A ec2test@%s to log in the instance.\n' %
130+ self.hostname)
131+ self._ec2test_user_has_keys = True
132+
133 def connect(self):
134 """Connect to the instance as a user with passwordless sudo.
135
136@@ -326,27 +362,14 @@
137 root_connection.perform(
138 'cat local_key >> ~/.ssh/authorized_keys && rm local_key')
139 root_connection.run_script(from_scratch_root % self._vals)
140- root_connection.close()
141- # Don't set self._from_scratch to True yet, we haven't run
142- # from_scratch_ec2test yet!
143- if not self._ec2test_user_has_keys:
144- root_connection = self._connect('root')
145- self._upload_local_key(root_connection, 'local_key')
146- root_connection.perform(
147- 'cat /root/.ssh/authorized_keys local_key '
148- '> /home/ec2test/.ssh/authorized_keys && rm local_key')
149- root_connection.perform('chown -R ec2test:ec2test /home/ec2test/')
150- root_connection.perform('chmod 644 /home/ec2test/.ssh/*')
151- root_connection.close()
152- self.log(
153- 'You can now use ssh -A ec2test@%s to log in the instance.\n' %
154- self.hostname)
155- self._ec2test_user_has_keys = True
156- conn = self._connect('ec2test')
157- if self._from_scratch:
158+ self._ensure_ec2test_user_has_keys(root_connection)
159+ root_connection.close()
160+ conn = self._connect('ec2test')
161 conn.run_script(from_scratch_ec2test % self._vals)
162 self._from_scratch = False
163- return conn
164+ return conn
165+ self._ensure_ec2test_user_has_keys()
166+ return self._connect('ec2test')
167
168 def set_up_and_run(self, postmortem, shutdown, func, *args, **kw):
169 """Start, run `func` and then maybe shut down.
170@@ -590,15 +613,20 @@
171 return res
172
173 def run_script(self, script_text):
174+ """Upload `script_text` to the instance and run it with bash."""
175 script = self.sftp.open('script.sh', 'w')
176 script.write(script_text)
177 script.close()
178 self.run_with_ssh_agent('/bin/bash script.sh')
179 # At least for mwhudson, the paramiko connection often drops while the
180 # script is running. Reconnect just in case.
181+ self.reconnect()
182+ self.perform('rm script.sh')
183+
184+ def reconnect(self):
185+ """Close the connection and reopen it."""
186 self.close()
187 self._ssh = self._instance._connect(self._username)._ssh
188- self.perform('rm script.sh')
189
190 def close(self):
191 if self._sftp is not None:

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/devscripts/ec2test/builtins.py'
2--- lib/devscripts/ec2test/builtins.py 2009-09-27 00:27:17 +0000
3+++ lib/devscripts/ec2test/builtins.py 2009-09-27 20:39:13 +0000
4@@ -95,6 +95,7 @@
5 'changes in your download cache, you must explicitly choose to '
6 'include or ignore the changes.'))
7
8+
9 postmortem_option = Option(
10 'postmortem', short_name='p',
11 help=('Drop to interactive prompt after the test and before shutting '
12@@ -308,7 +309,7 @@
13 postmortem = True
14 shutdown = True
15 instance.set_up_and_run(
16- postmortem, shutdown, self.run_server, runner,
17+ postmortem, shutdown, self.run_server, runner, instance,
18 demo_network_string)
19
20 def run_server(self, runner, instance, demo_network_string):
21@@ -335,7 +336,6 @@
22 "\n\n")
23
24
25-
26 class cmd_update_image(EC2Command):
27 """Make a new AMI."""
28
29@@ -349,12 +349,17 @@
30 help=('Run this command (with an ssh agent) on the image before '
31 'running the default update steps. Can be passed more than '
32 'once, the commands will be run in the order specified.')),
33+ Option(
34+ 'public',
35+ help=('Remove proprietary code from the sourcecode directory '
36+ 'before bundling.')),
37 ]
38
39 takes_args = ['ami_name']
40
41 def run(self, ami_name, machine=None, instance_type='m1.large',
42- debug=False, postmortem=False, extra_update_image_command=[]):
43+ debug=False, postmortem=False, extra_update_image_command=[],
44+ public=False):
45 if debug:
46 pdb.set_trace()
47
48@@ -367,19 +372,18 @@
49
50 instance.set_up_and_run(
51 postmortem, True, self.update_image, instance,
52- extra_update_image_command, ami_name, credentials)
53+ extra_update_image_command, ami_name, credentials, public)
54
55 def update_image(self, instance, extra_update_image_command, ami_name,
56- credentials):
57+ credentials, public):
58 """Bring the image up to date.
59
60 The steps we take are:
61
62 * run any commands specified with --extra-update-image-command
63- * update sourcecode via rsync.
64+ * update sourcecode
65 * update the launchpad branch to the tip of the trunk branch.
66 * update the copy of the download-cache.
67- * remove the user account.
68 * bundle the image
69
70 :param instance: `EC2Instance` to operate on.
71@@ -387,25 +391,27 @@
72 instance in addition to the usual ones.
73 :param ami_name: The name to give the created AMI.
74 :param credentials: An `EC2Credentials` object.
75+ :param public: If true, remove proprietary code from the sourcecode
76+ directory before bundling.
77 """
78- user_connection = instance.connect_as_user()
79+ user_connection = instance.connect()
80 user_connection.perform('bzr launchpad-login %(launchpad-login)s')
81 for cmd in extra_update_image_command:
82 user_connection.run_with_ssh_agent(cmd)
83 user_connection.run_with_ssh_agent(
84- "rsync -avp --partial --delete "
85- "--filter='P *.o' --filter='P *.pyc' --filter='P *.so' "
86- "devpad.canonical.com:/code/rocketfuel-built/launchpad/sourcecode/* "
87- "/var/launchpad/sourcecode/")
88- user_connection.run_with_ssh_agent(
89 'bzr pull -d /var/launchpad/test ' + TRUNK_BRANCH)
90 user_connection.run_with_ssh_agent(
91 'bzr pull -d /var/launchpad/download-cache lp:lp-source-dependencies')
92+ if public:
93+ update_sourcecode_options = '--public-only'
94+ else:
95+ update_sourcecode_options = ''
96+ user_connection.run_with_ssh_agent(
97+ "/var/launchpad/test/utilities/update-sourcecode "
98+ "/var/launchpad/sourcecode" + update_sourcecode_options)
99+ user_connection.perform(
100+ 'rm -rf .ssh/known_hosts .bazaar .bzr.log')
101 user_connection.close()
102- root_connection = instance.connect_as_root()
103- root_connection.perform(
104- 'deluser --remove-home %(USER)s', ignore_failure=True)
105- root_connection.close()
106 instance.bundle(ami_name, credentials)
107
108
109
110=== modified file 'lib/devscripts/ec2test/instance.py'
111--- lib/devscripts/ec2test/instance.py 2009-09-27 00:27:17 +0000
112+++ lib/devscripts/ec2test/instance.py 2009-09-27 20:39:13 +0000
113@@ -23,7 +23,6 @@
114
115 import paramiko
116
117-from devscripts.ec2test.sshconfig import SSHConfig
118 from devscripts.ec2test.credentials import EC2Credentials
119
120
121@@ -56,19 +55,126 @@
122 return user_key
123
124
125+# Commands to run to turn a blank image into one usable for the rest of the
126+# ec2 functionality. They come in two parts, one set that need to be run as
127+# root and another that should be run as the 'ec2test' user.
128+
129+from_scratch_root = """
130+# From 'help set':
131+# -x Print commands and their arguments as they are executed.
132+# -e Exit immediately if a command exits with a non-zero status.
133+set -xe
134+
135+sed -ie 's/main universe/main universe multiverse/' /etc/apt/sources.list
136+
137+. /etc/lsb-release
138+
139+cat >> /etc/apt/sources.list << EOF
140+deb http://ppa.launchpad.net/launchpad/ubuntu $DISTRIB_CODENAME main
141+deb http://ppa.launchpad.net/bzr/ubuntu $DISTRIB_CODENAME main
142+deb http://ppa.launchpad.net/bzr-beta-ppa/ubuntu $DISTRIB_CODENAME main
143+EOF
144+
145+# This next part is cribbed from rocketfuel-setup
146+dev_host() {
147+ sed -i \"s/^127.0.0.88.*$/&\ ${hostname}/\" /etc/hosts
148+}
149+
150+echo 'Adding development hosts on local machine'
151+echo '
152+# Launchpad virtual domains. This should be on one line.
153+127.0.0.88 launchpad.dev
154+' >> /etc/hosts
155+
156+declare -a hostnames
157+hostnames=$(cat <<EOF
158+ answers.launchpad.dev
159+ api.launchpad.dev
160+ bazaar-internal.launchpad.dev
161+ beta.launchpad.dev
162+ blueprints.launchpad.dev
163+ bugs.launchpad.dev
164+ code.launchpad.dev
165+ feeds.launchpad.dev
166+ id.launchpad.dev
167+ keyserver.launchpad.dev
168+ lists.launchpad.dev
169+ openid.launchpad.dev
170+ ppa.launchpad.dev
171+ private-ppa.launchpad.dev
172+ shipit.edubuntu.dev
173+ shipit.kubuntu.dev
174+ shipit.ubuntu.dev
175+ translations.launchpad.dev
176+ xmlrpc-private.launchpad.dev
177+ xmlrpc.launchpad.dev
178+EOF
179+ )
180+
181+for hostname in $hostnames; do
182+ dev_host;
183+done
184+
185+echo '
186+127.0.0.99 bazaar.launchpad.dev
187+' >> /etc/hosts
188+
189+# Add the keys for the three PPAs added to sources.list above.
190+apt-key adv --recv-keys --keyserver pool.sks-keyservers.net 2af499cb24ac5f65461405572d1ffb6c0a5174af
191+apt-key adv --recv-keys --keyserver pool.sks-keyservers.net ece2800bacf028b31ee3657cd702bf6b8c6c1efd
192+apt-key adv --recv-keys --keyserver pool.sks-keyservers.net cbede690576d1e4e813f6bb3ebaf723d37b19b80
193+
194+aptitude update
195+aptitude -y full-upgrade
196+
197+apt-get -y install launchpad-developer-dependencies apache2 apache2-mpm-worker
198+
199+# Creat the ec2test user, give them passwordless sudo.
200+adduser --gecos "" --disabled-password ec2test
201+echo 'ec2test\tALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers
202+
203+mkdir /home/ec2test/.ssh
204+cat > /home/ec2test/.ssh/config << EOF
205+CheckHostIP no
206+StrictHostKeyChecking no
207+EOF
208+
209+mkdir /var/launchpad
210+chown -R ec2test:ec2test /var/www /var/launchpad /home/ec2test/
211+"""
212+
213+
214+from_scratch_ec2test = """
215+# From 'help set':
216+# -x Print commands and their arguments as they are executed.
217+# -e Exit immediately if a command exits with a non-zero status.
218+set -xe
219+
220+bzr launchpad-login %(launchpad-login)s
221+bzr init-repo --2a /var/launchpad
222+bzr branch lp:~launchpad-pqm/launchpad/devel /var/launchpad/test
223+bzr branch --standalone lp:lp-source-dependencies /var/launchpad/download-cache
224+mkdir /var/launchpad/sourcecode
225+/var/launchpad/test/utilities/update-sourcecode /var/launchpad/sourcecode
226+"""
227+
228+
229 class EC2Instance:
230 """A single EC2 instance."""
231
232 @classmethod
233- def make(cls, name, instance_type, machine_id,
234- demo_networks=None, credentials=None):
235+ def make(cls, name, instance_type, machine_id, demo_networks=None,
236+ credentials=None):
237 """Construct an `EC2Instance`.
238
239 :param name: The name to use for the key pair and security group for
240 the instance.
241 :param instance_type: One of the AVAILABLE_INSTANCE_TYPES.
242 :param machine_id: The AMI to use, or None to do the usual regexp
243- matching.
244+ matching. If you put 'based-on:' before the AMI id, it is assumed
245+ that the id specifies a blank image that should be made into one
246+ suitable for the other ec2 functions (see `from_scratch_root` and
247+ `from_scratch_ec2test` above).
248 :param demo_networks: A list of networks to add to the security group
249 to allow access to the instance.
250 :param credentials: An `EC2Credentials` object.
251@@ -76,6 +182,10 @@
252 if instance_type not in AVAILABLE_INSTANCE_TYPES:
253 raise ValueError('unknown instance_type %s' % (instance_type,))
254
255+ # We call this here so that it has a chance to complain before the
256+ # instance is started (which can take some time).
257+ user_key = get_user_key()
258+
259 if credentials is None:
260 credentials = EC2Credentials.load_from_file()
261
262@@ -91,6 +201,12 @@
263 # generate it.
264 account.delete_previous_key_pair()
265
266+ if machine_id and machine_id.startswith('based-on:'):
267+ from_scratch = True
268+ machine_id = machine_id[len('based-on:'):]
269+ else:
270+ from_scratch = False
271+
272 # get the image
273 image = account.acquire_image(machine_id)
274
275@@ -102,12 +218,11 @@
276 vals['launchpad-login'] = login
277
278 return EC2Instance(
279- name, image, instance_type, demo_networks, account, vals)
280-
281- # XXX: JonathanLange 2009-05-31: Separate out demo server
282+ name, image, instance_type, demo_networks, account, vals,
283+ from_scratch, user_key)
284
285 def __init__(self, name, image, instance_type, demo_networks, account,
286- vals):
287+ vals, from_scratch, user_key):
288 self._name = name
289 self._image = image
290 self._account = account
291@@ -115,6 +230,8 @@
292 self._demo_networks = demo_networks
293 self._boto_instance = None
294 self._vals = vals
295+ self._from_scratch = from_scratch
296+ self._user_key = user_key
297
298 def log(self, msg):
299 """Log a message on stdout, flushing afterwards."""
300@@ -148,6 +265,7 @@
301 (elapsed // 60, elapsed % 60))
302 self._output = self._boto_instance.get_console_output()
303 self.log(self._output.output)
304+ self._ec2test_user_has_keys = False
305 else:
306 raise BzrCommandError(
307 'failed to start: %s\n' % self._boto_instance.state)
308@@ -170,22 +288,21 @@
309 return None
310 return self._boto_instance.public_dns_name
311
312- def _connect(self, user, use_agent):
313+ def _connect(self, username):
314 """Connect to the instance as `user`. """
315 ssh = paramiko.SSHClient()
316 ssh.set_missing_host_key_policy(AcceptAllPolicy())
317- connect_args = {'username': user}
318- if not use_agent:
319- connect_args.update({
320- 'pkey': self.private_key,
321- 'allow_agent': False,
322- 'look_for_keys': False,
323- })
324+ connect_args = {
325+ 'username': username,
326+ 'pkey': self.private_key,
327+ 'allow_agent': False,
328+ 'look_for_keys': False,
329+ }
330 for count in range(10):
331 try:
332 ssh.connect(self.hostname, **connect_args)
333 except (socket.error, paramiko.AuthenticationException), e:
334- self.log('_connect: %r' % (e,))
335+ self.log('_connect: %r\n' % (e,))
336 if count < 9:
337 time.sleep(5)
338 self.log('retrying...')
339@@ -193,83 +310,72 @@
340 raise
341 else:
342 break
343- return EC2InstanceConnection(self, user, ssh)
344-
345- def connect_as_root(self):
346- return self._connect('root', False)
347-
348- def connect_as_user(self):
349- return self._connect(self._vals['USER'], True)
350-
351- def set_up_user(self, user_key):
352- """Set up an account named after the local user."""
353- root_connection = self.connect_as_root()
354- as_root = root_connection.perform
355- if self._vals['USER'] == 'gary':
356- # This helps gary debug problems others are having by removing
357- # much of the initial setup used to work on the original image.
358- as_root('deluser --remove-home gary', ignore_failure=True)
359- # Let root perform sudo without a password.
360- as_root('echo "root\tALL=NOPASSWD: ALL" >> /etc/sudoers')
361- # Add the user.
362- as_root('adduser --gecos "" --disabled-password %(USER)s')
363- # Give user sudo without password.
364- as_root('echo "%(USER)s\tALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers')
365- # Update the system.
366- as_root('aptitude update')
367- as_root('aptitude -y full-upgrade')
368- # Set up ssh for user
369- # Make user's .ssh directory
370- as_root('sudo -u %(USER)s mkdir /home/%(USER)s/.ssh')
371- root_sftp = root_connection.ssh.open_sftp()
372- remote_ssh_dir = '/home/%(USER)s/.ssh' % self._vals
373- # Create config file
374- self.log('Creating %s/config\n' % (remote_ssh_dir,))
375- ssh_config_file_name = os.path.join(
376- self._vals['HOME'], '.ssh', 'config')
377- ssh_config_source = open(ssh_config_file_name)
378- config = SSHConfig()
379- config.parse(ssh_config_source)
380- ssh_config_source.close()
381- ssh_config_dest = root_sftp.open("%s/config" % remote_ssh_dir, 'w')
382- ssh_config_dest.write('CheckHostIP no\n')
383- ssh_config_dest.write('StrictHostKeyChecking no\n')
384- for hostname in ('devpad.canonical.com', 'chinstrap.canonical.com'):
385- ssh_config_dest.write('Host %s\n' % (hostname,))
386- data = config.lookup(hostname)
387- for key in ('hostname', 'gssapiauthentication', 'proxycommand',
388- 'user', 'forwardagent'):
389- value = data.get(key)
390- if value is not None:
391- ssh_config_dest.write(' %s %s\n' % (key, value))
392- ssh_config_dest.close()
393- # create authorized_keys
394- self.log('Setting up %s/authorized_keys\n' % remote_ssh_dir)
395- authorized_keys_file = root_sftp.open(
396- "%s/authorized_keys" % remote_ssh_dir, 'w')
397+ return EC2InstanceConnection(self, username, ssh)
398+
399+ def _upload_local_key(self, conn, remote_filename):
400+ """Upload a key from the local user's agent to `remote_filename`.
401+
402+ The key will be uploaded in a format suitable for
403+ ~/.ssh/authorized_keys.
404+ """
405+ authorized_keys_file = conn.sftp.open(remote_filename, 'w')
406 authorized_keys_file.write(
407- "%s %s\n" % (user_key.get_name(), user_key.get_base64()))
408+ "%s %s\n" % (self._user_key.get_name(), self._user_key.get_base64()))
409 authorized_keys_file.close()
410- root_sftp.close()
411- # Chown and chmod the .ssh directory and contents that we just
412- # created.
413- as_root('chown -R %(USER)s:%(USER)s /home/%(USER)s/')
414- as_root('chmod 644 /home/%(USER)s/.ssh/*')
415- self.log(
416- 'You can now use ssh -A %s to log in the instance.\n' %
417- self.hostname)
418- # What follows is somewhat ec2test specfic.
419- # give the user permission to do whatever in /var/www
420- as_root('chown -R %(USER)s:%(USER)s /var/www')
421- # Make /var/launchpad owned by user.
422- as_root('chown -R %(USER)s:%(USER)s /var/launchpad')
423- # Clean out left-overs from the instance image.
424- as_root('rm -fr /var/tmp/*')
425- root_connection.close()
426+
427+ def _ensure_ec2test_user_has_keys(self, connection=None):
428+ """Make sure that we can connect over ssh as the 'ec2test' user.
429+
430+ We add both the key that was used to start the instance (so
431+ _connect('ec2test') works and a key from the locally running ssh agent
432+ (so EC2InstanceConnection.run_with_ssh_agent works).
433+ """
434+ if not self._ec2test_user_has_keys:
435+ if connection is None:
436+ connection = self._connect('root')
437+ our_connection = True
438+ else:
439+ our_connection = False
440+ self._upload_local_key(connection, 'local_key')
441+ connection.perform(
442+ 'cat /root/.ssh/authorized_keys local_key '
443+ '> /home/ec2test/.ssh/authorized_keys && rm local_key')
444+ connection.perform('chown -R ec2test:ec2test /home/ec2test/')
445+ connection.perform('chmod 644 /home/ec2test/.ssh/*')
446+ if our_connection:
447+ connection.close()
448+ self.log(
449+ 'You can now use ssh -A ec2test@%s to log in the instance.\n' %
450+ self.hostname)
451+ self._ec2test_user_has_keys = True
452+
453+ def connect(self):
454+ """Connect to the instance as a user with passwordless sudo.
455+
456+ This may involve first connecting as root and adding SSH keys to the
457+ user's account, and in the case of a from scratch image, it will do a
458+ lot of set up.
459+ """
460+ if self._from_scratch:
461+ root_connection = self._connect('root')
462+ self._upload_local_key(root_connection, 'local_key')
463+ root_connection.perform(
464+ 'cat local_key >> ~/.ssh/authorized_keys && rm local_key')
465+ root_connection.run_script(from_scratch_root % self._vals)
466+ self._ensure_ec2test_user_has_keys(root_connection)
467+ root_connection.close()
468+ conn = self._connect('ec2test')
469+ conn.run_script(from_scratch_ec2test % self._vals)
470+ self._from_scratch = False
471+ return conn
472+ self._ensure_ec2test_user_has_keys()
473+ return self._connect('ec2test')
474
475 def set_up_and_run(self, postmortem, shutdown, func, *args, **kw):
476- """Start, set up a user account, run `func` and then maybe shut down.
477+ """Start, run `func` and then maybe shut down.
478
479+ :param config: A dictionary specifying details of how the instance
480+ should be run:
481 :param postmortem: If true, any exceptions will be caught and an
482 interactive session run to allow debugging the problem.
483 :param shutdown: If true, shut down the instance after `func` and
484@@ -279,10 +385,8 @@
485 :param args: Passed to `func`.
486 :param kw: Passed to `func`.
487 """
488- user_key = get_user_key()
489- self.start()
490 try:
491- self.set_up_user(user_key)
492+ self.start()
493 try:
494 return func(*args, **kw)
495 except Exception:
496@@ -309,7 +413,6 @@
497 finally:
498 if shutdown:
499 self.shutdown()
500-
501
502 def _copy_single_file(self, sftp, local_path, remote_dir):
503 """Copy `local_path` to `remote_dir` on this instance.
504@@ -333,7 +436,6 @@
505 :param sftp: A paramiko SFTP object.
506 """
507 remote_ec2_dir = '/mnt/ec2'
508- sftp.mkdir(remote_ec2_dir)
509 remote_pk = self._copy_single_file(
510 sftp, self.local_pk, remote_ec2_dir)
511 remote_cert = self._copy_single_file(
512@@ -379,20 +481,21 @@
513 :param name: The name-to-be of the new AMI.
514 :param credentials: An `EC2Credentials` object.
515 """
516- root_connection = self.connect_as_root()
517- sftp = root_connection.ssh.open_sftp()
518-
519- remote_pk, remote_cert = self.copy_key_and_certificate_to_image(sftp)
520-
521- sftp.close()
522+ connection = self.connect()
523+ connection.perform('rm -f .ssh/authorized_keys')
524+ connection.perform('sudo mkdir /mnt/ec2')
525+ connection.perform('sudo chown $USER:$USER /mnt/ec2')
526+
527+ remote_pk, remote_cert = self.copy_key_and_certificate_to_image(
528+ connection.sftp)
529
530 bundle_dir = os.path.join('/mnt', name)
531
532- root_connection.perform('mkdir ' + bundle_dir)
533- root_connection.perform(' '.join([
534- 'ec2-bundle-vol',
535+ connection.perform('sudo mkdir ' + bundle_dir)
536+ connection.perform(' '.join([
537+ 'sudo ec2-bundle-vol',
538 '-d %s' % bundle_dir,
539- '-b', # Set batch-mode, which doesn't use prompts.
540+ '--batch', # Set batch-mode, which doesn't use prompts.
541 '-k %s' % remote_pk,
542 '-c %s' % remote_cert,
543 '-u %s' % self.aws_user,
544@@ -404,18 +507,17 @@
545
546 # Best check that the manifest actually exists though.
547 test = 'test -f %s' % manifest
548- root_connection.perform(test)
549+ connection.perform(test)
550
551- root_connection.perform(' '.join([
552- 'ec2-upload-bundle',
553+ connection.perform(' '.join([
554+ 'sudo ec2-upload-bundle',
555 '-b %s' % name,
556 '-m %s' % manifest,
557 '-a %s' % credentials.identifier,
558 '-s %s' % credentials.secret,
559 ]))
560
561- sftp.close()
562- root_connection.close()
563+ connection.close()
564
565 # This is invoked locally.
566 mfilename = os.path.basename(manifest)
567@@ -438,9 +540,16 @@
568 """An ssh connection to an `EC2Instance`."""
569
570 def __init__(self, instance, username, ssh):
571- self.instance = instance
572- self.username = username
573- self.ssh = ssh
574+ self._instance = instance
575+ self._username = username
576+ self._ssh = ssh
577+ self._sftp = None
578+
579+ @property
580+ def sftp(self):
581+ if self._sftp is None:
582+ self._sftp = self._ssh.open_sftp()
583+ return self._sftp
584
585 def perform(self, cmd, ignore_failure=False, out=None):
586 """Perform 'cmd' on server.
587@@ -449,10 +558,11 @@
588 statuses.
589 :param out: A stream to write the output of the remote command to.
590 """
591- cmd = cmd % self.instance._vals
592- self.instance.log(
593- '%s@%s$ %s\n' % (self.username, self.instance._boto_instance.id, cmd))
594- session = self.ssh.get_transport().open_session()
595+ cmd = cmd % self._instance._vals
596+ self._instance.log(
597+ '%s@%s$ %s\n'
598+ % (self._username, self._instance._boto_instance.id, cmd))
599+ session = self._ssh.get_transport().open_session()
600 session.exec_command(cmd)
601 session.shutdown_write()
602 while 1:
603@@ -488,9 +598,11 @@
604 Use this to run commands that require local SSH credentials. For
605 example, getting private branches from Launchpad.
606 """
607- cmd = cmd % self.instance._vals
608- self.instance.log('%s@%s$ %s\n' % (self.username, self.instance._boto_instance.id, cmd))
609- call = ['ssh', '-A', self.instance.hostname,
610+ cmd = cmd % self._instance._vals
611+ self._instance.log(
612+ '%s@%s$ %s\n'
613+ % (self._username, self._instance._boto_instance.id, cmd))
614+ call = ['ssh', '-A', self._username + '@' + self._instance.hostname,
615 '-o', 'CheckHostIP no',
616 '-o', 'StrictHostKeyChecking no',
617 '-o', 'UserKnownHostsFile ~/.ec2/known_hosts',
618@@ -500,6 +612,25 @@
619 raise RuntimeError('Command failed: %s' % (cmd,))
620 return res
621
622+ def run_script(self, script_text):
623+ """Upload `script_text` to the instance and run it with bash."""
624+ script = self.sftp.open('script.sh', 'w')
625+ script.write(script_text)
626+ script.close()
627+ self.run_with_ssh_agent('/bin/bash script.sh')
628+ # At least for mwhudson, the paramiko connection often drops while the
629+ # script is running. Reconnect just in case.
630+ self.reconnect()
631+ self.perform('rm script.sh')
632+
633+ def reconnect(self):
634+ """Close the connection and reopen it."""
635+ self.close()
636+ self._ssh = self._instance._connect(self._username)._ssh
637+
638 def close(self):
639- self.ssh.close()
640- self.ssh = None
641+ if self._sftp is not None:
642+ self._sftp.close()
643+ self._sftp = None
644+ self._ssh.close()
645+ self._ssh = None
646
647=== removed file 'lib/devscripts/ec2test/sshconfig.py'
648--- lib/devscripts/ec2test/sshconfig.py 2009-09-27 01:51:50 +0000
649+++ lib/devscripts/ec2test/sshconfig.py 1970-01-01 00:00:00 +0000
650@@ -1,95 +0,0 @@
651-#############################################################################
652-# Modified from paramiko.config. The change should be pushed upstream.
653-# Our fork supports Host lines with more than one host.
654-
655-import fnmatch
656-
657-class SSHConfig (object):
658- """
659- Representation of config information as stored in the format used by
660- OpenSSH. Queries can be made via L{lookup}. The format is described in
661- OpenSSH's C{ssh_config} man page. This class is provided primarily as a
662- convenience to posix users (since the OpenSSH format is a de-facto
663- standard on posix) but should work fine on Windows too.
664-
665- @since: 1.6
666- """
667-
668- def __init__(self):
669- """
670- Create a new OpenSSH config object.
671- """
672- self._config = [ { 'host': '*' } ]
673-
674- def parse(self, file_obj):
675- """
676- Read an OpenSSH config from the given file object.
677-
678- @param file_obj: a file-like object to read the config file from
679- @type file_obj: file
680- """
681- configs = [self._config[0]]
682- for line in file_obj:
683- line = line.rstrip('\n').lstrip()
684- if (line == '') or (line[0] == '#'):
685- continue
686- if '=' in line:
687- key, value = line.split('=', 1)
688- key = key.strip().lower()
689- else:
690- # find first whitespace, and split there
691- i = 0
692- while (i < len(line)) and not line[i].isspace():
693- i += 1
694- if i == len(line):
695- raise Exception('Unparsable line: %r' % line)
696- key = line[:i].lower()
697- value = line[i:].lstrip()
698-
699- if key == 'host':
700- del configs[:]
701- # the value may be multiple hosts, space-delimited
702- for host in value.split():
703- # do we have a pre-existing host config to append to?
704- matches = [c for c in self._config if c['host'] == host]
705- if len(matches) > 0:
706- configs.append(matches[0])
707- else:
708- config = { 'host': host }
709- self._config.append(config)
710- configs.append(config)
711- else:
712- for config in configs:
713- config[key] = value
714-
715- def lookup(self, hostname):
716- """
717- Return a dict of config options for a given hostname.
718-
719- The host-matching rules of OpenSSH's C{ssh_config} man page are used,
720- which means that all configuration options from matching host
721- specifications are merged, with more specific hostmasks taking
722- precedence. In other words, if C{"Port"} is set under C{"Host *"}
723- and also C{"Host *.example.com"}, and the lookup is for
724- C{"ssh.example.com"}, then the port entry for C{"Host *.example.com"}
725- will win out.
726-
727- The keys in the returned dict are all normalized to lowercase (look for
728- C{"port"}, not C{"Port"}. No other processing is done to the keys or
729- values.
730-
731- @param hostname: the hostname to lookup
732- @type hostname: str
733- """
734- matches = [
735- x for x in self._config if fnmatch.fnmatch(hostname, x['host'])]
736- # sort in order of shortest match (usually '*') to longest
737- matches.sort(lambda x,y: cmp(len(x['host']), len(y['host'])))
738- ret = {}
739- for m in matches:
740- ret.update(m)
741- del ret['host']
742- return ret
743-
744-# END paramiko config fork
745-#############################################################################
746
747=== modified file 'lib/devscripts/ec2test/testrunner.py'
748--- lib/devscripts/ec2test/testrunner.py 2009-09-27 00:27:17 +0000
749+++ lib/devscripts/ec2test/testrunner.py 2009-09-27 20:39:13 +0000
750@@ -368,14 +368,13 @@
751
752
753 def configure_system(self):
754- user_connection = self._instance.connect_as_user()
755+ user_connection = self._instance.connect()
756 as_user = user_connection.perform
757- user_sftp = user_connection.ssh.open_sftp()
758 # Set up bazaar.conf with smtp information if necessary
759 if self.email or self.message:
760- as_user('mkdir /home/%(USER)s/.bazaar')
761- bazaar_conf_file = user_sftp.open(
762- "/home/%(USER)s/.bazaar/bazaar.conf" % self.vals, 'w')
763+ as_user('mkdir .bazaar')
764+ bazaar_conf_file = user_connection.sftp.open(
765+ ".bazaar/bazaar.conf", 'w')
766 bazaar_conf_file.write(
767 'smtp_server = %(smtp_server)s\n' % self.vals)
768 if self.vals['smtp_username']:
769@@ -387,25 +386,18 @@
770 bazaar_conf_file.close()
771 # Copy remote ec2-remote over
772 self.log('Copying ec2test-remote.py to remote machine.\n')
773- user_sftp.put(
774+ user_connection.sftp.put(
775 os.path.join(os.path.dirname(os.path.realpath(__file__)),
776 'ec2test-remote.py'),
777 '/var/launchpad/ec2test-remote.py')
778- user_sftp.close()
779 # Set up launchpad login and email
780 as_user('bzr launchpad-login %(launchpad-login)s')
781 user_connection.close()
782
783 def prepare_tests(self):
784- user_connection = self._instance.connect_as_user()
785+ user_connection = self._instance.connect()
786 # Clean up the test branch left in the instance image.
787 user_connection.perform('rm -rf /var/launchpad/test')
788- # get newest sources
789- user_connection.run_with_ssh_agent(
790- "rsync -avp --partial --delete "
791- "--filter='P *.o' --filter='P *.pyc' --filter='P *.so' "
792- "devpad.canonical.com:/code/rocketfuel-built/launchpad/sourcecode/* "
793- "/var/launchpad/sourcecode/")
794 # Get trunk.
795 user_connection.run_with_ssh_agent(
796 'bzr branch %(trunk_branch)s /var/launchpad/test')
797@@ -415,6 +407,10 @@
798 'cd /var/launchpad/test; bzr merge %(branch)s')
799 else:
800 self.log('(Testing trunk, so no branch merge.)')
801+ # get newest sources
802+ user_connection.run_with_ssh_agent(
803+ "/var/launchpad/test/utilities/update-sourcecode "
804+ "/var/launchpad/sourcecode")
805 # Get any new sourcecode branches as requested
806 for dest, src in self.branches:
807 fulldest = os.path.join('/var/launchpad/test/sourcecode', dest)
808@@ -455,20 +451,18 @@
809 p('mv /var/launchpad/download-cache /var/launchpad/tmp/download-cache')
810 if (self.include_download_cache_changes and
811 self.download_cache_additions):
812- sftp = user_connection.ssh.open_sftp()
813 root = os.path.realpath(
814 os.path.join(self.original_branch, 'download-cache'))
815 for info in self.download_cache_additions:
816 src = os.path.join(root, info[0])
817 self.log('Copying %s to remote machine.\n' % (src,))
818- sftp.put(
819+ user_connection.sftp.put(
820 src,
821 os.path.join('/var/launchpad/tmp/download-cache', info[0]))
822- sftp.close()
823 p('/var/launchpad/test/utilities/link-external-sourcecode '
824 '-p/var/launchpad/tmp -t/var/launchpad/test'),
825 # set up database
826- p('/var/launchpad/test/utilities/launchpad-database-setup %(USER)s')
827+ p('/var/launchpad/test/utilities/launchpad-database-setup $USER')
828 # close ssh connection
829 user_connection.close()
830
831@@ -476,7 +470,7 @@
832 """Turn ec2 instance into a demo server."""
833 self.configure_system()
834 self.prepare_tests()
835- user_connection = self._instance.connect_as_user()
836+ user_connection = self._instance.connect()
837 p = user_connection.perform
838 p('make -C /var/launchpad/test schema')
839 p('mkdir -p /var/tmp/bazaar.launchpad.dev/static')
840@@ -510,7 +504,7 @@
841 def run_tests(self):
842 self.configure_system()
843 self.prepare_tests()
844- user_connection = self._instance.connect_as_user()
845+ user_connection = self._instance.connect()
846
847 # Make sure we activate the failsafe --shutdown feature. This will
848 # make the server shut itself down after the test run completes, or
849@@ -580,11 +574,9 @@
850
851 # deliver results as requested
852 if self.file:
853- sftp = user_connection.ssh.open_sftp()
854 self.log(
855 'Writing abridged test results to %s.\n' % self.file)
856- sftp.get('/var/www/summary.log', self.file)
857- sftp.close()
858+ user_connection.sftp.get('/var/www/summary.log', self.file)
859 user_connection.close()
860
861 def get_remote_test_options(self):
862
863=== modified file 'utilities/update-sourcecode'
864--- utilities/update-sourcecode 2009-09-27 00:27:17 +0000
865+++ utilities/update-sourcecode 2009-09-27 20:39:13 +0000
866@@ -5,10 +5,12 @@
867
868 """Update the sourcecode managed in sourcecode/."""
869
870-import _pythonpath
871-
872+import os
873 import sys
874
875+sys.path.append(
876+ os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib'))
877+
878 from devscripts import sourcecode
879
880