~launchpad-pqm/launchpad/devel

9389.6.6 by Michael Hudson
move main to its own file, add standard header to all new files
1
# Copyright 2009 Canonical Ltd.  This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
3
9389.6.9 by Michael Hudson
docstrings, __all__s and a little whitespace
4
"""Code to actually run the tests in an EC2 instance."""
9389.6.6 by Michael Hudson
move main to its own file, add standard header to all new files
5
6
__metaclass__ = type
9389.6.9 by Michael Hudson
docstrings, __all__s and a little whitespace
7
__all__ = [
8
    'EC2TestRunner',
9
    'TRUNK_BRANCH',
10
    ]
9389.6.6 by Michael Hudson
move main to its own file, add standard header to all new files
11
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
12
import os
13
import pickle
14
import re
15
import sys
16
17
from bzrlib.branch import Branch
18
from bzrlib.bzrdir import BzrDir
19
from bzrlib.config import GlobalConfig
20
from bzrlib.errors import UncommittedChanges
21
from bzrlib.plugins.pqm.pqm_submit import (
22
    NoPQMSubmissionAddress, PQMSubmission)
23
24
25
TRUNK_BRANCH = 'bzr+ssh://bazaar.launchpad.net/~launchpad-pqm/launchpad/devel'
26
27
28
class UnknownBranchURL(Exception):
29
    """Raised when we try to parse an unrecognized branch url."""
30
31
    def __init__(self, branch_url):
32
        Exception.__init__(
33
            self,
34
            "Couldn't parse '%s', not a Launchpad branch." % (branch_url,))
35
9636.1.2 by Jonathan Lange
More PEP 8 cleanups
36
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
37
def parse_branch_url(branch_url):
38
    """Given the URL of a branch, return its components in a dict."""
39
    _lp_match = re.compile(
40
        r'lp:\~([^/]+)/([^/]+)/([^/]+)$').match
41
    _bazaar_match = re.compile(
42
        r'bzr+ssh://bazaar.launchpad.net/\~([^/]+)/([^/]+)/([^/]+)$').match
43
    match = _lp_match(branch_url)
44
    if match is None:
45
        match = _bazaar_match(branch_url)
46
    if match is None:
47
        raise UnknownBranchURL(branch_url)
48
    owner = match.group(1)
49
    product = match.group(2)
50
    branch = match.group(3)
51
    unique_name = '~%s/%s/%s' % (owner, product, branch)
52
    url = 'bzr+ssh://bazaar.launchpad.net/%s' % (unique_name,)
53
    return dict(
54
        owner=owner, product=product, branch=branch, unique_name=unique_name,
55
        url=url)
56
57
58
def normalize_branch_input(data):
59
    """Given 'data' return a ('dest', 'src') pair.
60
61
    :param data: One of::
62
       - a double of (sourcecode_location, branch_url).
63
         If 'sourcecode_location' is Launchpad, then 'branch_url' can
64
         also be the name of a branch of launchpad owned by
65
         launchpad-pqm.
66
       - a singleton of (branch_url,)
67
       - a singleton of (sourcecode_location,) where
68
         sourcecode_location corresponds to a Launchpad upstream
69
         project as well as a rocketfuel sourcecode location.
70
       - a string which could populate any of the above singletons.
71
72
    :return: ('dest', 'src') where 'dest' is the destination
73
        sourcecode location in the rocketfuel tree and 'src' is the
74
        URL of the branch to put there. The URL can be either a bzr+ssh
75
        URL or the name of a branch of launchpad owned by launchpad-pqm.
76
    """
77
    # XXX: JonathanLange 2009-06-05: Should convert lp: URL branches to
78
    # bzr+ssh:// branches.
79
    if isinstance(data, basestring):
80
        data = (data,)
81
    if len(data) == 2:
82
        # Already in dest, src format.
83
        return data
84
    if len(data) != 1:
85
        raise ValueError(
86
            'invalid argument for ``branches`` argument: %r' %
87
            (data,))
88
    branch_location = data[0]
89
    try:
90
        parsed_url = parse_branch_url(branch_location)
91
    except UnknownBranchURL:
92
        return branch_location, 'lp:%s' % (branch_location,)
93
    return parsed_url['product'], parsed_url['url']
