Merge lp:~jpds/launchpad/fix_361650_model_changes into lp:launchpad

Proposed by Jonathan Davies
Status: Merged
Approved by: Curtis Hovey
Approved revision: no longer in the source branch.
Merged at revision: 10619
Proposed branch: lp:~jpds/launchpad/fix_361650_model_changes
Merge into: lp:launchpad
Diff against target: 857 lines (+470/-93)
11 files modified
lib/lp/registry/configure.zcml (+47/-5)
lib/lp/registry/doc/distribution-mirror.txt (+119/-1)
lib/lp/registry/interfaces/distribution.py (+9/-1)
lib/lp/registry/interfaces/distributionmirror.py (+96/-47)
lib/lp/registry/model/distribution.py (+11/-0)
lib/lp/registry/model/distributionmirror.py (+63/-1)
lib/lp/registry/stories/distributionmirror/xx-distribution-mirrors.txt (+11/-11)
lib/lp/registry/stories/webservice/xx-distribution-mirror.txt (+20/-11)
lib/lp/registry/stories/webservice/xx-distribution.txt (+74/-0)
lib/lp/registry/tests/test_distributionmirror.py (+4/-14)
lib/lp/testing/factory.py (+16/-2)
To merge this branch: bzr merge lp:~jpds/launchpad/fix_361650_model_changes
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Review via email: mp+20785@code.launchpad.net

Commit message

Added models and tests for DistributionMirror.country_dns_mirror.

Description of the change

= Summary =

This branch builds on the schema changes introduced for bug #361650. Adding stuff to our models.

It also adds the API parts and mirror checks when marking mirrors as country mirrors with complete test suite.

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :
Download full text (23.1 KiB)

Your merge proposal should show the output of make lint to verify your
changes did not have any cruft. It will also inform you of style mistakes
that must be fixed before making a merge proposal.

I have some ideas to improve the code, and I think the interface and model
are missing essential documentation and unit tests. I suspect that the
story tests (which are integration tests) are test what should be testing
as documentation and unit tests.

> === modified file 'lib/lp/registry/interfaces/distribution.py'
> --- lib/lp/registry/interfaces/distribution.py 2010-02-27 10:19:18 +0000
> +++ lib/lp/registry/interfaces/distribution.py 2010-03-06 00:09:35 +0000
> @@ -318,6 +318,16 @@
> if it's not found.
> """
>
> + @operation_parameters(
> + country=copy_field(IDistributionMirror['country'], required=True),
> + mirror_type=copy_field(IDistributionMirror['content'], required=True))
> + @operation_returns_entry(IDistributionMirror)
> + @export_read_operation()
> + def getCountryMirror(country, mirror_type):
> + """Return the country DNS mirror for a given country and content
> + type.
> + """
> +

This docstring does not follow PEP 257. The first sentence must be one line.
Subsequent sentences may follow after a blank line:
    http://www.python.org/dev/peps/pep-0257/

Think this fixes the issue:

        """Return the country DNS mirror for a country and content type."""

> === modified file 'lib/lp/registry/interfaces/distributionmirror.py'
> --- lib/lp/registry/interfaces/distributionmirror.py 2010-02-22 15:50:06 +0000
> +++ lib/lp/registry/interfaces/distributionmirror.py 2010-03-06 00:09:35 +0000
> @@ -6,21 +6,23 @@
> __metaclass__ = type
>
> __all__ = [
> +'CannotTransitionToCountryMirror',
> +'CountryMirrorAlreadySet',
> 'IDistributionMirror',
> -'IDistributionMirrorAdminRestricted',
> -'IDistributionMirrorEditRestricted',
> -'IDistributionMirrorPublic',
> 'IMirrorDistroArchSeries',
> 'IMirrorDistroSeriesSource',
> 'IMirrorProbeRecord',
> 'IDistributionMirrorSet',
> 'IMirrorCDImageDistroSeries',
> 'PROBE_INTERVAL',
> -'UnableToFetchCDImageFileList',
> 'MirrorContent',
> 'MirrorFreshness',
> +'MirrorHasNoHTTPUrl',
> +'MirrorNotOfficial',
> +'MirrorNotProbed',
> 'MirrorSpeed',
> -'MirrorStatus']
> +'MirrorStatus',
> +'UnableToFetchCDImageFileList']

Per PEP 8, this list of single entries must each be indented and required a
trailing comma; the closing bracket on on a separate line to minimise diffs,
which I can see that this diff is already a victim:

    'MirrorStatus',
    'UnableToFetchCDImageFileList',
    ]

> @@ -47,6 +52,44 @@
> PROBE_INTERVAL = 23
>
> +class CannotTransitionToCountryMirror(Exception):
> + """Root exception for transitions to country mirrors.
> + """

The closing quotes belong on the previous line PEP 257.

> + webservice_error(400) # HTTP Error: 'Bad Request'.

Launchpad style does not use trailing comments because they interfere with
refactoring. I do not think comments about HTTP codes are informative;
we are expect to know them.

> @@ -386,6 +406,33 @@
> date_created = exported(Datetime(
> title=_('Date Crea...

review: Needs Fixing (code)
Revision history for this message
Curtis Hovey (sinzui) wrote :
Download full text (5.9 KiB)

Hi Jonathan.

I have some trivial suggestions to improve this branch. I think the implementation
is good and ready to land.

I can land the branch at the end of this week after these changes are made.

make lint reported these problems that need fixing:

lib/lp/registry/interfaces/distributionmirror.py
    339: [C0301] Line too long (83/78)
    436: [W0311] Bad indentation. Found 7 spaces, expected 8

> === modified file 'lib/lp/registry/doc/distribution-mirror.txt'
> --- lib/lp/registry/doc/distribution-mirror.txt 2009-12-09 10:03:20 +0000
> +++ lib/lp/registry/doc/distribution-mirror.txt 2010-03-29 15:54:12 +0000

@@ -814,3 +814,107 @@

> +Country DNS mirrors
> +-------------------
> +
> +Country DNS mirrors are mirrors which have been assigned $CC.archive.ubuntu.com
> +or $CC.releases.ubuntu.com. These assignments are tracked in Launchpad.

Wrap the narrative at 78 characters.

> + >>> login('<email address hidden>')
> + >>> ubuntu_distro = getUtility(IDistributionSet).getByName('ubuntu')
> + >>> de_archive_mirror = factory.makeMirror(ubuntu_distro,
> + ... "Technische Universitaet Dresden", country=82,
> + ... http_url="http://ubuntu.mirror.tudos.de/ubuntu/",
> + ... official_candidate=True)
> + >>> davis_station_archive = factory.makeMirror(ubuntu_distro,
> + ... "Davis Station", country=9,
> + ... http_url="http://mirror.davis.antarctica.org/ubuntu",
> + ... official_candidate=True)
> + >>> de_archive_mirror.status = MirrorStatus.OFFICIAL
> + >>> de_archive_prober_log = factory.makeMirrorProbeRecord(de_archive_mirror)
> + >>> logout()

Wrap the code at 78 characters.

...

> +Mirrors which are not official or do not have an HTTP URL may not be set as
> +country mirrors:
> +
> + >>> login('<email address hidden>')
> + >>> osuosl_mirror = factory.makeMirror(ubuntu_distro, "OSU Open Source Lab",
> + ... country=226,
> + ... ftp_url="ftp://ubuntu.osuosl.org/pub/ubuntu/",
> + ... official_candidate=True)
> + >>> osuosl_mirror.status = MirrorStatus.OFFICIAL
> + >>> print osuosl_mirror.http_base_url
> + None

Wrap the code at 78 characters.

> === modified file 'lib/lp/registry/interfaces/distribution.py'
> --- lib/lp/registry/interfaces/distribution.py 2010-03-24 21:59:58 +0000
> +++ lib/lp/registry/interfaces/distribution.py 2010-03-29 15:54:12 +0000
>
> @@ -321,6 +321,14 @@
> if it's not found.
> """
>
> + @operation_parameters(
> + country=copy_field(IDistributionMirror['country'], required=True),
> + mirror_type=copy_field(IDistributionMirror['content'], required=True))
> + @operation_returns_entry(IDistributionMirror)
> + @export_read_operation()
> + def getCountryMirror(country, mirror_type):
> + """Return the country DNS mirror for acountry and content type."""

