Merge lp:~jml/lp-dev-utils/public-ip into lp:lp-dev-utils

Proposed by Jonathan Lange on 2012-05-01
Status: Merged
Approved by: Aaron Bentley on 2012-05-02
Approved revision: 112
Merged at revision: 111
Proposed branch: lp:~jml/lp-dev-utils/public-ip
Merge into: lp:lp-dev-utils
Diff against target: 233 lines (+41/-21)
4 files modified
ec2test/account.py (+13/-8)
ec2test/builtins.py (+21/-8)
ec2test/instance.py (+6/-4)
ec2test/tests/test_ec2instance.py (+1/-1)
To merge this branch: bzr merge lp:~jml/lp-dev-utils/public-ip
Reviewer Review Type Date Requested Status
Aaron Bentley (community) 2012-05-01 Approve on 2012-05-02
Review via email: mp+104273@code.launchpad.net

Description of the Change

When using the 'ec2' tool behind a transparent proxy, such as is running at Canonical's Millbank offices, it will fail with an odd error:

  SSHException: Error reading SSH protocol banner

See http://pastebin.ubuntu.com/960374/ for the full version.

This is because Amazon's checkip service is honouring the X-Forwarded-For HTTP header. Since the IP being forwarded for is internal, checkip returns the internal IP, and then ec2test grants that internal IP ssh access, which means we don't get SSH access and so can't complete setting up the instance.

This branch solves the problem in two ways:
 1. Use whatismyip.akamai.com instead of checkip.amazonaws.com, which behaves properly
 2. Allow users to specify a --public-ip option to override that behaviour

Particular thanks to cjwatson who diagnosed this bug and told me how to fix it.

To post a comment you must log in.
Aaron Bentley (abentley) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ec2test/account.py'
2--- ec2test/account.py 2012-02-24 20:10:13 +0000
3+++ ec2test/account.py 2012-05-02 13:43:22 +0000
4@@ -49,8 +49,12 @@
5 Please double-check before reporting a problem.
6 """
7
8-
9-def get_ip():
10+# An external website to consult to determine the public IP address of this
11+# machine. Must return an IP address (and nothing else) on GET.
12+IP_GUESS_URL = 'http://whatismyip.akamai.com/'
13+
14+
15+def guess_public_ip():
16 """Uses AWS checkip to obtain this machine's IP address.
17
18 Consults an external website to determine the public IP address of this
19@@ -58,7 +62,7 @@
20
21 :return: This machine's net-visible IP address as a string.
22 """
23- return urllib.urlopen('http://checkip.amazonaws.com').read().strip()
24+ return urllib.urlopen(IP_GUESS_URL).read().strip()
25
26
27 class EC2Account:
28@@ -99,7 +103,7 @@
29 session_name.expires < now)):
30 yield artifact
31
32- def acquire_security_group(self, demo_networks=None):
33+ def acquire_security_group(self, demo_networks=None, public_ip=None):
34 """Get a security group with the appropriate configuration.
35
36 "Appropriate" means configured to allow this machine to connect via
37@@ -115,10 +119,11 @@
38 security_group = self.conn.create_security_group(
39 self.name, 'Authorization to access the test runner instance.')
40 # Authorize SSH and HTTP.
41- ip = get_ip()
42- security_group.authorize('tcp', 22, 22, '%s/32' % ip)
43- security_group.authorize('tcp', 80, 80, '%s/32' % ip)
44- security_group.authorize('tcp', 443, 443, '%s/32' % ip)
45+ if public_ip is None:
46+ public_ip = guess_public_ip()
47+ security_group.authorize('tcp', 22, 22, '%s/32' % public_ip)
48+ security_group.authorize('tcp', 80, 80, '%s/32' % public_ip)
49+ security_group.authorize('tcp', 443, 443, '%s/32' % public_ip)
50 for network in demo_networks:
51 # Add missing netmask info for single ips.
52 if '/' not in network:
53
54=== modified file 'ec2test/builtins.py'
55--- ec2test/builtins.py 2012-02-28 16:59:33 +0000
56+++ ec2test/builtins.py 2012-05-02 13:43:22 +0000
57@@ -93,6 +93,14 @@
58 (AVAILABLE_INSTANCE_TYPES, DEFAULT_INSTANCE_TYPE)))
59
60
61+public_ip_option = Option(
62+ 'public-ip', type=str, param_name='public_ip',
63+ help=('The public IP address to use. If not provided, '
64+ 'we try to guess it. If we guess wrong (say, you '
65+ 'are behind a transparent proxy) then you should use '
66+ 'this to correct the guess.'))
67+
68+
69 debug_option = Option(
70 'debug', short_name='d',
71 help=('Drop to pdb trace as soon as possible.'))
72@@ -226,6 +234,7 @@
73 machine_id_option,
74 instance_type_option,
75 region_option,
76+ public_ip_option,
77 Option(
78 'file', short_name='f', type=filename_type,
79 help=('Store abridged test results in FILE.')),
80@@ -293,7 +302,7 @@
81 pqm_submit_location=None, pqm_email=None, postmortem=False,
82 attached=False, debug=False, open_browser=False,
83 region=None,
84- include_download_cache_changes=False):
85+ include_download_cache_changes=False, public_ip=None):
86 set_trace_if(debug)
87 if branch is None:
88 branch = []
89@@ -322,7 +331,7 @@
90
91 session_name = EC2SessionName.make(EC2TestRunner.name)
92 instance = EC2Instance.make(session_name, instance_type, machine,
93- region=region)
94+ region=region, public_ip=public_ip)
95
96 runner = EC2TestRunner(
97 test_branch, email=email, file=file,
98@@ -346,6 +355,7 @@
99 instance_type_option,
100 region_option,
101 machine_id_option,
102+ public_ip_option,
103 Option('dry-run', help="Just print the equivalent ec2 test command."),
104 Option('print-commit', help="Print the full commit message."),
105 Option(
106@@ -396,7 +406,7 @@
107 debug=False, commit_text=None, dry_run=False, testfix=False,
108 no_qa=False, incremental=False, rollback=None, print_commit=False,
109 force=False, attached=False,
110- region=DEFAULT_REGION,
111+ region=DEFAULT_REGION, public_ip=None,
112 ):
113 from bzrlib.plugins.pqm.lpland import (
114 LaunchpadBranchLander, MissingReviewError, MissingBugsError,
115@@ -464,7 +474,8 @@
116
117 session_name = EC2SessionName.make(EC2TestRunner.name)
118 instance = EC2Instance.make(
119- session_name, instance_type, machine, region=region)
120+ session_name, instance_type, machine, region=region,
121+ public_ip=public_ip)
122
123 runner = EC2TestRunner(
124 mp.source_branch, email=emails,
125@@ -493,6 +504,7 @@
126 debug_option,
127 include_download_cache_changes_option,
128 region_option,
129+ public_ip_option,
130 ListOption(
131 'demo', type=str,
132 help="Allow this netmask to connect to the instance."),
133@@ -502,7 +514,7 @@
134
135 def run(self, test_branch=None, branch=None, trunk=False, machine=None,
136 instance_type=DEFAULT_INSTANCE_TYPE, debug=False,
137- include_download_cache_changes=False, demo=None):
138+ include_download_cache_changes=False, demo=None, public_ip=None):
139 set_trace_if(debug)
140 if branch is None:
141 branch = []
142@@ -511,7 +523,7 @@
143
144 session_name = EC2SessionName.make(EC2TestRunner.name)
145 instance = EC2Instance.make(
146- session_name, instance_type, machine, demo)
147+ session_name, instance_type, machine, demo, public_ip=public_ip)
148
149 runner = EC2TestRunner(
150 test_branch, branches=branches,
151@@ -562,6 +574,7 @@
152 postmortem_option,
153 debug_option,
154 region_option,
155+ public_ip_option,
156 ListOption(
157 'extra-update-image-command', type=str,
158 help=('Run this command (with an ssh agent) on the image before '
159@@ -579,7 +592,7 @@
160 def run(self, ami_name, machine=None, instance_type='m1.large',
161 debug=False, postmortem=False, extra_update_image_command=None,
162 region=None,
163- public=False):
164+ public=False, public_ip=None):
165 set_trace_if(debug)
166
167 if extra_update_image_command is None:
168@@ -595,7 +608,7 @@
169 session_name = EC2SessionName.make(EC2TestRunner.name)
170 instance = EC2Instance.make(
171 session_name, instance_type, machine,
172- region=region)
173+ region=region, public_ip=public_ip)
174 instance.check_bundling_prerequisites(ami_name)
175 instance.set_up_and_run(
176 postmortem, True, self.update_image, instance,
177
178=== modified file 'ec2test/instance.py'
179--- ec2test/instance.py 2012-03-19 11:51:18 +0000
180+++ ec2test/instance.py 2012-05-02 13:43:22 +0000
181@@ -201,7 +201,7 @@
182
183 @classmethod
184 def make(cls, name, instance_type, machine_id, demo_networks=None,
185- credentials=None, region=None):
186+ credentials=None, region=None, public_ip=None):
187 """Construct an `EC2Instance`.
188
189 :param name: The name to use for the key pair and security group for
190@@ -266,12 +266,13 @@
191
192 instance = EC2Instance(
193 name, image, instance_type, demo_networks, account,
194- from_scratch, user_key, login, region)
195+ from_scratch, user_key, login, region, public_ip=public_ip)
196 instance._credentials = credentials
197 return instance
198
199 def __init__(self, name, image, instance_type, demo_networks, account,
200- from_scratch, user_key, launchpad_login, region):
201+ from_scratch, user_key, launchpad_login, region,
202+ public_ip=None):
203 self._name = name
204 self._image = image
205 self._account = account
206@@ -282,6 +283,7 @@
207 self._user_key = user_key
208 self._launchpad_login = launchpad_login
209 self._region = region
210+ self._public_ip = public_ip
211
212 def log(self, msg):
213 """Log a message on stdout, flushing afterwards."""
214@@ -298,7 +300,7 @@
215 start = time.time()
216 self.private_key = self._account.acquire_private_key()
217 self.security_group = self._account.acquire_security_group(
218- demo_networks=self._demo_networks)
219+ demo_networks=self._demo_networks, public_ip=self._public_ip)
220 reservation = self._image.run(
221 key_name=self._name, security_groups=[self._name],
222 instance_type=self._instance_type)
223
224=== modified file 'ec2test/tests/test_ec2instance.py'
225--- ec2test/tests/test_ec2instance.py 2012-04-13 14:35:59 +0000
226+++ ec2test/tests/test_ec2instance.py 2012-05-02 13:43:22 +0000
227@@ -16,7 +16,7 @@
228 def acquire_private_key(self):
229 pass
230
231- def acquire_security_group(self, demo_networks=None):
232+ def acquire_security_group(self, demo_networks=None, public_ip=None):
233 pass
234
235

Subscribers

People subscribed via source and target branches