94
95
96
def parse_specified_branches(branches):
97
    """Given 'branches' from the command line, return a sanitized dict.
98
99
    The dict maps sourcecode locations to branch URLs, according to the
100
    rules in `normalize_branch_input`.
101
    """
102
    return dict(map(normalize_branch_input, branches))
103
104
105
class EC2TestRunner:
106
107
    name = 'ec2-test-runner'
108
9397.1.6 by Michael Hudson
fix typo and remove inanity that made the consequence surprising.
109
    message = image = None
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
110
    _running = False
111
10189.6.17 by Jonathan Lange
Get rid of other silly -vv options.
112
    def __init__(self, branch, email=False, file=None, test_options=None,
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
113
                 headless=False, branches=(),
114
                 pqm_message=None, pqm_public_location=None,
9397.2.5 by Michael Hudson
move a bunch of code from somewhere where it doesn't belong to somewhere else
115
                 pqm_submit_location=None,
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
116
                 open_browser=False, pqm_email=None,
9636.1.14 by Jonathan Lange
Drop launchpad-login from the vals dictionary.
117
                 include_download_cache_changes=None, instance=None,
10287.1.2 by Guilherme Salgado
Refactor the ec2 timeout code to make sure it's not used for 'ec2 land'
118
                 launchpad_login=None,
119
                 timeout=None):
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
120
        """Create a new EC2TestRunner.
121
10287.1.2 by Guilherme Salgado
Refactor the ec2 timeout code to make sure it's not used for 'ec2 land'
122
        :param timeout: Number of minutes before we force a shutdown. This is
123
            useful because sometimes the normal instance termination might
124
            fail.
125
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
126
          - original_branch
127
          - test_options
128
          - headless
129
          - include_download_cache_changes
130
          - download_cache_additions
131
          - branches (parses, validates)
132
          - message (after validating PQM submisson)
133
          - email (after validating email capabilities)
134
          - image (after connecting to ec2)
9636.1.14 by Jonathan Lange
Drop launchpad-login from the vals dictionary.
135
          - file
10287.1.2 by Guilherme Salgado
Refactor the ec2 timeout code to make sure it's not used for 'ec2 land'
136
          - timeout
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
137
        """
9636.1.6 by Jonathan Lange
Remove spurious comment.
138
        self.original_branch = branch
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
139
        self.test_options = test_options
140
        self.headless = headless
141
        self.include_download_cache_changes = include_download_cache_changes
142
        self.open_browser = open_browser
9636.1.10 by Jonathan Lange
Remove 'branch' from vals, since only the TestRunner uses it.
143
        self.file = file
9636.1.14 by Jonathan Lange
Drop launchpad-login from the vals dictionary.
144
        self._launchpad_login = launchpad_login
10287.1.2 by Guilherme Salgado
Refactor the ec2 timeout code to make sure it's not used for 'ec2 land'
145
        self.timeout = timeout
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
146
147
        trunk_specified = False
148
        trunk_branch = TRUNK_BRANCH
149
150
        # normalize and validate branches
151
        branches = parse_specified_branches(branches)
152
        try:
153
            launchpad_url = branches.pop('launchpad')
154
        except KeyError:
155
            # No Launchpad branch specified.
156
            pass
157
        else:
158
            try:
159
                parsed_url = parse_branch_url(launchpad_url)
160
            except UnknownBranchURL:
161
                user = 'launchpad-pqm'
162
                src = ('bzr+ssh://bazaar.launchpad.net/'
163
                       '~launchpad-pqm/launchpad/%s' % (launchpad_url,))
164
            else:
165
                user = parsed_url['owner']
166
                src = parsed_url['url']
167
            if user == 'launchpad-pqm':
168
                trunk_specified = True
169
            trunk_branch = src
9636.1.10 by Jonathan Lange
Remove 'branch' from vals, since only the TestRunner uses it.
170
        self._trunk_branch = trunk_branch
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
171
        self.branches = branches.items()
172
173
        # XXX: JonathanLange 2009-05-31: The trunk_specified stuff above and
174
        # the pqm location stuff below are actually doing the equivalent of
175
        # preparing a merge directive. Perhaps we can leverage that to make
176
        # this code simpler.
177
        self.download_cache_additions = None
178
        if branch is None:
179
            config = GlobalConfig()
180
            if pqm_message is not None:
181
                raise ValueError('Cannot submit trunk to pqm.')
182
        else:
183
            (tree,
184
             bzrbranch,
185
             relpath) = BzrDir.open_containing_tree_or_branch(branch)
10542.2.3 by Jelmer Vernooij
Use branch-specific configuration if possible, to allow overriding the SMTP server and from address for a few branches.
186
            config = bzrbranch.get_config()
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
187
188
            if pqm_message is not None or tree is not None:
189
                # if we are going to maybe send a pqm_message, we're going to
190
                # go down this path. Also, even if we are not but this is a
191
                # local branch, we're going to use the PQM machinery to make
192
                # sure that the local branch has been made public, and has all
193
                # working changes there.
194
                if tree is None:
195
                    # remote.  We will make some assumptions.
196
                    if pqm_public_location is None:
197
                        pqm_public_location = branch
198
                    if pqm_submit_location is None:
199
                        pqm_submit_location = trunk_branch
200
                elif pqm_submit_location is None and trunk_specified:
201
                    pqm_submit_location = trunk_branch
202
                # modified from pqm_submit.py
203
                submission = PQMSubmission(
204
                    source_branch=bzrbranch,
205
                    public_location=pqm_public_location,
206
                    message=pqm_message or '',
207
                    submit_location=pqm_submit_location,
208
                    tree=tree)
209
                if tree is not None:
210
                    # this is the part we want to do whether or not we're
211
                    # submitting.
9397.1.11 by Michael Hudson
never meant to commit this bit
212
                    submission.check_tree() # any working changes
213
                    submission.check_public_branch() # everything public
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
214
                    branch = submission.public_location
215
                    if (include_download_cache_changes is None or
216
                        include_download_cache_changes):
217
                        # We need to get the download cache settings
218
                        cache_tree, cache_bzrbranch, cache_relpath = (
219
                            BzrDir.open_containing_tree_or_branch(
220
                                os.path.join(
221
                                    self.original_branch, 'download-cache')))
222
                        cache_tree.lock_read()
223
                        try:
224
                            cache_basis_tree = cache_tree.basis_tree()
225
                            cache_basis_tree.lock_read()
226
                            try:
227
                                delta = cache_tree.changes_from(
228
                                    cache_basis_tree, want_unversioned=True)
229
                                unversioned = [
230
                                    un for un in delta.unversioned
231
                                    if not cache_tree.is_ignored(un[0])]
232
                                added = delta.added
233
                                self.download_cache_additions = (
234
                                    unversioned + added)
235
                            finally:
236
                                cache_basis_tree.unlock()
237
                        finally:
238
                            cache_tree.unlock()
239
                if pqm_message is not None:
240
                    if self.download_cache_additions:
241
                        raise UncommittedChanges(cache_tree)
242
                    # get the submission message
243
                    mail_from = config.get_user_option('pqm_user_email')
244
                    if not mail_from:
245
                        mail_from = config.username()
246
                    # Make sure this isn't unicode
247
                    mail_from = mail_from.encode('utf8')
248
                    if pqm_email is None:
249
                        if tree is None:
250
                            pqm_email = (
251
                                "Launchpad PQM <launchpad@pqm.canonical.com>")
252
                        else:
253
                            pqm_email = config.get_user_option('pqm_email')
254
                    if not pqm_email:
255
                        raise NoPQMSubmissionAddress(bzrbranch)
256
                    mail_to = pqm_email.encode('utf8') # same here
257
                    self.message = submission.to_email(mail_from, mail_to)
258
                elif (self.download_cache_additions and
259
                      self.include_download_cache_changes is None):
260
                    raise UncommittedChanges(
261
                        cache_tree,
262
                        'You must select whether to include download cache '
263
                        'changes (see --include-download-cache-changes and '
264
                        '--ignore-download-cache-changes, -c and -g '
265
                        'respectively), or '
266
                        'commit or remove the files in the download-cache.')
9636.1.10 by Jonathan Lange
Remove 'branch' from vals, since only the TestRunner uses it.
267
        self._branch = branch
268
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
269
        if email is not False:
270
            if email is True:
271
                email = [config.username()]
272
                if not email[0]:
273
                    raise ValueError('cannot find your email address.')
274
            elif isinstance(email, basestring):
275
                email = [email]
276
            else:
277
                tmp = []
278
                for item in email:
279
                    if not isinstance(item, basestring):
