~andrewjbeach/juju-ci-tools/make-local-patcher

« back to all changes in this revision

Viewing changes to candidate.py

  • Committer: Aaron Bentley
  • Date: 2014-02-24 17:18:29 UTC
  • mto: This revision was merged to the branch mainline in revision 252.
  • Revision ID: aaron.bentley@canonical.com-20140224171829-sz644yhoygu7m9dm
Use tags to identify and shut down instances.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python
2
 
"""Manage the blessed juju revision testing candiates."""
3
 
 
4
 
from __future__ import print_function
5
 
 
6
 
from argparse import ArgumentParser
7
 
import datetime
8
 
import json
9
 
import os
10
 
import shutil
11
 
import subprocess
12
 
import sys
13
 
import traceback
14
 
 
15
 
from jujuci import (
16
 
    add_credential_args,
17
 
    BUILD_REVISION,
18
 
    get_build_data,
19
 
    get_artifacts,
20
 
    get_credentials,
21
 
    JENKINS_URL,
22
 
    PUBLISH_REVISION
23
 
)
24
 
from utility import (
25
 
    extract_deb,
26
 
    get_deb_arch,
27
 
    get_revision_build,
28
 
    run_command,
29
 
    s3_cmd,
30
 
    temp_dir,
31
 
)
32
 
 
33
 
 
34
 
def find_publish_revision_number(credentials, br_number, limit=20):
35
 
    """Return the publish-revsion number paired with build-revision number."""
36
 
    found_number = None
37
 
    job_number = 'lastSuccessfulBuild'
38
 
    for i in range(limit):
39
 
        build_data = get_build_data(
40
 
            JENKINS_URL, credentials, PUBLISH_REVISION, build=job_number)
41
 
        if not build_data:
42
 
            return None
43
 
        # Ensure we have the real job number (an int), not an alias.
44
 
        job_number = build_data['number']
45
 
        if get_revision_build(build_data) == str(br_number):
46
 
            found_number = job_number
47
 
            break
48
 
        job_number = job_number - 1
49
 
    return found_number
50
 
 
51
 
 
52
 
def prepare_dir(dir_path, dry_run=False, verbose=False):
53
 
    """Create to clean a directory."""
54
 
    if os.path.isdir(dir_path):
55
 
        if verbose:
56
 
            print('Cleaning %s' % dir_path)
57
 
        if not dry_run:
58
 
            shutil.rmtree(dir_path)
59
 
    else:
60
 
        if verbose:
61
 
            print('Creating %s' % dir_path)
62
 
    if not dry_run:
63
 
        os.makedirs(dir_path)
64
 
 
65
 
 
66
 
def download_candidate_files(credentials, release_number, path, br_number,
67
 
                             pr_number=None, dry_run=False, verbose=False):
68
 
    """Download the files from the build-revision and publish-revision jobs.
69
 
 
70
 
    The buildvars.json for the specific build-revision number is downloaded.
71
 
    All the binary and source packages from the last successful build of
72
 
    publish revision are downloaded.
73
 
    """
74
 
    artifact_dir_name = '%s-artifacts' % release_number
75
 
    candidate_dir = os.path.join(path, artifact_dir_name)
76
 
    prepare_dir(candidate_dir, dry_run, verbose)
77
 
    get_artifacts(
78
 
        credentials, BUILD_REVISION, br_number, 'buildvars.json',
79
 
        candidate_dir, dry_run=dry_run, verbose=verbose)
80
 
    if not pr_number:
81
 
        pr_number = find_publish_revision_number(credentials, br_number)
82
 
    get_artifacts(
83
 
        credentials, PUBLISH_REVISION, pr_number, 'juju-core*', candidate_dir,
84
 
        dry_run=dry_run, verbose=verbose)
85
 
 
86
 
 
87
 
def get_artifact_dirs(path):
88
 
    """List the directories that contain artifacts."""
89
 
    dirs = []
90
 
    for name in os.listdir(path):
91
 
        artifacts_path = os.path.join(path, name)
92
 
        if name.endswith('-artifacts') and os.path.isdir(artifacts_path):
93
 
            dirs.append(name)
94
 
    return dirs
95
 
 
96
 
 
97
 
def get_package(artifacts_path, version):
98
 
    """Return the path to the expected juju-core package for the localhost."""
99
 
    release = subprocess.check_output(['lsb_release', '-sr']).strip()
100
 
    arch = get_deb_arch()
101
 
    package_name = 'juju-core_{}-0ubuntu1~{}.1~juju1_{}.deb'.format(
102
 
        version, release, arch)
103
 
    package_path = os.path.join(artifacts_path, package_name)
104
 
    return package_path
105
 
 
106
 
 
107
 
def extract_candidates(path, dry_run=False, verbose=False):
108
 
    """Extract all the candidate juju binaries for the local machine.
109
 
 
110
 
    Each candidate will be extracted to a directory named after the version
111
 
    the artifacts (packages) were made from. Thus the package that matches
112
 
    the localhost's series and architecture in the master-artifacts/ directory
113
 
    will be extracted to a sibling directory named "master/" The buildvars.json
114
 
    data will be copied to the top of "master" to provide information about
115
 
    the origin of the binaries.
116
 
    """
117
 
    for dir_name in get_artifact_dirs(path):
118
 
        artifacts_path = os.path.join(path, dir_name)
119
 
        buildvars_path = os.path.join(artifacts_path, 'buildvars.json')
120
 
        with open(buildvars_path) as bf:
121
 
            buildvars = json.load(bf)
122
 
        version = buildvars['version']
123
 
        package_path = get_package(artifacts_path, version)
124
 
        candidate_path = os.path.join(path, version)
125
 
        if verbose:
126
 
            print('extracting %s to %s' % (package_path, candidate_path))
127
 
        prepare_dir(candidate_path, dry_run, verbose)
128
 
        if not dry_run:
129
 
            extract_deb(package_path, candidate_path)
130
 
        if verbose:
131
 
            print('Copying %s to %s' % (buildvars_path, candidate_path))
132
 
        if not dry_run:
133
 
            new_path = os.path.join(candidate_path, 'buildvars.json')
134
 
            shutil.copyfile(buildvars_path, new_path)
135
 
            shutil.copystat(buildvars_path, new_path)
136
 
 
137
 
 
138
 
def get_scripts(juju_release_tools=None):
139
 
    """Return a tuple paths to the assemble_script and publish_scripts."""
140
 
    assemble_script = 'assemble-streams.bash'
141
 
    publish_script = 'publish-public-tools.bash'
142
 
    if juju_release_tools:
143
 
        assemble_script = os.path.join(
144
 
            juju_release_tools, assemble_script)
145
 
        publish_script = os.path.join(
146
 
            juju_release_tools, publish_script)
147
 
    return assemble_script, publish_script
148
 
 
149
 
 
150
 
def publish_candidates(path, streams_path,
151
 
                       juju_release_tools=None, dry_run=False, verbose=False):
152
 
    """Assemble and publish weekly streams from the candidates."""
153
 
    timestamp = datetime.datetime.utcnow().strftime('%Y_%m_%dT%H_%M_%S')
154
 
    with temp_dir() as debs_path:
155
 
        for dir_name in get_artifact_dirs(path):
156
 
            artifacts_path = os.path.join(path, dir_name)
157
 
            branch_name = dir_name.split('-')[0]
158
 
            for deb_name in os.listdir(artifacts_path):
159
 
                deb_path = os.path.join(artifacts_path, deb_name)
160
 
                if verbose:
161
 
                    print('Copying %s' % deb_path)
162
 
                new_path = os.path.join(debs_path, deb_name)
163
 
                shutil.copyfile(deb_path, new_path)
164
 
                if deb_name == 'buildvars.json':
165
 
                    # buildvars.json is also in the artifacts_path; copied by
166
 
                    # download_candidate_files(). Set it aside so it can be
167
 
                    # sync'd to S3 as a record of what was published.
168
 
                    buildvar_dir = '{}/weekly/{}/{}'.format(
169
 
                        path, timestamp, branch_name)
170
 
                    if not os.path.isdir(buildvar_dir):
171
 
                        os.makedirs(buildvar_dir)
172
 
                    buildvar_path = '{}/{}'.format(buildvar_dir, deb_name)
173
 
                    shutil.copyfile(deb_path, buildvar_path)
174
 
        assemble_script, publish_script = get_scripts(juju_release_tools)
175
 
        # XXX sinzui 2014-12-01: IGNORE uses the local juju, but when
176
 
        # testing juju's that change generate-tools, we may need to use
177
 
        # the highest version.
178
 
        command = [
179
 
            assemble_script, '-t', debs_path, 'weekly', 'IGNORE',
180
 
            streams_path]
181
 
        run_command(command, dry_run=dry_run, verbose=verbose)
182
 
    publish(streams_path, publish_script, dry_run=dry_run, verbose=verbose)
183
 
    # Sync buildvars.json files out to s3.
184
 
    url = 's3://juju-qa-data/juju-releases/weekly/'
185
 
    s3_path = '{}/weekly/{}'.format(path, timestamp)
186
 
    if verbose:
187
 
        print('Calling s3cmd to sync %s out to %s' % (s3_path, url))
188
 
    if not dry_run:
189
 
        s3_cmd(['sync', s3_path, url])
190
 
    extract_candidates(path, dry_run=dry_run, verbose=verbose)
191
 
 
192
 
 
193
 
def publish(streams_path, publish_script, dry_run=False, verbose=False):
194
 
    juju_dist_path = os.path.join(streams_path, 'juju-dist')
195
 
    command = [publish_script, 'weekly', juju_dist_path, 'cpc']
196
 
    for attempt in range(3):
197
 
        try:
198
 
            run_command(command, dry_run=dry_run, verbose=verbose)
199
 
            break
200
 
        except subprocess.CalledProcessError:
201
 
            # Raise an error when the third attempt fails; the cloud is ill.
202
 
            if attempt == 2:
203
 
                raise
204
 
 
205
 
 
206
 
def parse_args(args=None):
207
 
    """Return the argument parser for this program."""
208
 
    parser = ArgumentParser("Manage the successful Juju CI candidates.")
209
 
    parser.add_argument(
210
 
        '-d', '--dry-run', action='store_true', default=False,
211
 
        help='Do not make changes.')
212
 
    parser.add_argument(
213
 
        '-v', '--verbose', action='store_true', default=False,
214
 
        help='Increase verbosity.')
215
 
    subparsers = parser.add_subparsers(help='sub-command help', dest="command")
216
 
    # ./candidate download -b 1234 master ~/candidate
217
 
    parser_update = subparsers.add_parser(
218
 
        'download', help='download a candidate')
219
 
    parser_update.add_argument(
220
 
        '-b', '--br-number', default='lastSuccessfulBuild',
221
 
        help="The specific build-revision number.")
222
 
    parser_update.add_argument(
223
 
        '-p', '--pr-number',
224
 
        help="The specific publish-revision-revision number.")
225
 
    parser_update.add_argument(
226
 
        'release_number', help='The successfully test branch release number.')
227
 
    parser_update.add_argument(
228
 
        'path', help='The path to save the candiate data to.')
229
 
    add_credential_args(parser_update)
230
 
    # ./candidate extract master ~/candidate
231
 
    parser_extract = subparsers.add_parser(
232
 
        'extract',
233
 
        help='extract candidates that match the local series and arch.')
234
 
    parser_extract.add_argument(
235
 
        'path', help='The path to the candiate data dir.')
236
 
    # ./candidate --juju-release-tools $JUJU_RELEASE_TOOLS \
237
 
    #    publish ~/candidate ~/streams
238
 
    parser_publish = subparsers.add_parser(
239
 
        'publish', help='Publish streams for the candidates')
240
 
    parser_publish.add_argument(
241
 
        '-t', '--juju-release-tools',
242
 
        help='The path to the juju-release-tools dir.')
243
 
    parser_publish.add_argument(
244
 
        'path', help='The path to the candiate data dir.')
245
 
    parser_publish.add_argument(
246
 
        'streams_path', help='The path to the streams data dir.')
247
 
    parsed_args = parser.parse_args(args)
248
 
    return parsed_args, get_credentials(parsed_args)
249
 
 
250
 
 
251
 
def main(argv):
252
 
    """Manage successful Juju CI candiates."""
253
 
    args, credentials = parse_args(argv)
254
 
    try:
255
 
        if args.command == 'download':
256
 
            download_candidate_files(
257
 
                credentials, args.release_number, args.path, args.br_number,
258
 
                args.pr_number, dry_run=args.dry_run, verbose=args.verbose)
259
 
        elif args.command == 'extract':
260
 
            extract_candidates(
261
 
                args.path, dry_run=args.dry_run, verbose=args.verbose)
262
 
        elif args.command == 'publish':
263
 
            publish_candidates(
264
 
                args.path, args.streams_path,
265
 
                juju_release_tools=args.juju_release_tools,
266
 
                dry_run=args.dry_run, verbose=args.verbose)
267
 
    except Exception as e:
268
 
        print(e)
269
 
        if args.verbose:
270
 
            traceback.print_tb(sys.exc_info()[2])
271
 
        return 2
272
 
    if args.verbose:
273
 
        print("Done.")
274
 
    return 0
275
 
 
276
 
 
277
 
if __name__ == '__main__':
278
 
    sys.exit(main(sys.argv[1:]))