Merge lp:~wgrant/launchpad/no-more-vhost-config into lp:launchpad
- no-more-vhost-config
- Merge into devel
Proposed by
William Grant
Status: | Work in progress |
---|---|
Proposed branch: | lp:~wgrant/launchpad/no-more-vhost-config |
Merge into: | lp:launchpad |
Diff against target: |
1296 lines (+910/-80) (has conflicts) 12 files modified
configs/development/launchpad-lazr.conf (+17/-2) configs/testrunner-appserver/launchpad-lazr.conf (+5/-0) configs/testrunner/launchpad-lazr.conf (+4/-0) lib/devscripts/ec2test/instance.py.OTHER (+695/-0) lib/lp/services/config/schema-lazr.conf (+28/-19) lib/lp/services/openid/adapters/openid.py (+23/-0) lib/lp/services/webapp/doc/webapp-publication.txt (+16/-0) lib/lp/services/webapp/metazcml.py (+12/-3) lib/lp/services/webapp/servers.py (+14/-1) lib/lp/services/webapp/vhosts.py (+58/-53) lib/lp/testing/layers.py (+29/-1) lib/lp/testing/tests/test_layers_functional.py (+9/-1) Text conflict in configs/development/launchpad-lazr.conf Text conflict in configs/testrunner-appserver/launchpad-lazr.conf Text conflict in configs/testrunner/launchpad-lazr.conf Conflict adding files to lib/devscripts/ec2test. Created directory. Conflict because lib/devscripts/ec2test is not versioned, but has versioned children. Versioned directory. Contents conflict in lib/devscripts/ec2test/instance.py Text conflict in lib/lp/services/config/schema-lazr.conf Text conflict in lib/lp/services/openid/adapters/openid.py Text conflict in lib/lp/services/webapp/doc/webapp-publication.txt Text conflict in lib/lp/services/webapp/metazcml.py Text conflict in lib/lp/services/webapp/servers.py Text conflict in lib/lp/services/webapp/vhosts.py Text conflict in lib/lp/testing/layers.py Text conflict in lib/lp/testing/tests/test_layers_functional.py |
To merge this branch: | bzr merge lp:~wgrant/launchpad/no-more-vhost-config |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Launchpad code reviewers | Pending | ||
Review via email: mp+306686@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Unmerged revisions
- 14141. By William Grant
-
Drop unneeded vhost config sections from the schema.
- 14140. By William Grant
-
Fix AppServerLayer.
- 14139. By William Grant
-
appserver config now uses a custom port. Doesn't actually work, because the layer relies on config.
vhost.mainsite. rooturl. - 14138. By William Grant
-
Comment the config schema, and use a custom portin the vhost config if it's specified.
- 14137. By William Grant
-
Inline logic slightly.
- 14136. By William Grant
-
Refactor the vhost setup code a bit.
- 14135. By William Grant
-
VirtualHostConfig now takes a port, instead of a rooturl.
- 14134. By William Grant
-
Rename the xmlrpc_private vhost to xmlrpc-private, to match the subdomain.
- 14133. By William Grant
-
Since vhost config is pretty much defined from a base domain and a port, move all that stuff into code.
- 14132. By William Grant
-
Our publisher ZCML directive now checks allvhosts, rather than config.vhosts directly.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'configs/development/launchpad-lazr.conf' |
2 | --- configs/development/launchpad-lazr.conf 2016-06-30 16:05:11 +0000 |
3 | +++ configs/development/launchpad-lazr.conf 2016-09-24 06:42:11 +0000 |
4 | @@ -94,8 +94,12 @@ |
5 | |
6 | [launchpad] |
7 | enable_test_openid_provider: True |
8 | +<<<<<<< TREE |
9 | openid_canonical_root: https://testopenid.dev/ |
10 | openid_provider_root: https://testopenid.dev/ |
11 | +======= |
12 | +openid_provider_root: https://testopenid.dev/ |
13 | +>>>>>>> MERGE-SOURCE |
14 | code_domain: code.launchpad.dev |
15 | default_batch_size: 5 |
16 | max_attachment_size: 2097152 |
17 | @@ -213,15 +217,24 @@ |
18 | |
19 | [vhosts] |
20 | use_https: True |
21 | +base_hostname: launchpad.dev |
22 | +vostok_hostname: vostok.dev |
23 | +testopenid_hostname: testopenid.dev |
24 | +enable_apidoc: True |
25 | |
26 | [vhost.mainsite] |
27 | -hostname: launchpad.dev |
28 | -althostnames: localhost |
29 | openid_delegate_profile: True |
30 | |
31 | [vhost.api] |
32 | +<<<<<<< TREE |
33 | hostname: api.launchpad.dev |
34 | +======= |
35 | +# Turn this on once we've solved cache invalidation problems and are |
36 | +# ready to test. |
37 | +# enable_server_side_representation_cache: True |
38 | +>>>>>>> MERGE-SOURCE |
39 | |
40 | +<<<<<<< TREE |
41 | [vhost.blueprints] |
42 | hostname: blueprints.launchpad.dev |
43 | |
44 | @@ -249,6 +262,8 @@ |
45 | [vhost.feeds] |
46 | hostname: feeds.launchpad.dev |
47 | |
48 | +======= |
49 | +>>>>>>> MERGE-SOURCE |
50 | [immediate_mail] |
51 | # XXX sinzui 2008-03-26: |
52 | # A development box should never send email to the outer world, |
53 | |
54 | === modified file 'configs/testrunner-appserver/launchpad-lazr.conf' |
55 | --- configs/testrunner-appserver/launchpad-lazr.conf 2014-02-27 08:39:44 +0000 |
56 | +++ configs/testrunner-appserver/launchpad-lazr.conf 2016-09-24 06:42:11 +0000 |
57 | @@ -22,6 +22,7 @@ |
58 | xmlrpc_runner_sleep: 1 |
59 | register_bounces_every: 1 |
60 | |
61 | +<<<<<<< TREE |
62 | [vhost.mainsite] |
63 | rooturl: http://launchpad.dev:8085/ |
64 | |
65 | @@ -54,6 +55,10 @@ |
66 | |
67 | [vhost.feeds] |
68 | rooturl: http://feeds.launchpad.dev:8085/ |
69 | +======= |
70 | +[vhosts] |
71 | +port: 8085 |
72 | +>>>>>>> MERGE-SOURCE |
73 | |
74 | [immediate_mail] |
75 | # BarryWarsaw 04-Dec-2008: AppServerLayer tests should send email to the fake |
76 | |
77 | === modified file 'configs/testrunner/launchpad-lazr.conf' |
78 | --- configs/testrunner/launchpad-lazr.conf 2016-05-18 00:33:18 +0000 |
79 | +++ configs/testrunner/launchpad-lazr.conf 2016-09-24 06:42:11 +0000 |
80 | @@ -107,9 +107,13 @@ |
81 | # We use the stub Google Service here which maps URL fragment to |
82 | # to static content |
83 | homepage_recent_posts_feed: http://launchpad.dev:8092/blog-feed |
84 | +<<<<<<< TREE |
85 | openid_canonical_root: http://testopenid.dev/ |
86 | openid_provider_root: http://testopenid.dev/ |
87 | openid_alternate_provider_roots: http://login1.dev/, http://login2.dev/ |
88 | +======= |
89 | +openid_provider_root: http://testopenid.dev/ |
90 | +>>>>>>> MERGE-SOURCE |
91 | |
92 | [launchpad_session] |
93 | cookie: launchpad_tests |
94 | |
95 | === added directory 'lib/devscripts/ec2test' |
96 | === added file 'lib/devscripts/ec2test/instance.py.OTHER' |
97 | --- lib/devscripts/ec2test/instance.py.OTHER 1970-01-01 00:00:00 +0000 |
98 | +++ lib/devscripts/ec2test/instance.py.OTHER 2016-09-24 06:42:11 +0000 |
99 | @@ -0,0 +1,695 @@ |
100 | +# Copyright 2009-2011 Canonical Ltd. This software is licensed under the |
101 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
102 | + |
103 | +"""Code to represent a single machine instance in EC2.""" |
104 | + |
105 | +__metaclass__ = type |
106 | +__all__ = [ |
107 | + 'EC2Instance', |
108 | + ] |
109 | + |
110 | +import code |
111 | +import errno |
112 | +import glob |
113 | +import os |
114 | +import select |
115 | +import socket |
116 | +import subprocess |
117 | +import sys |
118 | +import time |
119 | +import traceback |
120 | +from datetime import datetime |
121 | +from bzrlib.errors import BzrCommandError |
122 | +from devscripts.ec2test.session import EC2SessionName |
123 | +import paramiko |
124 | + |
125 | + |
126 | +DEFAULT_INSTANCE_TYPE = 'c1.xlarge' |
127 | +AVAILABLE_INSTANCE_TYPES = ('m1.large', 'm1.xlarge', 'c1.xlarge') |
128 | + |
129 | + |
130 | +class AcceptAllPolicy: |
131 | + """We accept all unknown host key.""" |
132 | + |
133 | + def missing_host_key(self, client, hostname, key): |
134 | + # Normally the console output is supposed to contain the Host key but |
135 | + # it doesn't seem to be the case here, so we trust that the host we |
136 | + # are connecting to is the correct one. |
137 | + pass |
138 | + |
139 | + |
140 | +def get_user_key(): |
141 | + """Get a SSH key from the agent. Raise an error if no keys were found. |
142 | + |
143 | + This key will be used to let the user log in (as $USER) to the instance. |
144 | + """ |
145 | + agent = paramiko.Agent() |
146 | + keys = agent.get_keys() |
147 | + if len(keys) == 0: |
148 | + raise BzrCommandError( |
149 | + 'You must have an ssh agent running with keys installed that ' |
150 | + 'will allow the script to access Launchpad and get your ' |
151 | + 'branch.\n') |
152 | + |
153 | + # XXX mars 2010-05-07 bug=577118 |
154 | + # Popping the first key off of the stack can create problems if the person |
155 | + # has more than one key in their ssh-agent, but alas, we have no good way |
156 | + # to detect the right key to use. See bug 577118 for a workaround. |
157 | + return keys[0] |
158 | + |
159 | + |
160 | +# Commands to run to turn a blank image into one usable for the rest of the |
161 | +# ec2 functionality. They come in two parts, one set that need to be run as |
162 | +# root and another that should be run as the 'ec2test' user. |
163 | +# Note that the sources from http://us.ec2.archive.ubuntu.com/ubuntu/ are per |
164 | +# instructions described in http://is.gd/g1MIT . When we switch to |
165 | +# Eucalyptus, we can dump this. |
166 | + |
167 | +from_scratch_root = """ |
168 | +# From 'help set': |
169 | +# -x Print commands and their arguments as they are executed. |
170 | +# -e Exit immediately if a command exits with a non-zero status. |
171 | +set -xe |
172 | + |
173 | +sed -ie 's/main universe/main universe multiverse/' /etc/apt/sources.list |
174 | + |
175 | +. /etc/lsb-release |
176 | + |
177 | +cat >> /etc/apt/sources.list << EOF |
178 | +deb http://ppa.launchpad.net/launchpad/ubuntu $DISTRIB_CODENAME main |
179 | +deb http://ppa.launchpad.net/bzr/ubuntu $DISTRIB_CODENAME main |
180 | +deb http://ppa.launchpad.net/bzr-beta-ppa/ubuntu $DISTRIB_CODENAME main |
181 | +deb http://us.ec2.archive.ubuntu.com/ubuntu/ $DISTRIB_CODENAME multiverse |
182 | +deb-src http://us.ec2.archive.ubuntu.com/ubuntu/ $DISTRIB_CODENAME main |
183 | +EOF |
184 | + |
185 | +# This next part is cribbed from rocketfuel-setup |
186 | +dev_host() { |
187 | + sed -i \"s/^127.0.0.88.*$/&\ ${hostname}/\" /etc/hosts |
188 | +} |
189 | + |
190 | +echo 'Adding development hosts on local machine' |
191 | +echo ' |
192 | +# Launchpad virtual domains. This should be on one line. |
193 | +127.0.0.88 launchpad.dev |
194 | +' >> /etc/hosts |
195 | + |
196 | +declare -a hostnames |
197 | +hostnames=$(cat <<EOF |
198 | + answers.launchpad.dev |
199 | + archive.launchpad.dev |
200 | + api.launchpad.dev |
201 | + bazaar-internal.launchpad.dev |
202 | + blueprints.launchpad.dev |
203 | + bugs.launchpad.dev |
204 | + code.launchpad.dev |
205 | + feeds.launchpad.dev |
206 | + keyserver.launchpad.dev |
207 | + lists.launchpad.dev |
208 | + ppa.launchpad.dev |
209 | + private-ppa.launchpad.dev |
210 | + testopenid.dev |
211 | + translations.launchpad.dev |
212 | + xmlrpc-private.launchpad.dev |
213 | + xmlrpc.launchpad.dev |
214 | +EOF |
215 | + ) |
216 | + |
217 | +for hostname in $hostnames; do |
218 | + dev_host; |
219 | +done |
220 | + |
221 | +echo ' |
222 | +127.0.0.99 bazaar.launchpad.dev |
223 | +' >> /etc/hosts |
224 | + |
225 | +# Add the keys for the three PPAs added to sources.list above. |
226 | +apt-key adv --recv-keys --keyserver pool.sks-keyservers.net 2af499cb24ac5f65461405572d1ffb6c0a5174af |
227 | +apt-key adv --recv-keys --keyserver pool.sks-keyservers.net ece2800bacf028b31ee3657cd702bf6b8c6c1efd |
228 | +apt-key adv --recv-keys --keyserver pool.sks-keyservers.net cbede690576d1e4e813f6bb3ebaf723d37b19b80 |
229 | + |
230 | +aptitude update |
231 | +aptitude -y full-upgrade |
232 | + |
233 | +DEBIAN_FRONTEND=noninteractive apt-get -y install launchpad-developer-dependencies apache2 apache2-mpm-worker |
234 | + |
235 | +# Create the ec2test user, give them passwordless sudo. |
236 | +adduser --gecos "" --disabled-password ec2test |
237 | +echo 'ec2test\tALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers |
238 | + |
239 | +mkdir /home/ec2test/.ssh |
240 | +cat > /home/ec2test/.ssh/config << EOF |
241 | +CheckHostIP no |
242 | +StrictHostKeyChecking no |
243 | +EOF |
244 | + |
245 | +mkdir /var/launchpad |
246 | +chown -R ec2test:ec2test /var/www /var/launchpad /home/ec2test/ |
247 | +""" |
248 | + |
249 | + |
250 | +from_scratch_ec2test = """ |
251 | +# From 'help set': |
252 | +# -x Print commands and their arguments as they are executed. |
253 | +# -e Exit immediately if a command exits with a non-zero status. |
254 | +set -xe |
255 | + |
256 | +bzr launchpad-login %(launchpad-login)s |
257 | +bzr init-repo --2a /var/launchpad |
258 | +bzr branch lp:~launchpad-pqm/launchpad/devel /var/launchpad/test |
259 | +bzr branch --standalone lp:lp-source-dependencies /var/launchpad/download-cache |
260 | +mkdir /var/launchpad/sourcecode |
261 | +/var/launchpad/test/utilities/update-sourcecode /var/launchpad/sourcecode |
262 | +""" |
263 | + |
264 | + |
265 | +postmortem_banner = """\ |
266 | +Postmortem Console. EC2 instance is not yet dead. |
267 | +It will shut down when you exit this prompt (CTRL-D) |
268 | + |
269 | +Tab-completion is enabled. |
270 | +EC2Instance is available as `instance`. |
271 | +Also try these: |
272 | + http://%(dns)s/current_test.log |
273 | + ssh -A ec2test@%(dns)s |
274 | +""" |
275 | + |
276 | + |
277 | +class EC2Instance: |
278 | + """A single EC2 instance.""" |
279 | + |
280 | + @classmethod |
281 | + def make(cls, name, instance_type, machine_id, demo_networks=None, |
282 | + credentials=None): |
283 | + """Construct an `EC2Instance`. |
284 | + |
285 | + :param name: The name to use for the key pair and security group for |
286 | + the instance. |
287 | + :type name: `EC2SessionName` |
288 | + :param instance_type: One of the AVAILABLE_INSTANCE_TYPES. |
289 | + :param machine_id: The AMI to use, or None to do the usual regexp |
290 | + matching. If you put 'based-on:' before the AMI id, it is assumed |
291 | + that the id specifies a blank image that should be made into one |
292 | + suitable for the other ec2 functions (see `from_scratch_root` and |
293 | + `from_scratch_ec2test` above). |
294 | + :param demo_networks: A list of networks to add to the security group |
295 | + to allow access to the instance. |
296 | + :param credentials: An `EC2Credentials` object. |
297 | + """ |
298 | + # This import breaks in the test environment. Do it here so |
299 | + # that unit tests (which don't use this factory) can still |
300 | + # import EC2Instance. |
301 | + from bzrlib.plugins.launchpad.account import get_lp_login |
302 | + |
303 | + # XXX JeroenVermeulen 2009-11-27 bug=489073: EC2Credentials |
304 | + # imports boto, which isn't necessarily installed in our test |
305 | + # environment. Doing the import here so that unit tests (which |
306 | + # don't use this factory) can still import EC2Instance. |
307 | + from devscripts.ec2test.credentials import EC2Credentials |
308 | + |
309 | + assert isinstance(name, EC2SessionName) |
310 | + if instance_type not in AVAILABLE_INSTANCE_TYPES: |
311 | + raise ValueError('unknown instance_type %s' % (instance_type,)) |
312 | + |
313 | + # We call this here so that it has a chance to complain before the |
314 | + # instance is started (which can take some time). |
315 | + user_key = get_user_key() |
316 | + |
317 | + if credentials is None: |
318 | + credentials = EC2Credentials.load_from_file() |
319 | + |
320 | + # Make the EC2 connection. |
321 | + account = credentials.connect(name) |
322 | + |
323 | + # We do this here because it (1) cleans things up and (2) verifies |
324 | + # that the account is correctly set up. Both of these are appropriate |
325 | + # for initialization. |
326 | + # |
327 | + # We always recreate the keypairs because there is no way to |
328 | + # programmatically retrieve the private key component, unless we |
329 | + # generate it. |
330 | + account.collect_garbage() |
331 | + |
332 | + if machine_id and machine_id.startswith('based-on:'): |
333 | + from_scratch = True |
334 | + machine_id = machine_id[len('based-on:'):] |
335 | + else: |
336 | + from_scratch = False |
337 | + |
338 | + # get the image |
339 | + image = account.acquire_image(machine_id) |
340 | + |
341 | + login = get_lp_login() |
342 | + if not login: |
343 | + raise BzrCommandError( |
344 | + 'you must have set your launchpad login in bzr.') |
345 | + |
346 | + return EC2Instance( |
347 | + name, image, instance_type, demo_networks, account, |
348 | + from_scratch, user_key, login) |
349 | + |
350 | + def __init__(self, name, image, instance_type, demo_networks, account, |
351 | + from_scratch, user_key, launchpad_login): |
352 | + self._name = name |
353 | + self._image = image |
354 | + self._account = account |
355 | + self._instance_type = instance_type |
356 | + self._demo_networks = demo_networks |
357 | + self._boto_instance = None |
358 | + self._from_scratch = from_scratch |
359 | + self._user_key = user_key |
360 | + self._launchpad_login = launchpad_login |
361 | + |
362 | + def log(self, msg): |
363 | + """Log a message on stdout, flushing afterwards.""" |
364 | + # XXX: JonathanLange 2009-05-31 bug=383076: Should delete this and use |
365 | + # Python logging module instead. |
366 | + sys.stdout.write(msg) |
367 | + sys.stdout.flush() |
368 | + |
369 | + def start(self): |
370 | + """Start the instance.""" |
371 | + if self._boto_instance is not None: |
372 | + self.log('Instance %s already started' % self._boto_instance.id) |
373 | + return |
374 | + start = time.time() |
375 | + self.private_key = self._account.acquire_private_key() |
376 | + self.security_group = self._account.acquire_security_group( |
377 | + demo_networks=self._demo_networks) |
378 | + reservation = self._image.run( |
379 | + key_name=self._name, security_groups=[self._name], |
380 | + instance_type=self._instance_type) |
381 | + self._boto_instance = reservation.instances[0] |
382 | + self.log('Instance %s starting..' % self._boto_instance.id) |
383 | + while self._boto_instance.state == 'pending': |
384 | + self.log('.') |
385 | + time.sleep(5) |
386 | + self._boto_instance.update() |
387 | + if self._boto_instance.state == 'running': |
388 | + self.log(' started on %s\n' % self.hostname) |
389 | + elapsed = time.time() - start |
390 | + self.log('Started in %d minutes %d seconds\n' % |
391 | + (elapsed // 60, elapsed % 60)) |
392 | + self._output = self._boto_instance.get_console_output() |
393 | + self.log(self._output.output) |
394 | + self._ec2test_user_has_keys = False |
395 | + else: |
396 | + raise BzrCommandError( |
397 | + 'failed to start: %s\n' % self._boto_instance.state) |
398 | + |
399 | + def shutdown(self): |
400 | + """Shut down the instance.""" |
401 | + if self._boto_instance is None: |
402 | + self.log('no instance created\n') |
403 | + return |
404 | + self._boto_instance.update() |
405 | + if self._boto_instance.state not in ('shutting-down', 'terminated'): |
406 | + # terminate instance |
407 | + self._boto_instance.stop() |
408 | + self._boto_instance.update() |
409 | + self.log('instance %s\n' % (self._boto_instance.state,)) |
410 | + |
411 | + @property |
412 | + def hostname(self): |
413 | + if self._boto_instance is None: |
414 | + return None |
415 | + return self._boto_instance.public_dns_name |
416 | + |
417 | + def _connect(self, username): |
418 | + """Connect to the instance as `user`. """ |
419 | + ssh = paramiko.SSHClient() |
420 | + ssh.set_missing_host_key_policy(AcceptAllPolicy()) |
421 | + connect_args = { |
422 | + 'username': username, |
423 | + 'pkey': self.private_key, |
424 | + 'allow_agent': False, |
425 | + 'look_for_keys': False, |
426 | + } |
427 | + for count in range(10): |
428 | + try: |
429 | + ssh.connect(self.hostname, **connect_args) |
430 | + except (socket.error, paramiko.AuthenticationException), e: |
431 | + self.log('_connect: %r\n' % (e,)) |
432 | + if count < 9: |
433 | + time.sleep(5) |
434 | + self.log('retrying...') |
435 | + else: |
436 | + raise |
437 | + else: |
438 | + break |
439 | + return EC2InstanceConnection(self, username, ssh) |
440 | + |
441 | + def _upload_local_key(self, conn, remote_filename): |
442 | + """Upload a key from the local user's agent to `remote_filename`. |
443 | + |
444 | + The key will be uploaded in a format suitable for |
445 | + ~/.ssh/authorized_keys. |
446 | + """ |
447 | + authorized_keys_file = conn.sftp.open(remote_filename, 'w') |
448 | + authorized_keys_file.write( |
449 | + "%s %s\n" % ( |
450 | + self._user_key.get_name(), self._user_key.get_base64())) |
451 | + authorized_keys_file.close() |
452 | + |
453 | + def _ensure_ec2test_user_has_keys(self, connection=None): |
454 | + """Make sure that we can connect over ssh as the 'ec2test' user. |
455 | + |
456 | + We add both the key that was used to start the instance (so |
457 | + _connect('ec2test') works and a key from the locally running ssh agent |
458 | + (so EC2InstanceConnection.run_with_ssh_agent works). |
459 | + """ |
460 | + if not self._ec2test_user_has_keys: |
461 | + if connection is None: |
462 | + connection = self._connect('ubuntu') |
463 | + our_connection = True |
464 | + else: |
465 | + our_connection = False |
466 | + self._upload_local_key(connection, 'local_key') |
467 | + connection.perform( |
468 | + 'cat /home/ubuntu/.ssh/authorized_keys local_key ' |
469 | + '| sudo tee /home/ec2test/.ssh/authorized_keys > /dev/null' |
470 | + '&& rm local_key') |
471 | + connection.perform('sudo chown -R ec2test:ec2test /home/ec2test/') |
472 | + connection.perform('sudo chmod 644 /home/ec2test/.ssh/*') |
473 | + if our_connection: |
474 | + connection.close() |
475 | + self.log( |
476 | + 'You can now use ssh -A ec2test@%s to ' |
477 | + 'log in the instance.\n' % self.hostname) |
478 | + self._ec2test_user_has_keys = True |
479 | + |
480 | + def connect(self): |
481 | + """Connect to the instance as a user with passwordless sudo. |
482 | + |
483 | + This may involve first connecting as root and adding SSH keys to the |
484 | + user's account, and in the case of a from scratch image, it will do a |
485 | + lot of set up. |
486 | + """ |
487 | + if self._from_scratch: |
488 | + ubuntu_connection = self._connect('ubuntu') |
489 | + self._upload_local_key(ubuntu_connection, 'local_key') |
490 | + ubuntu_connection.perform( |
491 | + 'cat local_key >> ~/.ssh/authorized_keys && rm local_key') |
492 | + ubuntu_connection.run_script(from_scratch_root, sudo=True) |
493 | + self._ensure_ec2test_user_has_keys(ubuntu_connection) |
494 | + ubuntu_connection.close() |
495 | + conn = self._connect('ec2test') |
496 | + conn.run_script( |
497 | + from_scratch_ec2test |
498 | + % {'launchpad-login': self._launchpad_login}) |
499 | + self._from_scratch = False |
500 | + return conn |
501 | + self._ensure_ec2test_user_has_keys() |
502 | + return self._connect('ec2test') |
503 | + |
504 | + def _report_traceback(self): |
505 | + """Print traceback.""" |
506 | + traceback.print_exc() |
507 | + |
508 | + def set_up_and_run(self, postmortem, shutdown, func, *args, **kw): |
509 | + """Start, run `func` and then maybe shut down. |
510 | + |
511 | + :param config: A dictionary specifying details of how the instance |
512 | + should be run: |
513 | + :param postmortem: If true, any exceptions will be caught and an |
514 | + interactive session run to allow debugging the problem. |
515 | + :param shutdown: If true, shut down the instance after `func` and |
516 | + postmortem (if any) are completed. |
517 | + :param func: A callable that will be called when the instance is |
518 | + running and a user account has been set up on it. |
519 | + :param args: Passed to `func`. |
520 | + :param kw: Passed to `func`. |
521 | + """ |
522 | + # We ignore the value of the 'shutdown' argument and always shut down |
523 | + # unless `func` returns normally. |
524 | + really_shutdown = True |
525 | + retval = None |
526 | + try: |
527 | + self.start() |
528 | + try: |
529 | + retval = func(*args, **kw) |
530 | + except Exception: |
531 | + # When running in postmortem mode, it is really helpful to see |
532 | + # if there are any exceptions before it waits in the console |
533 | + # (in the finally block), and you can't figure out why it's |
534 | + # broken. |
535 | + self._report_traceback() |
536 | + else: |
537 | + really_shutdown = shutdown |
538 | + finally: |
539 | + try: |
540 | + if postmortem: |
541 | + console = code.InteractiveConsole(locals()) |
542 | + console.interact( |
543 | + postmortem_banner % {'dns': self.hostname}) |
544 | + print 'Postmortem console closed.' |
545 | + finally: |
546 | + if really_shutdown: |
547 | + self.shutdown() |
548 | + return retval |
549 | + |
550 | + def _copy_single_file(self, sftp, local_path, remote_dir): |
551 | + """Copy `local_path` to `remote_dir` on this instance. |
552 | + |
553 | + The name in the remote directory will be that of the local file. |
554 | + |
555 | + :param sftp: A paramiko SFTP object. |
556 | + :param local_path: The local path. |
557 | + :param remote_dir: The directory on the instance to copy into. |
558 | + """ |
559 | + name = os.path.basename(local_path) |
560 | + remote_path = os.path.join(remote_dir, name) |
561 | + remote_file = sftp.open(remote_path, 'w') |
562 | + remote_file.write(open(local_path).read()) |
563 | + remote_file.close() |
564 | + return remote_path |
565 | + |
566 | + def copy_key_and_certificate_to_image(self, sftp): |
567 | + """Copy the AWS private key and certificate to the image. |
568 | + |
569 | + :param sftp: A paramiko SFTP object. |
570 | + """ |
571 | + remote_ec2_dir = '/mnt/ec2' |
572 | + remote_pk = self._copy_single_file( |
573 | + sftp, self.local_pk, remote_ec2_dir) |
574 | + remote_cert = self._copy_single_file( |
575 | + sftp, self.local_cert, remote_ec2_dir) |
576 | + return (remote_pk, remote_cert) |
577 | + |
578 | + def _check_single_glob_match(self, local_dir, pattern, file_kind): |
579 | + """Check that `pattern` matches one file in `local_dir` and return it. |
580 | + |
581 | + :param local_dir: The local directory to look in. |
582 | + :param pattern: The glob patten to match. |
583 | + :param file_kind: The sort of file we're looking for, to be used in |
584 | + error messages. |
585 | + """ |
586 | + pattern = os.path.join(local_dir, pattern) |
587 | + matches = glob.glob(pattern) |
588 | + if len(matches) != 1: |
589 | + raise BzrCommandError( |
590 | + '%r must match a single %s file' % (pattern, file_kind)) |
591 | + return matches[0] |
592 | + |
593 | + def check_bundling_prerequisites(self, name, credentials): |
594 | + """Check, as best we can, that all the files we need to bundle exist. |
595 | + """ |
596 | + if subprocess.call(['which', 'ec2-register']): |
597 | + raise BzrCommandError( |
598 | + '`ec2-register` command not found. ' |
599 | + 'Try `sudo apt-get install ec2-api-tools`.') |
600 | + local_ec2_dir = os.path.expanduser('~/.ec2') |
601 | + if not os.path.exists(local_ec2_dir): |
602 | + raise BzrCommandError( |
603 | + "~/.ec2 must exist and contain aws_user, aws_id, a private " |
604 | + "key file and a certificate.") |
605 | + aws_user_file = os.path.expanduser('~/.ec2/aws_user') |
606 | + if not os.path.exists(aws_user_file): |
607 | + raise BzrCommandError( |
608 | + "~/.ec2/aws_user must exist and contain your numeric AWS id.") |
609 | + self.aws_user = open(aws_user_file).read().strip() |
610 | + self.local_cert = self._check_single_glob_match( |
611 | + local_ec2_dir, 'cert-*.pem', 'certificate') |
612 | + self.local_pk = self._check_single_glob_match( |
613 | + local_ec2_dir, 'pk-*.pem', 'private key') |
614 | + # The bucket `name` needs to exist and be accessible. We create it |
615 | + # here to reserve the name. If the bucket already exists and conforms |
616 | + # to the above requirements, this is a no-op. |
617 | + credentials.connect_s3().create_bucket(name) |
618 | + |
619 | + def bundle(self, name, credentials): |
620 | + """Bundle, upload and register the instance as a new AMI. |
621 | + |
622 | + :param name: The name-to-be of the new AMI. |
623 | + :param credentials: An `EC2Credentials` object. |
624 | + """ |
625 | + connection = self.connect() |
626 | + # See http://is.gd/g1MIT . When we switch to Eucalyptus, we can dump |
627 | + # this installation of the ec2-ami-tools. |
628 | + connection.perform( |
629 | + 'sudo env DEBIAN_FRONTEND=noninteractive ' |
630 | + 'apt-get -y install ec2-ami-tools') |
631 | + connection.perform('rm -f .ssh/authorized_keys') |
632 | + connection.perform('sudo mkdir /mnt/ec2') |
633 | + connection.perform('sudo chown $USER:$USER /mnt/ec2') |
634 | + |
635 | + remote_pk, remote_cert = self.copy_key_and_certificate_to_image( |
636 | + connection.sftp) |
637 | + |
638 | + bundle_dir = os.path.join('/mnt', name) |
639 | + |
640 | + connection.perform('sudo mkdir ' + bundle_dir) |
641 | + connection.perform(' '.join([ |
642 | + 'sudo ec2-bundle-vol', |
643 | + '-d %s' % bundle_dir, |
644 | + '--batch', # Set batch-mode, which doesn't use prompts. |
645 | + '-k %s' % remote_pk, |
646 | + '-c %s' % remote_cert, |
647 | + '-u %s' % self.aws_user, |
648 | + ])) |
649 | + |
650 | + # Assume that the manifest is 'image.manifest.xml', since "image" is |
651 | + # the default prefix. |
652 | + manifest = os.path.join(bundle_dir, 'image.manifest.xml') |
653 | + |
654 | + # Best check that the manifest actually exists though. |
655 | + test = 'test -f %s' % manifest |
656 | + connection.perform(test) |
657 | + |
658 | + connection.perform(' '.join([ |
659 | + 'sudo ec2-upload-bundle', |
660 | + '-b %s' % name, |
661 | + '-m %s' % manifest, |
662 | + '-a %s' % credentials.identifier, |
663 | + '-s %s' % credentials.secret, |
664 | + ])) |
665 | + |
666 | + connection.close() |
667 | + |
668 | + # This is invoked locally. |
669 | + mfilename = os.path.basename(manifest) |
670 | + manifest_path = os.path.join(name, mfilename) |
671 | + |
672 | + env = os.environ.copy() |
673 | + if 'JAVA_HOME' not in os.environ: |
674 | + env['JAVA_HOME'] = '/usr/lib/jvm/default-java' |
675 | + now = datetime.strftime(datetime.utcnow(), "%Y-%m-%d %H:%M:%S UTC") |
676 | + description = "Created %s" % now |
677 | + cmd = [ |
678 | + 'ec2-register', |
679 | + '--private-key=%s' % self.local_pk, |
680 | + '--cert=%s' % self.local_cert, |
681 | + '--name=%s' % (name,), |
682 | + '--description=%s' % description, |
683 | + manifest_path, |
684 | + ] |
685 | + self.log("Executing command: %s" % ' '.join(cmd)) |
686 | + subprocess.check_call(cmd, env=env) |
687 | + |
688 | + |
689 | +class EC2InstanceConnection: |
690 | + """An ssh connection to an `EC2Instance`.""" |
691 | + |
692 | + def __init__(self, instance, username, ssh): |
693 | + self._instance = instance |
694 | + self._username = username |
695 | + self._ssh = ssh |
696 | + self._sftp = None |
697 | + |
698 | + @property |
699 | + def sftp(self): |
700 | + if self._sftp is None: |
701 | + self._sftp = self._ssh.open_sftp() |
702 | + return self._sftp |
703 | + |
704 | + def perform(self, cmd, ignore_failure=False, out=None, err=None): |
705 | + """Perform 'cmd' on server. |
706 | + |
707 | + :param ignore_failure: If False, raise an error on non-zero exit |
708 | + statuses. |
709 | + :param out: A stream to write the output of the remote command to. |
710 | + :param err: A stream to write the error of the remote command to. |
711 | + """ |
712 | + if out is None: |
713 | + out = sys.stdout |
714 | + if err is None: |
715 | + err = sys.stderr |
716 | + self._instance.log( |
717 | + '%s@%s$ %s\n' |
718 | + % (self._username, self._instance._boto_instance.id, cmd)) |
719 | + session = self._ssh.get_transport().open_session() |
720 | + session.exec_command(cmd) |
721 | + session.shutdown_write() |
722 | + while 1: |
723 | + try: |
724 | + select.select([session], [], [], 0.5) |
725 | + except (IOError, select.error), e: |
726 | + if e.errno == errno.EINTR: |
727 | + continue |
728 | + if session.recv_ready(): |
729 | + data = session.recv(4096) |
730 | + if data: |
731 | + out.write(data) |
732 | + out.flush() |
733 | + if session.recv_stderr_ready(): |
734 | + data = session.recv_stderr(4096) |
735 | + if data: |
736 | + err.write(data) |
737 | + err.flush() |
738 | + if session.exit_status_ready(): |
739 | + break |
740 | + session.close() |
741 | + # XXX: JonathanLange 2009-05-31: If the command is killed by a signal |
742 | + # on the remote server, the SSH protocol does not send an exit_status, |
743 | + # it instead sends a different message with the number of the signal |
744 | + # that killed the process. AIUI, this code will fail confusingly if |
745 | + # that happens. |
746 | + res = session.recv_exit_status() |
747 | + if res and not ignore_failure: |
748 | + raise RuntimeError('Command failed: %s' % (cmd,)) |
749 | + return res |
750 | + |
751 | + def run_with_ssh_agent(self, cmd, ignore_failure=False): |
752 | + """Run 'cmd' in a subprocess. |
753 | + |
754 | + Use this to run commands that require local SSH credentials. For |
755 | + example, getting private branches from Launchpad. |
756 | + """ |
757 | + self._instance.log( |
758 | + '%s@%s$ %s\n' |
759 | + % (self._username, self._instance._boto_instance.id, cmd)) |
760 | + call = ['ssh', '-A', self._username + '@' + self._instance.hostname, |
761 | + '-o', 'CheckHostIP no', |
762 | + '-o', 'StrictHostKeyChecking no', |
763 | + '-o', 'UserKnownHostsFile ~/.ec2/known_hosts', |
764 | + cmd] |
765 | + res = subprocess.call(call) |
766 | + if res and not ignore_failure: |
767 | + raise RuntimeError('Command failed: %s' % (cmd,)) |
768 | + return res |
769 | + |
770 | + def run_script(self, script_text, sudo=False): |
771 | + """Upload `script_text` to the instance and run it with bash.""" |
772 | + script = self.sftp.open('script.sh', 'w') |
773 | + script.write(script_text) |
774 | + script.close() |
775 | + cmd = '/bin/bash script.sh' |
776 | + if sudo: |
777 | + cmd = 'sudo ' + cmd |
778 | + self.run_with_ssh_agent(cmd) |
779 | + # At least for mwhudson, the paramiko connection often drops while the |
780 | + # script is running. Reconnect just in case. |
781 | + self.reconnect() |
782 | + self.perform('rm script.sh') |
783 | + |
784 | + def reconnect(self): |
785 | + """Close the connection and reopen it.""" |
786 | + self.close() |
787 | + self._ssh = self._instance._connect(self._username)._ssh |
788 | + |
789 | + def close(self): |
790 | + if self._sftp is not None: |
791 | + self._sftp.close() |
792 | + self._sftp = None |
793 | + self._ssh.close() |
794 | + self._ssh = None |
795 | |
796 | === modified file 'lib/launchpad_loggerhead/app.py' |
797 | === modified file 'lib/lp/services/config/schema-lazr.conf' |
798 | --- lib/lp/services/config/schema-lazr.conf 2016-09-02 18:21:07 +0000 |
799 | +++ lib/lp/services/config/schema-lazr.conf 2016-09-24 06:42:11 +0000 |
800 | @@ -864,6 +864,7 @@ |
801 | # datatype: boolean |
802 | launch: True |
803 | |
804 | +<<<<<<< TREE |
805 | # The root URL of the canonical OpenID provider. This is used when constructing |
806 | # OpenID URLs as user identifiers for external services such as gpgservice. |
807 | # Since these user identifiers must remain constant forever, this setting must |
808 | @@ -878,6 +879,11 @@ |
809 | # for our accounts' OpenID identifiers. We only store the suffix, and |
810 | # the suffix is provided by two providers. |
811 | openid_alternate_provider_roots: none |
812 | +======= |
813 | +# The root URL of the OpenID provider used to log into Launchpad. |
814 | +# datatype: string |
815 | +openid_provider_root: none |
816 | +>>>>>>> MERGE-SOURCE |
817 | |
818 | # If true, the main template will be styled so that it is |
819 | # obvious to the end user that they are using a demo system |
820 | @@ -1700,26 +1706,23 @@ |
821 | # datatype: boolean |
822 | use_https: True |
823 | |
824 | -[vhost.template] |
825 | -# Host name of this virtual host. |
826 | -# This is matched from the incoming Host header, and |
827 | -# also used to put together URLs if rooturl is not provided. |
828 | +# Base hostname for the Launchpad virtual hosts. |
829 | # Example: launchpad.net |
830 | -# datatype: string |
831 | -hostname: none |
832 | - |
833 | -# Alternative host names to match, in addition to |
834 | -# the one given in hostname, comma separated. |
835 | -# Example: wwwww.launchpad.net, www.launchpad.net |
836 | -# datatype: string |
837 | -althostnames: none |
838 | - |
839 | -# Explicit root URL for this virtual host. |
840 | -# If this is not provided, the root URL is calculated |
841 | -# based on the host name. |
842 | -# Example: https://launchpad.net/ |
843 | -# datatype: string |
844 | -rooturl: none |
845 | +base_hostname: none |
846 | + |
847 | +# Custom port for all virtual hosts. |
848 | +# Only used by the test suite. |
849 | +port: none |
850 | + |
851 | +# Hostnames for non-Launchpad virtual hosts. |
852 | +# Presently only used in development and tests. |
853 | +vostok_hostname: none |
854 | +testopenid_hostname: none |
855 | + |
856 | +# Should the apidoc virtual host be enabled? |
857 | +# This is the internal Zope apidoc, not public lazr.restful API docs. |
858 | +enable_apidoc: False |
859 | + |
860 | |
861 | [vhost.mainsite] |
862 | # Can the profile page act as a OpenID delegated identity? |
863 | @@ -1728,6 +1731,7 @@ |
864 | |
865 | [vhost.api] |
866 | |
867 | +<<<<<<< TREE |
868 | [vhost.blueprints] |
869 | |
870 | [vhost.code] |
871 | @@ -1749,6 +1753,11 @@ |
872 | |
873 | # Stubbed Key server for testing purposes. It can serve a restricted set of |
874 | # keys in SKS format. |
875 | +======= |
876 | + |
877 | +# Stubed Key server for test proposes, it's able to serve |
878 | +# in SKS format, a restricted set of keys. |
879 | +>>>>>>> MERGE-SOURCE |
880 | [testkeyserver] |
881 | # Directory to be created to store the pre-installed key-files |
882 | # datatype: string |
883 | |
884 | === modified file 'lib/lp/services/openid/adapters/openid.py' |
885 | --- lib/lp/services/openid/adapters/openid.py 2016-05-24 04:45:38 +0000 |
886 | +++ lib/lp/services/openid/adapters/openid.py 2016-09-24 06:42:11 +0000 |
887 | @@ -10,9 +10,26 @@ |
888 | 'OpenIDPersistentIdentity', |
889 | ] |
890 | |
891 | +<<<<<<< TREE |
892 | from zope.component import adapter |
893 | from zope.interface import implementer |
894 | +======= |
895 | +from zope.component import ( |
896 | + adapter, |
897 | + adapts, |
898 | + ) |
899 | +from zope.interface import ( |
900 | + implementer, |
901 | + implements, |
902 | + ) |
903 | +>>>>>>> MERGE-SOURCE |
904 | |
905 | +<<<<<<< TREE |
906 | +======= |
907 | +from canonical.config import config |
908 | +from canonical.launchpad.interfaces.account import IAccount |
909 | +from canonical.launchpad.interfaces.lpstorm import IStore |
910 | +>>>>>>> MERGE-SOURCE |
911 | from lp.registry.interfaces.person import IPerson |
912 | from lp.services.config import config |
913 | from lp.services.database.interfaces import IStore |
914 | @@ -27,6 +44,7 @@ |
915 | @staticmethod |
916 | def getServiceURL(): |
917 | """The OpenID server URL (/+openid) for the current request.""" |
918 | +<<<<<<< TREE |
919 | return config.launchpad.openid_provider_root + '+openid' |
920 | |
921 | @staticmethod |
922 | @@ -42,6 +60,11 @@ |
923 | |
924 | @adapter(IAccount) |
925 | @implementer(IOpenIDPersistentIdentity) |
926 | +======= |
927 | + return config.openid_provider_root + '+openid' |
928 | + |
929 | + |
930 | +>>>>>>> MERGE-SOURCE |
931 | class OpenIDPersistentIdentity: |
932 | """A persistent OpenID identifier for a user.""" |
933 | |
934 | |
935 | === modified file 'lib/lp/services/webapp/doc/webapp-publication.txt' |
936 | --- lib/lp/services/webapp/doc/webapp-publication.txt 2016-09-21 02:50:41 +0000 |
937 | +++ lib/lp/services/webapp/doc/webapp-publication.txt 2016-09-24 06:42:11 +0000 |
938 | @@ -71,12 +71,24 @@ |
939 | rooturl: http://translations.launchpad.dev/ |
940 | althosts: |
941 | ---- |
942 | +<<<<<<< TREE |
943 | +======= |
944 | + vostok @ vostok.dev |
945 | + rooturl: http://vostok.dev/ |
946 | + althosts: |
947 | + ---- |
948 | +>>>>>>> MERGE-SOURCE |
949 | xmlrpc @ xmlrpc.launchpad.dev |
950 | rooturl: http://xmlrpc.launchpad.dev/ |
951 | althosts: |
952 | ---- |
953 | +<<<<<<< TREE |
954 | xmlrpc_private @ xmlrpc-private.launchpad.dev |
955 | rooturl: http://xmlrpc-private.launchpad.dev/ |
956 | +======= |
957 | + xmlrpc-private @ xmlrpc-private.launchpad.dev |
958 | + rooturl: http://launchpad.dev/ |
959 | +>>>>>>> MERGE-SOURCE |
960 | althosts: |
961 | ---- |
962 | |
963 | @@ -96,6 +108,10 @@ |
964 | localhost |
965 | testopenid.dev |
966 | translations.launchpad.dev |
967 | +<<<<<<< TREE |
968 | +======= |
969 | + vostok.dev |
970 | +>>>>>>> MERGE-SOURCE |
971 | xmlrpc-private.launchpad.dev |
972 | xmlrpc.launchpad.dev |
973 | |
974 | |
975 | === modified file 'lib/lp/services/webapp/login.py' |
976 | === modified file 'lib/lp/services/webapp/metazcml.py' |
977 | --- lib/lp/services/webapp/metazcml.py 2015-10-14 15:22:01 +0000 |
978 | +++ lib/lp/services/webapp/metazcml.py 2016-09-24 06:42:11 +0000 |
979 | @@ -49,10 +49,15 @@ |
980 | from zope.security.proxy import ProxyFactory |
981 | from zope.security.zcml import IPermissionDirective |
982 | |
983 | +<<<<<<< TREE |
984 | from lp.app.interfaces.security import IAuthorization |
985 | from lp.layers import FeedsLayer |
986 | from lp.services.config import config |
987 | from lp.services.webapp.interfaces import ( |
988 | +======= |
989 | +from canonical.launchpad.layers import FeedsLayer |
990 | +from canonical.launchpad.webapp.interfaces import ( |
991 | +>>>>>>> MERGE-SOURCE |
992 | IApplicationMenu, |
993 | ICanonicalUrlData, |
994 | IContextMenu, |
995 | @@ -60,7 +65,13 @@ |
996 | IFavicon, |
997 | INavigationMenu, |
998 | ) |
999 | +<<<<<<< TREE |
1000 | from lp.services.webapp.publisher import RenamedView |
1001 | +======= |
1002 | +from canonical.launchpad.webapp.publisher import RenamedView |
1003 | +from canonical.launchpad.webapp.vhosts import allvhosts |
1004 | +from lp.app.interfaces.security import IAuthorization |
1005 | +>>>>>>> MERGE-SOURCE |
1006 | |
1007 | |
1008 | class IAuthorizationsDirective(Interface): |
1009 | @@ -617,9 +628,7 @@ |
1010 | # supplied -- we don't care about the priority in Launchpad but it |
1011 | # needs to be unique -- and to do nothing if no hostname is |
1012 | # configured for this publisher. |
1013 | - |
1014 | - section = getattr(config.vhost, name, None) |
1015 | - if section is None or section.hostname is None: |
1016 | + if name not in allvhosts.configs: |
1017 | return |
1018 | global _arbitrary_priority |
1019 | if priority is None: |
1020 | |
1021 | === modified file 'lib/lp/services/webapp/servers.py' |
1022 | --- lib/lp/services/webapp/servers.py 2016-09-14 11:13:06 +0000 |
1023 | +++ lib/lp/services/webapp/servers.py 2016-09-24 06:42:11 +0000 |
1024 | @@ -1434,6 +1434,7 @@ |
1025 | ErrorReportRequest): |
1026 | """Request type for doing public XML-RPC in Launchpad.""" |
1027 | |
1028 | +<<<<<<< TREE |
1029 | def getRootURL(self, rootsite): |
1030 | """See IBasicLaunchpadRequest.""" |
1031 | # XML-RPC requests occasionally need to use canonical_url, for |
1032 | @@ -1444,6 +1445,18 @@ |
1033 | rootsite = 'mainsite' |
1034 | return super(PublicXMLRPCRequest, self).getRootURL(rootsite) |
1035 | |
1036 | +======= |
1037 | + def getRootURL(self, rootsite): |
1038 | + """See IBasicLaunchpadRequest.""" |
1039 | + # XML-RPC requests occasionally need to use canonical_url, for |
1040 | + # the likes of sending emails. Until these are tracked down and |
1041 | + # fixed to use mainsite explicitly, replace the XML-RPC root |
1042 | + # URLs with mainsite's, so that URLs are meaningful. |
1043 | + if rootsite in (None, 'xmlrpc', 'xmlrpc-private'): |
1044 | + rootsite = 'mainsite' |
1045 | + return super(PublicXMLRPCRequest, self).getRootURL(rootsite) |
1046 | + |
1047 | +>>>>>>> MERGE-SOURCE |
1048 | def _createResponse(self): |
1049 | return PublicXMLRPCResponse() |
1050 | |
1051 | @@ -1584,7 +1597,7 @@ |
1052 | |
1053 | if private_port is not None: |
1054 | factories.append(XMLRPCRequestPublicationFactory( |
1055 | - 'xmlrpc_private', PrivateXMLRPCRequest, |
1056 | + 'xmlrpc-private', PrivateXMLRPCRequest, |
1057 | PrivateXMLRPCPublication, port=private_port)) |
1058 | |
1059 | # Register those factories, in priority order corresponding to |
1060 | |
1061 | === modified file 'lib/lp/services/webapp/vhosts.py' |
1062 | --- lib/lp/services/webapp/vhosts.py 2015-10-14 15:22:01 +0000 |
1063 | +++ lib/lp/services/webapp/vhosts.py 2016-09-24 06:42:11 +0000 |
1064 | @@ -3,56 +3,28 @@ |
1065 | |
1066 | """Virtual host handling for the Launchpad webapp.""" |
1067 | |
1068 | -__all__ = ['allvhosts'] |
1069 | +__all__ = [ |
1070 | + 'allvhosts', |
1071 | + 'AllVirtualHostsConfiguration', |
1072 | + ] |
1073 | |
1074 | |
1075 | class VirtualHostConfig: |
1076 | """The configuration of a single virtual host.""" |
1077 | |
1078 | - def __init__(self, hostname, althostnames, rooturl, use_https): |
1079 | - if althostnames is None: |
1080 | - althostnames = [] |
1081 | + def __init__(self, hostname, althostnames, use_https, port=None): |
1082 | + if use_https: |
1083 | + protocol = 'https' |
1084 | else: |
1085 | - althostnames = self._hostnameStrToList(althostnames) |
1086 | - |
1087 | - if rooturl is None: |
1088 | - if use_https: |
1089 | - protocol = 'https' |
1090 | - else: |
1091 | - protocol = 'http' |
1092 | - rooturl = '%s://%s/' % (protocol, hostname) |
1093 | + protocol = 'http' |
1094 | + rooturl = ( |
1095 | + '%s://%s%s/' % |
1096 | + (protocol, hostname, (':%d' % port) if port is not None else '')) |
1097 | |
1098 | self.hostname = hostname |
1099 | self.rooturl = rooturl |
1100 | self.althostnames = althostnames |
1101 | |
1102 | - @staticmethod |
1103 | - def _hostnameStrToList(althostnames): |
1104 | - """Return list of hostname strings given a string of althostnames. |
1105 | - |
1106 | - This is to parse althostnames from the launchpad.conf file. |
1107 | - |
1108 | - Basically, it's a comma separated list, but we're quite flexible |
1109 | - about what is accepted. See the examples in the following doctest. |
1110 | - |
1111 | - >>> thismethod = VirtualHostConfig._hostnameStrToList |
1112 | - >>> thismethod('foo') |
1113 | - ['foo'] |
1114 | - >>> thismethod('foo,bar, baz') |
1115 | - ['foo', 'bar', 'baz'] |
1116 | - >>> thismethod('foo,,bar, ,baz ,') |
1117 | - ['foo', 'bar', 'baz'] |
1118 | - >>> thismethod('') |
1119 | - [] |
1120 | - >>> thismethod(' ') |
1121 | - [] |
1122 | - |
1123 | - """ |
1124 | - if not althostnames.strip(): |
1125 | - return [] |
1126 | - return [ |
1127 | - name.strip() for name in althostnames.split(',') if name.strip()] |
1128 | - |
1129 | |
1130 | class AllVirtualHostsConfiguration: |
1131 | """A representation of the virtual hosting configuration for |
1132 | @@ -69,7 +41,7 @@ |
1133 | self.hostnames : set of hostnames handled by the vhost config |
1134 | """ |
1135 | |
1136 | - def __init__(self): |
1137 | + def __init__(self, config=None): |
1138 | """Initialize all virtual host settings from launchpad.conf. |
1139 | |
1140 | launchpad_conf_vhosts: The virtual_hosts config item from |
1141 | @@ -77,27 +49,60 @@ |
1142 | |
1143 | """ |
1144 | self._has_vhost_data = False |
1145 | - |
1146 | + self._config = config |
1147 | + |
1148 | +<<<<<<< TREE |
1149 | +======= |
1150 | + def _addVHost(self, vhost, hostname, use_https=True, althostnames=None): |
1151 | + self._configs[vhost] = VirtualHostConfig( |
1152 | + hostname, althostnames or [], self._use_https and use_https, |
1153 | + self._port) |
1154 | + |
1155 | +>>>>>>> MERGE-SOURCE |
1156 | def _getVHostData(self): |
1157 | - """Parse the vhosts on demand.""" |
1158 | + """Construct the vhosts on demand.""" |
1159 | # Avoid the circular imports inherent with the use of canonical.lazr. |
1160 | if self._has_vhost_data: |
1161 | return |
1162 | +<<<<<<< TREE |
1163 | from lp.services.config import config |
1164 | +======= |
1165 | + if self._config is None: |
1166 | + import canonical.config |
1167 | + self._config = canonical.config.config |
1168 | + config = self._config |
1169 | +>>>>>>> MERGE-SOURCE |
1170 | self._use_https = config.vhosts.use_https |
1171 | + self._port = config.vhosts.port |
1172 | self._configs = {} |
1173 | self._hostnames = set() |
1174 | - for section in config.getByCategory('vhost'): |
1175 | - if section.hostname is None: |
1176 | - continue |
1177 | - category, vhost = section.category_and_section_names |
1178 | - self._configs[vhost] = config = VirtualHostConfig( |
1179 | - section.hostname, |
1180 | - section.althostnames, |
1181 | - section.rooturl, |
1182 | - self._use_https) |
1183 | - self._hostnames.add(config.hostname) |
1184 | - self._hostnames.update(config.althostnames) |
1185 | + |
1186 | + # Most vhosts are just subdomains of base_hostname. |
1187 | + normal_vhosts = [ |
1188 | + 'api', 'xmlrpc', 'xmlrpc-private', |
1189 | + 'answers', 'blueprints', 'bugs', 'code', 'translations'] |
1190 | + if config.vhosts.enable_apidoc: |
1191 | + normal_vhosts.append('apidoc') |
1192 | + for vhost in normal_vhosts: |
1193 | + self._addVHost( |
1194 | + vhost, '%s.%s' % (vhost, config.vhosts.base_hostname)) |
1195 | + # mainsite is base_hostname (eg. launchpad.dev), with an optional |
1196 | + # www subdomain. |
1197 | + self._addVHost( |
1198 | + 'mainsite', config.vhosts.base_hostname, |
1199 | + althostnames=['www.' + config.vhosts.base_hostname, 'localhost']) |
1200 | + # feeds never uses SSL, but is otherwise normal. |
1201 | + self._addVHost( |
1202 | + 'feeds', 'feeds.' + config.vhosts.base_hostname, use_https=False) |
1203 | + |
1204 | + if config.vhosts.testopenid_hostname: |
1205 | + self._addVHost('testopenid', config.vhosts.testopenid_hostname) |
1206 | + if config.vhosts.vostok_hostname: |
1207 | + self._addVHost('vostok', config.vhosts.vostok_hostname) |
1208 | + |
1209 | + for conf in self._configs.itervalues(): |
1210 | + self._hostnames.add(conf.hostname) |
1211 | + self._hostnames.update(conf.althostnames) |
1212 | self._has_vhost_data = True |
1213 | |
1214 | def reload(self): |
1215 | |
1216 | === modified file 'lib/lp/testing/layers.py' |
1217 | --- lib/lp/testing/layers.py 2016-02-22 23:43:33 +0000 |
1218 | +++ lib/lp/testing/layers.py 2016-09-24 06:42:11 +0000 |
1219 | @@ -113,8 +113,35 @@ |
1220 | ConfigFixture, |
1221 | ConfigUseFixture, |
1222 | ) |
1223 | +<<<<<<< TREE |
1224 | from lp.services.database.interfaces import IStore |
1225 | from lp.services.database.sqlbase import session_store |
1226 | +======= |
1227 | +from canonical.database.sqlbase import session_store |
1228 | +from canonical.launchpad.scripts import execute_zcml_for_scripts |
1229 | +from canonical.launchpad.webapp.interfaces import ( |
1230 | + DEFAULT_FLAVOR, |
1231 | + IOpenLaunchBag, |
1232 | + IStoreSelector, |
1233 | + MAIN_STORE, |
1234 | + ) |
1235 | +from canonical.launchpad.webapp.servers import ( |
1236 | + LaunchpadAccessLogger, |
1237 | + register_launchpad_request_publication_factories, |
1238 | + ) |
1239 | +import canonical.launchpad.webapp.session |
1240 | +from canonical.launchpad.webapp.vhosts import AllVirtualHostsConfiguration |
1241 | +from canonical.lazr import pidfile |
1242 | +from canonical.lazr.testing.layers import MockRootFolder |
1243 | +from canonical.lazr.timeout import ( |
1244 | + get_default_timeout_function, |
1245 | + set_default_timeout_function, |
1246 | + ) |
1247 | +from canonical.librarian.testing.server import LibrarianServerFixture |
1248 | +from canonical.testing import reset_logging |
1249 | +from canonical.testing.profiled import profiled |
1250 | +from canonical.testing.smtpd import SMTPController |
1251 | +>>>>>>> MERGE-SOURCE |
1252 | from lp.services.googlesearch.tests.googleserviceharness import ( |
1253 | GoogleServiceTestSetup, |
1254 | ) |
1255 | @@ -1850,7 +1877,8 @@ |
1256 | |
1257 | @classmethod |
1258 | def appserver_root_url(cls): |
1259 | - return cls.appserver_config.vhost.mainsite.rooturl |
1260 | + return AllVirtualHostsConfiguration( |
1261 | + cls.appserver_config).configs['mainsite'].rooturl |
1262 | |
1263 | @classmethod |
1264 | def _waitUntilAppServerIsReady(cls): |
1265 | |
1266 | === modified file 'lib/lp/testing/tests/test_layers_functional.py' |
1267 | --- lib/lp/testing/tests/test_layers_functional.py 2015-10-13 14:01:25 +0000 |
1268 | +++ lib/lp/testing/tests/test_layers_functional.py 2016-09-24 06:42:11 +0000 |
1269 | @@ -32,8 +32,15 @@ |
1270 | getUtility, |
1271 | ) |
1272 | |
1273 | +<<<<<<< TREE |
1274 | from lp.services.config import config |
1275 | from lp.services.librarian.client import ( |
1276 | +======= |
1277 | +from canonical.config import config |
1278 | +from canonical.launchpad.webapp.vhosts import AllVirtualHostsConfiguration |
1279 | +from canonical.lazr.pidfile import pidfile_path |
1280 | +from canonical.librarian.client import ( |
1281 | +>>>>>>> MERGE-SOURCE |
1282 | LibrarianClient, |
1283 | UploadFailed, |
1284 | ) |
1285 | @@ -498,7 +505,8 @@ |
1286 | |
1287 | def testAppServerIsAvailable(self): |
1288 | # Test that the app server is up and running. |
1289 | - mainsite = LayerProcessController.appserver_config.vhost.mainsite |
1290 | + mainsite = AllVirtualHostsConfiguration( |
1291 | + LayerProcessController.appserver_config).configs['mainsite'] |
1292 | home_page = urlopen(mainsite.rooturl).read() |
1293 | self.failUnless( |
1294 | 'Is your project registered yet?' in home_page, |
1295 | |
1296 | === modified file 'utilities/rocketfuel-setup' |