Merge lp:~wgrant/launchpad/no-more-vhost-config into lp:launchpad

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
Reviewer Review Type Date Requested Status
Launchpad code reviewers Pending
Review via email: mp+306686@code.launchpad.net
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'