grammar: s/acountry/a country/

> === modified file 'lib/lp/registry/interfaces/distributionmirror.py'
> --- lib/lp/registry/interfaces/distributionmirror.py 2010-02-22 15:50:06 +0000
> +++ lib/lp/registry/interfaces/distributionmirror.py 2010-03-29 15:54:12 +0000
> @@ -6,21 +6,24 @@
> __metaclass__ = type

 __all__ = [
...

> + ...

Read more...

review: Needs Fixing (code)
Revision history for this message
Curtis Hovey (sinzui) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/registry/configure.zcml'
2--- lib/lp/registry/configure.zcml 2010-03-19 11:33:44 +0000
3+++ lib/lp/registry/configure.zcml 2010-03-31 15:03:35 +0000
4@@ -1644,18 +1644,60 @@
5 <!-- DistributionMirror -->
6 <class class="lp.registry.model.distributionmirror.DistributionMirror">
7 <allow
8- interface="lp.registry.interfaces.distributionmirror.IDistributionMirrorPublic" />
9+ attributes="
10+ id
11+ name
12+ displayname
13+ description
14+ distribution
15+ http_base_url
16+ ftp_base_url
17+ rsync_base_url
18+ enabled
19+ speed
20+ status
21+ country
22+ content
23+ owner
24+ title
25+ cdimage_series
26+ source_series
27+ arch_series
28+ last_probe_record
29+ all_probe_records
30+ has_ftp_or_rsync_base_url
31+ base_url
32+ date_created
33+ country_dns_mirror
34+ mirrorMustHaveHTTPOrFTPURL
35+ getSummarizedMirroredSourceSeries
36+ getSummarizedMirroredArchSeries
37+ getOverallFreshness
38+ isOfficial
39+ shouldDisable
40+ disable
41+ newProbeRecord
42+ deleteMirrorDistroArchSeries
43+ ensureMirrorDistroArchSeries
44+ ensureMirrorDistroSeriesSource
45+ deleteMirrorDistroSeriesSource
46+ ensureMirrorCDImageSeries
47+ deleteMirrorCDImageSeries
48+ deleteAllMirrorCDImageSeries
49+ getExpectedPackagesPaths
50+ getExpectedSourcesPaths
51+ canTransitionToCountryMirror" />
52 <require
53 permission="launchpad.Edit"
54- interface="lp.registry.interfaces.distributionmirror.IDistributionMirrorEditRestricted"
55 set_attributes="name displayname description whiteboard
56 http_base_url ftp_base_url rsync_base_url enabled
57- speed country content official_candidate owner" />
58+ speed country content official_candidate owner"
59+ attributes="official_candidate whiteboard" />
60 <require
61 permission="launchpad.Admin"
62- interface="lp.registry.interfaces.distributionmirror.IDistributionMirrorAdminRestricted"
63 set_attributes="status reviewer date_reviewed"
64- attributes="destroySelf" />
65+ attributes="reviewer date_reviewed destroySelf
66+ transitionToCountryMirror" />
67 </class>
68
69 <adapter
70
71=== modified file 'lib/lp/registry/doc/distribution-mirror.txt'
72--- lib/lp/registry/doc/distribution-mirror.txt 2009-12-09 10:03:20 +0000
73+++ lib/lp/registry/doc/distribution-mirror.txt 2010-03-31 15:03:35 +0000
74@@ -8,7 +8,7 @@
75 >>> from canonical.launchpad.interfaces import (
76 ... ICountrySet, IDistributionSet, IDistributionMirrorSet,
77 ... IDistroArchSeriesSet, IDistroSeriesSet, ILibraryFileAliasSet,
78- ... IPersonSet, MirrorContent, MirrorSpeed)
79+ ... IPersonSet, MirrorContent, MirrorSpeed, MirrorStatus)
80 >>> from lp.registry.interfaces.pocket import PackagePublishingPocket
81 >>> mirrorset = getUtility(IDistributionMirrorSet)
82 >>> distroset = getUtility(IDistributionSet)
83@@ -814,3 +814,121 @@
84 >>> mirrorset.getByName('invalid-mirror') is None
85 True
86
87+Country DNS mirrors
88+-------------------
89+
90+Country DNS mirrors are mirrors which have been assigned
91+$CC.archive.ubuntu.com or $CC.releases.ubuntu.com. These assignments are
92+tracked in Launchpad.
93+
94+ >>> login('admin@canonical.com')
95+ >>> ubuntu_distro = getUtility(IDistributionSet).getByName('ubuntu')
96+ >>> de_archive_mirror = factory.makeMirror(ubuntu_distro,
97+ ... "Technische Universitaet Dresden", country=82,
98+ ... http_url="http://ubuntu.mirror.tudos.de/ubuntu/",
99+ ... official_candidate=True)
100+ >>> davis_station_archive = factory.makeMirror(ubuntu_distro,
101+ ... "Davis Station", country=9,
102+ ... http_url="http://mirror.davis.antarctica.org/ubuntu",
103+ ... official_candidate=True)
104+ >>> de_archive_mirror.status = MirrorStatus.OFFICIAL
105+ >>> de_archive_prober_log = factory.makeMirrorProbeRecord(de_archive_mirror)
106+ >>> logout()
107+
108+Normal users can access country_dns_mirror, can see if a mirror is eligible
109+for the status, however, they may not change it:
110+
111+ >>> login('test@canonical.com')
112+ >>> de_archive_mirror.canTransitionToCountryMirror()
113+ True
114+ >>> de_archive_mirror.transitionToCountryMirror(True)
115+ Traceback (most recent call last):
116+ ...
117+ Unauthorized: (<DistributionMirror at ...>, 'transitionToCountryMirror',
118+ 'launchpad.Admin')
119+ >>> logout()
120+
121+Mirror listing administrators may change the status however:
122+
123+ >>> login('karl@canonical.com')
124+ >>> de_archive_mirror.transitionToCountryMirror(True)
125+
126+Mirrors which are already set as country mirrors can't be 'set' as such
127+again:
128+
129+ >>> de_archive_mirror.canTransitionToCountryMirror()
130+ False
131+ >>> de_archive_mirror.transitionToCountryMirror(True)
132+ >>> logout()
133+
134+There cannot be multiple country mirrors of one type for one country:
135+
136+ >>> login('karl@canonical.com')
137+ >>> proberecord = factory.makeMirrorProbeRecord(davis_station_archive)
138+
139+ >>> print davis_station_archive.content.name
140+ ARCHIVE
141+ >>> print davis_station_archive.country_dns_mirror
142+ False
143+ >>> print davis_station_archive.country.name
144+ Antarctica
145+
146+ >>> archive_mirror2 = getUtility(IDistributionMirrorSet).getByName(
147+ ... 'archive-mirror2')
148+ >>> print archive_mirror2.content.name
149+ ARCHIVE
150+ >>> print archive_mirror2.country_dns_mirror
151+ False
152+ >>> print archive_mirror2.country.name
153+ Antarctica
154+
155+ >>> davis_station_archive.status = MirrorStatus.OFFICIAL
156+
157+ >>> davis_station_archive.transitionToCountryMirror(True)
158+ >>> archive_mirror2.transitionToCountryMirror(True)
159+ Traceback (most recent call last):
160+ ...
161+ CountryMirrorAlreadySet: Antarctica already has a country Archive mirror
162+ set.
163+
164+Mirrors which have not been probed may not be marked as country mirrors:
165+
166+ >>> linux_au_mirror = factory.makeMirror(ubuntu_distro,
167+ ... "Linux.org.au", country=14,
168+ ... http_url="http://mirror.linux.org.au/ubuntu",
169+ ... official_candidate=True)
170+ >>> linux_au_mirror.status = MirrorStatus.OFFICIAL
171+ >>> linux_au_mirror.transitionToCountryMirror(True)
172+ Traceback (most recent call last):
173+ ...
174+ MirrorNotProbed: This mirror may not be set as a country mirror as it has
175+ not been probed.
176+ >>> logout()
177+
178+Mirrors which are not official or do not have an HTTP URL may not be set as
179+country mirrors:
180+
181+ >>> login('admin@canonical.com')
182+ >>> osuosl_mirror = factory.makeMirror(ubuntu_distro,
183+ ... "OSU Open Source Lab", country=226,
184+ ... ftp_url="ftp://ubuntu.osuosl.org/pub/ubuntu/",
185+ ... official_candidate=True)
186+ >>> osuosl_mirror.status = MirrorStatus.OFFICIAL
187+ >>> print osuosl_mirror.http_base_url
188+ None
189+
190+ >>> osuosl_mirror.canTransitionToCountryMirror()
191+ False
192+
193+ >>> osuosl_mirror.transitionToCountryMirror(None)
194+ Traceback (most recent call last):
195+ ...
196+ NoneError: None isn't acceptable as a value for
197+ DistributionMirror.country_dns_mirror
198+
199+ >>> osuosl_mirror.transitionToCountryMirror(True)
200+ Traceback (most recent call last):
201+ ...
202+ MirrorHasNoHTTPURL: This mirror may not be set as a country mirror as it
203+ does not have an HTTP URL set.
204+ >>> logout()
205
206=== modified file 'lib/lp/registry/interfaces/distribution.py'
207--- lib/lp/registry/interfaces/distribution.py 2010-03-24 21:59:58 +0000
208+++ lib/lp/registry/interfaces/distribution.py 2010-03-31 15:03:35 +0000
209@@ -25,7 +25,7 @@
210
211 from lazr.restful.fields import CollectionField, Reference
212 from lazr.restful.declarations import (
213- collection_default_content, export_as_webservice_collection,
214+ collection_default_content, copy_field, export_as_webservice_collection,
215 export_as_webservice_entry, export_operation_as,
216 export_read_operation, exported, operation_parameters,
217 operation_returns_collection_of, operation_returns_entry,
218@@ -321,6 +321,14 @@
219 if it's not found.
220 """
221
222+ @operation_parameters(
223+ country=copy_field(IDistributionMirror['country'], required=True),
224+ mirror_type=copy_field(IDistributionMirror['content'], required=True))
225+ @operation_returns_entry(IDistributionMirror)
226+ @export_read_operation()
227+ def getCountryMirror(country, mirror_type):
228+ """Return the country DNS mirror for a country and content type."""
229+
230 def newMirror(owner, speed, country, content, displayname=None,
231 description=None, http_base_url=None,
232 ftp_base_url=None, rsync_base_url=None, enabled=False,
233
234=== modified file 'lib/lp/registry/interfaces/distributionmirror.py'
235--- lib/lp/registry/interfaces/distributionmirror.py 2010-02-22 15:50:06 +0000
236+++ lib/lp/registry/interfaces/distributionmirror.py 2010-03-31 15:03:35 +0000
237@@ -6,21 +6,24 @@
238 __metaclass__ = type
239
240 __all__ = [
241-'IDistributionMirror',
242-'IDistributionMirrorAdminRestricted',
243-'IDistributionMirrorEditRestricted',
244-'IDistributionMirrorPublic',
245-'IMirrorDistroArchSeries',
246-'IMirrorDistroSeriesSource',
247-'IMirrorProbeRecord',
248-'IDistributionMirrorSet',
249-'IMirrorCDImageDistroSeries',
250-'PROBE_INTERVAL',
251-'UnableToFetchCDImageFileList',
252-'MirrorContent',
253-'MirrorFreshness',
254-'MirrorSpeed',
255-'MirrorStatus']
256+ 'CannotTransitionToCountryMirror',
257+ 'CountryMirrorAlreadySet',
258+ 'IDistributionMirror',
259+ 'IMirrorDistroArchSeries',
260+ 'IMirrorDistroSeriesSource',
261+ 'IMirrorProbeRecord',
262+ 'IDistributionMirrorSet',
263+ 'IMirrorCDImageDistroSeries',
264+ 'PROBE_INTERVAL',
265+ 'MirrorContent',
266+ 'MirrorFreshness',
267+ 'MirrorHasNoHTTPURL',
268+ 'MirrorNotOfficial',
269+ 'MirrorNotProbed',
270+ 'MirrorSpeed',
271+ 'MirrorStatus',
272+ 'UnableToFetchCDImageFileList',
273+ ]
274
275 from cgi import escape
276
277@@ -31,8 +34,11 @@
278 from zope.component import getUtility
279 from lazr.enum import DBEnumeratedType, DBItem
280 from lazr.restful.declarations import (
281- export_as_webservice_entry, export_read_operation, exported)
282+ export_as_webservice_entry, export_read_operation,
283+ export_write_operation, exported, mutator_for, operation_parameters,
284+ webservice_error)
285 from lazr.restful.fields import Reference, ReferenceChoice
286+from lazr.restful.interface import copy_field
287
288 from canonical.launchpad import _
289 from canonical.launchpad.fields import (
290@@ -47,6 +53,43 @@
291 PROBE_INTERVAL = 23
292
293
294+class CannotTransitionToCountryMirror(Exception):
295+ """Root exception for transitions to country mirrors."""
296+ webservice_error(400)
297+
298+
299+class CountryMirrorAlreadySet(CannotTransitionToCountryMirror):
300+ """Distribution mirror cannot be set as a country mirror.
301+
302+ Raised when a user tries to change set a distribution mirror as a country
303+ mirror, however there is already one set for that country.
304+ """
305+
306+
307+class MirrorNotOfficial(CannotTransitionToCountryMirror):
308+ """Distribution mirror is not permitted to become a country mirror.
309+
310+ Raised when a user tries to change set a distribution mirror as a country
311+ mirror, however the mirror in question is not official.
312+ """
313+
314+
315+class MirrorHasNoHTTPURL(CannotTransitionToCountryMirror):
316+ """Distribution mirror has no HTTP URL.
317+
318+ Raised when a user tries to make an official mirror a country mirror,
319+ however the mirror has not HTTP URL set.
320+ """
321+
322+
323+class MirrorNotProbed(CannotTransitionToCountryMirror):
324+ """Distribution mirror has not been probed.
325+
326+ Raised when a user tries to set an official mirror as a country mirror,
327+ however the mirror has not been probed yet.
328+ """
329+
330+
331 class MirrorContent(DBEnumeratedType):
332 """The content that is mirrored."""
333
334@@ -284,33 +327,10 @@
335 def getMirrorByURI(self, url):
336 return getUtility(IDistributionMirrorSet).getByRsyncUrl(url)
337
338-class IDistributionMirrorAdminRestricted(Interface):
339- """IDistributionMirror properties requiring launchpad.Admin permission."""
340-
341- reviewer = exported(PublicPersonChoice(
342- title=_('Reviewer'), required=False, readonly=True,
343- vocabulary='ValidPersonOrTeam', description=_(
344- "The person who last reviewed this mirror.")))
345- date_reviewed = exported(Datetime(
346- title=_('Date reviewed'), required=False, readonly=True,
347- description=_(
348- "The date on which this mirror was last reviewed by a mirror admin.")))
349-
350-
351-class IDistributionMirrorEditRestricted(Interface):
352- """IDistributionMirror properties requiring launchpad.Edit permission."""
353-
354- official_candidate = exported(Bool(
355- title=_('Apply to be an official mirror of this distribution'),
356- required=False, readonly=False, default=True))
357- whiteboard = exported(Whiteboard(
358- title=_('Whiteboard'), required=False, readonly=False,
359- description=_("Notes on the current status of the mirror (only "
360- "visible to admins and the mirror's registrant).")))
361-
362-
363-class IDistributionMirrorPublic(Interface):
364- """Public IDistributionMirror properties."""
365+
366+class IDistributionMirror(Interface):
367+ """A mirror of a given distribution."""
368+ export_as_webservice_entry()
369
370 id = Int(title=_('The unique id'), required=True, readonly=True)
371 owner = exported(PublicPersonChoice(
372@@ -386,6 +406,39 @@
373 date_created = exported(Datetime(
374 title=_('Date Created'), required=True, readonly=True,
375 description=_("The date on which this mirror was registered.")))
376+ country_dns_mirror = exported(Bool(
377+ title=_('Country DNS Mirror'),
378+ description=_('Whether this is a country mirror in DNS.'),
379+ required=False, readonly=True, default=False))
380+
381+ reviewer = exported(PublicPersonChoice(
382+ title=_('Reviewer'), required=False, readonly=True,
383+ vocabulary='ValidPersonOrTeam', description=_(
384+ "The person who last reviewed this mirror.")))
385+ date_reviewed = exported(Datetime(
386+ title=_('Date reviewed'), required=False, readonly=True,
387+ description=_(
388+ "The date on which this mirror was last reviewed by a mirror "
389+ "admin.")))
390+
391+ official_candidate = exported(Bool(
392+ title=_('Apply to be an official mirror of this distribution'),
393+ required=False, readonly=False, default=True))
394+ whiteboard = exported(Whiteboard(
395+ title=_('Whiteboard'), required=False, readonly=False,
396+ description=_("Notes on the current status of the mirror (only "
397+ "visible to admins and the mirror's registrant).")))
398+
399+ @export_read_operation()
400+ def canTransitionToCountryMirror():
401+ """Verify if a mirror can be set as a country mirror or return
402+ False."""
403+
404+ @mutator_for(country_dns_mirror)
405+ @operation_parameters(country_dns_mirror=copy_field(country_dns_mirror))
406+ @export_write_operation()
407+ def transitionToCountryMirror(country_dns_mirror):
408+ """Method run on changing country_dns_mirror."""
409
410 @invariant
411 def mirrorMustHaveHTTPOrFTPURL(mirror):
412@@ -521,10 +574,6 @@
413 Sources.gz file refer to and the path to the file itself.
414 """
415
416-class IDistributionMirror(IDistributionMirrorAdminRestricted,
417- IDistributionMirrorEditRestricted, IDistributionMirrorPublic):
418- """A mirror of a given distribution."""
419- export_as_webservice_entry()
420
421
422 class UnableToFetchCDImageFileList(Exception):
423
424=== modified file 'lib/lp/registry/model/distribution.py'
425--- lib/lp/registry/model/distribution.py 2010-03-24 02:53:42 +0000
426+++ lib/lp/registry/model/distribution.py 2010-03-31 15:03:35 +0000
427@@ -402,6 +402,17 @@
428 """See `IDistribution`."""
429 return DistributionMirror.selectOneBy(distribution=self, name=name)
430
431+ def getCountryMirror(self, country, mirror_type):
432+ """See `IDistribution`."""
433+ store = Store.of(self)
434+ results = store.find(
435+ DistributionMirror,
436+ DistributionMirror.distribution == self,
437+ DistributionMirror.country == country,
438+ DistributionMirror.content == mirror_type,
439+ DistributionMirror.country_dns_mirror == True)
440+ return results.one()
441+
442 def newMirror(self, owner, speed, country, content, displayname=None,
443 description=None, http_base_url=None,
444 ftp_base_url=None, rsync_base_url=None,
445
446=== modified file 'lib/lp/registry/model/distributionmirror.py'
447--- lib/lp/registry/model/distributionmirror.py 2010-03-23 20:42:23 +0000
448+++ lib/lp/registry/model/distributionmirror.py 2010-03-31 15:03:35 +0000
449@@ -45,9 +45,11 @@
450 from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
451 from lp.soyuz.interfaces.publishing import PackagePublishingStatus
452 from lp.registry.interfaces.distributionmirror import (
453+ CannotTransitionToCountryMirror, CountryMirrorAlreadySet,
454 IDistributionMirror, IDistributionMirrorSet, IMirrorCDImageDistroSeries,
455 IMirrorDistroArchSeries, IMirrorDistroSeriesSource, IMirrorProbeRecord,
456- MirrorContent, MirrorFreshness, MirrorSpeed, MirrorStatus, PROBE_INTERVAL)
457+ MirrorContent, MirrorFreshness, MirrorHasNoHTTPURL, MirrorNotOfficial,
458+ MirrorNotProbed, MirrorSpeed, MirrorStatus, PROBE_INTERVAL)
459 from lp.registry.interfaces.distroseries import IDistroSeries
460 from lp.registry.interfaces.sourcepackage import SourcePackageFileType
461 from canonical.launchpad.mail import simple_sendmail, format_address
462@@ -100,6 +102,8 @@
463 date_reviewed = UtcDateTimeCol(default=None)
464 whiteboard = StringCol(
465 notNull=False, default=None)
466+ country_dns_mirror = BoolCol(
467+ notNull=True, default=False)
468
469 @property
470 def base_url(self):
471@@ -145,6 +149,64 @@
472 "This mirror has been probed and thus can't be removed.")
473 SQLBase.destroySelf(self)
474
475+ def verifyTransitionToCountryMirror(self):
476+ """Verify that a mirror can be set as a country mirror.
477+
478+ Return True if valid, otherwise raise a subclass of
479+ CannotTransitionToCountryMirror.
480+ """
481+
482+ current_country_mirror = self.distribution.getCountryMirror(
483+ self.country, self.content)
484+
485+ if current_country_mirror is not None:
486+ # Country already has a country mirror.
487+ raise CountryMirrorAlreadySet(
488+ "%s already has a country %s mirror set." % (
489+ self.country.name, self.content))
490+
491+ if not self.isOfficial():
492+ # Only official mirrors may be set as country mirrors.
493+ raise MirrorNotOfficial(
494+ "This mirror may not be set as a country mirror as it is not "
495+ "an official mirror.")
496+
497+ if self.http_base_url is None:
498+ # Country mirrors must have HTTP URLs set.
499+ raise MirrorHasNoHTTPURL(
500+ "This mirror may not be set as a country mirror as it does "
501+ "not have an HTTP URL set.")
502+
503+ if not self.last_probe_record:
504+ # Only mirrors which have been probed may be set as country
505+ # mirrors.
506+ raise MirrorNotProbed(
507+ "This mirror may not be set as a country mirror as it has "
508+ "not been probed.")
509+
510+ # Verification done.
511+ return True
512+
513+ def canTransitionToCountryMirror(self):
514+ """See `IDistributionMirror`."""
515+ try:
516+ return self.verifyTransitionToCountryMirror()
517+ except CannotTransitionToCountryMirror:
518+ return False
519+
520+ def transitionToCountryMirror(self, country_dns_mirror):
521+ """See `IDistributionMirror`."""
522+
523+ # country_dns_mirror has not been changed, do nothing.
524+ if self.country_dns_mirror == country_dns_mirror:
525+ return
526+
527+ # Environment sanity checks.
528+ if country_dns_mirror:
529+ self.verifyTransitionToCountryMirror()
530+
531+ self.country_dns_mirror = country_dns_mirror
532+
533 def getOverallFreshness(self):
534 """See IDistributionMirror"""
535 # XXX Guilherme Salgado 2006-08-16:
536
537=== modified file 'lib/lp/registry/stories/distributionmirror/xx-distribution-mirrors.txt'
538--- lib/lp/registry/stories/distributionmirror/xx-distribution-mirrors.txt 2009-12-10 23:32:13 +0000
539+++ lib/lp/registry/stories/distributionmirror/xx-distribution-mirrors.txt 2010-03-31 15:03:35 +0000
540@@ -40,13 +40,14 @@
541 >>> print browser.title
542 Mirrors of Ubuntu Linux...
543 >>> print_mirrors_by_countries(browser.contents)
544- Antarctica:
545- [(u'Archive-mirror2', u'http', u'128 Kbps', u'Six hours behind')]
546+ Antarctica: [(u'Archive-mirror2', u'http', u'128 Kbps',
547+ u'Six hours behind')]
548 France:
549 [(u'Archive-404-mirror', u'http', u'512 Kbps', u'Last update unknown'),
550- (u'Archive-mirror', u'http', u'128 Kbps', u'Last update unknown')]
551- United Kingdom:
552- [(u'Canonical-archive', u'http', u'100 Mbps', u'Last update unknown')]
553+ (u'Archive-mirror', u'http', u'128 Kbps', u'Last update unknown')]
554+ United Kingdom: [(u'Canonical-archive', u'http', u'100 Mbps',
555+ u'Last update unknown')]
556+
557 >>> find_tags_by_class(browser.contents, 'distromirrorstatusSIXHOURSBEHIND')
558 [<span class="distromirrorstatusSIXHOURSBEHIND">Six hours behind</span>]
559 >>> find_tags_by_class(browser.contents, 'distromirrorstatusUNKNOWN')[0]
560@@ -59,13 +60,12 @@
561 >>> browser.url
562 'http://launchpad.dev/ubuntu/+cdmirrors'
563 >>> print_mirrors_by_countries(browser.contents)
564- France:
565- [(u'Releases-mirror', u'http', u'2 Mbps'),
566+ France:
567+ [(u'Releases-mirror', u'http', u'2 Mbps'),
568 (u'Unreachable-mirror', u'http', u'512 Kbps')]
569- Germany:
570- [(u'Releases-mirror2', u'http', u'2 Mbps')]
571- United Kingdom:
572- [(u'Canonical-releases', u'http', u'100 Mbps')]
573+ Germany: [(u'Releases-mirror2', u'http', u'2 Mbps')]
574+ United Kingdom: [(u'Canonical-releases', u'http', u'100 Mbps')]
575+
576
577 === Disabled mirrors ===
578
579
580=== modified file 'lib/lp/registry/stories/webservice/xx-distribution-mirror.txt'
581--- lib/lp/registry/stories/webservice/xx-distribution-mirror.txt 2010-02-23 19:40:45 +0000
582+++ lib/lp/registry/stories/webservice/xx-distribution-mirror.txt 2010-03-31 15:03:35 +0000
583@@ -8,10 +8,12 @@
584 >>> distro = distros['entries'][0]
585 >>> ubuntu = webservice.get(distro['self_link']).jsonBody()
586 >>> ubuntu_archive_mirrors = webservice.get(ubuntu['archive_mirrors_collection_link']).jsonBody()
587- >>> canonical_archive = ubuntu_archive_mirrors['entries'][0]
588- >>> canonical_archive_json = webservice.get(canonical_archive['self_link']).jsonBody()
589- >>> pprint_entry(canonical_archive_json)
590+ >>> canonical_archive = webservice.named_get(
591+ ... ubuntu['self_link'], 'getMirrorByName',
592+ ... name='canonical-archive').jsonBody()
593+ >>> pprint_entry(canonical_archive)
594 content: u'Archive'
595+ country_dns_mirror: False
596 country_link: u'http://.../+countries/GB'
597 date_created: u'2006-10-16T18:31:43.434567+00:00'
598 date_reviewed: None
599@@ -39,6 +41,7 @@
600 >>> canonical_releases_json = webservice.get(canonical_releases['self_link']).jsonBody()
601 >>> pprint_entry(canonical_releases_json)
602 content: u'CD Image'
603+ country_dns_mirror: False
604 country_link: u'http://.../+countries/GB'
605 date_created: u'2006-10-16T18:31:43.434567+00:00'
606 date_reviewed: None
607@@ -73,12 +76,12 @@
608 >>> karl_db = getUtility(IPersonSet).getByName('karl')
609 >>> test_db = getUtility(IPersonSet).getByName('name12')
610 >>> no_priv_db = getUtility(IPersonSet).getByName('no-priv')
611- >>> karl_webservice = webservice_for_person(karl_db,
612- ... permission=OAuthPermission.WRITE_PUBLIC)
613- >>> test_webservice = webservice_for_person(test_db,
614- ... permission=OAuthPermission.WRITE_PUBLIC)
615- >>> no_priv_webservice = webservice_for_person(no_priv_db,
616- ... permission=OAuthPermission.READ_PUBLIC)
617+ >>> karl_webservice = webservice_for_person(
618+ ... karl_db, permission=OAuthPermission.WRITE_PUBLIC)
619+ >>> test_webservice = webservice_for_person(
620+ ... test_db, permission=OAuthPermission.WRITE_PUBLIC)
621+ >>> no_priv_webservice = webservice_for_person(
622+ ... no_priv_db, permission=OAuthPermission.READ_PUBLIC)
623 >>> logout()
624
625 Ensure that anonymous API sessions can view mirror listings; archive/releases.
626@@ -97,7 +100,9 @@
627
628 One must have special permissions to access certain attributes:
629
630- >>> archive_404_mirror = ubuntu_archive_mirrors['entries'][1]
631+ >>> archive_404_mirror = webservice.named_get(
632+ ... ubuntu['self_link'], 'getMirrorByName',
633+ ... name="archive-404-mirror").jsonBody()
634 >>> response = no_priv_webservice.get(
635 ... archive_404_mirror['self_link']).jsonBody()
636 >>> pprint_entry(response)
637@@ -128,6 +133,7 @@
638 ... archive_404_mirror['self_link']).jsonBody()
639 >>> pprint_entry(response)
640 content: u'Archive'
641+ country_dns_mirror: False
642 country_link: u'http://.../+countries/FR'
643 date_created: u'2006-10-16T18:31:43.438573+00:00'
644 date_reviewed: None
645@@ -209,6 +215,7 @@
646 ... canonical_releases['self_link'], 'application/json', dumps(patch)).jsonBody()
647 >>> pprint_entry(response)
648 content: u'CD Image'
649+ country_dns_mirror: False
650 country_link: u'http://.../+countries/GL'
651 date_created: u'2006-10-16T18:31:43.434567+00:00'
652 date_reviewed: None
653@@ -244,7 +251,9 @@
654 "getOverallFreshness" returns the freshness of the mirror determined by the
655 mirror prober from the mirror's last probe.
656
657- >>> releases_mirror2 = ubuntu_cd_mirrors['entries'][2]
658+ >>> releases_mirror2 = webservice.named_get(
659+ ... ubuntu['self_link'], 'getMirrorByName',
660+ ... name='releases-mirror2').jsonBody()
661 >>> freshness = webservice.named_get(releases_mirror2['self_link'],
662 ... 'getOverallFreshness').jsonBody()
663 >>> print freshness
664
665=== modified file 'lib/lp/registry/stories/webservice/xx-distribution.txt'
666--- lib/lp/registry/stories/webservice/xx-distribution.txt 2010-02-23 17:36:27 +0000
667+++ lib/lp/registry/stories/webservice/xx-distribution.txt 2010-03-31 15:03:35 +0000
668@@ -123,6 +123,7 @@
669 ... name='canonical-releases').jsonBody()
670 >>> pprint_entry(canonical_releases)
671 content: u'CD Image'
672+ country_dns_mirror: False
673 country_link: u'http://.../+countries/GB'
674 date_created: u'2006-10-16T18:31:43.434567+00:00'
675 date_reviewed: None
676@@ -142,3 +143,76 @@
677 speed: u'100 Mbps'
678 status: u'Official'
679 whiteboard: None
680+
681+"getCountryMirror" returns the country DNS mirror for a given country;
682+returning None if there isn't one.
683+
684+ >>> # Prepare stuff.
685+ >>> from lp.registry.interfaces.distribution import IDistributionSet
686+ >>> from zope.component import getUtility
687+ >>> from canonical.launchpad.testing.pages import webservice_for_person
688+ >>> from canonical.launchpad.webapp.interfaces import OAuthPermission
689+ >>> from lp.registry.interfaces.person import IPersonSet
690+ >>> from simplejson import dumps
691+
692+ >>> login('admin@canonical.com')
693+ >>> ubuntu_distro = getUtility(IDistributionSet).getByName('ubuntu')
694+ >>> showa_station = factory.makeMirror(ubuntu_distro,
695+ ... "Showa Station", country=9,
696+ ... http_url="http://mirror.showa.antarctica.org/ubuntu",
697+ ... official_candidate=True)
698+ >>> showa_station_log = factory.makeMirrorProbeRecord(showa_station)
699+ >>> logout()
700+
701+ >>> login(ANONYMOUS)
702+ >>> karl_db = getUtility(IPersonSet).getByName('karl')
703+ >>> karl_webservice = webservice_for_person(karl_db,
704+ ... permission=OAuthPermission.WRITE_PUBLIC)
705+ >>> logout()
706+
707+ >>> # Mark new mirror as official and a country mirror.
708+ >>> patch = {
709+ ... u'status': 'Official',
710+ ... u'country_dns_mirror': True
711+ ... }
712+
713+ >>> antarctica_patch_target = webservice.named_get(
714+ ... ubuntu['self_link'], 'getMirrorByName',
715+ ... name='mirror.showa.antarctica.org-archive').jsonBody()
716+ ... )
717+
718+ >>> response = karl_webservice.patch(
719+ ... antarctica_patch_target['self_link'], 'application/json',
720+ ... dumps(patch))
721+
722+ >>> antarctica = webservice.get("/+countries/AQ").jsonBody()
723+ >>> antarctica_country_mirror_archive = webservice.named_get(
724+ ... ubuntu['self_link'], 'getCountryMirror',
725+ ... country=antarctica['self_link'],
726+ ... mirror_type="Archive").jsonBody()
727+ >>> pprint_entry(antarctica_country_mirror_archive)
728+ content: u'Archive'
729+ country_dns_mirror: True
730+ country_link: u'http://.../+countries/AQ'
731+ ...
732+
733+ >>> uk = webservice.get("/+countries/GB").jsonBody()
734+ >>> uk_country_mirror_archive = webservice.named_get(
735+ ... ubuntu['self_link'], 'getCountryMirror',
736+ ... country=uk['self_link'],
737+ ... mirror_type="Archive")
738+ >>> print uk_country_mirror_archive.jsonBody()
739+ None
740+
741+For "getCountryMirror", the mirror_type parameter must be "Archive" or
742+"CD Images":
743+
744+ >>> uk_country_mirror_archive = webservice.named_get(
745+ ... ubuntu['self_link'], 'getCountryMirror',
746+ ... country=uk['self_link'],
747+ ... mirror_type="Bogus")
748+ >>> print uk_country_mirror_archive.jsonBody()
749+ Traceback (most recent call last):
750+ ...
751+ ValueError: mirror_type: Invalid value "Bogus". Acceptable values are:
752+ Archive, CD Image
753
754=== modified file 'lib/lp/registry/tests/test_distributionmirror.py'
755--- lib/lp/registry/tests/test_distributionmirror.py 2009-10-26 18:40:04 +0000
756+++ lib/lp/registry/tests/test_distributionmirror.py 2010-03-31 15:03:35 +0000
757@@ -3,7 +3,6 @@
758
759 __metaclass__ = type
760
761-from StringIO import StringIO
762 import unittest
763
764 import transaction
765@@ -17,19 +16,19 @@
766 from lp.registry.interfaces.distributionmirror import (
767 IDistributionMirrorSet, MirrorContent, MirrorFreshness)
768 from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
769-from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet
770 from lp.registry.interfaces.pocket import PackagePublishingPocket
771 from lp.registry.interfaces.distribution import IDistributionSet
772 from lp.services.mail import stub
773+from lp.testing.factory import LaunchpadObjectFactory
774
775 from canonical.testing import LaunchpadFunctionalLayer
776
777-
778 class TestDistributionMirror(unittest.TestCase):
779 layer = LaunchpadFunctionalLayer
780
781 def setUp(self):
782 login('test@canonical.com')
783+ self.factory = LaunchpadObjectFactory()
784 mirrorset = getUtility(IDistributionMirrorSet)
785 self.cdimage_mirror = getUtility(IDistributionMirrorSet).getByName(
786 'releases-mirror')
787@@ -132,15 +131,6 @@
788 self.archive_mirror.getOverallFreshness(),
789 MirrorFreshness.TWODAYSBEHIND)
790
791- def _create_probe_record(self, mirror):
792- log_file = StringIO()
793- log_file.write("Fake probe, nothing useful here.")
794- log_file.seek(0)
795- library_alias = getUtility(ILibraryFileAliasSet).create(
796- name='foo', size=len(log_file.getvalue()),
797- file=log_file, contentType='text/plain')
798- proberecord = mirror.newProbeRecord(library_alias)
799-
800 def test_disabling_mirror_and_notifying_owner(self):
801 login('karl@canonical.com')
802
803@@ -148,7 +138,7 @@
804 # If a mirror has been probed only once, the owner will always be
805 # notified when it's disabled --it doesn't matter whether it was
806 # previously enabled or disabled.
807- self._create_probe_record(mirror)
808+ self.factory.makeMirrorProbeRecord(mirror)
809 self.failUnless(mirror.enabled)
810 log = 'Got a 404 on http://foo/baz'
811 mirror.disable(notify_owner=True, log=log)
812@@ -166,7 +156,7 @@
813
814 # For mirrors that have been probed more than once, we'll only notify
815 # the owner if the mirror was previously enabled.
816- self._create_probe_record(mirror)
817+ self.factory.makeMirrorProbeRecord(mirror)
818 mirror.enabled = True
819 mirror.disable(notify_owner=True, log=log)
820 # A notification was sent to the owner and other to the mirror admins.
821
822=== modified file 'lib/lp/testing/factory.py'
823--- lib/lp/testing/factory.py 2010-03-25 02:21:15 +0000
824+++ lib/lp/testing/factory.py 2010-03-31 15:03:35 +0000
825@@ -1981,8 +1981,22 @@
826 team_list = self.makeMailingList(team, owner)
827 return team, team_list
828
829+ def makeMirrorProbeRecord(self, mirror):
830+ """Create a probe record for a mirror of a distribution."""
831+ log_file = StringIO()
832+ log_file.write("Fake probe, nothing useful here.")
833+ log_file.seek(0)
834+
835+ library_alias = getUtility(ILibraryFileAliasSet).create(
836+ name='foo', size=len(log_file.getvalue()),
837+ file=log_file, contentType='text/plain')
838+
839+ proberecord = mirror.newProbeRecord(library_alias)
840+ return proberecord
841+
842 def makeMirror(self, distribution, displayname, country=None,
843- http_url=None, ftp_url=None, rsync_url=None):
844+ http_url=None, ftp_url=None, rsync_url=None,
845+ official_candidate=False):
846 """Create a mirror for the distribution."""
847 # If no URL is specified create an HTTP URL.
848 if http_url is None and ftp_url is None and rsync_url is None:
849@@ -2001,7 +2015,7 @@
850 http_base_url=http_url,
851 ftp_base_url=ftp_url,
852 rsync_base_url=rsync_url,
853- official_candidate=False)
854+ official_candidate=official_candidate)
855 return mirror
856
857 def makeUniqueRFC822MsgId(self):