280
                        raise ValueError(
9636.1.2 by Jonathan Lange
More PEP 8 cleanups
281
                            'email must be True, False, a string, or a list '
282
                            'of strings')
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
283
                    tmp.append(item)
284
                email = tmp
285
        else:
286
            email = None
287
        self.email = email
288
289
        # Email configuration.
290
        if email is not None or pqm_message is not None:
9636.1.11 by Jonathan Lange
The SMTP config doesn't need to be in vals.
291
            self._smtp_server = config.get_user_option('smtp_server')
10224.3.2 by Martin Pool
Clarify ec2 requirement for an smtp server
292
            # Refuse localhost, because there's no SMTP server _on the actual
293
            # EC2 instance._
9636.1.11 by Jonathan Lange
The SMTP config doesn't need to be in vals.
294
            if self._smtp_server is None or self._smtp_server == 'localhost':
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
295
                raise ValueError(
296
                    'To send email, a remotely accessible smtp_server (and '
297
                    'smtp_username and smtp_password, if necessary) must be '
298
                    'configured in bzr.  See the SMTP server information '
10224.3.2 by Martin Pool
Clarify ec2 requirement for an smtp server
299
                    'here: https://wiki.canonical.com/EmailSetup .'
300
                    'This server must be reachable from the EC2 instance.')
9636.1.11 by Jonathan Lange
The SMTP config doesn't need to be in vals.
301
            self._smtp_username = config.get_user_option('smtp_username')
302
            self._smtp_password = config.get_user_option('smtp_password')
10542.2.1 by Jelmer Vernooij
Use configured email address in Bazaar as From address.
303
            self._from_email = config.username()
304
            if not self._from_email:
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
305
                raise ValueError(
306
                    'To send email, your bzr email address must be set '
307
                    '(use ``bzr whoami``).')
308
9397.2.5 by Michael Hudson
move a bunch of code from somewhere where it doesn't belong to somewhere else
309
        self._instance = instance
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
310
311
    def log(self, msg):
312
        """Log a message on stdout, flushing afterwards."""
313
        # XXX: JonathanLange 2009-05-31 bug=383076: This should use Python
314
        # logging, rather than printing to stdout.
315
        sys.stdout.write(msg)
316
        sys.stdout.flush()
317
318
    def configure_system(self):
9550.13.2 by Michael Hudson
move towards using a pre-setup account
319
        user_connection = self._instance.connect()
10287.1.2 by Guilherme Salgado
Refactor the ec2 timeout code to make sure it's not used for 'ec2 land'
320
        if self.timeout is not None:
11346.1.2 by Maris Fogels
Dropped the fail-safe timeout from 8 hours to 5. Added a comment while I was at it.
321
            # Activate a fail-safe shutdown just in case something goes
322
            # really wrong with the server or suite.
11346.1.3 by Maris Fogels
More comments for the code, based on reviewer feedback.
323
            #
324
            # We need to use a call to /usr/bin/at here instead of a call to
325
            # /sbin/shutdown because the test suite already uses the shutdown
326
            # command after the suite finishes. If we called shutdown
327
            # here, it would prevent the end-of-suite shutdown from executing,
328
            # leaving the server running until the failsafe finally activates.
329
            # See bug 617598 for the details.
11346.1.1 by Maris Fogels
Backed out revision 11224. This fixes a bug where one shutdown call blocks the suite-ending shutdown call from succeeding.
330
            user_connection.perform(
331
                "echo sudo shutdown -h now | at today + %d minutes"
332
                % self.timeout)
9397.1.14 by Michael Hudson
user_p -> as_user, root_p -> as_root
333
        as_user = user_connection.perform
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
334
        # Set up bazaar.conf with smtp information if necessary
335
        if self.email or self.message:
10271.3.3 by Michael Hudson
another fix...
336
            as_user('[ -d .bazaar ] || mkdir .bazaar')
9550.13.34 by Michael Hudson
make using sftp more convenient
337
            bazaar_conf_file = user_connection.sftp.open(
9550.13.40 by Michael Hudson
self-review-ish comments
338
                ".bazaar/bazaar.conf", 'w')
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
339
            bazaar_conf_file.write(
10697.1.1 by Maris Fogels
Fix a bug where unicode email addresses with international characters in them raise an error when they are copied to the remote bazaar.conf via SFTP.
340
                'email = %s\n' % (self._from_email.encode('utf-8'),))
