Merge lp:~mwhudson/launchpad/ec2-entrypoints into lp:launchpad
- ec2-entrypoints
- Merge into devel
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp:~mwhudson/launchpad/ec2-entrypoints |
Merge into: | lp:launchpad |
Diff against target: | None lines |
To merge this branch: | bzr merge lp:~mwhudson/launchpad/ec2-entrypoints |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jonathan Lange (community) | Approve | ||
Review via email: mp+12024@code.launchpad.net |
Commit message
Description of the change
Michael Hudson-Doyle (mwhudson) wrote : | # |
Jonathan Lange (jml) wrote : | # |
Hey Michael,
We've already talked & had an email conversation about this, so I've been brief in my comments. Apologies if it looks like brusqueness.
A fair few questions, so marking as 'needs fixing', even though the general direction is great and the vast majority of the code is fine.
jml
> === added file 'lib/devscripts
> --- lib/devscripts/
> +++ lib/devscripts/
> @@ -0,0 +1,404 @@
Copyright notice, __all__, metaclass thingy.
> +import pdb
> +
> +from bzrlib.commands import Command
> +from bzrlib.errors import BzrCommandError
> +from bzrlib.help import help_commands
> +from bzrlib.option import ListOption, Option
> +
> +import socket
> +
> +from devscripts.
> +from devscripts.
> + AVAILABLE_
> +from devscripts.
> +
> +
A comment before each of these options explaining what they are for would
probably help future maintainers.
> +branch_option = ListOption(
> + 'branch', type=str, short_name='b', argname='BRANCH',
> + help=('Branches to include in this run in sourcecode. '
> + 'If the argument is only the project name, the trunk will be '
> + 'used (e.g., ``-b launchpadlib``). If you want to use a '
> + 'specific branch, if it is on launchpad, you can usually '
> + 'simply specify it instead (e.g., '
> + '``-b lp:~username/launchpadlib/branchname``). If this does '
> + 'not appear to work, or if the desired branch is not on '
> + 'launchpad, specify the project name and then the branch '
> + 'after an equals sign (e.g., '
> + '``-b launchpadlib=lp:~username/launchpadlib/branchname``). '
> + 'Branches for multiple projects may be specified with '
> + 'multiple instances of this option. '
> + 'You may also use this option to specify the branch of launchpad '
> + 'into which your branch may be merged. This defaults to %s. '
> + 'Because typically the important branches of launchpad are owned '
> + 'by the launchpad-pqm user, you can shorten this to only the '
> + 'branch name, if desired, and the launchpad-pqm user will be '
> + 'assumed. For instance, if you specify '
> + '``-b launchpad=
> + '``-b lp:~launchpad-pqm/launchpad/db-devel``, or the even longer'
> + '``-b launchpad=lp:~launchpad-pqm/launchpad/db-devel``.'
> + % (TRUNK_BRANCH,)))
> +
> +
> +machine_id_option = Option(
> + 'machine', short_name='m', type=str,
> + help=('The AWS machine identifier (AMI) on which to base this run. '
> + 'You should typically only have to supply this if you are '
> + 'testing new AWS images. Defaults to trying to find the most '
> + 'recent one with an approved owner.'))
> +
> +
> +def _convert_
> + """Ensure that `arg` is acceptable as an instance type."""
> + if arg not in ...
Michael Hudson-Doyle (mwhudson) wrote : | # |
Jonathan Lange wrote:
> Review: Needs Fixing
> Hey Michael,
>
> We've already talked & had an email conversation about this, so I've been brief in my comments. Apologies if it looks like brusqueness.
>
> A fair few questions, so marking as 'needs fixing', even though the general direction is great and the vast majority of the code is fine.
>
> jml
>
>> === added file 'lib/devscripts
>> --- lib/devscripts/
>> +++ lib/devscripts/
>> @@ -0,0 +1,404 @@
>
> Copyright notice, __all__, metaclass thingy.
Doh!
>> +import pdb
>> +
>> +from bzrlib.commands import Command
>> +from bzrlib.errors import BzrCommandError
>> +from bzrlib.help import help_commands
>> +from bzrlib.option import ListOption, Option
>> +
>> +import socket
>> +
>> +from devscripts.
>> +from devscripts.
>> + AVAILABLE_
>> +from devscripts.
>> +
>> +
>
> A comment before each of these options explaining what they are for would
> probably help future maintainers.
I added one comment, and beefed up the help strings a bit. I think if
any are still confusing, the help should probably be improved...
>> +branch_option = ListOption(
>> + 'branch', type=str, short_name='b', argname='BRANCH',
>> + help=('Branches to include in this run in sourcecode. '
>> + 'If the argument is only the project name, the trunk will be '
>> + 'used (e.g., ``-b launchpadlib``). If you want to use a '
>> + 'specific branch, if it is on launchpad, you can usually '
>> + 'simply specify it instead (e.g., '
>> + '``-b lp:~username/launchpadlib/branchname``). If this does '
>> + 'not appear to work, or if the desired branch is not on '
>> + 'launchpad, specify the project name and then the branch '
>> + 'after an equals sign (e.g., '
>> + '``-b launchpadlib=lp:~username/launchpadlib/branchname``). '
>> + 'Branches for multiple projects may be specified with '
>> + 'multiple instances of this option. '
>> + 'You may also use this option to specify the branch of launchpad '
>> + 'into which your branch may be merged. This defaults to %s. '
>> + 'Because typically the important branches of launchpad are owned '
>> + 'by the launchpad-pqm user, you can shorten this to only the '
>> + 'branch name, if desired, and the launchpad-pqm user will be '
>> + 'assumed. For instance, if you specify '
>> + '``-b launchpad=
>> + '``-b lp:~launchpad-pqm/launchpad/db-devel``, or the even longer'
>> + '``-b launchpad=lp:~launchpad-pqm/launchpad/db-devel``.'
>> + % (TRUNK_BRANCH,)))
>> +
>> +
>> +machine_id_option = Option(
>> + 'machine', short_name='m', type=str,
>> + help=('The AWS machine identifier (AMI) on which to base this run. '
>> + 'You should typically only have to supply this if you ar...
1 | === modified file 'lib/devscripts/ec2test/builtins.py' |
2 | --- lib/devscripts/ec2test/builtins.py 2009-09-18 03:16:29 +0000 |
3 | +++ lib/devscripts/ec2test/builtins.py 2009-09-18 05:33:59 +0000 |
4 | @@ -1,3 +1,11 @@ |
5 | +# Copyright 2009 Canonical Ltd. This software is licensed under the |
6 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
7 | + |
8 | +"""The command classes for the 'ec2' utility.""" |
9 | + |
10 | +__metaclass__ = type |
11 | +__all__ = [] |
12 | + |
13 | import pdb |
14 | |
15 | from bzrlib.commands import Command |
16 | @@ -13,6 +21,11 @@ |
17 | from devscripts.ec2test.testrunner import EC2TestRunner, TRUNK_BRANCH |
18 | |
19 | |
20 | +# Options accepted by more than one command. |
21 | + |
22 | +# Branches is a complicated option that lets the user specify which branches |
23 | +# to use in the sourcecode directory. Most of the complexity is still in |
24 | +# EC2TestRunner.__init__, which probably isn't ideal. |
25 | branch_option = ListOption( |
26 | 'branch', type=str, short_name='b', argname='BRANCH', |
27 | help=('Branches to include in this run in sourcecode. ' |
28 | @@ -69,7 +82,8 @@ |
29 | |
30 | trunk_option = Option( |
31 | 'trunk', short_name='t', |
32 | - help=('Run the trunk as the branch')) |
33 | + help=('Run the trunk as the branch, rather than the branch of the ' |
34 | + 'current working directory.')) |
35 | |
36 | |
37 | include_download_cache_changes_option = Option( |
38 | @@ -81,6 +95,12 @@ |
39 | 'changes in your download cache, you must explicitly choose to ' |
40 | 'include or ignore the changes.')) |
41 | |
42 | +postmortem_option = Option( |
43 | + 'postmortem', short_name='p', |
44 | + help=('Drop to interactive prompt after the test and before shutting ' |
45 | + 'down the instance for postmortem analysis of the EC2 instance ' |
46 | + 'and/or of this script.')) |
47 | + |
48 | |
49 | class EC2Command(Command): |
50 | """Subclass of `Command` that customizes usage to say 'ec2' not 'bzr'. |
51 | @@ -108,6 +128,26 @@ |
52 | s = s[:-1] # remove last space |
53 | return s |
54 | |
55 | +def _get_branches_and_test_branch(trunk, branch, test_branch): |
56 | + """Interpret the command line options to find which branch to test. |
57 | + |
58 | + :param trunk: The value of the --trunk option. |
59 | + :param branch: The value of the --branch options. |
60 | + :param test_branch: The value of the TEST_BRANCH argument. |
61 | + """ |
62 | + if trunk: |
63 | + if test_branch is not None: |
64 | + raise BzrCommandError( |
65 | + "Cannot specify both a branch to test and --trunk") |
66 | + else: |
67 | + test_branch = TRUNK_BRANCH |
68 | + else: |
69 | + if test_branch is None: |
70 | + test_branch = '.' |
71 | + branches = [data.split('=', 1) for data in branch] |
72 | + return branches, test_branch |
73 | + |
74 | + |
75 | |
76 | class cmd_test(EC2Command): |
77 | """Run the test suite in ec2.""" |
78 | @@ -163,11 +203,7 @@ |
79 | 'If the branch is local, then the bzr configuration is ' |
80 | 'consulted; for remote branches "Launchpad PQM ' |
81 | '<launchpad@pqm.canonical.com>" is used by default.')), |
82 | - Option( |
83 | - 'postmortem', short_name='p', |
84 | - help=('Drop to interactive prompt after the test and before shutting ' |
85 | - 'down the instance for postmortem analysis of the EC2 instance ' |
86 | - 'and/or of this script.')), |
87 | + postmortem_option, |
88 | Option( |
89 | 'headless', |
90 | help=('After building the instance and test, run the remote tests ' |
91 | @@ -191,15 +227,8 @@ |
92 | include_download_cache_changes=False): |
93 | if debug: |
94 | pdb.set_trace() |
95 | - if trunk: |
96 | - if test_branch is not None: |
97 | - raise BzrCommandError( |
98 | - "Cannot specify both a branch to test and --trunk") |
99 | - else: |
100 | - test_branch = TRUNK_BRANCH |
101 | - else: |
102 | - if test_branch is None: |
103 | - test_branch = '.' |
104 | + branches, test_branch = _get_branches_and_test_branch( |
105 | + trunk, branch, test_branch) |
106 | if ((postmortem or file) and headless): |
107 | raise BzrCommandError( |
108 | 'Headless mode currently does not support postmortem or file ' |
109 | @@ -211,7 +240,6 @@ |
110 | else: |
111 | if email == []: |
112 | email = True |
113 | - branches = [data.split('=', 1) for data in branch] |
114 | |
115 | if headless and not (email or submit_pqm_message): |
116 | raise BzrCommandError( |
117 | @@ -219,7 +247,7 @@ |
118 | 'of your headless test run.') |
119 | |
120 | |
121 | - instance = EC2Instance( |
122 | + instance = EC2Instance.make( |
123 | EC2TestRunner.name, instance_type, machine) |
124 | |
125 | runner = EC2TestRunner( |
126 | @@ -246,11 +274,7 @@ |
127 | trunk_option, |
128 | machine_id_option, |
129 | instance_type_option, |
130 | - Option( |
131 | - 'postmortem', short_name='p', |
132 | - help=('Drop to interactive prompt after the test and before shutting ' |
133 | - 'down the instance for postmortem analysis of the EC2 instance ' |
134 | - 'and/or of this script.')), |
135 | + postmortem_option, |
136 | debug_option, |
137 | include_download_cache_changes_option, |
138 | ListOption( |
139 | @@ -265,16 +289,8 @@ |
140 | include_download_cache_changes=False, demo=None): |
141 | if debug: |
142 | pdb.set_trace() |
143 | - if trunk: |
144 | - if test_branch is not None: |
145 | - raise BzrCommandError( |
146 | - "Cannot specify both a branch to test and --trunk") |
147 | - else: |
148 | - test_branch = TRUNK_BRANCH |
149 | - else: |
150 | - if test_branch is None: |
151 | - test_branch = '.' |
152 | - branches = [data.split('=', 1) for data in branch] |
153 | + branches, test_branch = _get_branches_and_test_branch( |
154 | + trunk, branch, test_branch) |
155 | |
156 | instance = EC2Instance.make( |
157 | EC2TestRunner.name, instance_type, machine, demo) |
158 | @@ -287,14 +303,17 @@ |
159 | demo_network_string = '\n'.join( |
160 | ' ' + network for network in demo) |
161 | |
162 | + # Wait until the user exits the postmortem session, then kill the |
163 | + # instance. |
164 | + postmortem = True |
165 | + shutdown = True |
166 | instance.set_up_and_run( |
167 | - True, False, self.run_server, runner, |
168 | - instance.hostname, demo_network_string) |
169 | - |
170 | - |
171 | - def run_server(self, runner, hostname, demo_network_string): |
172 | + postmortem, shutdown, self.run_server, runner, |
173 | + demo_network_string) |
174 | + |
175 | + def run_server(self, runner, instance, demo_network_string): |
176 | runner.run_demo_server() |
177 | - ec2_ip = socket.gethostbyname(hostname) |
178 | + ec2_ip = socket.gethostbyname(instance.hostname) |
179 | print ( |
180 | "\n\n" |
181 | "********************** DEMO *************************\n" |
182 | @@ -303,7 +322,7 @@ |
183 | "network access to the ec2 instance from their IPs by\n" |
184 | "entering command like this in the interactive python\n" |
185 | "interpreter at the end of the setup. " |
186 | - "\n instance.security_group.authorize(" |
187 | + "\n self.security_group.authorize(" |
188 | "'tcp', 443, 443, '10.0.0.5/32')\n\n" |
189 | "These demo networks have already been granted access on " |
190 | "port 80 and 443:\n" + demo_network_string + |
191 | @@ -323,11 +342,7 @@ |
192 | takes_options = [ |
193 | machine_id_option, |
194 | instance_type_option, |
195 | - Option( |
196 | - 'postmortem', short_name='p', |
197 | - help=('Drop to interactive prompt after the test and before shutting ' |
198 | - 'down the instance for postmortem analysis of the EC2 instance ' |
199 | - 'and/or of this script.')), |
200 | + postmortem_option, |
201 | debug_option, |
202 | ListOption( |
203 | 'extra-update-image-command', type=str, |
204 | @@ -356,6 +371,23 @@ |
205 | |
206 | def update_image(self, instance, extra_update_image_command, ami_name, |
207 | credentials): |
208 | + """Bring the image up to date. |
209 | + |
210 | + The steps we take are: |
211 | + |
212 | + * run any commands specified with --extra-update-image-command |
213 | + * update sourcecode via rsync. |
214 | + * update the launchpad branch to the tip of the trunk branch. |
215 | + * update the copy of the download-cache. |
216 | + * remove the user account. |
217 | + * bundle the image |
218 | + |
219 | + :param instance: `EC2Instance` to operate on. |
220 | + :param extra_update_image_command: List of commands to run on the |
221 | + instance in addition to the usual ones. |
222 | + :param ami_name: The name to give the created AMI. |
223 | + :param credentials: An `EC2Credentials` object. |
224 | + """ |
225 | user_connection = instance.connect_as_user() |
226 | user_connection.perform('bzr launchpad-login %(launchpad-login)s') |
227 | for cmd in extra_update_image_command: |
228 | @@ -391,14 +423,13 @@ |
229 | to show some basic usage information. |
230 | """ |
231 | if topic is None: |
232 | - print >>self.outf, 'Usage: ec2 <command> <options>' |
233 | - print >>self.outf |
234 | - print >>self.outf, 'Available commands:' |
235 | + self.outf.write('Usage: ec2 <command> <options>\n\n') |
236 | + self.outf.write('Available commands:\n') |
237 | help_commands(self.outf) |
238 | else: |
239 | command = self.controller._get_command(None, topic) |
240 | if command is None: |
241 | - print >>self.outf, "%s is an unknown command." % (topic,) |
242 | + self.outf.write("%s is an unknown command.\n" % (topic,)) |
243 | text = command.get_help_text() |
244 | if text: |
245 | - print >>self.outf, text |
246 | + self.outf.write(text) |
247 | |
248 | === modified file 'lib/devscripts/ec2test/entrypoint.py' |
249 | --- lib/devscripts/ec2test/entrypoint.py 2009-09-18 03:10:25 +0000 |
250 | +++ lib/devscripts/ec2test/entrypoint.py 2009-09-18 05:35:18 +0000 |
251 | @@ -1,3 +1,13 @@ |
252 | +# Copyright 2009 Canonical Ltd. This software is licensed under the |
253 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
254 | + |
255 | +"""The entry point for the 'ec2' utility.""" |
256 | + |
257 | +__metaclass__ = type |
258 | +__all__ = [ |
259 | + 'main', |
260 | + ] |
261 | + |
262 | import readline |
263 | import rlcompleter |
264 | import sys |
265 | @@ -14,11 +24,14 @@ |
266 | readline.parse_and_bind('tab: complete') |
267 | |
268 | class EC2CommandController(CommandRegistry, CommandExecutionMixin): |
269 | - def __init__(self): |
270 | - CommandRegistry.__init__(self) |
271 | + """The 'ec2' utility registers and executes commands.""" |
272 | |
273 | |
274 | def main(): |
275 | + """The entry point for the 'ec2' script. |
276 | + |
277 | + We run the specified command, or give help if none was specified. |
278 | + """ |
279 | controller = EC2CommandController() |
280 | controller.install_bzrlib_hooks() |
281 | controller.load_module(builtins) |
282 | |
283 | === modified file 'lib/devscripts/ec2test/instance.py' |
284 | --- lib/devscripts/ec2test/instance.py 2009-09-18 02:23:13 +0000 |
285 | +++ lib/devscripts/ec2test/instance.py 2009-09-18 05:25:50 +0000 |
286 | @@ -26,6 +26,7 @@ |
287 | from devscripts.ec2test.sshconfig import SSHConfig |
288 | from devscripts.ec2test.credentials import EC2Credentials |
289 | |
290 | + |
291 | DEFAULT_INSTANCE_TYPE = 'c1.xlarge' |
292 | AVAILABLE_INSTANCE_TYPES = ('m1.large', 'm1.xlarge', 'c1.xlarge') |
293 | |
294 | @@ -40,7 +41,7 @@ |
295 | |
296 | |
297 | def get_user_key(): |
298 | - """Get a SSH key from the agent. Exit if not found. |
299 | + """Get a SSH key from the agent. Raise an error if not found. |
300 | |
301 | This key will be used to let the user log in (as $USER) to the instance. |
302 | """ |
303 | @@ -68,8 +69,8 @@ |
304 | :param instance_type: One of the AVAILABLE_INSTANCE_TYPES. |
305 | :param machine_id: The AMI to use, or None to do the usual regexp |
306 | matching. |
307 | - :param demo_networks: The networks to add to the security group to |
308 | - allow access to the instance. |
309 | + :param demo_networks: A list of networks to add to the security group |
310 | + to allow access to the instance. |
311 | :param credentials: An `EC2Credentials` object. |
312 | """ |
313 | if instance_type not in AVAILABLE_INSTANCE_TYPES: |
314 | @@ -267,12 +268,12 @@ |
315 | root_connection.close() |
316 | |
317 | def set_up_and_run(self, postmortem, shutdown, func, *args, **kw): |
318 | - """Start and set up, run `func` and then optionally shut down. |
319 | + """Start, set up a user account, run `func` and then maybe shut down. |
320 | |
321 | :param postmortem: If true, any exceptions will be caught and an |
322 | interactive session run to allow debugging the problem. |
323 | - :param shutdown: If true, the instance will be shut down before this |
324 | - function returns. |
325 | + :param shutdown: If true, shut down the instance after `func` and |
326 | + postmortem (if any) are completed. |
327 | :param func: A callable that will be called when the instance is |
328 | running and a user account has been set up on it. |
329 | :param args: Passed to `func`. |
Jonathan Lange (jml) wrote : | # |
You have three blank lines after _get_branches_
Preview Diff
1 | === modified file 'lib/devscripts/ec2test/__init__.py' |
2 | --- lib/devscripts/ec2test/__init__.py 2009-09-14 05:43:04 +0000 |
3 | +++ lib/devscripts/ec2test/__init__.py 2009-09-18 02:23:13 +0000 |
4 | @@ -5,13 +5,11 @@ |
5 | |
6 | __metaclass__ = type |
7 | |
8 | -__all__ = [ |
9 | - 'error_and_quit', |
10 | - 'main', |
11 | - ] |
12 | +__all__ = [] |
13 | |
14 | +from bzrlib.plugin import load_plugins |
15 | +load_plugins() |
16 | import paramiko |
17 | -import sys |
18 | |
19 | ############################################################################# |
20 | # Try to guide users past support problems we've encountered before |
21 | @@ -21,11 +19,3 @@ |
22 | # maybe add similar check for bzrlib? |
23 | # End |
24 | ############################################################################# |
25 | - |
26 | -def error_and_quit(msg): |
27 | - """Print error message and exit.""" |
28 | - sys.stderr.write(msg) |
29 | - sys.exit(1) |
30 | - |
31 | -from devscripts.ec2test.commandline import main |
32 | -main # shut up pyflakes |
33 | |
34 | === added file 'lib/devscripts/ec2test/builtins.py' |
35 | --- lib/devscripts/ec2test/builtins.py 1970-01-01 00:00:00 +0000 |
36 | +++ lib/devscripts/ec2test/builtins.py 2009-09-18 03:16:29 +0000 |
37 | @@ -0,0 +1,404 @@ |
38 | +import pdb |
39 | + |
40 | +from bzrlib.commands import Command |
41 | +from bzrlib.errors import BzrCommandError |
42 | +from bzrlib.help import help_commands |
43 | +from bzrlib.option import ListOption, Option |
44 | + |
45 | +import socket |
46 | + |
47 | +from devscripts.ec2test.credentials import EC2Credentials |
48 | +from devscripts.ec2test.instance import ( |
49 | + AVAILABLE_INSTANCE_TYPES, DEFAULT_INSTANCE_TYPE, EC2Instance) |
50 | +from devscripts.ec2test.testrunner import EC2TestRunner, TRUNK_BRANCH |
51 | + |
52 | + |
53 | +branch_option = ListOption( |
54 | + 'branch', type=str, short_name='b', argname='BRANCH', |
55 | + help=('Branches to include in this run in sourcecode. ' |
56 | + 'If the argument is only the project name, the trunk will be ' |
57 | + 'used (e.g., ``-b launchpadlib``). If you want to use a ' |
58 | + 'specific branch, if it is on launchpad, you can usually ' |
59 | + 'simply specify it instead (e.g., ' |
60 | + '``-b lp:~username/launchpadlib/branchname``). If this does ' |
61 | + 'not appear to work, or if the desired branch is not on ' |
62 | + 'launchpad, specify the project name and then the branch ' |
63 | + 'after an equals sign (e.g., ' |
64 | + '``-b launchpadlib=lp:~username/launchpadlib/branchname``). ' |
65 | + 'Branches for multiple projects may be specified with ' |
66 | + 'multiple instances of this option. ' |
67 | + 'You may also use this option to specify the branch of launchpad ' |
68 | + 'into which your branch may be merged. This defaults to %s. ' |
69 | + 'Because typically the important branches of launchpad are owned ' |
70 | + 'by the launchpad-pqm user, you can shorten this to only the ' |
71 | + 'branch name, if desired, and the launchpad-pqm user will be ' |
72 | + 'assumed. For instance, if you specify ' |
73 | + '``-b launchpad=db-devel`` then this is equivalent to ' |
74 | + '``-b lp:~launchpad-pqm/launchpad/db-devel``, or the even longer' |
75 | + '``-b launchpad=lp:~launchpad-pqm/launchpad/db-devel``.' |
76 | + % (TRUNK_BRANCH,))) |
77 | + |
78 | + |
79 | +machine_id_option = Option( |
80 | + 'machine', short_name='m', type=str, |
81 | + help=('The AWS machine identifier (AMI) on which to base this run. ' |
82 | + 'You should typically only have to supply this if you are ' |
83 | + 'testing new AWS images. Defaults to trying to find the most ' |
84 | + 'recent one with an approved owner.')) |
85 | + |
86 | + |
87 | +def _convert_instance_type(arg): |
88 | + """Ensure that `arg` is acceptable as an instance type.""" |
89 | + if arg not in AVAILABLE_INSTANCE_TYPES: |
90 | + raise BzrCommandError('Unknown instance type %r' % arg) |
91 | + return arg |
92 | + |
93 | + |
94 | +instance_type_option = Option( |
95 | + 'instance', short_name='i', type=_convert_instance_type, |
96 | + param_name='instance_type', |
97 | + help=('The AWS instance type on which to base this run. ' |
98 | + 'Available options are %r. Defaults to `%s`.' % |
99 | + (AVAILABLE_INSTANCE_TYPES, DEFAULT_INSTANCE_TYPE))) |
100 | + |
101 | + |
102 | +debug_option = Option( |
103 | + 'debug', short_name='d', |
104 | + help=('Drop to pdb trace as soon as possible.')) |
105 | + |
106 | + |
107 | +trunk_option = Option( |
108 | + 'trunk', short_name='t', |
109 | + help=('Run the trunk as the branch')) |
110 | + |
111 | + |
112 | +include_download_cache_changes_option = Option( |
113 | + 'include-download-cache-changes', short_name='c', |
114 | + help=('Include any changes in the download cache (added or unknown) ' |
115 | + 'in the download cache of the test run. Note that, if you have ' |
116 | + 'any changes in your download cache, trying to submit to pqm ' |
117 | + 'will always raise an error. Also note that, if you have any ' |
118 | + 'changes in your download cache, you must explicitly choose to ' |
119 | + 'include or ignore the changes.')) |
120 | + |
121 | + |
122 | +class EC2Command(Command): |
123 | + """Subclass of `Command` that customizes usage to say 'ec2' not 'bzr'. |
124 | + |
125 | + When https://bugs.edge.launchpad.net/bzr/+bug/431054 is fixed, we can |
126 | + delete this class, or at least make it less of a copy/paste/hack of the |
127 | + superclass. |
128 | + """ |
129 | + |
130 | + def _usage(self): |
131 | + """Return single-line grammar for this command. |
132 | + |
133 | + Only describes arguments, not options. |
134 | + """ |
135 | + s = 'ec2 ' + self.name() + ' ' |
136 | + for aname in self.takes_args: |
137 | + aname = aname.upper() |
138 | + if aname[-1] in ['$', '+']: |
139 | + aname = aname[:-1] + '...' |
140 | + elif aname[-1] == '?': |
141 | + aname = '[' + aname[:-1] + ']' |
142 | + elif aname[-1] == '*': |
143 | + aname = '[' + aname[:-1] + '...]' |
144 | + s += aname + ' ' |
145 | + s = s[:-1] # remove last space |
146 | + return s |
147 | + |
148 | + |
149 | +class cmd_test(EC2Command): |
150 | + """Run the test suite in ec2.""" |
151 | + |
152 | + takes_options = [ |
153 | + branch_option, |
154 | + trunk_option, |
155 | + machine_id_option, |
156 | + instance_type_option, |
157 | + Option( |
158 | + 'file', short_name='f', |
159 | + help=('Store abridged test results in FILE.')), |
160 | + ListOption( |
161 | + 'email', short_name='e', argname='EMAIL', type=str, |
162 | + help=('Email address to which results should be mailed. Defaults to ' |
163 | + 'the email address from `bzr whoami`. May be supplied multiple ' |
164 | + 'times. The first supplied email address will be used as the ' |
165 | + 'From: address.')), |
166 | + Option( |
167 | + 'noemail', short_name='n', |
168 | + help=('Do not try to email results.')), |
169 | + Option( |
170 | + 'test-options', short_name='o', type=str, |
171 | + help=('Test options to pass to the remote test runner. Defaults to ' |
172 | + "``-o '-vv'``. For instance, to run specific tests, you might " |
173 | + "use ``-o '-vvt my_test_pattern'``.")), |
174 | + Option( |
175 | + 'submit-pqm-message', short_name='s', type=str, argname="MSG", |
176 | + help=('A pqm message to submit if the test run is successful. If ' |
177 | + 'provided, you will be asked for your GPG passphrase before ' |
178 | + 'the test run begins.')), |
179 | + Option( |
180 | + 'pqm-public-location', type=str, |
181 | + help=('The public location for the pqm submit, if a pqm message is ' |
182 | + 'provided (see --submit-pqm-message). If this is not provided, ' |
183 | + 'for local branches, bzr configuration is consulted; for ' |
184 | + 'remote branches, it is assumed that the remote branch *is* ' |
185 | + 'a public branch.')), |
186 | + Option( |
187 | + 'pqm-submit-location', type=str, |
188 | + help=('The submit location for the pqm submit, if a pqm message is ' |
189 | + 'provided (see --submit-pqm-message). If this option is not ' |
190 | + 'provided, the script will look for an explicitly specified ' |
191 | + 'launchpad branch using the -b/--branch option; if that branch ' |
192 | + 'was specified and is owned by the launchpad-pqm user on ' |
193 | + 'launchpad, it is used as the pqm submit location. Otherwise, ' |
194 | + 'for local branches, bzr configuration is consulted; for ' |
195 | + 'remote branches, it is assumed that the submit branch is %s.' |
196 | + % (TRUNK_BRANCH,))), |
197 | + Option( |
198 | + 'pqm-email', type=str, |
199 | + help=('Specify the email address of the PQM you are submitting to. ' |
200 | + 'If the branch is local, then the bzr configuration is ' |
201 | + 'consulted; for remote branches "Launchpad PQM ' |
202 | + '<launchpad@pqm.canonical.com>" is used by default.')), |
203 | + Option( |
204 | + 'postmortem', short_name='p', |
205 | + help=('Drop to interactive prompt after the test and before shutting ' |
206 | + 'down the instance for postmortem analysis of the EC2 instance ' |
207 | + 'and/or of this script.')), |
208 | + Option( |
209 | + 'headless', |
210 | + help=('After building the instance and test, run the remote tests ' |
211 | + 'headless. Cannot be used with postmortem ' |
212 | + 'or file.')), |
213 | + debug_option, |
214 | + Option( |
215 | + 'open-browser', |
216 | + help=('Open the results page in your default browser')), |
217 | + include_download_cache_changes_option, |
218 | + ] |
219 | + |
220 | + takes_args = ['test_branch?'] |
221 | + |
222 | + def run(self, test_branch=None, branch=[], trunk=False, machine=None, |
223 | + instance_type=DEFAULT_INSTANCE_TYPE, |
224 | + file=None, email=None, test_options='-vv', noemail=False, |
225 | + submit_pqm_message=None, pqm_public_location=None, |
226 | + pqm_submit_location=None, pqm_email=None, postmortem=False, |
227 | + headless=False, debug=False, open_browser=False, |
228 | + include_download_cache_changes=False): |
229 | + if debug: |
230 | + pdb.set_trace() |
231 | + if trunk: |
232 | + if test_branch is not None: |
233 | + raise BzrCommandError( |
234 | + "Cannot specify both a branch to test and --trunk") |
235 | + else: |
236 | + test_branch = TRUNK_BRANCH |
237 | + else: |
238 | + if test_branch is None: |
239 | + test_branch = '.' |
240 | + if ((postmortem or file) and headless): |
241 | + raise BzrCommandError( |
242 | + 'Headless mode currently does not support postmortem or file ' |
243 | + ' options.') |
244 | + if noemail: |
245 | + if email: |
246 | + raise BzrCommandError( |
247 | + 'May not supply both --no-email and an --email address') |
248 | + else: |
249 | + if email == []: |
250 | + email = True |
251 | + branches = [data.split('=', 1) for data in branch] |
252 | + |
253 | + if headless and not (email or submit_pqm_message): |
254 | + raise BzrCommandError( |
255 | + 'You have specified no way to get the results ' |
256 | + 'of your headless test run.') |
257 | + |
258 | + |
259 | + instance = EC2Instance( |
260 | + EC2TestRunner.name, instance_type, machine) |
261 | + |
262 | + runner = EC2TestRunner( |
263 | + test_branch, email=email, file=file, |
264 | + test_options=test_options, headless=headless, |
265 | + branches=branches, pqm_message=submit_pqm_message, |
266 | + pqm_public_location=pqm_public_location, |
267 | + pqm_submit_location=pqm_submit_location, |
268 | + open_browser=open_browser, pqm_email=pqm_email, |
269 | + include_download_cache_changes=include_download_cache_changes, |
270 | + instance=instance, vals=instance._vals) |
271 | + |
272 | + instance.set_up_and_run(postmortem, not headless, runner.run_tests) |
273 | + |
274 | + |
275 | +class cmd_demo(EC2Command): |
276 | + """Start a demo instance of Launchpad. |
277 | + |
278 | + See https://wiki.canonical.com/Launchpad/EC2Test/ForDemos |
279 | + """ |
280 | + |
281 | + takes_options = [ |
282 | + branch_option, |
283 | + trunk_option, |
284 | + machine_id_option, |
285 | + instance_type_option, |
286 | + Option( |
287 | + 'postmortem', short_name='p', |
288 | + help=('Drop to interactive prompt after the test and before shutting ' |
289 | + 'down the instance for postmortem analysis of the EC2 instance ' |
290 | + 'and/or of this script.')), |
291 | + debug_option, |
292 | + include_download_cache_changes_option, |
293 | + ListOption( |
294 | + 'demo', type=str, |
295 | + help="Allow this netmask to connect to the instance."), |
296 | + ] |
297 | + |
298 | + takes_args = ['test_branch?'] |
299 | + |
300 | + def run(self, test_branch=None, branch=[], trunk=False, machine=None, |
301 | + instance_type=DEFAULT_INSTANCE_TYPE, debug=False, |
302 | + include_download_cache_changes=False, demo=None): |
303 | + if debug: |
304 | + pdb.set_trace() |
305 | + if trunk: |
306 | + if test_branch is not None: |
307 | + raise BzrCommandError( |
308 | + "Cannot specify both a branch to test and --trunk") |
309 | + else: |
310 | + test_branch = TRUNK_BRANCH |
311 | + else: |
312 | + if test_branch is None: |
313 | + test_branch = '.' |
314 | + branches = [data.split('=', 1) for data in branch] |
315 | + |
316 | + instance = EC2Instance.make( |
317 | + EC2TestRunner.name, instance_type, machine, demo) |
318 | + |
319 | + runner = EC2TestRunner( |
320 | + test_branch, branches=branches, |
321 | + include_download_cache_changes=include_download_cache_changes, |
322 | + instance=instance, vals=instance._vals) |
323 | + |
324 | + demo_network_string = '\n'.join( |
325 | + ' ' + network for network in demo) |
326 | + |
327 | + instance.set_up_and_run( |
328 | + True, False, self.run_server, runner, |
329 | + instance.hostname, demo_network_string) |
330 | + |
331 | + |
332 | + def run_server(self, runner, hostname, demo_network_string): |
333 | + runner.run_demo_server() |
334 | + ec2_ip = socket.gethostbyname(hostname) |
335 | + print ( |
336 | + "\n\n" |
337 | + "********************** DEMO *************************\n" |
338 | + "It may take 20 seconds for the demo server to start up." |
339 | + "\nTo demo to other users, you still need to open up\n" |
340 | + "network access to the ec2 instance from their IPs by\n" |
341 | + "entering command like this in the interactive python\n" |
342 | + "interpreter at the end of the setup. " |
343 | + "\n instance.security_group.authorize(" |
344 | + "'tcp', 443, 443, '10.0.0.5/32')\n\n" |
345 | + "These demo networks have already been granted access on " |
346 | + "port 80 and 443:\n" + demo_network_string + |
347 | + "\n\nYou also need to edit your /etc/hosts to point\n" |
348 | + "launchpad.dev at the ec2 instance's IP like this:\n" |
349 | + " " + ec2_ip + " launchpad.dev\n\n" |
350 | + "See " |
351 | + "<https://wiki.canonical.com/Launchpad/EC2Test/ForDemos>." |
352 | + "\n*****************************************************" |
353 | + "\n\n") |
354 | + |
355 | + |
356 | + |
357 | +class cmd_update_image(EC2Command): |
358 | + """Make a new AMI.""" |
359 | + |
360 | + takes_options = [ |
361 | + machine_id_option, |
362 | + instance_type_option, |
363 | + Option( |
364 | + 'postmortem', short_name='p', |
365 | + help=('Drop to interactive prompt after the test and before shutting ' |
366 | + 'down the instance for postmortem analysis of the EC2 instance ' |
367 | + 'and/or of this script.')), |
368 | + debug_option, |
369 | + ListOption( |
370 | + 'extra-update-image-command', type=str, |
371 | + help=('Run this command (with an ssh agent) on the image before ' |
372 | + 'running the default update steps. Can be passed more than ' |
373 | + 'once, the commands will be run in the order specified.')), |
374 | + ] |
375 | + |
376 | + takes_args = ['ami_name'] |
377 | + |
378 | + def run(self, ami_name, machine=None, instance_type='m1.large', |
379 | + debug=False, postmortem=False, extra_update_image_command=[]): |
380 | + if debug: |
381 | + pdb.set_trace() |
382 | + |
383 | + credentials = EC2Credentials.load_from_file() |
384 | + |
385 | + instance = EC2Instance.make( |
386 | + EC2TestRunner.name, instance_type, machine, |
387 | + credentials=credentials) |
388 | + instance.check_bundling_prerequisites() |
389 | + |
390 | + instance.set_up_and_run( |
391 | + postmortem, True, self.update_image, instance, |
392 | + extra_update_image_command, ami_name, credentials) |
393 | + |
394 | + def update_image(self, instance, extra_update_image_command, ami_name, |
395 | + credentials): |
396 | + user_connection = instance.connect_as_user() |
397 | + user_connection.perform('bzr launchpad-login %(launchpad-login)s') |
398 | + for cmd in extra_update_image_command: |
399 | + user_connection.run_with_ssh_agent(cmd) |
400 | + user_connection.run_with_ssh_agent( |
401 | + "rsync -avp --partial --delete " |
402 | + "--filter='P *.o' --filter='P *.pyc' --filter='P *.so' " |
403 | + "devpad.canonical.com:/code/rocketfuel-built/launchpad/sourcecode/* " |
404 | + "/var/launchpad/sourcecode/") |
405 | + user_connection.run_with_ssh_agent( |
406 | + 'bzr pull -d /var/launchpad/test ' + TRUNK_BRANCH) |
407 | + user_connection.run_with_ssh_agent( |
408 | + 'bzr pull -d /var/launchpad/download-cache lp:lp-source-dependencies') |
409 | + user_connection.close() |
410 | + root_connection = instance.connect_as_root() |
411 | + root_connection.perform( |
412 | + 'deluser --remove-home %(USER)s', ignore_failure=True) |
413 | + root_connection.close() |
414 | + instance.bundle(ami_name, credentials) |
415 | + |
416 | + |
417 | +class cmd_help(EC2Command): |
418 | + """Show general help or help for a command.""" |
419 | + |
420 | + aliases = ["?", "--help", "-?", "-h"] |
421 | + takes_args = ["topic?"] |
422 | + |
423 | + def run(self, topic=None): |
424 | + """ |
425 | + Show help for the C{bzrlib.commands.Command} matching C{topic}. |
426 | + |
427 | + @param topic: Optionally, the name of the topic to show. Default is |
428 | + to show some basic usage information. |
429 | + """ |
430 | + if topic is None: |
431 | + print >>self.outf, 'Usage: ec2 <command> <options>' |
432 | + print >>self.outf |
433 | + print >>self.outf, 'Available commands:' |
434 | + help_commands(self.outf) |
435 | + else: |
436 | + command = self.controller._get_command(None, topic) |
437 | + if command is None: |
438 | + print >>self.outf, "%s is an unknown command." % (topic,) |
439 | + text = command.get_help_text() |
440 | + if text: |
441 | + print >>self.outf, text |
442 | |
443 | === removed file 'lib/devscripts/ec2test/commandline.py' |
444 | --- lib/devscripts/ec2test/commandline.py 2009-09-15 01:37:01 +0000 |
445 | +++ lib/devscripts/ec2test/commandline.py 1970-01-01 00:00:00 +0000 |
446 | @@ -1,369 +0,0 @@ |
447 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
448 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
449 | - |
450 | -"""The command line parsing and entrypoint for ec2test.""" |
451 | - |
452 | -__metaclass__ = type |
453 | -__all__ = [ |
454 | - 'main', |
455 | - ] |
456 | - |
457 | -import code |
458 | -import optparse |
459 | -import socket |
460 | -import traceback |
461 | -# The rlcompleter and readline modules change the behavior of the python |
462 | -# interactive interpreter just by being imported. |
463 | -import readline |
464 | -import rlcompleter |
465 | -# Shut up pyflakes. |
466 | -rlcompleter |
467 | - |
468 | -import paramiko |
469 | - |
470 | -from devscripts.ec2test import error_and_quit |
471 | -from devscripts.ec2test.credentials import CredentialsError, EC2Credentials |
472 | -from devscripts.ec2test.instance import ( |
473 | - AVAILABLE_INSTANCE_TYPES, DEFAULT_INSTANCE_TYPE, EC2Instance) |
474 | -from devscripts.ec2test.testrunner import EC2TestRunner, TRUNK_BRANCH |
475 | - |
476 | -readline.parse_and_bind('tab: complete') |
477 | - |
478 | - |
479 | -# XXX: JonathanLange 2009-05-31: Strongly considering turning this into a |
480 | -# Bazaar plugin -- probably would make the option parsing and validation |
481 | -# easier. |
482 | - |
483 | -def run_with_instance(instance, run, demo_networks, postmortem): |
484 | - """Call run(), then allow post mortem debugging and shut down `instance`. |
485 | - |
486 | - :param instance: A running `EC2Instance`. If `run` returns True, it will |
487 | - be shut down before this function returns. |
488 | - :param run: A callable that will be called with no arguments to do |
489 | - whatever needs to be done with the instance. |
490 | - :param demo_networks: ??? |
491 | - :param postmortem: If this flag is true, any exceptions will be caught and |
492 | - an interactive session run to allow debugging the problem. |
493 | - """ |
494 | - shutdown = True |
495 | - try: |
496 | - try: |
497 | - shutdown = run() |
498 | - except Exception: |
499 | - # If we are running in demo or postmortem mode, it is really |
500 | - # helpful to see if there are any exceptions before it waits |
501 | - # in the console (in the finally block), and you can't figure |
502 | - # out why it's broken. |
503 | - traceback.print_exc() |
504 | - finally: |
505 | - try: |
506 | - if demo_networks: |
507 | - demo_network_string = '\n'.join( |
508 | - ' ' + network for network in demo_networks) |
509 | - ec2_ip = socket.gethostbyname(instance.hostname) |
510 | - print ( |
511 | - "\n\n" |
512 | - "********************** DEMO *************************\n" |
513 | - "It may take 20 seconds for the demo server to start up." |
514 | - "\nTo demo to other users, you still need to open up\n" |
515 | - "network access to the ec2 instance from their IPs by\n" |
516 | - "entering command like this in the interactive python\n" |
517 | - "interpreter at the end of the setup. " |
518 | - "\n runner.security_group.authorize(" |
519 | - "'tcp', 443, 443, '10.0.0.5/32')\n\n" |
520 | - "These demo networks have already been granted access on " |
521 | - "port 80 and 443:\n" + demo_network_string + |
522 | - "\n\nYou also need to edit your /etc/hosts to point\n" |
523 | - "launchpad.dev at the ec2 instance's IP like this:\n" |
524 | - " " + ec2_ip + " launchpad.dev\n\n" |
525 | - "See " |
526 | - "<https://wiki.canonical.com/Launchpad/EC2Test/ForDemos>." |
527 | - "\n*****************************************************" |
528 | - "\n\n") |
529 | - if postmortem: |
530 | - console = code.InteractiveConsole(locals()) |
531 | - console.interact(( |
532 | - 'Postmortem Console. EC2 instance is not yet dead.\n' |
533 | - 'It will shut down when you exit this prompt (CTRL-D).\n' |
534 | - '\n' |
535 | - 'Tab-completion is enabled.' |
536 | - '\n' |
537 | - 'Test runner instance is available as `runner`.\n' |
538 | - 'Also try these:\n' |
539 | - ' http://%(dns)s/current_test.log\n' |
540 | - ' ssh -A %(dns)s') % |
541 | - {'dns': instance.hostname}) |
542 | - print 'Postmortem console closed.' |
543 | - finally: |
544 | - if shutdown: |
545 | - instance.shutdown() |
546 | - |
547 | - |
548 | -def main(): |
549 | - parser = optparse.OptionParser( |
550 | - usage="%prog [options] [branch]", |
551 | - description=( |
552 | - "Check out a Launchpad branch and run all tests on an Amazon " |
553 | - "EC2 instance.")) |
554 | - parser.add_option( |
555 | - '-f', '--file', dest='file', default=None, |
556 | - help=('Store abridged test results in FILE.')) |
557 | - parser.add_option( |
558 | - '-n', '--no-email', dest='no_email', default=False, |
559 | - action='store_true', |
560 | - help=('Do not try to email results.')) |
561 | - parser.add_option( |
562 | - '-e', '--email', action='append', dest='email', default=None, |
563 | - help=('Email address to which results should be mailed. Defaults to ' |
564 | - 'the email address from `bzr whoami`. May be supplied multiple ' |
565 | - 'times. The first supplied email address will be used as the ' |
566 | - 'From: address.')) |
567 | - parser.add_option( |
568 | - '-o', '--test-options', dest='test_options', default='-vv', |
569 | - help=('Test options to pass to the remote test runner. Defaults to ' |
570 | - "``-o '-vv'``. For instance, to run specific tests, you might " |
571 | - "use ``-o '-vvt my_test_pattern'``.")) |
572 | - parser.add_option( |
573 | - '-b', '--branch', action='append', dest='branches', |
574 | - help=('Branches to include in this run in sourcecode. ' |
575 | - 'If the argument is only the project name, the trunk will be ' |
576 | - 'used (e.g., ``-b launchpadlib``). If you want to use a ' |
577 | - 'specific branch, if it is on launchpad, you can usually ' |
578 | - 'simply specify it instead (e.g., ' |
579 | - '``-b lp:~username/launchpadlib/branchname``). If this does ' |
580 | - 'not appear to work, or if the desired branch is not on ' |
581 | - 'launchpad, specify the project name and then the branch ' |
582 | - 'after an equals sign (e.g., ' |
583 | - '``-b launchpadlib=lp:~username/launchpadlib/branchname``). ' |
584 | - 'Branches for multiple projects may be specified with ' |
585 | - 'multiple instances of this option. ' |
586 | - 'You may also use this option to specify the branch of launchpad ' |
587 | - 'into which your branch may be merged. This defaults to %s. ' |
588 | - 'Because typically the important branches of launchpad are owned ' |
589 | - 'by the launchpad-pqm user, you can shorten this to only the ' |
590 | - 'branch name, if desired, and the launchpad-pqm user will be ' |
591 | - 'assumed. For instance, if you specify ' |
592 | - '``-b launchpad=db-devel`` then this is equivalent to ' |
593 | - '``-b lp:~launchpad-pqm/launchpad/db-devel``, or the even longer' |
594 | - '``-b launchpad=lp:~launchpad-pqm/launchpad/db-devel``.' |
595 | - % (TRUNK_BRANCH,))) |
596 | - parser.add_option( |
597 | - '-t', '--trunk', dest='trunk', default=False, |
598 | - action='store_true', |
599 | - help=('Run the trunk as the branch')) |
600 | - parser.add_option( |
601 | - '-s', '--submit-pqm-message', dest='pqm_message', default=None, |
602 | - help=('A pqm message to submit if the test run is successful. If ' |
603 | - 'provided, you will be asked for your GPG passphrase before ' |
604 | - 'the test run begins.')) |
605 | - parser.add_option( |
606 | - '--pqm-public-location', dest='pqm_public_location', default=None, |
607 | - help=('The public location for the pqm submit, if a pqm message is ' |
608 | - 'provided (see --submit-pqm-message). If this is not provided, ' |
609 | - 'for local branches, bzr configuration is consulted; for ' |
610 | - 'remote branches, it is assumed that the remote branch *is* ' |
611 | - 'a public branch.')) |
612 | - parser.add_option( |
613 | - '--pqm-submit-location', dest='pqm_submit_location', default=None, |
614 | - help=('The submit location for the pqm submit, if a pqm message is ' |
615 | - 'provided (see --submit-pqm-message). If this option is not ' |
616 | - 'provided, the script will look for an explicitly specified ' |
617 | - 'launchpad branch using the -b/--branch option; if that branch ' |
618 | - 'was specified and is owned by the launchpad-pqm user on ' |
619 | - 'launchpad, it is used as the pqm submit location. Otherwise, ' |
620 | - 'for local branches, bzr configuration is consulted; for ' |
621 | - 'remote branches, it is assumed that the submit branch is %s.' |
622 | - % (TRUNK_BRANCH,))) |
623 | - parser.add_option( |
624 | - '--pqm-email', dest='pqm_email', default=None, |
625 | - help=('Specify the email address of the PQM you are submitting to. ' |
626 | - 'If the branch is local, then the bzr configuration is ' |
627 | - 'consulted; for remote branches "Launchpad PQM ' |
628 | - '<launchpad@pqm.canonical.com>" is used by default.')) |
629 | - parser.add_option( |
630 | - '-m', '--machine', dest='machine_id', default=None, |
631 | - help=('The AWS machine identifier (AMID) on which to base this run. ' |
632 | - 'You should typically only have to supply this if you are ' |
633 | - 'testing new AWS images. Defaults to trying to find the most ' |
634 | - 'recent one with an approved owner.')) |
635 | - parser.add_option( |
636 | - '-i', '--instance', dest='instance_type', |
637 | - default=DEFAULT_INSTANCE_TYPE, |
638 | - help=('The AWS instance type on which to base this run. ' |
639 | - 'Available options are %r. Defaults to `%s`.' % |
640 | - (AVAILABLE_INSTANCE_TYPES, DEFAULT_INSTANCE_TYPE))) |
641 | - parser.add_option( |
642 | - '-p', '--postmortem', dest='postmortem', default=False, |
643 | - action='store_true', |
644 | - help=('Drop to interactive prompt after the test and before shutting ' |
645 | - 'down the instance for postmortem analysis of the EC2 instance ' |
646 | - 'and/or of this script.')) |
647 | - parser.add_option( |
648 | - '--headless', dest='headless', default=False, |
649 | - action='store_true', |
650 | - help=('After building the instance and test, run the remote tests ' |
651 | - 'headless. Cannot be used with postmortem ' |
652 | - 'or file.')) |
653 | - parser.add_option( |
654 | - '-d', '--debug', dest='debug', default=False, |
655 | - action='store_true', |
656 | - help=('Drop to pdb trace as soon as possible.')) |
657 | - # Use tabs to force a newline in the help text. |
658 | - fake_newline = "\t\t\t\t\t\t\t" |
659 | - parser.add_option( |
660 | - '--demo', action='append', dest='demo_networks', |
661 | - help=("Don't run tests. Instead start a demo instance of Launchpad. " |
662 | - "You can allow multiple networks to access the demo by " |
663 | - "repeating the argument." + fake_newline + |
664 | - "Example: --demo 192.168.1.100 --demo 10.1.13.0/24" + |
665 | - fake_newline + |
666 | - "See" + fake_newline + |
667 | - "https://wiki.canonical.com/Launchpad/EC2Test/ForDemos" )) |
668 | - parser.add_option( |
669 | - '--open-browser', dest='open_browser', default=False, |
670 | - action='store_true', |
671 | - help=('Open the results page in your default browser')) |
672 | - parser.add_option( |
673 | - '-c', '--include-download-cache-changes', |
674 | - dest='include_download_cache_changes', action='store_true', |
675 | - help=('Include any changes in the download cache (added or unknown) ' |
676 | - 'in the download cache of the test run. Note that, if you have ' |
677 | - 'any changes in your download cache, trying to submit to pqm ' |
678 | - 'will always raise an error. Also note that, if you have any ' |
679 | - 'changes in your download cache, you must explicitly choose to ' |
680 | - 'include or ignore the changes.')) |
681 | - parser.add_option( |
682 | - '-g', '--ignore-download-cache-changes', |
683 | - dest='include_download_cache_changes', action='store_false', |
684 | - help=('Ignore any changes in the download cache (added or unknown) ' |
685 | - 'in the download cache of the test run. Note that, if you have ' |
686 | - 'any changes in your download cache, trying to submit to pqm ' |
687 | - 'will always raise an error. Also note that, if you have any ' |
688 | - 'changes in your download cache, you must explicitly choose to ' |
689 | - 'include or ignore the changes.')) |
690 | - parser.add_option( |
691 | - '--update-image', dest='bundle', action='store', |
692 | - help=('Start the image, update the system packages, sourcecode and ' |
693 | - 'Launchpad branch then bundle, upload and register a new AMI ' |
694 | - 'with the given name.')) |
695 | - parser.add_option( |
696 | - '--extra-update-image-command', dest='extra_update_image_commands', |
697 | - action='append', metavar="CMD", |
698 | - help=('Run this command (with an ssh agent) on the image before ' |
699 | - 'running the default update steps. Can be passed more than ' |
700 | - 'once, the commands will be run in the order specified.')) |
701 | - options, args = parser.parse_args() |
702 | - if options.debug: |
703 | - import pdb; pdb.set_trace() |
704 | - if options.demo_networks: |
705 | - # We need the postmortem console to open the ec2 instance's |
706 | - # network access, and to keep the ec2 instance from being shutdown. |
707 | - options.postmortem = True |
708 | - if len(args) == 1: |
709 | - if options.trunk: |
710 | - parser.error( |
711 | - 'Cannot supply both a branch and the --trunk argument.') |
712 | - branch = args[0] |
713 | - elif len(args) > 1: |
714 | - parser.error('Too many arguments.') |
715 | - elif options.trunk: |
716 | - branch = None |
717 | - else: |
718 | - branch = '.' |
719 | - if ((options.postmortem or options.file or options.demo_networks) |
720 | - and options.headless): |
721 | - parser.error( |
722 | - 'Headless mode currently does not support postmortem, file ' |
723 | - 'or demo options.') |
724 | - if options.no_email: |
725 | - if options.email: |
726 | - parser.error( |
727 | - 'May not supply both --no-email and an --email address') |
728 | - email = False |
729 | - else: |
730 | - email = options.email |
731 | - if email is None: |
732 | - email = True |
733 | - if options.instance_type not in AVAILABLE_INSTANCE_TYPES: |
734 | - parser.error('Unknown instance type.') |
735 | - if options.branches is None: |
736 | - branches = () |
737 | - else: |
738 | - branches = [data.split('=', 1) for data in options.branches] |
739 | - |
740 | - agent = paramiko.Agent() |
741 | - keys = agent.get_keys() |
742 | - if len(keys) == 0: |
743 | - error_and_quit( |
744 | - 'You must have an ssh agent running with keys installed that ' |
745 | - 'will allow the script to rsync to devpad and get your ' |
746 | - 'branch.\n') |
747 | - user_key = agent.get_keys()[0] |
748 | - |
749 | - if options.demo_networks is None: |
750 | - demo_networks = () |
751 | - else: |
752 | - demo_networks = options.demo_networks |
753 | - |
754 | - # Get the AWS identifier and secret identifier. |
755 | - try: |
756 | - credentials = EC2Credentials.load_from_file() |
757 | - except CredentialsError, e: |
758 | - error_and_quit(str(e)) |
759 | - |
760 | - instance = EC2Instance.make( |
761 | - credentials, EC2TestRunner.name, instance_type=options.instance_type, |
762 | - machine_id=options.machine_id, demo_networks=demo_networks) |
763 | - |
764 | - if not options.bundle: |
765 | - runner = EC2TestRunner( |
766 | - branch, email=email, file=options.file, |
767 | - test_options=options.test_options, headless=options.headless, |
768 | - branches=branches, |
769 | - pqm_message=options.pqm_message, |
770 | - pqm_public_location=options.pqm_public_location, |
771 | - pqm_submit_location=options.pqm_submit_location, |
772 | - open_browser=options.open_browser, pqm_email=options.pqm_email, |
773 | - include_download_cache_changes=options.include_download_cache_changes, |
774 | - instance=instance, vals=instance._vals, |
775 | - ) |
776 | - def run_tests(): |
777 | - runner.configure_system() |
778 | - runner.prepare_tests() |
779 | - if demo_networks: |
780 | - runner.start_demo_webserver() |
781 | - else: |
782 | - runner.run_tests() |
783 | - return not options.headless |
784 | - run = run_tests |
785 | - else: |
786 | - instance.check_bundling_prerequisites() |
787 | - def make_new_image(): |
788 | - user_connection = instance.connect_as_user() |
789 | - user_connection.perform('bzr launchpad-login %(launchpad-login)s') |
790 | - if options.extra_update_image_commands: |
791 | - for cmd in options.extra_update_image_commands: |
792 | - user_connection.run_with_ssh_agent(cmd) |
793 | - user_connection.run_with_ssh_agent( |
794 | - "rsync -avp --partial --delete " |
795 | - "--filter='P *.o' --filter='P *.pyc' --filter='P *.so' " |
796 | - "devpad.canonical.com:/code/rocketfuel-built/launchpad/sourcecode/* " |
797 | - "/var/launchpad/sourcecode/") |
798 | - user_connection.run_with_ssh_agent( |
799 | - 'bzr pull -d /var/launchpad/test ' + TRUNK_BRANCH) |
800 | - user_connection.run_with_ssh_agent( |
801 | - 'bzr pull -d /var/launchpad/download-cache lp:lp-source-dependencies') |
802 | - user_connection.close() |
803 | - root_connection = instance.connect_as_root() |
804 | - root_connection.perform( |
805 | - 'deluser --remove-home %(USER)s', ignore_failure=True) |
806 | - root_connection.close() |
807 | - instance.bundle(options.bundle, credentials) |
808 | - return True |
809 | - run = make_new_image |
810 | - |
811 | - instance.start() |
812 | - instance.set_up_user(user_key) |
813 | - |
814 | - run_with_instance( |
815 | - instance, run, options.demo_networks, options.postmortem) |
816 | |
817 | === added file 'lib/devscripts/ec2test/controller.py' |
818 | --- lib/devscripts/ec2test/controller.py 1970-01-01 00:00:00 +0000 |
819 | +++ lib/devscripts/ec2test/controller.py 2009-09-18 03:00:35 +0000 |
820 | @@ -0,0 +1,178 @@ |
821 | +# This file is incuded almost verbatim from commandant, |
822 | +# https://launchpad.net/commandant. The only changes are removing some code |
823 | +# we don't use that depends on other parts of commandant. When Launchpad is |
824 | +# on Python 2.5 we can include commandant as an egg. |
825 | + |
826 | + |
827 | +# Commandant is a framework for building command-oriented tools. |
828 | +# Copyright (C) 2009 Jamshed Kakar. |
829 | +# |
830 | +# This program is free software; you can redistribute it and/or modify |
831 | +# it under the terms of the GNU General Public License as published by |
832 | +# the Free Software Foundation; either version 2 of the License, or |
833 | +# (at your option) any later version. |
834 | +# |
835 | +# This program is distributed in the hope that it will be useful, |
836 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
837 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
838 | +# GNU General Public License for more details. |
839 | +# |
840 | +# You should have received a copy of the GNU General Public License along |
841 | +# with this program; if not, write to the Free Software Foundation, Inc., |
842 | +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
843 | + |
844 | +"""Infrastructure to run C{bzrlib.commands.Command}s and L{HelpTopic}s.""" |
845 | + |
846 | +import os |
847 | +import sys |
848 | + |
849 | +from bzrlib.commands import run_bzr, Command |
850 | + |
851 | + |
852 | +class CommandRegistry(object): |
853 | + |
854 | + def __init__(self): |
855 | + self._commands = {} |
856 | + |
857 | + def install_bzrlib_hooks(self): |
858 | + """ |
859 | + Register this controller with C{Command.hooks} so that the controller |
860 | + can take advantage of Bazaar's command infrastructure. |
861 | + |
862 | + L{_list_commands} and L{_get_command} are registered as callbacks for |
863 | + the C{list_commands} and C{get_commands} hooks, respectively. |
864 | + """ |
865 | + Command.hooks.install_named_hook( |
866 | + "list_commands", self._list_commands, "commandant commands") |
867 | + Command.hooks.install_named_hook( |
868 | + "get_command", self._get_command, "commandant commands") |
869 | + |
870 | + def _list_commands(self, names): |
871 | + """Hook to find C{bzrlib.commands.Command} names is called by C{bzrlib}. |
872 | + |
873 | + @param names: A set of C{bzrlib.commands.Command} names to update with |
874 | + names from this controller. |
875 | + """ |
876 | + names.update(self._commands.iterkeys()) |
877 | + return names |
878 | + |
879 | + def _get_command(self, command, name): |
880 | + """ |
881 | + Hook to get the C{bzrlib.commands.Command} for C{name} is called by |
882 | + C{bzrlib}. |
883 | + |
884 | + @param command: A C{bzrlib.commands.Command}, or C{None}, to be |
885 | + returned if a command matching C{name} can't be found. |
886 | + @param name: The name of the C{bzrlib.commands.Command} to retrieve. |
887 | + @return: The C{bzrlib.commands.Command} from the index or C{command} |
888 | + if one isn't available for C{name}. |
889 | + """ |
890 | + try: |
891 | + local_command = self._commands[name]() |
892 | + except KeyError: |
893 | + for cmd in self._commands.itervalues(): |
894 | + if name in cmd.aliases: |
895 | + local_command = cmd() |
896 | + break |
897 | + else: |
898 | + return command |
899 | + local_command.controller = self |
900 | + return local_command |
901 | + |
902 | + def register_command(self, name, command_class): |
903 | + """Register a C{bzrlib.commands.Command} with this controller. |
904 | + |
905 | + @param name: The name to register the command with. |
906 | + @param command_class: A type object, typically a subclass of |
907 | + C{bzrlib.commands.Command} to use when the command is invoked. |
908 | + """ |
909 | + self._commands[name] = command_class |
910 | + |
911 | + def load_module(self, module): |
912 | + """Load C{bzrlib.commands.Command}s and L{HelpTopic}s from C{module}. |
913 | + |
914 | + Objects found in the module with names that start with C{cmd_} are |
915 | + treated as C{bzrlib.commands.Command}s and objects with names that |
916 | + start with C{topic_} are treated as L{HelpTopic}s. |
917 | + """ |
918 | + for name in module.__dict__: |
919 | + if name.startswith("cmd_"): |
920 | + sanitized_name = name[4:].replace("_", "-") |
921 | + self.register_command(sanitized_name, module.__dict__[name]) |
922 | + elif name.startswith("topic_"): |
923 | + sanitized_name = name[6:].replace("_", "-") |
924 | + self.register_help_topic(sanitized_name, module.__dict__[name]) |
925 | + |
926 | + |
927 | +class HelpTopicRegistry(object): |
928 | + |
929 | + def __init__(self): |
930 | + self._help_topics = {} |
931 | + |
932 | + def register_help_topic(self, name, help_topic_class): |
933 | + """Register a C{bzrlib.commands.Command} to this controller. |
934 | + |
935 | + @param name: The name to register the command with. |
936 | + @param command_class: A type object, typically a subclass of |
937 | + C{bzrlib.commands.Command} to use when the command is invoked. |
938 | + """ |
939 | + self._help_topics[name] = help_topic_class |
940 | + |
941 | + def get_help_topic_names(self): |
942 | + """Get a C{set} of help topic names.""" |
943 | + return set(self._help_topics.iterkeys()) |
944 | + |
945 | + def get_help_topic(self, name): |
946 | + """ |
947 | + Get the help topic matching C{name} or C{None} if a match isn't found. |
948 | + """ |
949 | + try: |
950 | + help_topic = self._help_topics[name]() |
951 | + except KeyError: |
952 | + return None |
953 | + help_topic.controller = self |
954 | + return help_topic |
955 | + |
956 | + |
957 | + |
958 | +class CommandExecutionMixin(object): |
959 | + |
960 | + def run(self, argv): |
961 | + """Run the C{bzrlib.commands.Command} specified in C{argv}. |
962 | + |
963 | + @raise BzrCommandError: Raised if a matching command can't be found. |
964 | + """ |
965 | + run_bzr(argv) |
966 | + |
967 | + |
968 | + |
969 | +def import_module(filename, file_path, package_path): |
970 | + """Import a module and make it a child of C{commandant_command}. |
971 | + |
972 | + The module source in C{filename} at C{file_path} is copied to a temporary |
973 | + directory, a Python package called C{commandant_command}. |
974 | + |
975 | + @param filename: The name of the module file. |
976 | + @param file_path: The path to the module file. |
977 | + @param package_path: The path for the new C{commandant_command} package. |
978 | + @return: The new module. |
979 | + """ |
980 | + module_path = os.path.join(package_path, "commandant_command") |
981 | + if not os.path.exists(module_path): |
982 | + os.mkdir(module_path) |
983 | + |
984 | + init_path = os.path.join(module_path, "__init__.py") |
985 | + open(init_path, "w").close() |
986 | + |
987 | + source_code = open(file_path, "r").read() |
988 | + module_file_path = os.path.join(module_path, filename) |
989 | + module_file = open(module_file_path, "w") |
990 | + module_file.write(source_code) |
991 | + module_file.close() |
992 | + |
993 | + name = filename[:-3] |
994 | + sys.path.append(package_path) |
995 | + try: |
996 | + return __import__("commandant_command.%s" % (name,), fromlist=[name]) |
997 | + finally: |
998 | + sys.path.pop() |
999 | |
1000 | === modified file 'lib/devscripts/ec2test/credentials.py' |
1001 | --- lib/devscripts/ec2test/credentials.py 2009-09-14 05:14:44 +0000 |
1002 | +++ lib/devscripts/ec2test/credentials.py 2009-09-18 01:33:42 +0000 |
1003 | @@ -13,9 +13,11 @@ |
1004 | |
1005 | import boto |
1006 | |
1007 | +from bzrlib.errors import BzrCommandError |
1008 | + |
1009 | from devscripts.ec2test.account import EC2Account |
1010 | |
1011 | -class CredentialsError(Exception): |
1012 | +class CredentialsError(BzrCommandError): |
1013 | """Raised when AWS credentials could not be loaded.""" |
1014 | |
1015 | def __init__(self, filename, extra=None): |
1016 | |
1017 | === added file 'lib/devscripts/ec2test/entrypoint.py' |
1018 | --- lib/devscripts/ec2test/entrypoint.py 1970-01-01 00:00:00 +0000 |
1019 | +++ lib/devscripts/ec2test/entrypoint.py 2009-09-18 03:10:25 +0000 |
1020 | @@ -0,0 +1,32 @@ |
1021 | +import readline |
1022 | +import rlcompleter |
1023 | +import sys |
1024 | + |
1025 | +from bzrlib.errors import BzrCommandError |
1026 | + |
1027 | +from devscripts.ec2test import builtins |
1028 | +from devscripts.ec2test.controller import ( |
1029 | + CommandRegistry, CommandExecutionMixin) |
1030 | + |
1031 | +# Shut up pyflakes. |
1032 | +rlcompleter |
1033 | + |
1034 | +readline.parse_and_bind('tab: complete') |
1035 | + |
1036 | +class EC2CommandController(CommandRegistry, CommandExecutionMixin): |
1037 | + def __init__(self): |
1038 | + CommandRegistry.__init__(self) |
1039 | + |
1040 | + |
1041 | +def main(): |
1042 | + controller = EC2CommandController() |
1043 | + controller.install_bzrlib_hooks() |
1044 | + controller.load_module(builtins) |
1045 | + |
1046 | + args = sys.argv[1:] |
1047 | + if not args: |
1048 | + args = ['help'] |
1049 | + try: |
1050 | + controller.run(args) |
1051 | + except BzrCommandError, e: |
1052 | + sys.exit('ec2: ERROR: ' + str(e)) |
1053 | |
1054 | === modified file 'lib/devscripts/ec2test/instance.py' |
1055 | --- lib/devscripts/ec2test/instance.py 2009-09-14 05:43:04 +0000 |
1056 | +++ lib/devscripts/ec2test/instance.py 2009-09-18 02:23:13 +0000 |
1057 | @@ -8,6 +8,7 @@ |
1058 | 'EC2Instance', |
1059 | ] |
1060 | |
1061 | +import code |
1062 | import glob |
1063 | import os |
1064 | import select |
1065 | @@ -15,13 +16,15 @@ |
1066 | import subprocess |
1067 | import sys |
1068 | import time |
1069 | +import traceback |
1070 | |
1071 | +from bzrlib.errors import BzrCommandError |
1072 | from bzrlib.plugins.launchpad.account import get_lp_login |
1073 | |
1074 | import paramiko |
1075 | |
1076 | -from devscripts.ec2test import error_and_quit |
1077 | from devscripts.ec2test.sshconfig import SSHConfig |
1078 | +from devscripts.ec2test.credentials import EC2Credentials |
1079 | |
1080 | DEFAULT_INSTANCE_TYPE = 'c1.xlarge' |
1081 | AVAILABLE_INSTANCE_TYPES = ('m1.large', 'm1.xlarge', 'c1.xlarge') |
1082 | @@ -36,23 +39,45 @@ |
1083 | pass |
1084 | |
1085 | |
1086 | +def get_user_key(): |
1087 | + """Get a SSH key from the agent. Exit if not found. |
1088 | + |
1089 | + This key will be used to let the user log in (as $USER) to the instance. |
1090 | + """ |
1091 | + agent = paramiko.Agent() |
1092 | + keys = agent.get_keys() |
1093 | + if len(keys) == 0: |
1094 | + raise BzrCommandError( |
1095 | + 'You must have an ssh agent running with keys installed that ' |
1096 | + 'will allow the script to rsync to devpad and get your ' |
1097 | + 'branch.\n') |
1098 | + user_key = agent.get_keys()[0] |
1099 | + return user_key |
1100 | + |
1101 | + |
1102 | class EC2Instance: |
1103 | """A single EC2 instance.""" |
1104 | |
1105 | @classmethod |
1106 | - def make(cls, credentials, name, instance_type, machine_id, demo_networks): |
1107 | + def make(cls, name, instance_type, machine_id, |
1108 | + demo_networks=None, credentials=None): |
1109 | """Construct an `EC2Instance`. |
1110 | |
1111 | - :param credentials: An `EC2Credentials` object. |
1112 | :param name: The name to use for the key pair and security group for |
1113 | the instance. |
1114 | :param instance_type: One of the AVAILABLE_INSTANCE_TYPES. |
1115 | - :param machine_id: ??? |
1116 | - :param demo_networks: ??? |
1117 | + :param machine_id: The AMI to use, or None to do the usual regexp |
1118 | + matching. |
1119 | + :param demo_networks: The networks to add to the security group to |
1120 | + allow access to the instance. |
1121 | + :param credentials: An `EC2Credentials` object. |
1122 | """ |
1123 | if instance_type not in AVAILABLE_INSTANCE_TYPES: |
1124 | raise ValueError('unknown instance_type %s' % (instance_type,)) |
1125 | |
1126 | + if credentials is None: |
1127 | + credentials = EC2Credentials.load_from_file() |
1128 | + |
1129 | # Make the EC2 connection. |
1130 | account = credentials.connect(name) |
1131 | |
1132 | @@ -71,7 +96,7 @@ |
1133 | vals = os.environ.copy() |
1134 | login = get_lp_login() |
1135 | if not login: |
1136 | - error_and_quit( |
1137 | + raise BzrCommandError( |
1138 | 'you must have set your launchpad login in bzr.') |
1139 | vals['launchpad-login'] = login |
1140 | |
1141 | @@ -104,7 +129,7 @@ |
1142 | return |
1143 | start = time.time() |
1144 | self.private_key = self._account.acquire_private_key() |
1145 | - self._account.acquire_security_group( |
1146 | + self.security_group = self._account.acquire_security_group( |
1147 | demo_networks=self._demo_networks) |
1148 | reservation = self._image.run( |
1149 | key_name=self._name, security_groups=[self._name], |
1150 | @@ -123,7 +148,7 @@ |
1151 | self._output = self._boto_instance.get_console_output() |
1152 | self.log(self._output.output) |
1153 | else: |
1154 | - error_and_quit( |
1155 | + raise BzrCommandError( |
1156 | 'failed to start: %s\n' % self._boto_instance.state) |
1157 | |
1158 | def shutdown(self): |
1159 | @@ -241,6 +266,50 @@ |
1160 | as_root('rm -fr /var/tmp/*') |
1161 | root_connection.close() |
1162 | |
1163 | + def set_up_and_run(self, postmortem, shutdown, func, *args, **kw): |
1164 | + """Start and set up, run `func` and then optionally shut down. |
1165 | + |
1166 | + :param postmortem: If true, any exceptions will be caught and an |
1167 | + interactive session run to allow debugging the problem. |
1168 | + :param shutdown: If true, the instance will be shut down before this |
1169 | + function returns. |
1170 | + :param func: A callable that will be called when the instance is |
1171 | + running and a user account has been set up on it. |
1172 | + :param args: Passed to `func`. |
1173 | + :param kw: Passed to `func`. |
1174 | + """ |
1175 | + user_key = get_user_key() |
1176 | + self.start() |
1177 | + try: |
1178 | + self.set_up_user(user_key) |
1179 | + try: |
1180 | + return func(*args, **kw) |
1181 | + except Exception: |
1182 | + # When running in postmortem mode, it is really helpful to see if |
1183 | + # there are any exceptions before it waits in the console (in the |
1184 | + # finally block), and you can't figure out why it's broken. |
1185 | + traceback.print_exc() |
1186 | + finally: |
1187 | + try: |
1188 | + if postmortem: |
1189 | + console = code.InteractiveConsole(locals()) |
1190 | + console.interact(( |
1191 | + 'Postmortem Console. EC2 instance is not yet dead.\n' |
1192 | + 'It will shut down when you exit this prompt (CTRL-D).\n' |
1193 | + '\n' |
1194 | + 'Tab-completion is enabled.' |
1195 | + '\n' |
1196 | + 'EC2Instance is available as `instance`.\n' |
1197 | + 'Also try these:\n' |
1198 | + ' http://%(dns)s/current_test.log\n' |
1199 | + ' ssh -A %(dns)s') % |
1200 | + {'dns': self.hostname}) |
1201 | + print 'Postmortem console closed.' |
1202 | + finally: |
1203 | + if shutdown: |
1204 | + self.shutdown() |
1205 | + |
1206 | + |
1207 | def _copy_single_file(self, sftp, local_path, remote_dir): |
1208 | """Copy `local_path` to `remote_dir` on this instance. |
1209 | |
1210 | @@ -281,7 +350,7 @@ |
1211 | pattern = os.path.join(local_dir, pattern) |
1212 | matches = glob.glob(pattern) |
1213 | if len(matches) != 1: |
1214 | - error_and_quit( |
1215 | + raise BzrCommandError( |
1216 | '%r must match a single %s file' % (pattern, file_kind)) |
1217 | return matches[0] |
1218 | |
1219 | @@ -290,12 +359,12 @@ |
1220 | """ |
1221 | local_ec2_dir = os.path.expanduser('~/.ec2') |
1222 | if not os.path.exists(local_ec2_dir): |
1223 | - error_and_quit( |
1224 | + raise BzrCommandError( |
1225 | "~/.ec2 must exist and contain aws_user, aws_id, a private " |
1226 | "key file and a certificate.") |
1227 | aws_user_file = os.path.expanduser('~/.ec2/aws_user') |
1228 | if not os.path.exists(aws_user_file): |
1229 | - error_and_quit( |
1230 | + raise BzrCommandError( |
1231 | "~/.ec2/aws_user must exist and contain your numeric AWS id.") |
1232 | self.aws_user = open(aws_user_file).read().strip() |
1233 | self.local_cert = self._check_single_glob_match( |
1234 | |
1235 | === modified file 'lib/devscripts/ec2test/testrunner.py' |
1236 | --- lib/devscripts/ec2test/testrunner.py 2009-09-15 00:14:56 +0000 |
1237 | +++ lib/devscripts/ec2test/testrunner.py 2009-09-18 02:23:13 +0000 |
1238 | @@ -14,9 +14,6 @@ |
1239 | import re |
1240 | import sys |
1241 | |
1242 | - |
1243 | -from bzrlib.plugin import load_plugins |
1244 | -load_plugins() |
1245 | from bzrlib.branch import Branch |
1246 | from bzrlib.bzrdir import BzrDir |
1247 | from bzrlib.config import GlobalConfig |
1248 | @@ -169,12 +166,6 @@ |
1249 | self.headless = headless |
1250 | self.include_download_cache_changes = include_download_cache_changes |
1251 | self.open_browser = open_browser |
1252 | - if headless and file: |
1253 | - raise ValueError( |
1254 | - 'currently do not support files with headless mode.') |
1255 | - if headless and not (email or pqm_message): |
1256 | - raise ValueError('You have specified no way to get the results ' |
1257 | - 'of your headless test run.') |
1258 | |
1259 | if test_options != '-vv' and pqm_message is not None: |
1260 | raise ValueError( |
1261 | @@ -375,12 +366,6 @@ |
1262 | sys.stdout.write(msg) |
1263 | sys.stdout.flush() |
1264 | |
1265 | - def shutdown(self): |
1266 | - if self.headless and self._running: |
1267 | - self.log('letting instance run, to shut down headlessly ' |
1268 | - 'at completion of tests.\n') |
1269 | - return |
1270 | - return self._instance.shutdown() |
1271 | |
1272 | def configure_system(self): |
1273 | user_connection = self._instance.connect_as_user() |
1274 | @@ -487,10 +472,13 @@ |
1275 | # close ssh connection |
1276 | user_connection.close() |
1277 | |
1278 | - def start_demo_webserver(self): |
1279 | + def run_demo_server(self): |
1280 | """Turn ec2 instance into a demo server.""" |
1281 | + self.configure_system() |
1282 | + self.prepare_tests() |
1283 | user_connection = self._instance.connect_as_user() |
1284 | p = user_connection.perform |
1285 | + p('make -C /var/launchpad/test schema') |
1286 | p('mkdir -p /var/tmp/bazaar.launchpad.dev/static') |
1287 | p('mkdir -p /var/tmp/bazaar.launchpad.dev/mirrors') |
1288 | p('sudo a2enmod proxy > /dev/null') |
1289 | @@ -520,6 +508,8 @@ |
1290 | user_connection.close() |
1291 | |
1292 | def run_tests(self): |
1293 | + self.configure_system() |
1294 | + self.prepare_tests() |
1295 | user_connection = self._instance.connect_as_user() |
1296 | |
1297 | # Make sure we activate the failsafe --shutdown feature. This will |
1298 | |
1299 | === added file 'utilities/ec2' |
1300 | --- utilities/ec2 1970-01-01 00:00:00 +0000 |
1301 | +++ utilities/ec2 2009-09-16 01:34:30 +0000 |
1302 | @@ -0,0 +1,17 @@ |
1303 | +#!/usr/bin/python |
1304 | + |
1305 | +# Copyright 2009 Canonical Ltd. This software is licensed under the |
1306 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
1307 | + |
1308 | +"""Executable for the ec2 scripts.""" |
1309 | + |
1310 | +__metaclass__ = type |
1311 | + |
1312 | +import os |
1313 | +import sys |
1314 | + |
1315 | +sys.path.append( |
1316 | + os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib')) |
1317 | + |
1318 | +from devscripts.ec2test.entrypoint import main |
1319 | +main() |
1320 | |
1321 | === modified file 'utilities/ec2test.py' |
1322 | --- utilities/ec2test.py 2009-09-11 22:49:43 +0000 |
1323 | +++ utilities/ec2test.py 2009-09-18 03:10:03 +0000 |
1324 | @@ -1,17 +1,10 @@ |
1325 | -#!/usr/bin/python |
1326 | +#!/bin/sh |
1327 | |
1328 | # Copyright 2009 Canonical Ltd. This software is licensed under the |
1329 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1330 | |
1331 | -"""Executable for the ec2test script.""" |
1332 | - |
1333 | -__metaclass__ = type |
1334 | - |
1335 | -import os |
1336 | -import sys |
1337 | - |
1338 | -sys.path.append( |
1339 | - os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib')) |
1340 | - |
1341 | -from devscripts.ec2test import main |
1342 | -main() |
1343 | +echo "You should run $(dirname $0)/ec2 test" $@ "instead." >/dev/null 1>&2 |
1344 | +echo "Waiting for 5 seconds in case you're not reading this." >/dev/null 1>&2 |
1345 | +sleep 5 |
1346 | + |
1347 | +exec $(dirname $0)/ec2 test "$@" |
Hi Jono,
You've seen this branch before. Since last time:
1) A good degree of tidying, mostly as discussed.
2) ec2 help works in most ways that I think it should.
3) No tests :( The fact that the ec2 stuff only works with 2.4+ bites, basically.