~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
112
    def __init__(self, branch, email=False, file=None, test_options='-vv',
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')
292
            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
293
                raise ValueError(
294
                    'To send email, a remotely accessible smtp_server (and '
295
                    'smtp_username and smtp_password, if necessary) must be '
296
                    'configured in bzr.  See the SMTP server information '
297
                    'here: https://wiki.canonical.com/EmailSetup .')
9636.1.11 by Jonathan Lange
The SMTP config doesn't need to be in vals.
298
            self._smtp_username = config.get_user_option('smtp_username')
299
            self._smtp_password = config.get_user_option('smtp_password')
10542.2.1 by Jelmer Vernooij
Use configured email address in Bazaar as From address.
300
            self._from_email = config.username()
301
            if not self._from_email:
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
302
                raise ValueError(
303
                    'To send email, your bzr email address must be set '
304
                    '(use ``bzr whoami``).')
305
9397.2.5 by Michael Hudson
move a bunch of code from somewhere where it doesn't belong to somewhere else
306
        self._instance = instance
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
307
308
    def log(self, msg):
309
        """Log a message on stdout, flushing afterwards."""
310
        # XXX: JonathanLange 2009-05-31 bug=383076: This should use Python
311
        # logging, rather than printing to stdout.
312
        sys.stdout.write(msg)
313
        sys.stdout.flush()
314
315
    def configure_system(self):
9550.13.2 by Michael Hudson
move towards using a pre-setup account
316
        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'
317
        if self.timeout is not None:
318
            user_connection.perform(
319
                "echo sudo shutdown -h now | at today + %d minutes"
320
                % self.timeout)
9397.1.14 by Michael Hudson
user_p -> as_user, root_p -> as_root
321
        as_user = user_connection.perform
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
322
        # Set up bazaar.conf with smtp information if necessary
323
        if self.email or self.message:
10271.3.3 by Michael Hudson
another fix...
324
            as_user('[ -d .bazaar ] || mkdir .bazaar')
9550.13.34 by Michael Hudson
make using sftp more convenient
325
            bazaar_conf_file = user_connection.sftp.open(
9550.13.40 by Michael Hudson
self-review-ish comments
326
                ".bazaar/bazaar.conf", 'w')
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
327
            bazaar_conf_file.write(
10542.2.1 by Jelmer Vernooij
Use configured email address in Bazaar as From address.
328
                'email = %s\n' % (self._from_email,))
329
            bazaar_conf_file.write(
9636.1.11 by Jonathan Lange
The SMTP config doesn't need to be in vals.
330
                'smtp_server = %s\n' % (self._smtp_server,))
331
            if self._smtp_username:
332
                bazaar_conf_file.write(
333
                    'smtp_username = %s\n' % (self._smtp_username,))
334
            if self._smtp_password:
335
                bazaar_conf_file.write(
336
                    'smtp_password = %s\n' % (self._smtp_password,))
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
337
            bazaar_conf_file.close()
338
        # Copy remote ec2-remote over
339
        self.log('Copying ec2test-remote.py to remote machine.\n')
9550.13.34 by Michael Hudson
make using sftp more convenient
340
        user_connection.sftp.put(
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
341
            os.path.join(os.path.dirname(os.path.realpath(__file__)),
342
                         'ec2test-remote.py'),
343
            '/var/launchpad/ec2test-remote.py')
344
        # Set up launchpad login and email
9636.1.14 by Jonathan Lange
Drop launchpad-login from the vals dictionary.
345
        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
346
        user_connection.close()
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
347
348
    def prepare_tests(self):
9550.13.2 by Michael Hudson
move towards using a pre-setup account
349
        user_connection = self._instance.connect()
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
350
        # 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
351
        user_connection.perform('rm -rf /var/launchpad/test')
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
352
        # Get trunk.
9397.1.3 by Michael Hudson
resolve one of Jono's XXXs: make EC2Instance less stateful
353
        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.
354
            'bzr branch %s /var/launchpad/test' % (self._trunk_branch,))
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
355
        # Merge the branch in.
9636.1.10 by Jonathan Lange
Remove 'branch' from vals, since only the TestRunner uses it.
356
        if self._branch is not None:
9397.1.3 by Michael Hudson
resolve one of Jono's XXXs: make EC2Instance less stateful
357
            user_connection.run_with_ssh_agent(
9636.1.10 by Jonathan Lange
Remove 'branch' from vals, since only the TestRunner uses it.
358
                'cd /var/launchpad/test; bzr merge %s' % (self._branch,))
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
359
        else:
360
            self.log('(Testing trunk, so no branch merge.)')
9550.12.8 by Michael Hudson
null merge trunk r9576 (which reverted merge of this branch)
361
        # get newest sources