10542.2.1 by Jelmer Vernooij
Use configured email address in Bazaar as From address.
341
            bazaar_conf_file.write(
9636.1.11 by Jonathan Lange
The SMTP config doesn't need to be in vals.
342
                'smtp_server = %s\n' % (self._smtp_server,))
343
            if self._smtp_username:
344
                bazaar_conf_file.write(
345
                    'smtp_username = %s\n' % (self._smtp_username,))
346
            if self._smtp_password:
347
                bazaar_conf_file.write(
348
                    'smtp_password = %s\n' % (self._smtp_password,))
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
349
            bazaar_conf_file.close()
350
        # Copy remote ec2-remote over
10766.6.1 by Jonathan Lange
Give the ec2test-remote script an easily-importable name so we can test it.
351
        self.log('Copying remote.py to remote machine.\n')
9550.13.34 by Michael Hudson
make using sftp more convenient
352
        user_connection.sftp.put(
10766.6.1 by Jonathan Lange
Give the ec2test-remote script an easily-importable name so we can test it.
353
            os.path.join(
354
                os.path.dirname(os.path.realpath(__file__)), 'remote.py'),
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
355
            '/var/launchpad/ec2test-remote.py')
356
        # Set up launchpad login and email
9636.1.14 by Jonathan Lange
Drop launchpad-login from the vals dictionary.
357
        as_user('bzr launchpad-login %s' % (self._launchpad_login,))
9397.1.3 by Michael Hudson
resolve one of Jono's XXXs: make EC2Instance less stateful
358
        user_connection.close()
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
359
360
    def prepare_tests(self):
9550.13.2 by Michael Hudson
move towards using a pre-setup account
361
        user_connection = self._instance.connect()
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
362
        # Clean up the test branch left in the instance image.
9397.1.3 by Michael Hudson
resolve one of Jono's XXXs: make EC2Instance less stateful
363
        user_connection.perform('rm -rf /var/launchpad/test')
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
364
        # Get trunk.
9397.1.3 by Michael Hudson
resolve one of Jono's XXXs: make EC2Instance less stateful
365
        user_connection.run_with_ssh_agent(
9636.1.9 by Jonathan Lange
Don't store trunk_branch in vals. It's only needed in one place.
366
            'bzr branch %s /var/launchpad/test' % (self._trunk_branch,))
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
367
        # Merge the branch in.
9636.1.10 by Jonathan Lange
Remove 'branch' from vals, since only the TestRunner uses it.
368
        if self._branch is not None:
9397.1.3 by Michael Hudson
resolve one of Jono's XXXs: make EC2Instance less stateful
369
            user_connection.run_with_ssh_agent(
9636.1.10 by Jonathan Lange
Remove 'branch' from vals, since only the TestRunner uses it.
370
                'cd /var/launchpad/test; bzr merge %s' % (self._branch,))
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
371
        else:
372
            self.log('(Testing trunk, so no branch merge.)')
9550.12.8 by Michael Hudson
null merge trunk r9576 (which reverted merge of this branch)
373
        # get newest sources
374
        user_connection.run_with_ssh_agent(
375
            "/var/launchpad/test/utilities/update-sourcecode "
376
            "/var/launchpad/sourcecode")
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
377
        # Get any new sourcecode branches as requested
378
        for dest, src in self.branches:
379
            fulldest = os.path.join('/var/launchpad/test/sourcecode', dest)
9964.1.3 by Francis J. Lacoste
Fix from Michael to test c-i-p and shipit branches.
380
            # Most sourcecode branches share no history with Launchpad and
381
            # might be in different formats so we "branch --standalone" them
382
            # to avoid terribly slow on-the-fly conversions.  However, neither
383
            # thing is true of canonical-identity or shipit, so we let them
384
            # use the Launchpad repository.
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
385
            if dest in ('canonical-identity-provider', 'shipit'):
9964.1.3 by Francis J. Lacoste
Fix from Michael to test c-i-p and shipit branches.
386
                standalone = ''
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
387
            else:
9964.1.3 by Francis J. Lacoste
Fix from Michael to test c-i-p and shipit branches.
388
                standalone = '--standalone'
389
            user_connection.run_with_ssh_agent(
390
                'bzr branch %s %s %s' % (standalone, src, fulldest))
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
391
        # prepare fresh copy of sourcecode and buildout sources for building
9397.1.3 by Michael Hudson
resolve one of Jono's XXXs: make EC2Instance less stateful
392
        p = user_connection.perform
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
393
        p('rm -rf /var/launchpad/tmp')
394
        p('mkdir /var/launchpad/tmp')
9437.3.3 by Michael Hudson
move the sourcecode, no need to copy it!
395
        p('mv /var/launchpad/sourcecode /var/launchpad/tmp/sourcecode')
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
396
        p('mkdir /var/launchpad/tmp/eggs')
9397.1.3 by Michael Hudson
resolve one of Jono's XXXs: make EC2Instance less stateful
397
        user_connection.run_with_ssh_agent(
9437.3.4 by Michael Hudson
update the download-cache we now assume is present on the image
398
            'bzr pull lp:lp-source-dependencies '
399
            '-d /var/launchpad/download-cache')
400
        p('mv /var/launchpad/download-cache /var/launchpad/tmp/download-cache')
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
401
        if (self.include_download_cache_changes and
402
            self.download_cache_additions):
403
            root = os.path.realpath(
404
                os.path.join(self.original_branch, 'download-cache'))
405
            for info in self.download_cache_additions:
406
                src = os.path.join(root, info[0])
407
                self.log('Copying %s to remote machine.\n' % (src,))
9550.13.34 by Michael Hudson
make using sftp more convenient
408
                user_connection.sftp.put(
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
409
                    src,
410
                    os.path.join('/var/launchpad/tmp/download-cache', info[0]))
411
        p('/var/launchpad/test/utilities/link-external-sourcecode '
412
          '-p/var/launchpad/tmp -t/var/launchpad/test'),
413
        # set up database
9550.13.29 by Michael Hudson
some more, some less genericity, make the remote user called ec2test
414
        p('/var/launchpad/test/utilities/launchpad-database-setup $USER')
10546.1.2 by Michael Hudson
run tests in ec2 with /var/tmp/launchpad_mailqueue read-only
415
        p('mkdir -p /var/tmp/launchpad_mailqueue/cur')
416
        p('mkdir -p /var/tmp/launchpad_mailqueue/new')
417
        p('mkdir -p /var/tmp/launchpad_mailqueue/tmp')
418
        p('chmod -R a-w /var/tmp/launchpad_mailqueue/')
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
419
        # close ssh connection
9397.1.3 by Michael Hudson
resolve one of Jono's XXXs: make EC2Instance less stateful
420
        user_connection.close()
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
421
9453.2.12 by Michael Hudson
fooey
422
    def run_demo_server(self):
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
423
        """Turn ec2 instance into a demo server."""
9453.2.7 by Michael Hudson
flesh out demo command
424
        self.configure_system()
425
        self.prepare_tests()
9550.13.2 by Michael Hudson
move towards using a pre-setup account
426
        user_connection = self._instance.connect()
9397.1.3 by Michael Hudson
resolve one of Jono's XXXs: make EC2Instance less stateful
427
        p = user_connection.perform
9453.2.7 by Michael Hudson
flesh out demo command
428
        p('make -C /var/launchpad/test schema')
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
429
        p('mkdir -p /var/tmp/bazaar.launchpad.dev/static')
430
        p('mkdir -p /var/tmp/bazaar.launchpad.dev/mirrors')
431
        p('sudo a2enmod proxy > /dev/null')
432
        p('sudo a2enmod proxy_http > /dev/null')
433
        p('sudo a2enmod rewrite > /dev/null')
434
        p('sudo a2enmod ssl > /dev/null')
435
        p('sudo a2enmod deflate > /dev/null')
436
        p('sudo a2enmod headers > /dev/null')
437
        # Install apache config file.
438
        p('cd /var/launchpad/test/; sudo make install')
439
        # Use raw string to eliminate the need to escape the backslash.
440
        # Put eth0's ip address in the /tmp/ip file.
441
        p(r"ifconfig eth0 | grep 'inet addr' "
442
          r"| sed -re 's/.*addr:([0-9.]*) .*/\1/' > /tmp/ip")
443
        # Replace 127.0.0.88 in Launchpad's apache config file with the
444
        # ip address just stored in the /tmp/ip file. Perl allows for
445
        # inplace editing unlike sed.
446
        p('sudo perl -pi -e "s/127.0.0.88/$(cat /tmp/ip)/g" '
447
          '/etc/apache2/sites-available/local-launchpad')
