Merge lp:~leonardr/launchpad/multiversion-apidoc into lp:launchpad/db-devel

Proposed by Leonard Richardson
Status: Merged
Approved by: Aaron Bentley
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~leonardr/launchpad/multiversion-apidoc
Merge into: lp:launchpad/db-devel
Prerequisite: lp:~leonardr/launchpad/no-mutator-named-operations
Diff against target: 1156 lines (+535/-157)
21 files modified
.bzrignore (+2/-2)
Makefile (+5/-9)
lib/canonical/buildd/debian/launchpad-buildd.init (+1/-1)
lib/canonical/launchpad/pagetests/webservice/apidoc.txt (+17/-3)
lib/canonical/launchpad/pagetests/webservice/multiversion.txt (+32/-0)
lib/canonical/launchpad/rest/configuration.py (+21/-0)
lib/canonical/launchpad/webapp/tests/test_servers.py (+1/-1)
lib/lp/bugs/stories/webservice/xx-bug.txt (+9/-40)
lib/lp/buildmaster/interfaces/buildfarmjobbehavior.py (+1/-1)
lib/lp/buildmaster/model/buildfarmjobbehavior.py (+94/-10)
lib/lp/buildmaster/tests/test_buildfarmjobbehavior.py (+80/-8)
lib/lp/code/model/recipebuilder.py (+11/-2)
lib/lp/scripts/utilities/apiindex.py (+0/-21)
lib/lp/soyuz/doc/buildd-slavescanner.txt (+4/-3)
lib/lp/testing/factory.py (+87/-40)
lib/lp/translations/doc/translationtemplatesbuildbehavior.txt (+41/-12)
lib/lp/translations/model/translationtemplatesbuildbehavior.py (+17/-1)
lib/lp/translations/tests/test_translationtemplatesbuildbehavior.py (+37/-0)
utilities/apidoc-index.pt (+37/-0)
utilities/create-lp-wadl-and-apidoc.py (+35/-0)
versions.cfg (+3/-3)
To merge this branch: bzr merge lp:~leonardr/launchpad/multiversion-apidoc
Reviewer Review Type Date Requested Status
Aaron Bentley (community) Approve
Review via email: mp+21181@code.launchpad.net

Description of the change

This branch makes Launchpad generate an HTML apidoc for every version of the web service, not just the devel version. The apidoc for version "foo" is written to lib/canonical/launchpad/apidoc/foo.html, and index.html is now a short list of links to the version-specific documentation files.