362
        user_connection.run_with_ssh_agent(
363
            "/var/launchpad/test/utilities/update-sourcecode "
364
            "/var/launchpad/sourcecode")
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
365
        # Get any new sourcecode branches as requested
366
        for dest, src in self.branches:
367
            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.
368
            # Most sourcecode branches share no history with Launchpad and
369
            # might be in different formats so we "branch --standalone" them
370
            # to avoid terribly slow on-the-fly conversions.  However, neither
371
            # thing is true of canonical-identity or shipit, so we let them
372
            # use the Launchpad repository.
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
373
            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.
374
                standalone = ''
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
375
            else:
9964.1.3 by Francis J. Lacoste
Fix from Michael to test c-i-p and shipit branches.
376
                standalone = '--standalone'
377
            user_connection.run_with_ssh_agent(
378
                'bzr branch %s %s %s' % (standalone, src, fulldest))
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
379
        # 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
380
        p = user_connection.perform
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
381
        p('rm -rf /var/launchpad/tmp')
382
        p('mkdir /var/launchpad/tmp')
9437.3.3 by Michael Hudson
move the sourcecode, no need to copy it!
383
        p('mv /var/launchpad/sourcecode /var/launchpad/tmp/sourcecode')
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
384
        p('mkdir /var/launchpad/tmp/eggs')
9397.1.3 by Michael Hudson
resolve one of Jono's XXXs: make EC2Instance less stateful
385
        user_connection.run_with_ssh_agent(
9437.3.4 by Michael Hudson
update the download-cache we now assume is present on the image
386
            'bzr pull lp:lp-source-dependencies '
387
            '-d /var/launchpad/download-cache')
388
        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
389
        if (self.include_download_cache_changes and
390
            self.download_cache_additions):
391
            root = os.path.realpath(
392
                os.path.join(self.original_branch, 'download-cache'))
393
            for info in self.download_cache_additions:
394
                src = os.path.join(root, info[0])
395
                self.log('Copying %s to remote machine.\n' % (src,))
9550.13.34 by Michael Hudson
make using sftp more convenient
396
                user_connection.sftp.put(
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
397
                    src,
398
                    os.path.join('/var/launchpad/tmp/download-cache', info[0]))
399
        p('/var/launchpad/test/utilities/link-external-sourcecode '
400
          '-p/var/launchpad/tmp -t/var/launchpad/test'),
401
        # set up database
9550.13.29 by Michael Hudson
some more, some less genericity, make the remote user called ec2test
402
        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
403
        p('mkdir -p /var/tmp/launchpad_mailqueue/cur')
404
        p('mkdir -p /var/tmp/launchpad_mailqueue/new')
405
        p('mkdir -p /var/tmp/launchpad_mailqueue/tmp')
406
        p('chmod -R a-w /var/tmp/launchpad_mailqueue/')
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
407
        # close ssh connection
9397.1.3 by Michael Hudson
resolve one of Jono's XXXs: make EC2Instance less stateful
408
        user_connection.close()
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
409
9453.2.12 by Michael Hudson
fooey
410
    def run_demo_server(self):
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
411
        """Turn ec2 instance into a demo server."""
9453.2.7 by Michael Hudson
flesh out demo command
412
        self.configure_system()
413
        self.prepare_tests()
9550.13.2 by Michael Hudson
move towards using a pre-setup account
414
        user_connection = self._instance.connect()
9397.1.3 by Michael Hudson
resolve one of Jono's XXXs: make EC2Instance less stateful
415
        p = user_connection.perform
9453.2.7 by Michael Hudson
flesh out demo command
416
        p('make -C /var/launchpad/test schema')
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
417
        p('mkdir -p /var/tmp/bazaar.launchpad.dev/static')
418
        p('mkdir -p /var/tmp/bazaar.launchpad.dev/mirrors')
419
        p('sudo a2enmod proxy > /dev/null')
420
        p('sudo a2enmod proxy_http > /dev/null')
421
        p('sudo a2enmod rewrite > /dev/null')
422
        p('sudo a2enmod ssl > /dev/null')
423
        p('sudo a2enmod deflate > /dev/null')
424
        p('sudo a2enmod headers > /dev/null')
425
        # Install apache config file.
426
        p('cd /var/launchpad/test/; sudo make install')
427
        # Use raw string to eliminate the need to escape the backslash.
428
        # Put eth0's ip address in the /tmp/ip file.
429
        p(r"ifconfig eth0 | grep 'inet addr' "
430
          r"| sed -re 's/.*addr:([0-9.]*) .*/\1/' > /tmp/ip")
431
        # Replace 127.0.0.88 in Launchpad's apache config file with the
432
        # ip address just stored in the /tmp/ip file. Perl allows for
433
        # inplace editing unlike sed.
434
        p('sudo perl -pi -e "s/127.0.0.88/$(cat /tmp/ip)/g" '
435
          '/etc/apache2/sites-available/local-launchpad')
436
        # Restart apache.
437
        p('sudo /etc/init.d/apache2 restart')
438
        # Build mailman and minified javascript, etc.
439
        p('cd /var/launchpad/test/; make')
440
        # Start launchpad in the background.
441
        p('cd /var/launchpad/test/; make start')
442
        # close ssh connection
9397.1.3 by Michael Hudson
resolve one of Jono's XXXs: make EC2Instance less stateful
443
        user_connection.close()
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
444
445
    def run_tests(self):
9453.2.7 by Michael Hudson
flesh out demo command
446
        self.configure_system()
447
        self.prepare_tests()
9550.13.2 by Michael Hudson
move towards using a pre-setup account
448
        user_connection = self._instance.connect()
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
449
450
        # Make sure we activate the failsafe --shutdown feature.  This will
451
        # make the server shut itself down after the test run completes, or
452
        # if the test harness suffers a critical failure.
453
        cmd = ['python /var/launchpad/ec2test-remote.py --shutdown']
454
455
        # Do we want to email the results to the user?
456
        if self.email:
457
            for email in self.email:
458
                cmd.append("--email='%s'" % (
459
                    email.encode('utf8').encode('string-escape'),))
460
461
        # Do we want to submit the branch to PQM if the tests pass?
462
        if self.message is not None:
463
            cmd.append(
464
                "--submit-pqm-message='%s'" % (
465
                    pickle.dumps(
466
                        self.message).encode(
467
                        'base64').encode('string-escape'),))
468
469
        # Do we want to disconnect the terminal once the test run starts?
470
        if self.headless:
471
            cmd.append('--daemon')
472
473
        # Which branch do we want to test?
9636.1.10 by Jonathan Lange
Remove 'branch' from vals, since only the TestRunner uses it.
474
        if self._branch is not None:
475
            branch = self._branch
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
476
            remote_branch = Branch.open(branch)
477
            branch_revno = remote_branch.revno()
478
        else:
9636.1.9 by Jonathan Lange
Don't store trunk_branch in vals. It's only needed in one place.
479
            branch = self._trunk_branch
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
480
            branch_revno = None
9636.1.2 by Jonathan Lange
More PEP 8 cleanups
481
        cmd.append('--public-branch=%s' % branch)
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
482
        if branch_revno is not None:
483
            cmd.append('--public-branch-revno=%d' % branch_revno)
484
485
        # Add any additional options for ec2test-remote.py
486
        cmd.extend(self.get_remote_test_options())
487
        self.log(
488
            'Running tests... (output is available on '
489
            'http://%s/)\n' % self._instance.hostname)
490
491
        # Try opening a browser pointed at the current test results.
492
        if self.open_browser:
493
            try:
494
                import webbrowser
495
            except ImportError:
496
                self.log("Could not open web browser due to ImportError.")
497
            else:
498
                status = webbrowser.open(self._instance.hostname)
499
                if not status:
500
                    self.log("Could not open web browser.")
501
502
        # Run the remote script!  Our execution will block here until the
503
        # remote side disconnects from the terminal.
9397.1.3 by Michael Hudson
resolve one of Jono's XXXs: make EC2Instance less stateful
504
        user_connection.perform(' '.join(cmd))
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
505
        self._running = True
506
507
        if not self.headless:
508
            # We ran to completion locally, so we'll be in charge of shutting
509
            # down the instance, in case the user has requested a postmortem.
510
            #
511
            # We only have 60 seconds to do this before the remote test
512
            # script shuts the server down automatically.
9397.1.3 by Michael Hudson
resolve one of Jono's XXXs: make EC2Instance less stateful
513
            user_connection.perform(
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
514
                'kill `cat /var/launchpad/ec2test-remote.pid`')
515
516
            # deliver results as requested
517
            if self.file:
518
                self.log(
519
                    'Writing abridged test results to %s.\n' % self.file)
9550.13.34 by Michael Hudson
make using sftp more convenient
520
                user_connection.sftp.get('/var/www/summary.log', self.file)
9397.1.10 by Michael Hudson
more fixes
521
        user_connection.close()
9389.6.5 by Michael Hudson
move EC2Credentials and EC2TestRunner to their own files
522
523
    def get_remote_test_options(self):
524
        """Return the test command that will be passed to ec2test-remote.py.
525
526
        Returns a tuple of command-line options and switches.
527
        """
528
        if '--jscheck' in self.test_options:
529
            # We want to run the JavaScript test suite.
530
            return ('--jscheck',)
531
        else:
532
            # Run the normal testsuite with our Zope testrunner options.
533
            # ec2test-remote.py wants the extra options to be after a double-
534
            # dash.
535
            return ('--', self.test_options)