448
        # Restart apache.
449
        p('sudo /etc/init.d/apache2 restart')
450
        # Build mailman and minified javascript, etc.
451
        p('cd /var/launchpad/test/; make')
452
        # Start launchpad in the background.
453
        p('cd /var/launchpad/test/; make start')
454
        # close ssh connection
9397.1.3 by Michael Hudson
resolve one of Jono's XXXs: make EC2Instance less stateful
455
        user_connection.close()
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
456
11128.13.58 by Jonathan Lange
Add a way to get very very close to running the tests.
457
    def _build_command(self):
458
        """Build the command that we'll use to run the tests."""
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
459
        # Make sure we activate the failsafe --shutdown feature.  This will
460
        # make the server shut itself down after the test run completes, or
461
        # if the test harness suffers a critical failure.
462
        cmd = ['python /var/launchpad/ec2test-remote.py --shutdown']
463
464
        # Do we want to email the results to the user?
465
        if self.email:
466
            for email in self.email:
467
                cmd.append("--email='%s'" % (
468
                    email.encode('utf8').encode('string-escape'),))
469
470
        # Do we want to submit the branch to PQM if the tests pass?
471
        if self.message is not None:
472
            cmd.append(
473
                "--submit-pqm-message='%s'" % (
474
                    pickle.dumps(
475
                        self.message).encode(
476
                        'base64').encode('string-escape'),))
477
478
        # Do we want to disconnect the terminal once the test run starts?
479
        if self.headless:
480
            cmd.append('--daemon')
481
482
        # Which branch do we want to test?
9636.1.10 by Jonathan Lange
Remove 'branch' from vals, since only the TestRunner uses it.
483
        if self._branch is not None:
484
            branch = self._branch
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
485
            remote_branch = Branch.open(branch)
486
            branch_revno = remote_branch.revno()
487
        else:
9636.1.9 by Jonathan Lange
Don't store trunk_branch in vals. It's only needed in one place.
488
            branch = self._trunk_branch
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
489
            branch_revno = None
9636.1.2 by Jonathan Lange
More PEP 8 cleanups
490
        cmd.append('--public-branch=%s' % branch)
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
491
        if branch_revno is not None:
492
            cmd.append('--public-branch-revno=%d' % branch_revno)
493
494
        # Add any additional options for ec2test-remote.py
10189.6.5 by Jonathan Lange
Remove the unused JSCheckTestRunner.
495
        cmd.extend(['--', self.test_options])
11128.13.58 by Jonathan Lange
Add a way to get very very close to running the tests.
496
        return ' '.join(cmd)
497
498
    def run_tests(self):
499
        self.configure_system()
500
        self.prepare_tests()
501
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
502
        self.log(
503
            'Running tests... (output is available on '
504
            'http://%s/)\n' % self._instance.hostname)
505
506
        # Try opening a browser pointed at the current test results.
507
        if self.open_browser:
508
            try:
509
                import webbrowser
510
            except ImportError:
511
                self.log("Could not open web browser due to ImportError.")
512
            else:
513
                status = webbrowser.open(self._instance.hostname)
514
                if not status:
515
                    self.log("Could not open web browser.")
516
517
        # Run the remote script!  Our execution will block here until the
518
        # remote side disconnects from the terminal.
11128.13.58 by Jonathan Lange
Add a way to get very very close to running the tests.
519
        cmd = self._build_command()
520
        user_connection = self._instance.connect()
521
        user_connection.perform(cmd)
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
522
        self._running = True
523
524
        if not self.headless:
525
            # We ran to completion locally, so we'll be in charge of shutting
526
            # down the instance, in case the user has requested a postmortem.
527
            #
528
            # We only have 60 seconds to do this before the remote test
529
            # script shuts the server down automatically.
9397.1.3 by Michael Hudson
resolve one of Jono's XXXs: make EC2Instance less stateful
530
            user_connection.perform(
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
531
                'kill `cat /var/launchpad/ec2test-remote.pid`')
532
533
            # deliver results as requested
534
            if self.file:
535
                self.log(
536
                    'Writing abridged test results to %s.\n' % self.file)
9550.13.34 by Michael Hudson
make using sftp more convenient
537
                user_connection.sftp.get('/var/www/summary.log', self.file)
9397.1.10 by Michael Hudson
more fixes
538
        user_connection.close()