This branch renames create-lp-wadl.py to create-lp-wadl-and-apidoc.py, giving it repsonsibility for converting the WADL into the API doc and generating the index.html. The apiindex.py file is removed. (I couldn't this file used anywhere but the Makefile.)

I would really like feedback on the following issues:

1. Is there a way to put apidoc-index.pt somewhere else? Maybe in lib/canonical/launchpad/templates? I couldn't get this to work with pkg_resources because l.c.l.t isn't a module.
2. Is there a way to look up config/active_versions/$version in the template file? If so, I can get rid of versions_and_descriptions.

This branch still needs a little bit of work--I need to fix up the apidoc test so it tests the new system.

To post a comment you must log in.
Revision history for this message
Leonard Richardson (leonardr) wrote :

The diff is screwed up due to the precursor branch being out-of-date, but here's a paste: http://pastebin.ubuntu.com/393568/

Revision history for this message
Leonard Richardson (leonardr) wrote :

One test on EC2 failed: the title of the generated apidoc is now "About this service", not "Launchpad Web Service API". Our XSLT stylesheet (which is based on Mark Nottingham's general WADL stylesheet) has this code:

        <xsl:variable name="title">
            <xsl:choose>
                <xsl:when test="wadl:doc[@title]">
                    <xsl:value-of select="wadl:doc[@title][1]/@title"/>
                </xsl:when>
                <xsl:otherwise>Launchpad Web Service API</xsl:otherwise>
            </xsl:choose>
        </xsl:variable>

Previously the WADL's <application> tag had no <doc> children, so we used the default. Now there are two <doc> tags, "About this service" and "About version {version}".

This is actually pretty nice; I was afraid I would have to hack the XSLT to add the <doc> tags to the HTML, but it already processes them. The only problem is that the XSLT stylesheet uses the title of the first <doc> tag as the title of the whole document, and ignores the title of all subsequent <doc> tags. I was hoping for this (#1):

---
<h1>Launchpad Web Service API</h1>

<h2>About this service</h2>

The Launchpad web service allows automated clients to access most of the functionality available on the Launchpad web site. For help getting started, see the help wiki.

<h2>About version beta</h2>

This is the first version of the web service ever published. Its end-of-life date is April 2011, the same as the Ubuntu release "Karmic Koala".
---

And upon reflection I think this would be even better (#2):

---
<h1>Launchpad web service, version beta</h1>

The Launchpad web service allows automated clients to access most of the functionality available on the Launchpad web site. For help getting started, see the help wiki.

This is the first version of the web service ever published. Its end-of-life date is April 2011, the same as the Ubuntu release "Karmic Koala".
---

Instead, we have this (#3):

---
<h1>About this service</h1>

The Launchpad web service allows automated clients to access most of the functionality available on the Launchpad web site. For help getting started, see the help wiki.

This is the first version of the web service ever published. Its end-of-life date is April 2011, the same as the Ubuntu release "Karmic Koala".
---

We can change the stylesheet to get #1 and change lazr.restful to get #2. But I think what we have now is good enough that I can file a bug and come back to fix the generated documentation later. What do you think?

Revision history for this message
Leonard Richardson (leonardr) wrote :

And here's another diff against the precursor branch: http://paste.ubuntu.com/395661

Revision history for this message
Aaron Bentley (abentley) wrote :

Please use extract_text if possible. Otherwise, this looks fine.

review: Approve
Revision history for this message
Gary Poster (gary) wrote :

Leonard asked me to take a look at this because of his two specific questions.

1. Is there a way to put apidoc-index.pt somewhere else? Maybe in lib/canonical/launchpad/templates? I couldn't get this to work with pkg_resources because l.c.l.t isn't a module.

The FilePageTemplate just expects a path. I'm relatively confident that you can use os.path fun, combined with module __file__ fun, to do what you describe. However, as we agreed on IRC, I'm fine with what you are doing now.

2. Is there a way to look up config/active_versions/$version in the template file? If so, I can get rid of versions_and_descriptions.

The infrequently used syntax for this is "?," I believe. In other words, if you have a variable named "config" and a variable named "version" that contains the string "beta", "config/active_versions/?version" is supposed to be equivalent to "config/active_versions/beta".

On a separate note, also as we discussed on IRC, this segment from create-lp-wadl-and-apidoc.py looks to me like it ought to be simplified.

1115 + # Finally, create an index.html with links to all the HTML
1116 + # documentation files we just generated.
1117 + template_file = 'apidoc-index.pt'
1118 + template = PageTemplateFile(template_file)
1119 + namespace = template.pt_getContext()
1120 + namespace['config'] = config
1121 + versions_and_descriptions = []
1122 + for version in config.active_versions:
1123 + versions_and_descriptions.append(
1124 + dict(version=version,
1125 + description=config.version_descriptions[version]))
1126 + namespace['versions_and_descriptions'] = versions_and_descriptions
1127 +
1128 + f = open(os.path.join(directory, "index.html"), 'w')
1129 + f.write(template.pt_render(namespace))

This could be simplified to the following:

    template_file = 'apidoc-index.pt'
    template = PageTemplateFile(template_file)
    versions_and_descriptions = []
    for version in config.active_versions:
        versions_and_descriptions.append(
            dict(version=version,
                 description=config.version_descriptions[version]))

    f = open(os.path.join(directory, "index.html"), 'w')
    f.write(template(config=config,
                     versions_and_descriptions=versions_and_descriptions))

Then in your template you would access config and versions_and_descriptions off of the "options" object in the page template.

If you use the ? syntax I mentioned above for options/config/version_descriptions/?version, of course, then this could be further simplified to just this:

    template_file = 'apidoc-index.pt'
    template = PageTemplateFile(template_file)
    f = open(os.path.join(directory, "index.html"), 'w')
    f.write(template(config=config))

That's way nicer, I think.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file '.bzrignore'
--- .bzrignore 2010-03-09 02:42:40 +0000
+++ .bzrignore 2010-03-15 21:11:07 +0000
@@ -30,9 +30,9 @@
30database/sampledata/newsampledata-dev.sql30database/sampledata/newsampledata-dev.sql
31database/sampledata/lintdata.sql31database/sampledata/lintdata.sql
32database/sampledata/lintdata-dev.sql32database/sampledata/lintdata-dev.sql
33lib/canonical/launchpad/apidoc/index.html33lib/canonical/launchpad/apidoc/*.html
34xxx-report.*34xxx-report.*
35lib/canonical/launchpad/apidoc/wadl-development.xml35lib/canonical/launchpad/apidoc/wadl-development-*.xml
36lib/canonical/launchpad/apidoc/wadl-test-playground.xml36lib/canonical/launchpad/apidoc/wadl-test-playground.xml
37lib/canonical/launchpad/icing/build/*37lib/canonical/launchpad/icing/build/*
38lib/canonical/launchpad/icing/combo.css38lib/canonical/launchpad/icing/combo.css
3939
=== modified file 'Makefile'
--- Makefile 2010-03-05 21:47:03 +0000
+++ Makefile 2010-03-15 21:11:07 +0000
@@ -30,7 +30,6 @@
3030
31APIDOC_DIR = lib/canonical/launchpad/apidoc31APIDOC_DIR = lib/canonical/launchpad/apidoc
32WADL_TEMPLATE = $(APIDOC_DIR).tmp/wadl-$(LPCONFIG)-%(version)s.xml32WADL_TEMPLATE = $(APIDOC_DIR).tmp/wadl-$(LPCONFIG)-%(version)s.xml
33DEVEL_WADL_FILE = $(APIDOC_DIR)/wadl-$(LPCONFIG)-devel.xml
34API_INDEX = $(APIDOC_DIR)/index.html33API_INDEX = $(APIDOC_DIR)/index.html
3534
36# Do not add bin/buildout to this list.35# Do not add bin/buildout to this list.
@@ -60,16 +59,12 @@
60hosted_branches: $(PY)59hosted_branches: $(PY)
61 $(PY) ./utilities/make-dummy-hosted-branches60 $(PY) ./utilities/make-dummy-hosted-branches
6261
63$(DEVEL_WADL_FILE): $(BZR_VERSION_INFO)62$(API_INDEX): $(BZR_VERSION_INFO)
64 mkdir $(APIDOC_DIR).tmp63 mkdir $(APIDOC_DIR).tmp
65 LPCONFIG=$(LPCONFIG) $(PY) ./utilities/create-lp-wadl.py "$(WADL_TEMPLATE)"64 LPCONFIG=$(LPCONFIG) $(PY) ./utilities/create-lp-wadl-and-apidoc.py "$(WADL_TEMPLATE)"
66 mv $(APIDOC_DIR).tmp/* $(APIDOC_DIR)65 mv $(APIDOC_DIR).tmp/* $(APIDOC_DIR)
67 rmdir $(APIDOC_DIR).tmp66 rmdir $(APIDOC_DIR).tmp
6867
69$(API_INDEX): $(DEVEL_WADL_FILE)
70 bin/apiindex $(DEVEL_WADL_FILE) > $@.tmp
71 mv $@.tmp $@
72
73apidoc: compile $(API_INDEX)68apidoc: compile $(API_INDEX)
7469
75check_merge: $(PY)70check_merge: $(PY)
@@ -259,7 +254,7 @@
259$(BZR_VERSION_INFO):254$(BZR_VERSION_INFO):
260 scripts/update-bzr-version-info.sh255 scripts/update-bzr-version-info.sh
261256
262support_files: $(DEVEL_WADL_FILE) $(BZR_VERSION_INFO)257support_files: $(API_INDEX) $(BZR_VERSION_INFO)
263258
264# Intended for use on developer machines259# Intended for use on developer machines
265start: inplace stop support_files initscript-start260start: inplace stop support_files initscript-start
@@ -330,9 +325,10 @@
330 $(RM) -r $(CODEHOSTING_ROOT)325 $(RM) -r $(CODEHOSTING_ROOT)
331 mv $(APIDOC_DIR)/wadl-testrunner-devel.xml \326 mv $(APIDOC_DIR)/wadl-testrunner-devel.xml \
332 $(APIDOC_DIR)/wadl-testrunner-devel.xml.bak327 $(APIDOC_DIR)/wadl-testrunner-devel.xml.bak
333 $(RM) $(APIDOC_DIR)/wadl*.xml $(API_INDEX)328 $(RM) $(APIDOC_DIR)/wadl*.xml $(APIDOC_DIR)/*.html
334 mv $(APIDOC_DIR)/wadl-testrunner-devel.xml.bak \329 mv $(APIDOC_DIR)/wadl-testrunner-devel.xml.bak \
335 $(APIDOC_DIR)/wadl-testrunner-devel.xml330 $(APIDOC_DIR)/wadl-testrunner-devel.xml
331 $(RM) -rf $(APIDOC_DIR).tmp
336 $(RM) $(BZR_VERSION_INFO)332 $(RM) $(BZR_VERSION_INFO)
337 $(RM) _pythonpath.py333 $(RM) _pythonpath.py
338 $(RM) -rf \334 $(RM) -rf \
339335
=== modified file 'lib/canonical/buildd/debian/launchpad-buildd.init'
--- lib/canonical/buildd/debian/launchpad-buildd.init 2009-12-16 00:16:24 +0000
+++ lib/canonical/buildd/debian/launchpad-buildd.init 2010-03-15 21:11:07 +0000
@@ -29,7 +29,7 @@
29 CONF=$129 CONF=$1
30 PIDFILE="$PIDROOT"/"$CONF".pid30 PIDFILE="$PIDROOT"/"$CONF".pid
31 LOGFILE="$LOGROOT"/"$CONF".log31 LOGFILE="$LOGROOT"/"$CONF".log
32 su - buildd -c "BUILDD_SLAVE_CONFIG=$CONFROOT/$CONF PYTHONPATH=/usr/share/launchpad-buildd twistd --no_save --pidfile $PIDFILE --python $TACFILE --logfile $LOGFILE"32 su - buildd -c "BUILDD_SLAVE_CONFIG=$CONFROOT/$CONF PYTHONPATH=/usr/share/launchpad-buildd twistd --no_save --pidfile $PIDFILE --python $TACFILE --logfile $LOGFILE --umask 022"
33}33}
3434
35#35#
3636
=== modified file 'lib/canonical/launchpad/pagetests/webservice/apidoc.txt'
--- lib/canonical/launchpad/pagetests/webservice/apidoc.txt 2008-07-31 21:23:02 +0000
+++ lib/canonical/launchpad/pagetests/webservice/apidoc.txt 2010-03-15 21:11:07 +0000
@@ -1,9 +1,23 @@
1= API Documentation =1= API Documentation =
22
3The up-to-date Launchpad API documentation is available from3The Launchpad API documentation portal is available at
4http://launchpad.dev/+apidoc.4http://launchpad.dev/+apidoc. It contains summaries of the different
5web service versions, and links to version-specific documents.
56
6 >>> browser.open('http://launchpad.dev/+apidoc')7 >>> browser.open('http://launchpad.dev/+apidoc')
8 >>> print extract_text(browser.contents)
9 Launchpad Web Service API
10 Launchpad web service API documentation
11 ...
12 Active versions
13 ...
14 devel: This version of the web service reflects the most recent...
15 ...
16
17The documentation for a specific version is located at
18http://launchpad.dev/+apidoc/{version}.html.
19
20 >>> browser.open('http://launchpad.dev/+apidoc/devel.html')
7 >>> print browser.title21 >>> print browser.title
8 Launchpad Web Service API22 About this service
923
1024
=== added file 'lib/canonical/launchpad/pagetests/webservice/multiversion.txt'
--- lib/canonical/launchpad/pagetests/webservice/multiversion.txt 1970-01-01 00:00:00 +0000
+++ lib/canonical/launchpad/pagetests/webservice/multiversion.txt 2010-03-15 21:11:07 +0000
@@ -0,0 +1,32 @@
1Differences between versions
2----------------------------
3
4In the 'beta' version of the web service, mutator methods like
5IBugTask.transitionToStatus are published as named operations. In
6subsequent versions, those named operations are not published.
7
8 >>> def get_bugtask_path(version):
9 ... bug_one = webservice.get("/bugs/1", api_version=version).jsonBody()
10 ... bug_one_bugtasks_url = bug_one['bug_tasks_collection_link']
11 ... bug_one_bugtasks = sorted(webservice.get(
12 ... bug_one_bugtasks_url).jsonBody()['entries'])
13 ... bugtask_path = bug_one_bugtasks[0]['self_link']
14 ... return bugtask_path
15
16Here's the 'beta' version, where the named operation works.
17
18 >>> url = get_bugtask_path('beta')
19 >>> print webservice.named_post(
20 ... url, 'transitionToImportance', importance='Low',
21 ... api_version='beta')
22 HTTP/1.1 200 Ok
23 ...
24
25Now let's try the same thing in the '1.0' version, where it fails.
26
27 >>> url = get_bugtask_path('1.0')
28 >>> print webservice.named_post(
29 ... url, 'transitionToImportance', importance='Low',
30 ... api_version='devel')
31 HTTP/1.1 405 Method Not Allowed
32 ...
033
=== modified file 'lib/canonical/launchpad/rest/configuration.py'
--- lib/canonical/launchpad/rest/configuration.py 2010-02-15 02:09:32 +0000
+++ lib/canonical/launchpad/rest/configuration.py 2010-03-15 21:11:07 +0000
@@ -24,9 +24,30 @@
2424
25 path_override = "api"25 path_override = "api"
26 active_versions = ["beta", "1.0", "devel"]26 active_versions = ["beta", "1.0", "devel"]
27 last_version_with_mutator_named_operations = "beta"
27 view_permission = "launchpad.View"28 view_permission = "launchpad.View"
28 set_hop_by_hop_headers = True29 set_hop_by_hop_headers = True
2930
31 service_description = """The Launchpad web service allows automated
32 clients to access most of the functionality available on the
33 Launchpad web site. For help getting started, see
34 <a href="https://help.launchpad.net/API/">the help wiki.</a>"""
35
36 version_descriptions = {
37 "beta": """This is the first version of the web service ever
38 published. Its end-of-life date is April 2011, the same as the
39 Ubuntu release "Karmic Koala".""",
40
41 "1.0": """This version of the web service removes unnecessary
42 named operations. It was introduced in March 2010, and its
43 end-of-life date is April 2015, the same as the server version
44 of the Ubuntu release "Lucid Lynx".""",
45
46 "devel": """This version of the web service reflects the most
47 recent changes made. It may abruptly change without
48 warning. Periodically, these changes are bundled up and given a
49 permanent version number.""" }
50
30 @property51 @property
31 def use_https(self):52 def use_https(self):
32 return config.vhosts.use_https53 return config.vhosts.use_https
3354
=== modified file 'lib/canonical/launchpad/webapp/tests/test_servers.py'
--- lib/canonical/launchpad/webapp/tests/test_servers.py 2010-02-24 14:45:33 +0000
+++ lib/canonical/launchpad/webapp/tests/test_servers.py 2010-03-15 21:11:07 +0000
@@ -14,7 +14,7 @@
14from lazr.restful.interfaces import (14from lazr.restful.interfaces import (
15 IServiceRootResource, IWebServiceConfiguration)15 IServiceRootResource, IWebServiceConfiguration)
16from lazr.restful.simple import RootResource16from lazr.restful.simple import RootResource
17from lazr.restful.tests.test_webservice import (17from lazr.restful.testing.webservice import (
18 IGenericCollection, IGenericEntry, WebServiceTestCase)18 IGenericCollection, IGenericEntry, WebServiceTestCase)
1919
20from lp.testing import TestCase20from lp.testing import TestCase
2121
=== modified file 'lib/lp/bugs/stories/webservice/xx-bug.txt'
--- lib/lp/bugs/stories/webservice/xx-bug.txt 2010-03-10 14:14:38 +0000
+++ lib/lp/bugs/stories/webservice/xx-bug.txt 2010-03-15 21:11:07 +0000
@@ -357,7 +357,7 @@
357 http://.../~cprov357 http://.../~cprov
358358
359359
360The task's importance can be modified directly...360The task's importance can be modified directly.
361361
362 >>> body = webservice.get(bugtask_path).jsonBody()362 >>> body = webservice.get(bugtask_path).jsonBody()
363 >>> body['importance']363 >>> body['importance']
@@ -371,20 +371,6 @@
371 >>> body['importance']371 >>> body['importance']
372 u'High'372 u'High'
373373
374...or through a named operation.
375
376 >>> print webservice.named_post(
377 ... bugtask_path, 'transitionToImportance', importance='Low')
378 HTTP/1.1 200 Ok
379 ...
380 Content-Type: application/json
381 ...
382 <BLANKLINE>
383 null
384 >>> body = webservice.get(bugtask_path).jsonBody()
385 >>> body['importance']
386 u'Low'
387
388Only bug supervisors or people who can otherwise edit the bugtask's374Only bug supervisors or people who can otherwise edit the bugtask's
389pillar are authorised to edit the importance.375pillar are authorised to edit the importance.
390376
@@ -394,10 +380,9 @@
394380
395 >>> body = webservice.get(bugtask_path).jsonBody()381 >>> body = webservice.get(bugtask_path).jsonBody()
396 >>> body['importance']382 >>> body['importance']
397 u'Low'383 u'High'
398384
399The task's status is another aspect of the task that can be modified385The task's status can also be modified directly.
400either directly or through a named operation.
401386
402 >>> print webservice.get(bugtask_path).jsonBody()['status']387 >>> print webservice.get(bugtask_path).jsonBody()['status']
403 Confirmed388 Confirmed
@@ -409,21 +394,14 @@
409 >>> print webservice.get(bugtask_path).jsonBody()['status']394 >>> print webservice.get(bugtask_path).jsonBody()['status']
410 Fix Committed395 Fix Committed
411396
412 >>> print webservice.named_post(
413 ... bugtask_path, 'transitionToStatus', status='New')
414 HTTP/1.1 200 Ok...
415
416 >>> print webservice.get(bugtask_path).jsonBody()['status']
417 New
418
419If an error occurs during a request that sets both 'status' and397If an error occurs during a request that sets both 'status' and
420'importance', neither one will be set.398'importance', neither one will be set.
421399
422 >>> task = webservice.get(bugtask_path).jsonBody()400 >>> task = webservice.get(bugtask_path).jsonBody()
423 >>> print task['status']401 >>> print task['status']
424 New402 Fix Committed
425 >>> print task['importance']403 >>> print task['importance']
426 Low404 High
427405
428 >>> patch = {u'importance': 'High', u'status': u'No Such Status'}406 >>> patch = {u'importance': 'High', u'status': u'No Such Status'}
429 >>> print webservice.patch(bugtask_path, 'application/json', dumps(patch))407 >>> print webservice.patch(bugtask_path, 'application/json', dumps(patch))
@@ -431,12 +409,11 @@
431409
432 >>> task = webservice.get(bugtask_path).jsonBody()410 >>> task = webservice.get(bugtask_path).jsonBody()
433 >>> print task['status']411 >>> print task['status']
434 New412 Fix Committed
435 >>> print task['importance']413 >>> print task['importance']
436 Low414 High
437415
438Like the importance, the milestone can be set either directly or via a416The milestone can only be set by appropriately privileged users.
439named operation, and only by appropriately privileged users.
440417
441 >>> print webservice.get(bugtask_path).jsonBody()['milestone_link']418 >>> print webservice.get(bugtask_path).jsonBody()['milestone_link']
442 None419 None
@@ -449,20 +426,12 @@
449 >>> print webservice.get(bugtask_path).jsonBody()['milestone_link']426 >>> print webservice.get(bugtask_path).jsonBody()['milestone_link']
450 http://.../debian/+milestone/3.1427 http://.../debian/+milestone/3.1
451428
452 >>> print webservice.named_post(
453 ... bugtask_path, 'transitionToMilestone',
454 ... milestone=webservice.getAbsoluteUrl('/debian/+milestone/3.1-rc1'))
455 HTTP/1.1 200 Ok...
456
457 >>> print webservice.get(bugtask_path).jsonBody()['milestone_link']
458 http://.../debian/+milestone/3.1-rc1
459
460 >>> print user_webservice.patch(429 >>> print user_webservice.patch(
461 ... bugtask_path, 'application/json', dumps(patch))430 ... bugtask_path, 'application/json', dumps(patch))
462 HTTP/1.1 401 Unauthorized...431 HTTP/1.1 401 Unauthorized...
463432
464 >>> print webservice.get(bugtask_path).jsonBody()['milestone_link']433 >>> print webservice.get(bugtask_path).jsonBody()['milestone_link']
465 http://.../debian/+milestone/3.1-rc1434 http://.../debian/+milestone/3.1
466435
467We can change the task's target. Here we change the task's target from436We can change the task's target. Here we change the task's target from
468the mozilla-firefox package to alsa-utils.437the mozilla-firefox package to alsa-utils.
469438
=== modified file 'lib/lp/buildmaster/interfaces/buildfarmjobbehavior.py'
--- lib/lp/buildmaster/interfaces/buildfarmjobbehavior.py 2010-01-21 05:03:16 +0000
+++ lib/lp/buildmaster/interfaces/buildfarmjobbehavior.py 2010-03-15 21:11:07 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4# pylint: disable-msg=E0211,E02134# pylint: disable-msg=E0211,E0213
55
=== modified file 'lib/lp/buildmaster/model/buildfarmjobbehavior.py'
--- lib/lp/buildmaster/model/buildfarmjobbehavior.py 2010-03-05 13:52:32 +0000
+++ lib/lp/buildmaster/model/buildfarmjobbehavior.py 2010-03-15 21:11:07 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4# pylint: disable-msg=E0211,E02134# pylint: disable-msg=E0211,E0213
@@ -17,17 +17,22 @@
17import xmlrpclib17import xmlrpclib
1818
19from sqlobject import SQLObjectNotFound19from sqlobject import SQLObjectNotFound
20
20from zope.component import getUtility21from zope.component import getUtility
21from zope.interface import implements22from zope.interface import implements
22from zope.security.proxy import removeSecurityProxy23from zope.security.proxy import removeSecurityProxy, isinstance as zisinstance
2324
24from canonical import encoding25from canonical import encoding
25from canonical.librarian.interfaces import ILibrarianClient26from canonical.librarian.interfaces import ILibrarianClient
27
28from canonical.launchpad.webapp.interfaces import NotFoundError
26from lp.buildmaster.interfaces.builder import CorruptBuildID29from lp.buildmaster.interfaces.builder import CorruptBuildID
27from lp.buildmaster.interfaces.buildfarmjobbehavior import (30from lp.buildmaster.interfaces.buildfarmjobbehavior import (
28 BuildBehaviorMismatch, IBuildFarmJobBehavior)31 BuildBehaviorMismatch, IBuildFarmJobBehavior)
29from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet32from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet
33from lp.buildmaster.model.buildqueue import BuildQueue
30from lp.services.job.interfaces.job import JobStatus34from lp.services.job.interfaces.job import JobStatus
35from lp.soyuz.interfaces.build import IBuildSet
3136
3237
33class BuildFarmJobBehaviorBase:38class BuildFarmJobBehaviorBase:
@@ -59,22 +64,101 @@
59 The default behavior is that we don't add any extra values."""64 The default behavior is that we don't add any extra values."""
60 return {}65 return {}
6166
67 def _helpVerifyBuildIDComponent(self, raw_id, item_type, finder):
68 """Helper for verifying parts of a `BuildFarmJob` name.
69
70 Different `IBuildFarmJob` implementations can have different
71 ways of constructing their identifying names. The names are
72 produced by `IBuildFarmJob.getName` and verified by
73 `IBuildFarmJobBehavior.verifySlaveBuildID`.
74
75 This little helper makes it easier to verify an object id
76 embedded in that name, check that it's a valid number, and
77 retrieve the associated database object.
78
79 :param raw_id: An unverified id string as extracted from the
80 build name. The method will verify that it is a number, and
81 try to retrieve the associated object.
82 :param item_type: The type of object this id represents. Should
83 be a class.
84 :param finder: A function that, given an integral id, finds the
85 associated object of type `item_type`.
86 :raise CorruptBuildID: If `raw_id` is malformed in some way or
87 the associated `item_type` object is not found.
88 :return: An object that is an instance of `item_type`.
89 """
90 type_name = item_type.__name__
91 try:
92 numeric_id = int(raw_id)
93 except ValueError:
94 raise CorruptBuildID(
95 "%s ID is not a number: '%s'" % (type_name, raw_id))
96
97 try:
98 item = finder(numeric_id)
99 except (NotFoundError, SQLObjectNotFound), reason:
100 raise CorruptBuildID(
101 "%s %d is not available: %s" % (
102 type_name, numeric_id, reason))
103 except Exception, reason:
104 raise CorruptBuildID(
105 "Error while looking up %s %d: %s" % (
106 type_name, numeric_id, reason))
107
108 if item is None:
109 raise CorruptBuildID("There is no %s with id %d." % (
110 type_name, numeric_id))
111
112 assert zisinstance(item, item_type), (
113 "Looked for %s, but found %s." % (type_name, repr(item)))
114
115 return item
116
117 def getVerifiedBuild(self, raw_id):
118 """Verify and retrieve the `Build` component of a slave build id.
119
120 This does part of the verification for `verifySlaveBuildID`.
121
122 By default, a `BuildFarmJob` has an identifying name of the form
123 "b-q", where b is the id of its `Build` and q is the id of its
124 `BuildQueue` record.
125
126 Use `getVerifiedBuild` to verify the "b" part, and retrieve the
127 associated `Build`.
128 """
129 # Avoid circular import.
130 from lp.soyuz.model.build import Build
131
132 return self._helpVerifyBuildIDComponent(
133 raw_id, Build, getUtility(IBuildSet).getByBuildID)
134
135 def getVerifiedBuildQueue(self, raw_id):
136 """Verify and retrieve the `BuildQueue` component of a slave build id.
137
138 This does part of the verification for `verifySlaveBuildID`.
139
140 By default, a `BuildFarmJob` has an identifying name of the form
141 "b-q", where b is the id of its `Build` and q is the id of its
142 `BuildQueue` record.
143
144 Use `getVerifiedBuildQueue` to verify the "q" part, and retrieve
145 the associated `BuildQueue` object.
146 """
147 return self._helpVerifyBuildIDComponent(
148 raw_id, BuildQueue, getUtility(IBuildQueueSet).get)
149
62 def verifySlaveBuildID(self, slave_build_id):150 def verifySlaveBuildID(self, slave_build_id):
63 """See `IBuildFarmJobBehavior`."""151 """See `IBuildFarmJobBehavior`."""
64 # Extract information from the identifier.152 # Extract information from the identifier.
65 try:153 try:
66 build_id, queue_item_id = slave_build_id.split('-')154 build_id, queue_item_id = slave_build_id.split('-')
67 build_id = int(build_id)
68 queue_item_id = int(queue_item_id)
69 except ValueError:155 except ValueError:
70 raise CorruptBuildID('Malformed build ID')156 raise CorruptBuildID('Malformed build ID')
157
158 build = self.getVerifiedBuild(build_id)
159 queue_item = self.getVerifiedBuildQueue(queue_item_id)
71160
72 try:161 if build != queue_item.specific_job.build:
73 queue_item = getUtility(IBuildQueueSet).get(queue_item_id)
74 # Check whether build and buildqueue are properly related.
75 except SQLObjectNotFound, reason:
76 raise CorruptBuildID(str(reason))
77 if queue_item.specific_job.build.id != build_id:
78 raise CorruptBuildID('Job build entry mismatch')162 raise CorruptBuildID('Job build entry mismatch')
79163
80 def updateBuild(self, queueItem):164 def updateBuild(self, queueItem):
81165
=== modified file 'lib/lp/buildmaster/tests/test_buildfarmjobbehavior.py'
--- lib/lp/buildmaster/tests/test_buildfarmjobbehavior.py 2010-03-06 04:57:40 +0000
+++ lib/lp/buildmaster/tests/test_buildfarmjobbehavior.py 2010-03-15 21:11:07 +0000
@@ -3,10 +3,18 @@
33
4"""Unit tests for BuildFarmJobBehaviorBase."""4"""Unit tests for BuildFarmJobBehaviorBase."""
55
6from unittest import TestCase, TestLoader6from unittest import TestLoader
7
8from zope.component import getUtility
9
10from canonical.testing.layers import ZopelessDatabaseLayer
11from lp.testing import TestCaseWithFactory
712
8from lp.buildmaster.interfaces.buildbase import BuildStatus13from lp.buildmaster.interfaces.buildbase import BuildStatus
14from lp.buildmaster.interfaces.builder import CorruptBuildID
9from lp.buildmaster.model.buildfarmjobbehavior import BuildFarmJobBehaviorBase15from lp.buildmaster.model.buildfarmjobbehavior import BuildFarmJobBehaviorBase
16from lp.registry.interfaces.pocket import PackagePublishingPocket
17from lp.soyuz.interfaces.processor import IProcessorFamilySet
1018
1119
12class FakeBuildFarmJob:20class FakeBuildFarmJob:
@@ -14,27 +22,91 @@
14 pass22 pass
1523
1624
17class TestBuildFarmJobBehaviorBase(TestCase):25class TestBuildFarmJobBehaviorBase(TestCaseWithFactory):
18 """Test very small, basic bits of BuildFarmJobBehaviorBase."""26 """Test very small, basic bits of BuildFarmJobBehaviorBase."""
1927
20 def setUp(self):28 layer = ZopelessDatabaseLayer
21 self.behavior = BuildFarmJobBehaviorBase(FakeBuildFarmJob())29
30 def _makeBehavior(self, buildfarmjob=None):
31 """Create a `BuildFarmJobBehaviorBase`."""
32 if buildfarmjob is None:
33 buildfarmjob = FakeBuildFarmJob()
34 return BuildFarmJobBehaviorBase(buildfarmjob)
35
36 def _makeBuild(self):
37 """Create a `Build` object."""
38 x86 = getUtility(IProcessorFamilySet).getByName('x86')
39 distroarchseries = self.factory.makeDistroArchSeries(
40 architecturetag='x86', processorfamily=x86)
41 distroseries = distroarchseries.distroseries
42 archive = self.factory.makeArchive(
43 distribution=distroseries.distribution)
44 pocket = PackagePublishingPocket.RELEASE
45 spr = self.factory.makeSourcePackageRelease(
46 distroseries=distroseries, archive=archive)
47
48 return spr.createBuild(
49 distroarchseries=distroarchseries, pocket=pocket, archive=archive)
50
51 def _makeBuildQueue(self):
52 """Create a `BuildQueue` object."""
53 return self.factory.makeSourcePackageRecipeBuildJob()
2254
23 def test_extractBuildStatus_baseline(self):55 def test_extractBuildStatus_baseline(self):
24 # extractBuildStatus picks the name of the build status out of a56 # extractBuildStatus picks the name of the build status out of a
25 # dict describing the slave's status.57 # dict describing the slave's status.
26 slave_status = {'build_status': 'BuildStatus.BUILDING'}58 slave_status = {'build_status': 'BuildStatus.BUILDING'}
59 behavior = self._makeBehavior()
27 self.assertEqual(60 self.assertEqual(
28 BuildStatus.BUILDING.name,61 BuildStatus.BUILDING.name,
29 self.behavior.extractBuildStatus(slave_status))62 behavior.extractBuildStatus(slave_status))
3063
31 def test_extractBuildStatus_malformed(self):64 def test_extractBuildStatus_malformed(self):
32 # extractBuildStatus errors out when the status string is not65 # extractBuildStatus errors out when the status string is not
33 # of the form it expects.66 # of the form it expects.
34 slave_status = {'build_status': 'BUILDING'}67 slave_status = {'build_status': 'BUILDING'}
35 self.assertRaises(68 behavior = self._makeBehavior()
36 AssertionError,69 self.assertRaises(
37 self.behavior.extractBuildStatus, slave_status)70 AssertionError, behavior.extractBuildStatus, slave_status)
71
72 def test_getVerifiedBuild_success(self):
73 build = self._makeBuild()
74 behavior = self._makeBehavior()
75 raw_id = str(build.id)
76
77 self.assertEqual(build, behavior.getVerifiedBuild(raw_id))
78
79 def test_getVerifiedBuild_malformed(self):
80 behavior = self._makeBehavior()
81 self.assertRaises(CorruptBuildID, behavior.getVerifiedBuild, 'hi!')
82
83 def test_getVerifiedBuild_notfound(self):
84 build = self._makeBuild()
85 behavior = self._makeBehavior()
86 nonexistent_id = str(build.id + 99)
87
88 self.assertRaises(
89 CorruptBuildID, behavior.getVerifiedBuild, nonexistent_id)
90
91 def test_getVerifiedBuildQueue_success(self):
92 buildqueue = self._makeBuildQueue()
93 behavior = self._makeBehavior()
94 raw_id = str(buildqueue.id)
95
96 self.assertEqual(buildqueue, behavior.getVerifiedBuildQueue(raw_id))
97
98 def test_getVerifiedBuildQueue_malformed(self):
99 behavior = self._makeBehavior()
100 self.assertRaises(
101 CorruptBuildID, behavior.getVerifiedBuildQueue, 'bye!')
102
103 def test_getVerifiedBuildQueue_notfound(self):
104 buildqueue = self._makeBuildQueue()
105 behavior = self._makeBehavior()
106 nonexistent_id = str(buildqueue.id + 99)
107
108 self.assertRaises(
109 CorruptBuildID, behavior.getVerifiedBuildQueue, nonexistent_id)
38110
39111
40def test_suite():112def test_suite():
41113
=== modified file 'lib/lp/code/model/recipebuilder.py'
--- lib/lp/code/model/recipebuilder.py 2010-02-05 15:06:28 +0000
+++ lib/lp/code/model/recipebuilder.py 2010-03-15 21:11:07 +0000
@@ -8,7 +8,7 @@
8 'RecipeBuildBehavior',8 'RecipeBuildBehavior',
9 ]9 ]
1010
11from zope.component import adapts11from zope.component import adapts, getUtility
12from zope.interface import implements12from zope.interface import implements
1313
14from lp.archiveuploader.permission import check_upload_to_pocket14from lp.archiveuploader.permission import check_upload_to_pocket
@@ -18,7 +18,8 @@
18from lp.buildmaster.model.buildfarmjobbehavior import (18from lp.buildmaster.model.buildfarmjobbehavior import (
19 BuildFarmJobBehaviorBase)19 BuildFarmJobBehaviorBase)
20from lp.code.interfaces.sourcepackagerecipebuild import (20from lp.code.interfaces.sourcepackagerecipebuild import (
21 ISourcePackageRecipeBuildJob)21 ISourcePackageRecipeBuildJob, ISourcePackageRecipeBuildSource)
22from lp.code.model.sourcepackagerecipebuild import SourcePackageRecipeBuild
22from lp.registry.interfaces.pocket import PackagePublishingPocket23from lp.registry.interfaces.pocket import PackagePublishingPocket
23from lp.soyuz.adapters.archivedependencies import (24from lp.soyuz.adapters.archivedependencies import (
24 get_primary_current_component, get_sources_list_for_building)25 get_primary_current_component, get_sources_list_for_building)
@@ -168,3 +169,11 @@
168 extra_info['logtail'] = raw_slave_status[2]169 extra_info['logtail'] = raw_slave_status[2]
169170
170 return extra_info171 return extra_info
172
173 def getVerifiedBuild(self, raw_id):
174 """See `IBuildFarmJobBehavior`."""
175 # This type of job has a build that is of type BuildBase but not
176 # actually a Build.
177 return self._helpVerifyBuildIDComponent(
178 raw_id, SourcePackageRecipeBuild,
179 getUtility(ISourcePackageRecipeBuildSource).getById)
171180
=== removed file 'lib/lp/scripts/utilities/apiindex.py'
--- lib/lp/scripts/utilities/apiindex.py 2009-06-30 16:56:07 +0000
+++ lib/lp/scripts/utilities/apiindex.py 1970-01-01 00:00:00 +0000
@@ -1,21 +0,0 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""A script to build reference html from wadl.
5
6Uses an xsl from the launchpadlib package."""
7
8__metaclass__ = type
9__all__ = ['main']
10
11
12import pkg_resources
13import subprocess
14import sys
15
16def main():
17 source = sys.argv[1]
18 stylesheet = pkg_resources.resource_filename(
19 'launchpadlib', 'wadl-to-refhtml.xsl')
20 subprocess.call(['xsltproc', stylesheet, source])
21 pkg_resources.cleanup_resources()
220
=== modified file 'lib/lp/soyuz/doc/buildd-slavescanner.txt'
--- lib/lp/soyuz/doc/buildd-slavescanner.txt 2010-03-10 12:50:18 +0000
+++ lib/lp/soyuz/doc/buildd-slavescanner.txt 2010-03-15 21:11:07 +0000
@@ -95,15 +95,16 @@
95 >>> lostbuilding_builder = MockBuilder(95 >>> lostbuilding_builder = MockBuilder(
96 ... 'Lost Building Slave', LostBuildingSlave())96 ... 'Lost Building Slave', LostBuildingSlave())
97 >>> buildergroup.rescueBuilderIfLost(lostbuilding_builder)97 >>> buildergroup.rescueBuilderIfLost(lostbuilding_builder)
98 WARNING:root:Builder 'Lost Building Slave' rescued from98 WARNING:root:Builder 'Lost Building Slave' rescued from '1000-10000':
99 '1000-10000': 'Object not found'99 'Build 1000 is not available: 'Object not found''
100100
101Then a lost slave in status 'WAITING':101Then a lost slave in status 'WAITING':
102102
103 >>> lostwaiting_builder = MockBuilder(103 >>> lostwaiting_builder = MockBuilder(
104 ... 'Lost Waiting Slave', LostWaitingSlave())104 ... 'Lost Waiting Slave', LostWaitingSlave())
105 >>> buildergroup.rescueBuilderIfLost(lostwaiting_builder)105 >>> buildergroup.rescueBuilderIfLost(lostwaiting_builder)
106 WARNING:root:Builder 'Lost Waiting Slave' rescued from '1000-10000': 'Object not found'106 WARNING:root:Builder 'Lost Waiting Slave' rescued from '1000-10000':
107 'Build 1000 is not available: 'Object not found''
107108
108Both got rescued, as expected.109Both got rescued, as expected.
109110
110111
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2010-03-11 20:54:36 +0000
+++ lib/lp/testing/factory.py 2010-03-15 21:11:07 +0000
@@ -2008,6 +2008,81 @@
2008 def getAnySourcePackageUrgency(self):2008 def getAnySourcePackageUrgency(self):
2009 return SourcePackageUrgency.MEDIUM2009 return SourcePackageUrgency.MEDIUM
20102010
2011 def makeSourcePackageRelease(self, archive=None, sourcepackagename=None,
2012 distroseries=None, maintainer=None,
2013 creator=None, component=None, section=None,
2014 urgency=None, version=None,
2015 builddepends=None, builddependsindep=None,
2016 build_conflicts=None,
2017 build_conflicts_indep=None,
2018 architecturehintlist='all',
2019 dsc_maintainer_rfc822=None,
2020 dsc_standards_version='3.6.2',
2021 dsc_format='1.0', dsc_binaries='foo-bin',
2022 date_uploaded=UTC_NOW):
2023 """Make a `SourcePackageRelease`."""
2024 if distroseries is None:
2025 if archive is None:
2026 distribution = None
2027 else:
2028 distribution = archive.distribution
2029 distroseries = self.makeDistroRelease(distribution=distribution)
2030
2031 if archive is None:
2032 archive = self.makeArchive(
2033 distribution=distroseries.distribution,
2034 purpose=ArchivePurpose.PRIMARY)
2035
2036 if sourcepackagename is None:
2037 sourcepackagename = self.makeSourcePackageName()
2038
2039 if component is None:
2040 component = self.makeComponent()
2041
2042 if urgency is None:
2043 urgency = self.getAnySourcePackageUrgency()
2044
2045 if section is None:
2046 section = self.getUniqueString('section')
2047 section = getUtility(ISectionSet).ensure(section)
2048
2049 if maintainer is None:
2050 maintainer = self.makePerson()
2051
2052 maintainer_email = '%s <%s>' % (
2053 maintainer.displayname,
2054 maintainer.preferredemail.email)
2055
2056 if creator is None:
2057 creator = self.makePerson()
2058
2059 if version is None:
2060 version = self.getUniqueString('version')
2061
2062 return distroseries.createUploadedSourcePackageRelease(
2063 sourcepackagename=sourcepackagename,
2064 maintainer=maintainer,
2065 creator=creator,
2066 component=component,
2067 section=section,
2068 urgency=urgency,
2069 version=version,
2070 builddepends=builddepends,
2071 builddependsindep=builddependsindep,
2072 build_conflicts=build_conflicts,
2073 build_conflicts_indep=build_conflicts_indep,
2074 architecturehintlist=architecturehintlist,
2075 changelog_entry=None,
2076 dsc=None,
2077 copyright=self.getUniqueString(),
2078 dscsigningkey=None,
2079 dsc_maintainer_rfc822=maintainer_email,
2080 dsc_standards_version=dsc_standards_version,
2081 dsc_format=dsc_format,
2082 dsc_binaries=dsc_binaries,
2083 archive=archive,
2084 dateuploaded=date_uploaded)
2085
2011 def makeSourcePackagePublishingHistory(self, sourcepackagename=None,2086 def makeSourcePackagePublishingHistory(self, sourcepackagename=None,
2012 distroseries=None, maintainer=None,2087 distroseries=None, maintainer=None,
2013 creator=None, component=None,2088 creator=None, component=None,
@@ -2027,54 +2102,31 @@
2027 dsc_format='1.0',2102 dsc_format='1.0',
2028 dsc_binaries='foo-bin',2103 dsc_binaries='foo-bin',
2029 ):2104 ):
2030 if sourcepackagename is None:2105 """Make a `SourcePackagePublishingHistory`."""
2031 sourcepackagename = self.makeSourcePackageName()
2032 spn = sourcepackagename
2033
2034 if distroseries is None:2106 if distroseries is None:
2035 distroseries = self.makeDistroRelease()2107 if archive is None:
2108 distribution = None
2109 else:
2110 distribution = archive.distribution
2111 distroseries = self.makeDistroRelease(distribution=distribution)
20362112
2037 if archive is None:2113 if archive is None:
2038 archive = self.makeArchive(2114 archive = self.makeArchive(
2039 distribution=distroseries.distribution,2115 distribution=distroseries.distribution,
2040 purpose=ArchivePurpose.PRIMARY)2116 purpose=ArchivePurpose.PRIMARY)
20412117
2042 if component is None:
2043 component = self.makeComponent()
2044
2045 if pocket is None:2118 if pocket is None:
2046 pocket = self.getAnyPocket()2119 pocket = self.getAnyPocket()
20472120
2048 if status is None:2121 if status is None:
2049 status = PackagePublishingStatus.PENDING2122 status = PackagePublishingStatus.PENDING
20502123
2051 if urgency is None:2124 spr = self.makeSourcePackageRelease(
2052 urgency = self.getAnySourcePackageUrgency()2125 archive=archive,
20532126 sourcepackagename=sourcepackagename,
2054 if section is None:2127 distroseries=distroseries,
2055 section = self.getUniqueString('section')
2056 section = getUtility(ISectionSet).ensure(section)
2057
2058 if maintainer is None:
2059 maintainer = self.makePerson()
2060
2061 maintainer_email = '%s <%s>' % (
2062 maintainer.displayname,
2063 maintainer.preferredemail.email)
2064
2065 if creator is None:
2066 creator = self.makePerson()
2067
2068 if version is None:
2069 version = self.getUniqueString('version')
2070
2071 gpg_key = self.makeGPGKey(creator)
2072
2073 spr = distroseries.createUploadedSourcePackageRelease(
2074 sourcepackagename=spn,
2075 maintainer=maintainer,2128 maintainer=maintainer,
2076 creator=creator,2129 creator=creator, component=component,
2077 component=component,
2078 section=section,2130 section=section,
2079 urgency=urgency,2131 urgency=urgency,
2080 version=version,2132 version=version,
@@ -2083,15 +2135,10 @@
2083 build_conflicts=build_conflicts,2135 build_conflicts=build_conflicts,
2084 build_conflicts_indep=build_conflicts_indep,2136 build_conflicts_indep=build_conflicts_indep,
2085 architecturehintlist=architecturehintlist,2137 architecturehintlist=architecturehintlist,
2086 changelog_entry=None,
2087 dsc=None,
2088 copyright=self.getUniqueString(),
2089 dscsigningkey=gpg_key,
2090 dsc_maintainer_rfc822=maintainer_email,
2091 dsc_standards_version=dsc_standards_version,2138 dsc_standards_version=dsc_standards_version,
2092 dsc_format=dsc_format,2139 dsc_format=dsc_format,
2093 dsc_binaries=dsc_binaries,2140 dsc_binaries=dsc_binaries,
2094 archive=archive, dateuploaded=date_uploaded)2141 date_uploaded=date_uploaded)
20952142
2096 sspph = SourcePackagePublishingHistory(2143 sspph = SourcePackagePublishingHistory(
2097 distroseries=distroseries,2144 distroseries=distroseries,
20982145
=== modified file 'lib/lp/translations/doc/translationtemplatesbuildbehavior.txt'
--- lib/lp/translations/doc/translationtemplatesbuildbehavior.txt 2010-03-05 13:52:32 +0000
+++ lib/lp/translations/doc/translationtemplatesbuildbehavior.txt 2010-03-15 21:11:07 +0000
@@ -2,13 +2,21 @@
22
3== Setup ==3== Setup ==
44
5Set up build environment.5Set up build environment. Clear out the build queue.
66
7 >>> import transaction7 >>> import transaction
8 >>> import logging8 >>> import logging
9 >>> logger = logging.getLogger()9 >>> logger = logging.getLogger()
10 >>> logger.setLevel(logging.CRITICAL)10 >>> logger.setLevel(logging.CRITICAL)
1111
12 >>> from canonical.database.sqlbase import quote
13 >>> from canonical.launchpad.interfaces.lpstorm import IMasterStore
14 >>> from lp.services.job.interfaces.job import JobStatus
15 >>> from lp.services.job.model.job import Job
16 >>> store = IMasterStore(Job)
17 >>> query = store.execute("UPDATE Job SET status = %s" % quote(
18 ... JobStatus.FAILED))
19
12 >>> from lp.buildmaster.master import BuilddMaster20 >>> from lp.buildmaster.master import BuilddMaster
13 >>> from canonical.buildd.tests import BuilddSlaveTestSetup21 >>> from canonical.buildd.tests import BuilddSlaveTestSetup
14 >>> bm = BuilddMaster(logger, transaction)22 >>> bm = BuilddMaster(logger, transaction)
@@ -39,7 +47,21 @@
3947
40Make a builder to process our build request.48Make a builder to process our build request.
4149
42 >>> builder = factory.makeBuilder(virtualized=False, processor=processor)50 >>> builder = factory.makeBuilder(
51 ... virtualized=True, processor=processor, vm_host='hostname')
52
53The builder doesn't talk to a real slave. We don't have those in our
54test suite. But we give it a fake one.
55
56 >>> from lp.testing.fakemethod import FakeMethod
57 >>> class FakeSlave:
58 ... build = FakeMethod()
59 ... cacheFile = FakeMethod()
60 ... resume = FakeMethod(result=('Output here', 'Errors here', 0))
61
62 >>> from zope.security.proxy import removeSecurityProxy
63 >>> slave = FakeSlave()
64 >>> removeSecurityProxy(builder).slave = slave
4365
4466
45== Get a job! ==67== Get a job! ==
@@ -60,20 +82,27 @@
60 >>> print buildqueue.date_started82 >>> print buildqueue.date_started
61 None83 None
6284
63Dispatch the job to the build slave. The proper method to call here85Our job is now first in line to be executed.
64would have been Builder.findAndStartJob, but it has not been generalised86
65to handle new job types yet.87 >>> removeSecurityProxy(builder)._findBuildCandidate() == buildqueue
6688 True
67# XXX JeroenVermeulen bug=506617: call findAndStartJob instead.89
6890Dispatch the first job in line (ours!) to the build slave.
69 >>> from zope.security.proxy import removeSecurityProxy91
70 >>> removeSecurityProxy(builder)._dispatchBuildCandidate(buildqueue)92 >>> activated_job = builder.findAndStartJob()
7193 >>> activated_job == buildqueue
72The build is now marked as started.94 True
95
96Our build is now marked as started.
7397
74 >>> buildqueue.date_started is None98 >>> buildqueue.date_started is None
75 False99 False
76100
101The slave's build method has been called.
102
103 >>> slave.build.call_count
104 1
105
77106
78== Teardown ==107== Teardown ==
79108
80109
=== modified file 'lib/lp/translations/model/translationtemplatesbuildbehavior.py'
--- lib/lp/translations/model/translationtemplatesbuildbehavior.py 2010-02-11 19:11:11 +0000
+++ lib/lp/translations/model/translationtemplatesbuildbehavior.py 2010-03-15 21:11:07 +0000
@@ -17,6 +17,7 @@
1717
18from canonical.launchpad.interfaces import ILaunchpadCelebrities18from canonical.launchpad.interfaces import ILaunchpadCelebrities
1919
20from lp.buildmaster.interfaces.builder import CorruptBuildID
20from lp.buildmaster.interfaces.buildfarmjobbehavior import (21from lp.buildmaster.interfaces.buildfarmjobbehavior import (
21 IBuildFarmJobBehavior)22 IBuildFarmJobBehavior)
22from lp.buildmaster.model.buildfarmjobbehavior import (23from lp.buildmaster.model.buildfarmjobbehavior import (
@@ -40,12 +41,27 @@
40 self._builder.slave.cacheFile(logger, chroot)41 self._builder.slave.cacheFile(logger, chroot)
41 buildid = self.buildfarmjob.getName()42 buildid = self.buildfarmjob.getName()
4243
43 args = { 'branch_url': self.buildfarmjob.branch.url }44 args = self.buildfarmjob.metadata
44 filemap = {}45 filemap = {}
4546
46 self._builder.slave.build(47 self._builder.slave.build(
47 buildid, self.build_type, chroot_sha1, filemap, args)48 buildid, self.build_type, chroot_sha1, filemap, args)
4849
50 def verifySlaveBuildID(self, slave_build_id):
51 """See `IBuildFarmJobBehavior`."""
52 try:
53 branch_name, queue_item_id = slave_build_id.rsplit('-', 1)
54 except ValueError:
55 raise CorruptBuildID(
56 "Malformed translation templates build id: '%s'" % (
57 slave_build_id))
58
59 buildqueue = self.getVerifiedBuildQueue(queue_item_id)
60 if buildqueue.job != self.buildfarmjob.job:
61 raise CorruptBuildID(
62 "ID mismatch for translation templates build '%s'" % (
63 slave_build_id))
64
49 def _getChroot(self):65 def _getChroot(self):
50 ubuntu = getUtility(ILaunchpadCelebrities).ubuntu66 ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
51 return ubuntu.currentseries.nominatedarchindep.getChroot()67 return ubuntu.currentseries.nominatedarchindep.getChroot()
5268
=== modified file 'lib/lp/translations/tests/test_translationtemplatesbuildbehavior.py'
--- lib/lp/translations/tests/test_translationtemplatesbuildbehavior.py 2010-03-12 08:31:43 +0000
+++ lib/lp/translations/tests/test_translationtemplatesbuildbehavior.py 2010-03-15 21:11:07 +0000
@@ -19,6 +19,7 @@
19from lp.buildmaster.interfaces.buildbase import BuildStatus19from lp.buildmaster.interfaces.buildbase import BuildStatus
20from lp.buildmaster.interfaces.buildfarmjobbehavior import (20from lp.buildmaster.interfaces.buildfarmjobbehavior import (
21 IBuildFarmJobBehavior)21 IBuildFarmJobBehavior)
22from lp.buildmaster.interfaces.builder import CorruptBuildID
22from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet23from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet
23from lp.testing import TestCaseWithFactory24from lp.testing import TestCaseWithFactory
24from lp.testing.fakemethod import FakeMethod25from lp.testing.fakemethod import FakeMethod
@@ -123,6 +124,10 @@
123 self.assertEqual(124 self.assertEqual(
124 'translation-templates', slave_status['test_build_type'])125 'translation-templates', slave_status['test_build_type'])
125 self.assertIn('branch_url', slave_status['test_build_args'])126 self.assertIn('branch_url', slave_status['test_build_args'])
127 # The slave receives the public http URL for the branch.
128 self.assertEqual(
129 behavior.buildfarmjob.branch.composePublicURL(),
130 slave_status['test_build_args']['branch_url'])
126131
127 def test_getChroot(self):132 def test_getChroot(self):
128 # _getChroot produces the current chroot for the current Ubuntu133 # _getChroot produces the current chroot for the current Ubuntu
@@ -170,6 +175,38 @@
170 self.assertEqual(1, builder.cleanSlave.call_count)175 self.assertEqual(1, builder.cleanSlave.call_count)
171 self.assertEqual(0, behavior._uploadTarball.call_count)176 self.assertEqual(0, behavior._uploadTarball.call_count)
172177
178 def test_verifySlaveBuildID_success(self):
179 # TranslationTemplatesBuildJob.getName generates slave build ids
180 # that TranslationTemplatesBuildBehavior.verifySlaveBuildID
181 # accepts.
182 behavior = self._makeBehavior()
183 buildfarmjob = behavior.buildfarmjob
184 job = buildfarmjob.job
185
186 # The test is that this not raise CorruptBuildID (or anything
187 # else, for that matter).
188 behavior.verifySlaveBuildID(behavior.buildfarmjob.getName())
189
190 def test_verifySlaveBuildID_handles_dashes(self):
191 # TranslationTemplatesBuildBehavior.verifySlaveBuildID can deal
192 # with dashes in branch names.
193 behavior = self._makeBehavior()
194 buildfarmjob = behavior.buildfarmjob
195 job = buildfarmjob.job
196 buildfarmjob.branch.name = 'x-y-z--'
197
198 # The test is that this not raise CorruptBuildID (or anything
199 # else, for that matter).
200 behavior.verifySlaveBuildID(behavior.buildfarmjob.getName())
201
202 def test_verifySlaveBuildID_malformed(self):
203 behavior = self._makeBehavior()
204 self.assertRaises(CorruptBuildID, behavior.verifySlaveBuildID, 'huh?')
205
206 def test_verifySlaveBuildID_notfound(self):
207 behavior = self._makeBehavior()
208 self.assertRaises(CorruptBuildID, behavior.verifySlaveBuildID, '1-1')
209
173210
174def test_suite():211def test_suite():
175 return TestLoader().loadTestsFromName(__name__)212 return TestLoader().loadTestsFromName(__name__)
176213
=== added file 'utilities/apidoc-index.pt'
--- utilities/apidoc-index.pt 1970-01-01 00:00:00 +0000
+++ utilities/apidoc-index.pt 2010-03-15 21:11:07 +0000
@@ -0,0 +1,37 @@
1<html
2 xmlns="http://www.w3.org/1999/xhtml"
3 xmlns:tal="http://xml.zope.org/namespaces/tal"
4 xmlns:metal="http://xml.zope.org/namespaces/metal"
5 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
6 i18n:domain="launchpad"
7>
8 <head>
9 <title>Launchpad Web Service API</title>
10 </head>
11
12 <body>
13
14 <h1>Launchpad web service API documentation</h1>
15
16 <p tal:content="structure config/service_description">
17 Description of the Launchpad web service.
18 </p>
19
20 <h2>Active versions</h2>
21
22 <ul>
23 <li tal:repeat="version_and_description versions_and_descriptions">
24
25 <tal:version tal:define="version version_and_description/version;
26 description version_and_description/description">
27
28 <a tal:attributes="href string:$version.html"
29 tal:content="version">Version name</a>:
30 <tal:description replace="description" />
31 </tal:version>
32 </li>
33 </ul>
34
35 </body>
36
37</html>
038
=== renamed file 'utilities/create-lp-wadl.py' => 'utilities/create-lp-wadl-and-apidoc.py'
--- utilities/create-lp-wadl.py 2010-03-01 21:16:06 +0000
+++ utilities/create-lp-wadl-and-apidoc.py 2010-03-15 21:11:07 +0000
@@ -12,10 +12,15 @@
1212
13import _pythonpath13import _pythonpath
1414
15from cStringIO import StringIO
16import os
17import pkg_resources
18import subprocess
15import sys19import sys
16import urlparse20import urlparse
1721
18from zope.component import getUtility22from zope.component import getUtility
23from zope.pagetemplate.pagetemplatefile import PageTemplateFile
1924
20from canonical.launchpad.ftests import login, ANONYMOUS25from canonical.launchpad.ftests import login, ANONYMOUS
21from canonical.launchpad.scripts import execute_zcml_for_scripts26from canonical.launchpad.scripts import execute_zcml_for_scripts
@@ -30,6 +35,9 @@
30 execute_zcml_for_scripts()35 execute_zcml_for_scripts()
31 config = getUtility(IWebServiceConfiguration)36 config = getUtility(IWebServiceConfiguration)
3237
38 stylesheet = pkg_resources.resource_filename(
39 'launchpadlib', 'wadl-to-refhtml.xsl')
40
33 # Request the WADL from the root resource.41 # Request the WADL from the root resource.
34 # We do this by creating a request object asking for a WADL42 # We do this by creating a request object asking for a WADL
35 # representation.43 # representation.
@@ -52,6 +60,33 @@
52 content = request.publication.getApplication(request)(request)60 content = request.publication.getApplication(request)(request)
53 f.write(content)61 f.write(content)
54 f.close()62 f.close()
63
64 # Now, convert the WADL into an human-readable description and
65 # put the HTML in the same directory as the WADL.
66 directory, ignore = os.path.split(path_template)
67 html_filename = os.path.join(directory, version + ".html")
68 print "Writing apidoc for version %s to %s" % (
69 version, html_filename)
70 stdout = open(html_filename, "w")
71 subprocess.Popen(['xsltproc', stylesheet, filename], stdout=stdout)
72 stdout.close()
73
74 # Finally, create an index.html with links to all the HTML
75 # documentation files we just generated.
76 template_file = 'apidoc-index.pt'
77 template = PageTemplateFile(template_file)
78 namespace = template.pt_getContext()
79 namespace['config'] = config
80 versions_and_descriptions = []
81 for version in config.active_versions:
82 versions_and_descriptions.append(
83 dict(version=version,
84 description=config.version_descriptions[version]))
85 namespace['versions_and_descriptions'] = versions_and_descriptions
86
87 f = open(os.path.join(directory, "index.html"), 'w')
88 f.write(template.pt_render(namespace))
89
55 return 090 return 0
5691
57if __name__ == '__main__':92if __name__ == '__main__':
5893
=== modified file 'versions.cfg'
--- versions.cfg 2010-03-08 22:26:29 +0000
+++ versions.cfg 2010-03-15 21:11:07 +0000
@@ -21,15 +21,15 @@
21grokcore.component = 1.621grokcore.component = 1.6
22httplib2 = 0.6.022httplib2 = 0.6.0
23ipython = 0.9.123ipython = 0.9.1
24launchpadlib = 1.5.524launchpadlib = 1.5.6
25lazr.authentication = 0.1.125lazr.authentication = 0.1.1
26lazr.batchnavigator = 1.126lazr.batchnavigator = 1.1
27lazr.config = 1.1.327lazr.config = 1.1.3
28lazr.delegates = 1.1.028lazr.delegates = 1.1.0
29lazr.enum = 1.1.229lazr.enum = 1.1.2
30lazr.lifecycle = 1.130lazr.lifecycle = 1.1
31lazr.restful = 0.9.2131lazr.restful = 0.9.23
32lazr.restfulclient = 0.9.1132lazr.restfulclient = 0.9.12
33lazr.smtptest = 1.133lazr.smtptest = 1.1
34lazr.testing = 0.1.134lazr.testing = 0.1.1
35lazr.uri = 1.0.235lazr.uri = 1.0.2

Subscribers

People subscribed via source and target branches

to status/vote changes: