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

Proposed by Jonathan Davies
Status: Rejected
Rejected by: Curtis Hovey
Proposed branch: lp:~jpds/launchpad/fix_361650
Merge into: lp:launchpad
Diff against target: 545 lines (+306/-19)
12 files modified
database/sampledata/current-dev.sql (+6/-0)
database/sampledata/current.sql (+6/-0)
database/schema/comments.sql (+1/-0)
database/schema/patch-2207-20-0.sql (+19/-0)
lib/lp/registry/browser/distributionmirror.py (+63/-4)
lib/lp/registry/browser/tests/distributionmirror-views.txt (+1/-1)
lib/lp/registry/configure.zcml (+1/-1)
lib/lp/registry/interfaces/distributionmirror.py (+9/-1)
lib/lp/registry/model/distributionmirror.py (+22/-0)
lib/lp/registry/stories/distributionmirror/xx-distribution-mirrors.txt (+162/-11)
lib/lp/registry/templates/distributionmirror-index.pt (+9/-0)
lib/lp/registry/templates/distributionmirror-macros.pt (+7/-1)
To merge this branch: bzr merge lp:~jpds/launchpad/fix_361650
Reviewer Review Type Date Requested Status
Curtis Hovey (community) ui and code Needs Fixing
Jonathan Lange (community) db Approve
Michael Nelson (community) ui Needs Information
Stuart Bishop (community) db Approve
Canonical Launchpad Engineering code Pending
Review via email: mp+16749@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Jonathan Davies (jpds) wrote :

= Summary =

Launchpad should know which mirrors are set as official country mirrors (eg. gb.archive.ubuntu.com), so that we can track these easily on mirror listings.

This branch also includes a database change (patch-2207-20-0.sql) which adds a official_country_mirror column to distributionmirror. It also includes two constraints to ensure that there can not be more than one (archive|releases) mirror per country.

New mirrors have been added to current(-dev).sql for testing purposes too.

A mirror should only be eligible for country mirror status when:

1) it has been reviewed and set as an official mirror by an admin,
2) it has been probed for integrity,
3) it has an HTTP URL set.
4) there is no country mirror for that mirror type in the country.

Checks have been added to the code to ensure that these conditions are met.

Revision history for this message
Jonathan Lange (jml) wrote :

Hello Jonathan,

Thanks for this patch -- I can see how this would be useful.

I don't have any serious issues with the database patch -- the uniqueness constraint looks correct to me. My main concern is that using the word 'official' to describe this new concept will create confusion with the already existing concept of 'official mirror'. Would 'primary_mirror' make sense?

Other than that, the db patch looks good to me.

jml

review: Needs Fixing (db)
Revision history for this message
Stuart Bishop (stub) wrote :

I don't much like the column name either, but it does match the domain language used at http://www.ubuntu.com/getubuntu/mirror/1 so I guess it is correct.

Why are we limiting things to one official country mirror per country? Is there a technical issue we need to ensure doesn't happen, or is this an arbitrary choice made by us or the mirror maintainers?

Patch number is patch-2207-23-0.sql. Its good to land if you can confirm that the UNIQUE constraints are actually desirable.

review: Approve (db)
Revision history for this message
Stuart Bishop (stub) wrote :

> I don't much like the column name either, but it does match the domain
> language used at http://www.ubuntu.com/getubuntu/mirror/1 so I guess it is
> correct.
>
> Why are we limiting things to one official country mirror per country? Is
> there a technical issue we need to ensure doesn't happen, or is this an
> arbitrary choice made by us or the mirror maintainers?
>
> Patch number is patch-2207-23-0.sql. Its good to land if you can confirm that
> the UNIQUE constraints are actually desirable.

One thing that needs changing - You should use 'IS TRUE' and 'IS FALSE' rather than '= TRUE' or '= FALSE'. = and IS give different results in SQL's three valued boolean arithmetic which can confuse the query planner since it isn't as smart as you.

Revision history for this message
Jonathan Davies (jpds) wrote :

Morning Jonathan,

Country mirrors are official mirrors whose FQDNs have been CNAME'ed to a $CC.[archive,releases].ubuntu.com domain. This is why we refer to them as official country mirrors.

Stuart,

We're limited to one country mirror because it's not possible to load balance between two different Ubuntu archives mirrors because of limitations with the apt-get software unless the two mirrors are in perfect sync.

Also I believe, the plan is to, hopefully, auto-generate our CNAME DNS records of country mirrors using the ones specified in LP's DB by admins (with this patch). And it's not possible to have multiple CNAME records to a FQDN.

Revision history for this message
Michael Nelson (michael.nelson) wrote :

Hi Jonothan, thanks for yet another long-awaited feature :)

I'd really like to get Curtis to look at this one, although I've got a few comments.

The first question that came to my mind was, why is this another separate form (currently there is "Change details", "Review", and now "Set/Unset as country mirror".

I personally wonder whether the 'Review' form could accommodate this (perhaps renamed with something that implied official and/or country-mirror status?), so that both items could be presented and dealt with there on the one form. This would also alleviate the need for the changing menu item name ('Set/Unset'). But Curtis might have other ideas.

Also, when editing the mirror after setting it as a country mirror, the validation is great (for country/content-type), but afaik it shouldn't redirect back to the +index page, but instead +country-mirror form itself?

IRC snippet:

<noodles775> jpds: what's the reasoning behind not doing this as an admin-only field displayed on the normal edit form?
<jpds> noodles775: Do you mean the +review form?
<noodles775> jpds: no, I meant the +edit form (but i'm logged in as an admin, maybe that's not presented for mirror-admins...). Checking now.
<jpds> noodles775: I wanted to make it a little obvious by having the separate button for it.
<jpds> noodles775: And mirror registrants shouldn't be able to see the checkbox themselves.

review: Needs Information (ui)
Revision history for this message
Michael Nelson (michael.nelson) wrote :

> Also, when editing the mirror after setting it as a country mirror, the
> validation is great (for country/content-type), but afaik it shouldn't
> redirect back to the +index page, but instead +country-mirror form itself?

Sorry, that's very unclear (and incorrect). What I meant was, once you have set a mirror as a country mirror, when you then go back and change the details (on the +edit page), the validation there is great for country/content-type, but when that validation is triggered, the page should be redirected back to the +edit form, not back to the +index, as there could be other things that were updated by the user?

Revision history for this message
Jonathan Lange (jml) wrote :

OK, since it matches the existing documented domain language, I'm happy with the column name.

review: Approve (db)
Revision history for this message
Curtis Hovey (sinzui) wrote :
Download full text (22.2 KiB)

Hi Johnathan.

Thanks for undertaking this large effort.

I reviewed the code and the UI. There is a lot that needs fixing, but I
can help with some of it.

Starting with the UI.

I like your change to move country_dns_mirror to +edit, but per your original
concern, it is not obvious that the mirror admin uses this page to change the
state. We strive to move edit and view links into the page where the
information is presented so that it is clear to the user that he can change
what he is reading. This is a simple one-line addition to the template.

However, the addition of a new portlet makes the layout worse. This page
has known issues because it was one of the first to be changed for 3.0,
but it was not updated as 3.0 style evolved. The registration and status
information are not in their correct places: the registering slot and the
Mirror information portlet. If they were, the addition of the new field
would be easy and obvious. I tinkered with the template to fix the layout
and I have a diff that you can apply to your branch to get these changes.
In the fixed layout, the official country dns status appears higher on
the page and I think that is good.

    Suggested layout: http://people.canonical.com/~curtis/mirror.png
    Diff to revise layout: http://people.canonical.com/~curtis/mirror.diff

I do not like the info notifications. They make me worry that my other
changes were not accepted. Since the form did what I asked it to, there
is no need to take such an extraordinary measure. I think they should be
removed.

I pondered the addition of the star to the mirrors list. Your approach is
just like badges that are applied to bugs, so I think it is correct. I think
my misgivings are from my unfamiliarity with the star as an icon. I did
learn it was official when I placed my mouse over it. I think we can leave
it as is.

BTW. There is a problem with this merge proposal. It must be merged into
db-devel since the schema changes are incompatible with lpnet/edge. If you
had proposed the merge with db-devel you would have seen that your db
patch conflicts. You need to rename it and update it as suggested by Stuart
before this can be tested properly.

> === modified file 'lib/lp/registry/browser/distributionmirror.py'
> --- lib/lp/registry/browser/distributionmirror.py 2009-12-12 22:15:31 +0000
> +++ lib/lp/registry/browser/distributionmirror.py 2010-01-05 15:14:20 +0000

...

> @@ -275,10 +278,66 @@
> """See `LaunchpadFormView`."""
> return canonical_url(self.context)
>
> + def validate(self, data):
> + dns_mirror_choice = data.get('country_dns_mirror')
> +
> + # This mirror has not been marked as a country mirror.
> + if dns_mirror_choice == True:
> + # Safe-guard against multiple country mirrors for one country.
> + current_country_mirror = (
> + getUtility(IDistributionMirrorSet).getCountryMirrorForCountry(
> + self.context.country, self.context.content))
> +
> + # User has decided to mark it as one.
> + if current_country_mirror is None:
> + # There are none - mark this one as the one.
> + sel...

review: Needs Fixing (ui and code)
Revision history for this message
Jonathan Lange (jml) wrote :

Curtis & Michael, I can't believe that you _both_ spelled Jonathan's name incorrectly. :P

Revision history for this message
Michael Nelson (michael.nelson) wrote :

On Fri, Jan 8, 2010 at 5:59 AM, Jonathan Lange <email address hidden> wrote:
> Curtis & Michael, I can't believe that you _both_ spelled Jonathan's name incorrectly. :P

Sorry Jonathan... too many Aussie friends who go by "Jono" :/

> --
> https://code.edge.launchpad.net/~jpds/launchpad/fix_361650/+merge/16749
> You are reviewing the proposed merge of lp:~jpds/launchpad/fix_361650 into lp:launchpad/devel.
>

Revision history for this message
Graham Binns (gmb) wrote :

Can someone who knows the situation of this branch update its status? It'd be great if it was either WIP or Approved, just to get it out of the "reviews I can do" queue...

Revision history for this message
Curtis Hovey (sinzui) wrote :

I rejected this branch because the implementation and testing were unviable. Johnathan. Has landed the schema change and some prerequisite branches per my advice. His replacement branch to land this feature as a model change is in review.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'database/sampledata/current-dev.sql'
--- database/sampledata/current-dev.sql 2009-12-14 13:49:03 +0000
+++ database/sampledata/current-dev.sql 2010-01-05 15:14:20 +0000
@@ -2219,6 +2219,9 @@
2219INSERT INTO distributionmirror (id, distribution, name, http_base_url, ftp_base_url, rsync_base_url, displayname, description, owner, speed, country, content, official_candidate, enabled, date_created, whiteboard, status, date_reviewed, reviewer) VALUES (8, 1, 'canonical-archive', 'http://archive.ubuntu.com/ubuntu/', NULL, NULL, NULL, NULL, 1, 70, 225, 1, true, true, '2006-10-16 18:31:43.434567', NULL, 30, NULL, NULL);2219INSERT INTO distributionmirror (id, distribution, name, http_base_url, ftp_base_url, rsync_base_url, displayname, description, owner, speed, country, content, official_candidate, enabled, date_created, whiteboard, status, date_reviewed, reviewer) VALUES (8, 1, 'canonical-archive', 'http://archive.ubuntu.com/ubuntu/', NULL, NULL, NULL, NULL, 1, 70, 225, 1, true, true, '2006-10-16 18:31:43.434567', NULL, 30, NULL, NULL);
2220INSERT INTO distributionmirror (id, distribution, name, http_base_url, ftp_base_url, rsync_base_url, displayname, description, owner, speed, country, content, official_candidate, enabled, date_created, whiteboard, status, date_reviewed, reviewer) VALUES (9, 1, 'canonical-releases', 'http://releases.ubuntu.com/', NULL, NULL, NULL, NULL, 1, 70, 225, 2, true, true, '2006-10-16 18:31:43.434567', NULL, 30, NULL, NULL);2220INSERT INTO distributionmirror (id, distribution, name, http_base_url, ftp_base_url, rsync_base_url, displayname, description, owner, speed, country, content, official_candidate, enabled, date_created, whiteboard, status, date_reviewed, reviewer) VALUES (9, 1, 'canonical-releases', 'http://releases.ubuntu.com/', NULL, NULL, NULL, NULL, 1, 70, 225, 2, true, true, '2006-10-16 18:31:43.434567', NULL, 30, NULL, NULL);
2221INSERT INTO distributionmirror (id, distribution, name, http_base_url, ftp_base_url, rsync_base_url, displayname, description, owner, speed, country, content, official_candidate, enabled, date_created, whiteboard, status, date_reviewed, reviewer) VALUES (10, 1, 'random-releases-mirror', 'http://releases.random.com/', NULL, NULL, NULL, NULL, 1, 70, 225, 2, true, true, '2006-10-16 18:31:43.434567', NULL, 10, NULL, NULL);2221INSERT INTO distributionmirror (id, distribution, name, http_base_url, ftp_base_url, rsync_base_url, displayname, description, owner, speed, country, content, official_candidate, enabled, date_created, whiteboard, status, date_reviewed, reviewer) VALUES (10, 1, 'random-releases-mirror', 'http://releases.random.com/', NULL, NULL, NULL, NULL, 1, 70, 225, 2, true, true, '2006-10-16 18:31:43.434567', NULL, 10, NULL, NULL);
2222INSERT INTO distributionmirror (id, distribution, name, http_base_url, ftp_base_url, rsync_base_url, displayname, description, owner, speed, country, content, official_candidate, enabled, date_created, whiteboard, status, date_reviewed, reviewer) VALUES (11, 1, 'mirror.davis.antarctica.org-archive', 'http://mirror.davis.antarctica.org/ubuntu', NULL, NULL, 'Davis Station', NULL, 1, 70, 9, 1, true, true, '2010-01-03 00:07:44.412317', NULL, 30, NULL, NULL);
2223INSERT INTO distributionmirror (id, distribution, name, http_base_url, ftp_base_url, rsync_base_url, displayname, description, owner, speed, country, content, official_candidate, enabled, date_created, whiteboard, status, date_reviewed, reviewer) VALUES (12, 1, 'ubuntu.mirror.tudos.de-archive', 'http://ubuntu.mirror.tudos.de/ubuntu', NULL, 'rsync://ubuntu.mirror.tudos.de/ubuntu', 'Technische Universität Dresden', NULL, 12, 110, 82, 1, true, true, '2010-01-03 01:48:02.93419', NULL, 30, NULL, NULL);
2224INSERT INTO distributionmirror (id, distribution, name, http_base_url, ftp_base_url, rsync_base_url, displayname, description, owner, speed, country, content, official_candidate, enabled, date_created, whiteboard, status, date_reviewed, reviewer) VALUES (13, 1, 'mirror.mawson.antarctica.org-releases', 'http://mirror.mawson.antarctica.org/ubuntu-releases', NULL, NULL, 'Mawson Station', NULL, 1, 70, 9, 2, true, true, '2010-01-03 02:05:10.424767', NULL, 30, NULL, NULL);
22222225
22232226
2224ALTER TABLE distributionmirror ENABLE TRIGGER ALL;2227ALTER TABLE distributionmirror ENABLE TRIGGER ALL;
@@ -4857,6 +4860,9 @@
48574860
4858INSERT INTO mirrorproberecord (id, distribution_mirror, log_file, date_created) VALUES (1, 6, 46, '2006-05-24 17:11:59.37369');4861INSERT INTO mirrorproberecord (id, distribution_mirror, log_file, date_created) VALUES (1, 6, 46, '2006-05-24 17:11:59.37369');
4859INSERT INTO mirrorproberecord (id, distribution_mirror, log_file, date_created) VALUES (2, 7, 47, '2006-05-24 17:12:03.714206');4862INSERT INTO mirrorproberecord (id, distribution_mirror, log_file, date_created) VALUES (2, 7, 47, '2006-05-24 17:12:03.714206');
4863INSERT INTO mirrorproberecord (id, distribution_mirror, log_file, date_created) VALUES (3, 11, 47, '2010-01-03 00:20:30.412312');
4864INSERT INTO mirrorproberecord (id, distribution_mirror, log_file, date_created) VALUES (4, 12, 46, '2010-01-03 01:49:27.432231');
4865INSERT INTO mirrorproberecord (id, distribution_mirror, log_file, date_created) VALUES (5, 13, 47, '2010-01-03 02:07:17.434121');
48604866
48614867
4862ALTER TABLE mirrorproberecord ENABLE TRIGGER ALL;4868ALTER TABLE mirrorproberecord ENABLE TRIGGER ALL;
48634869
=== modified file 'database/sampledata/current.sql'
--- database/sampledata/current.sql 2009-12-14 13:49:03 +0000
+++ database/sampledata/current.sql 2010-01-05 15:14:20 +0000
@@ -2201,6 +2201,9 @@
2201INSERT INTO distributionmirror (id, distribution, name, http_base_url, ftp_base_url, rsync_base_url, displayname, description, owner, speed, country, content, official_candidate, enabled, date_created, whiteboard, status, date_reviewed, reviewer) VALUES (8, 1, 'canonical-archive', 'http://archive.ubuntu.com/ubuntu/', NULL, NULL, NULL, NULL, 1, 70, 225, 1, true, true, '2006-10-16 18:31:43.434567', NULL, 30, NULL, NULL);2201INSERT INTO distributionmirror (id, distribution, name, http_base_url, ftp_base_url, rsync_base_url, displayname, description, owner, speed, country, content, official_candidate, enabled, date_created, whiteboard, status, date_reviewed, reviewer) VALUES (8, 1, 'canonical-archive', 'http://archive.ubuntu.com/ubuntu/', NULL, NULL, NULL, NULL, 1, 70, 225, 1, true, true, '2006-10-16 18:31:43.434567', NULL, 30, NULL, NULL);
2202INSERT INTO distributionmirror (id, distribution, name, http_base_url, ftp_base_url, rsync_base_url, displayname, description, owner, speed, country, content, official_candidate, enabled, date_created, whiteboard, status, date_reviewed, reviewer) VALUES (9, 1, 'canonical-releases', 'http://releases.ubuntu.com/', NULL, NULL, NULL, NULL, 1, 70, 225, 2, true, true, '2006-10-16 18:31:43.434567', NULL, 30, NULL, NULL);2202INSERT INTO distributionmirror (id, distribution, name, http_base_url, ftp_base_url, rsync_base_url, displayname, description, owner, speed, country, content, official_candidate, enabled, date_created, whiteboard, status, date_reviewed, reviewer) VALUES (9, 1, 'canonical-releases', 'http://releases.ubuntu.com/', NULL, NULL, NULL, NULL, 1, 70, 225, 2, true, true, '2006-10-16 18:31:43.434567', NULL, 30, NULL, NULL);
2203INSERT INTO distributionmirror (id, distribution, name, http_base_url, ftp_base_url, rsync_base_url, displayname, description, owner, speed, country, content, official_candidate, enabled, date_created, whiteboard, status, date_reviewed, reviewer) VALUES (10, 1, 'random-releases-mirror', 'http://releases.random.com/', NULL, NULL, NULL, NULL, 1, 70, 225, 2, true, true, '2006-10-16 18:31:43.434567', NULL, 10, NULL, NULL);2203INSERT INTO distributionmirror (id, distribution, name, http_base_url, ftp_base_url, rsync_base_url, displayname, description, owner, speed, country, content, official_candidate, enabled, date_created, whiteboard, status, date_reviewed, reviewer) VALUES (10, 1, 'random-releases-mirror', 'http://releases.random.com/', NULL, NULL, NULL, NULL, 1, 70, 225, 2, true, true, '2006-10-16 18:31:43.434567', NULL, 10, NULL, NULL);
2204INSERT INTO distributionmirror (id, distribution, name, http_base_url, ftp_base_url, rsync_base_url, displayname, description, owner, speed, country, content, official_candidate, enabled, date_created, whiteboard, status, date_reviewed, reviewer) VALUES (11, 1, 'mirror.davis.antarctica.org-archive', 'http://mirror.davis.antarctica.org/ubuntu', NULL, NULL, 'Davis Station', NULL, 1, 70, 9, 1, true, true, '2010-01-03 00:07:44.412317', NULL, 30, NULL, NULL);
2205INSERT INTO distributionmirror (id, distribution, name, http_base_url, ftp_base_url, rsync_base_url, displayname, description, owner, speed, country, content, official_candidate, enabled, date_created, whiteboard, status, date_reviewed, reviewer) VALUES (12, 1, 'ubuntu.mirror.tudos.de-archive', 'http://ubuntu.mirror.tudos.de/ubuntu', NULL, 'rsync://ubuntu.mirror.tudos.de/ubuntu', 'Technische Universität Dresden', NULL, 12, 110, 82, 1, true, true, '2010-01-03 01:48:02.93419', NULL, 30, NULL, NULL);
2206INSERT INTO distributionmirror (id, distribution, name, http_base_url, ftp_base_url, rsync_base_url, displayname, description, owner, speed, country, content, official_candidate, enabled, date_created, whiteboard, status, date_reviewed, reviewer) VALUES (13, 1, 'mirror.mawson.antarctica.org-releases', 'http://mirror.mawson.antarctica.org/ubuntu-releases', NULL, NULL, 'Mawson Station', NULL, 1, 70, 9, 2, true, true, '2010-01-03 02:05:10.424767', NULL, 30, NULL, NULL);
22042207
22052208
2206ALTER TABLE distributionmirror ENABLE TRIGGER ALL;2209ALTER TABLE distributionmirror ENABLE TRIGGER ALL;
@@ -4776,6 +4779,9 @@
47764779
4777INSERT INTO mirrorproberecord (id, distribution_mirror, log_file, date_created) VALUES (1, 6, 46, '2006-05-24 17:11:59.37369');4780INSERT INTO mirrorproberecord (id, distribution_mirror, log_file, date_created) VALUES (1, 6, 46, '2006-05-24 17:11:59.37369');
4778INSERT INTO mirrorproberecord (id, distribution_mirror, log_file, date_created) VALUES (2, 7, 47, '2006-05-24 17:12:03.714206');4781INSERT INTO mirrorproberecord (id, distribution_mirror, log_file, date_created) VALUES (2, 7, 47, '2006-05-24 17:12:03.714206');
4782INSERT INTO mirrorproberecord (id, distribution_mirror, log_file, date_created) VALUES (3, 11, 47, '2010-01-03 00:20:30.412312');
4783INSERT INTO mirrorproberecord (id, distribution_mirror, log_file, date_created) VALUES (4, 12, 46, '2010-01-03 01:49:27.432231');
4784INSERT INTO mirrorproberecord (id, distribution_mirror, log_file, date_created) VALUES (5, 13, 47, '2010-01-03 02:07:17.434121');
47794785
47804786
4781ALTER TABLE mirrorproberecord ENABLE TRIGGER ALL;4787ALTER TABLE mirrorproberecord ENABLE TRIGGER ALL;
47824788
=== modified file 'database/schema/comments.sql'
--- database/schema/comments.sql 2009-12-01 13:45:58 +0000
+++ database/schema/comments.sql 2010-01-05 15:14:20 +0000
@@ -1794,6 +1794,7 @@
1794COMMENT ON COLUMN DistributionMirror.whiteboard IS 'Notes on the current status of the mirror';1794COMMENT ON COLUMN DistributionMirror.whiteboard IS 'Notes on the current status of the mirror';
1795COMMENT ON COLUMN DistributionMirror.date_created IS 'The date and time the mirror was created.';1795COMMENT ON COLUMN DistributionMirror.date_created IS 'The date and time the mirror was created.';
1796COMMENT ON COLUMN DistributionMirror.date_reviewed IS 'The date and time the mirror was reviewed.';1796COMMENT ON COLUMN DistributionMirror.date_reviewed IS 'The date and time the mirror was reviewed.';
1797COMMENT ON COLUMN DistributionMirror.country_dns_mirror IS 'Is the mirror an country mirror in DNS?';
17971798
1798-- MirrorDistroArchSeries1799-- MirrorDistroArchSeries
1799COMMENT ON TABLE MirrorDistroArchSeries IS 'The mirror of the packages of a given Distro Arch Release.';1800COMMENT ON TABLE MirrorDistroArchSeries IS 'The mirror of the packages of a given Distro Arch Release.';
18001801
=== added file 'database/schema/patch-2207-20-0.sql'
--- database/schema/patch-2207-20-0.sql 1970-01-01 00:00:00 +0000
+++ database/schema/patch-2207-20-0.sql 2010-01-05 15:14:20 +0000
@@ -0,0 +1,19 @@
1-- Copyright 2010 Canonical Ltd. This software is licensed under the
2-- GNU Affero General Public License version 3 (see the file LICENSE).
3
4SET client_min_messages=ERROR;
5
6ALTER TABLE DistributionMirror
7 ADD COLUMN country_dns_mirror boolean DEFAULT FALSE NOT NULL;
8
9--- Index for archive mirrors.
10CREATE UNIQUE INDEX distributionmirror__archive__distribution__country__key
11ON DistributionMirror(distribution, country, content)
12WHERE country_dns_mirror IS TRUE AND content = 1;
13
14--- Index for releases mirrors.
15CREATE UNIQUE INDEX distributionmirror__releases__distribution__country__key
16ON DistributionMirror(distribution, country, content)
17WHERE country_dns_mirror IS TRUE AND content = 2;
18
19INSERT INTO LaunchpadDatabaseRevision VALUES (2207, 20, 0);
020
=== modified file 'lib/lp/registry/browser/distributionmirror.py'
--- lib/lp/registry/browser/distributionmirror.py 2009-12-12 22:15:31 +0000
+++ lib/lp/registry/browser/distributionmirror.py 2010-01-05 15:14:20 +0000
@@ -18,6 +18,7 @@
18from datetime import datetime18from datetime import datetime
19import pytz19import pytz
2020
21from zope.component import getUtility
21from zope.lifecycleevent import ObjectCreatedEvent22from zope.lifecycleevent import ObjectCreatedEvent
22from zope.event import notify23from zope.event import notify
23from zope.interface import implements24from zope.interface import implements
@@ -32,7 +33,8 @@
32from lp.registry.interfaces.distribution import (33from lp.registry.interfaces.distribution import (
33 IDistributionMirrorMenuMarker)34 IDistributionMirrorMenuMarker)
34from lp.registry.interfaces.distributionmirror import (35from lp.registry.interfaces.distributionmirror import (
35 IDistributionMirror)36 IDistributionMirror, IDistributionMirrorSet)
37from canonical.launchpad.webapp.authorization import check_permission
36from canonical.launchpad.webapp.batching import BatchNavigator38from canonical.launchpad.webapp.batching import BatchNavigator
37from canonical.launchpad.webapp.publisher import LaunchpadView39from canonical.launchpad.webapp.publisher import LaunchpadView
38from canonical.launchpad.webapp import (40from canonical.launchpad.webapp import (
@@ -259,7 +261,8 @@
259 schema = IDistributionMirror261 schema = IDistributionMirror
260 field_names = ["name", "displayname", "description", "whiteboard",262 field_names = ["name", "displayname", "description", "whiteboard",
261 "http_base_url", "ftp_base_url", "rsync_base_url", "speed",263 "http_base_url", "ftp_base_url", "rsync_base_url", "speed",
262 "country", "content", "official_candidate"]264 "country", "content", "official_candidate",
265 "country_dns_mirror"]
263 @property266 @property
264 def label(self):267 def label(self):
265 """See `LaunchpadFormView`."""268 """See `LaunchpadFormView`."""
@@ -275,10 +278,66 @@
275 """See `LaunchpadFormView`."""278 """See `LaunchpadFormView`."""
276 return canonical_url(self.context)279 return canonical_url(self.context)
277280
281 def validate(self, data):
282 dns_mirror_choice = data.get('country_dns_mirror')
283
284 # This mirror has not been marked as a country mirror.
285 if dns_mirror_choice == True:
286 # Safe-guard against multiple country mirrors for one country.
287 current_country_mirror = (
288 getUtility(IDistributionMirrorSet).getCountryMirrorForCountry(
289 self.context.country, self.context.content))
290
291 # User has decided to mark it as one.
292 if current_country_mirror is None:
293 # There are none - mark this one as the one.
294 self.request.response.addInfoNotification(
295 "The mirror %s has been set as the country DNS mirror for "
296 "%s." % (self.context.title, self.context.country.name))
297 elif current_country_mirror == self.context:
298 return # We are editing the country DNS mirror.
299 else:
300 # Make sure that there isn't already a mirror for this
301 # country already.
302 self.setFieldError("country_dns_mirror", "%s already "
303 "has a country DNS mirror set, please unset the country "
304 "DNS mirror status of %s before continuing." % (
305 self.context.country.name,
306 current_country_mirror.title))
307 elif dns_mirror_choice == False and self.context.country_dns_mirror:
308 self.request.response.addInfoNotification(
309 "The mirror %s has been unset as the country DNS mirror for "
310 "%s." % (self.context.title, self.context.country.name))
311
312 def setUpFields(self):
313 super(DistributionMirrorEditView, self).setUpFields()
314
315 is_admin = check_permission('launchpad.Admin', self.context)
316
317 mirror_qualifies = (
318 self.context.last_probe_record is not None
319 and self.context.isOfficial()
320 and self.context.http_base_url is not None)
321
322 # User must be an admin and the mirror must pass the above.
323 if not is_admin or not mirror_qualifies:
324 self.form_fields = self.form_fields.omit('country_dns_mirror')
325
278 @action(_("Save"), name="save")326 @action(_("Save"), name="save")
279 def action_save(self, action, data):327 def action_save(self, action, data):
280 self.updateContextFromData(data)328 # Ensure that we are not changing an archive mirror to a releases one
281 self.next_url = canonical_url(self.context)329 # and that it's a country mirror at the same time.
330 if data.get('content') != self.context.content:
331 if self.context.country_dns_mirror:
332 self.setFieldError("content", "Changing the mirror content "
333 "type of country DNS mirrors is forbidden.")
334 elif data.get('country') != self.context.country:
335 if self.context.country_dns_mirror:
336 self.setFieldError("country", "Changing the country of country "
337 "DNS mirrors is forbidden.")
338 else:
339 self.updateContextFromData(data)
340 self.next_url = canonical_url(self.context)
282341
283342
284class DistributionMirrorReassignmentView(ObjectReassignmentView):343class DistributionMirrorReassignmentView(ObjectReassignmentView):
285344
=== modified file 'lib/lp/registry/browser/tests/distributionmirror-views.txt'
--- lib/lp/registry/browser/tests/distributionmirror-views.txt 2009-12-14 15:22:53 +0000
+++ lib/lp/registry/browser/tests/distributionmirror-views.txt 2010-01-05 15:14:20 +0000
@@ -243,7 +243,7 @@
243 >>> view.field_names243 >>> view.field_names
244 ['name', 'displayname', 'description', 'whiteboard', 'http_base_url',244 ['name', 'displayname', 'description', 'whiteboard', 'http_base_url',
245 'ftp_base_url', 'rsync_base_url', 'speed', 'country', 'content',245 'ftp_base_url', 'rsync_base_url', 'speed', 'country', 'content',
246 'official_candidate']246 'official_candidate', 'country_dns_mirror']
247247
248 >>> print mirror.ftp_base_url248 >>> print mirror.ftp_base_url
249 None249 None
250250
=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml 2009-12-09 14:55:02 +0000
+++ lib/lp/registry/configure.zcml 2010-01-05 15:14:20 +0000
@@ -1611,7 +1611,7 @@
1611 speed country content official_candidate owner" />1611 speed country content official_candidate owner" />
1612 <require1612 <require
1613 permission="launchpad.Admin"1613 permission="launchpad.Admin"
1614 set_attributes="status reviewer date_reviewed"1614 set_attributes="status reviewer date_reviewed country_dns_mirror"
1615 attributes="destroySelf" />1615 attributes="destroySelf" />
1616 </class>1616 </class>
16171617
16181618
=== modified file 'lib/lp/registry/interfaces/distributionmirror.py'
--- lib/lp/registry/interfaces/distributionmirror.py 2009-10-26 18:40:04 +0000
+++ lib/lp/registry/interfaces/distributionmirror.py 2010-01-05 15:14:20 +0000
@@ -330,8 +330,11 @@
330 'mirror of packages for this distribution.'),330 'mirror of packages for this distribution.'),
331 vocabulary=MirrorContent)331 vocabulary=MirrorContent)
332 official_candidate = Bool(332 official_candidate = Bool(
333 title=_('Apply to be an official mirror of this distribution'),333 title=_('Apply to be an official mirror of this distribution.'),
334 required=False, readonly=False, default=True)334 required=False, readonly=False, default=True)
335 country_dns_mirror = Bool(
336 title=_('Set this mirror as an country DNS mirror.'),
337 required=False, readonly=False, default=False)
335 status = Choice(338 status = Choice(
336 title=_('Status'), required=True, readonly=False,339 title=_('Status'), required=True, readonly=False,
337 vocabulary=MirrorStatus)340 vocabulary=MirrorStatus)
@@ -536,6 +539,11 @@
536 def getByRsyncUrl(url):539 def getByRsyncUrl(url):
537 """Return the mirror with the given Rsync URL or None."""540 """Return the mirror with the given Rsync URL or None."""
538541
542 def getCountryMirrorForCountry(country, mirror_type):
543 """Return the country mirror for a certain country for the specified
544 mirror type.
545 """
546
539547
540class IMirrorDistroArchSeries(Interface):548class IMirrorDistroArchSeries(Interface):
541 """The mirror of the packages of a given Distro Arch Series"""549 """The mirror of the packages of a given Distro Arch Series"""
542550
=== modified file 'lib/lp/registry/model/distributionmirror.py'
--- lib/lp/registry/model/distributionmirror.py 2009-10-26 18:40:04 +0000
+++ lib/lp/registry/model/distributionmirror.py 2010-01-05 15:14:20 +0000
@@ -94,6 +94,8 @@
94 notNull=True, enum=MirrorContent)94 notNull=True, enum=MirrorContent)
95 official_candidate = BoolCol(95 official_candidate = BoolCol(
96 notNull=True, default=False)96 notNull=True, default=False)
97 country_dns_mirror = BoolCol(
98 notNull=True, default=False)
97 status = EnumCol(99 status = EnumCol(
98 notNull=True, default=MirrorStatus.PENDING_REVIEW, enum=MirrorStatus)100 notNull=True, default=MirrorStatus.PENDING_REVIEW, enum=MirrorStatus)
99 date_created = UtcDateTimeCol(notNull=True, default=UTC_NOW)101 date_created = UtcDateTimeCol(notNull=True, default=UTC_NOW)
@@ -402,6 +404,26 @@
402 """See IDistributionMirrorSet"""404 """See IDistributionMirrorSet"""
403 return DistributionMirror.get(mirror_id)405 return DistributionMirror.get(mirror_id)
404406
407 def getCountryMirrorForCountry(self, country, mirror_type):
408 """See IDistributionMirrorSet."""
409 country_id = None
410 if country is not None:
411 country_id = country.id
412
413 base_query = AND(
414 DistributionMirror.q.content == mirror_type,
415 DistributionMirror.q.enabled == True,
416 DistributionMirror.q.http_base_url != None,
417 DistributionMirror.q.official_candidate == True,
418 DistributionMirror.q.status == MirrorStatus.OFFICIAL,
419 DistributionMirror.q.country_dns_mirror == True)
420
421 query = AND(DistributionMirror.q.countryID == country_id, base_query)
422
423 country_mirror = DistributionMirror.selectOne(query)
424
425 return country_mirror
426
405 def getBestMirrorsForCountry(self, country, mirror_type):427 def getBestMirrorsForCountry(self, country, mirror_type):
406 """See IDistributionMirrorSet"""428 """See IDistributionMirrorSet"""
407 # As per mvo's request we only return mirrors which have an429 # As per mvo's request we only return mirrors which have an
408430
=== modified file 'lib/lp/registry/stories/distributionmirror/xx-distribution-mirrors.txt'
--- lib/lp/registry/stories/distributionmirror/xx-distribution-mirrors.txt 2009-12-10 23:32:13 +0000
+++ lib/lp/registry/stories/distributionmirror/xx-distribution-mirrors.txt 2010-01-05 15:14:20 +0000
@@ -41,12 +41,16 @@
41 Mirrors of Ubuntu Linux...41 Mirrors of Ubuntu Linux...
42 >>> print_mirrors_by_countries(browser.contents)42 >>> print_mirrors_by_countries(browser.contents)
43 Antarctica:43 Antarctica:
44 [(u'Archive-mirror2', u'http', u'128 Kbps', u'Six hours behind')]44 [(u'Davis Station', u'', u'http', u'100 Mbps', u'Last update unknown'),
45 (u'Archive-mirror2', u'', u'http', u'128 Kbps', u'Six hours behind')]
45 France:46 France:
46 [(u'Archive-404-mirror', u'http', u'512 Kbps', u'Last update unknown'),47 [(u'Archive-404-mirror', u'', u'http', u'512 Kbps', u'Last update unknown'),
47 (u'Archive-mirror', u'http', u'128 Kbps', u'Last update unknown')]48 (u'Archive-mirror', u'', u'http', u'128 Kbps', u'Last update unknown')]
49 Germany:
50 [(u'Technische Universit\xe4t Dresden', u'', u'http\nrsync', u'10 Gbps',
51 u'Last update unknown')]
48 United Kingdom:52 United Kingdom:
49 [(u'Canonical-archive', u'http', u'100 Mbps', u'Last update unknown')]53 [(u'Canonical-archive', u'', u'http', u'100 Mbps', u'Last update unknown')]
50 >>> find_tags_by_class(browser.contents, 'distromirrorstatusSIXHOURSBEHIND')54 >>> find_tags_by_class(browser.contents, 'distromirrorstatusSIXHOURSBEHIND')
51 [<span class="distromirrorstatusSIXHOURSBEHIND">Six hours behind</span>]55 [<span class="distromirrorstatusSIXHOURSBEHIND">Six hours behind</span>]
52 >>> find_tags_by_class(browser.contents, 'distromirrorstatusUNKNOWN')[0]56 >>> find_tags_by_class(browser.contents, 'distromirrorstatusUNKNOWN')[0]
@@ -59,13 +63,160 @@
59 >>> browser.url63 >>> browser.url
60 'http://launchpad.dev/ubuntu/+cdmirrors'64 'http://launchpad.dev/ubuntu/+cdmirrors'
61 >>> print_mirrors_by_countries(browser.contents)65 >>> print_mirrors_by_countries(browser.contents)
66 Antarctica: [(u'Mawson Station', u'', u'http', u'100 Mbps')]
62 France:67 France:
63 [(u'Releases-mirror', u'http', u'2 Mbps'),68 [(u'Releases-mirror', u'', u'http', u'2 Mbps'),
64 (u'Unreachable-mirror', u'http', u'512 Kbps')]69 (u'Unreachable-mirror', u'', u'http', u'512 Kbps')]
65 Germany:70 Germany:
66 [(u'Releases-mirror2', u'http', u'2 Mbps')]71 [(u'Releases-mirror2', u'', u'http', u'2 Mbps')]
67 United Kingdom:72 United Kingdom:
68 [(u'Canonical-releases', u'http', u'100 Mbps')]73 [(u'Canonical-releases', u'', u'http', u'100 Mbps')]
74
75
76=== Country DNS mirrors ===
77
78Country DNS mirrors are official mirrors whose FQDN have been CNAME'd for
79domain records such as gb.archive.ubuntu.com. They are highly reliable and set
80during installation, depending on which country the user specified they were
81in.
82
83Mirror listing administrators are the ones with the authority to set mirrors as
84country mirrors.
85
86 >>> browser = setupBrowser(auth='Basic karl@canonical.com:test')
87 >>> browser.open('https://launchpad.dev/ubuntu/+mirror/archive-mirror2')
88 >>> browser.getLink("Change details").click()
89 >>> print browser.title
90 Edit mirror Archive-mirror2...
91 >>> browser.getControl(name='field.country_dns_mirror').value = True
92 >>> browser.getControl('Save').click()
93 >>> print find_tags_by_class(browser.contents, 'informational message')[0]
94 <div class="informational message">The mirror Archive-mirror2 has been set as
95 the country DNS mirror for Antarctica.</div>
96 >>> print find_tag_by_id(browser.contents, 'maincontent').renderContents()
97 <BLANKLINE>
98 ...Country DNS mirror...
99 <BLANKLINE>
100 ...This mirror is the country DNS mirror for <span>Antarctica</span>...
101
102And also to unset mirrors as country mirrors:
103
104 >>> browser = setupBrowser(auth='Basic karl@canonical.com:test')
105 >>> browser.open('https://launchpad.dev/ubuntu/+mirror/archive-mirror2')
106 >>> browser.getLink("Change details").click()
107 >>> print browser.title
108 Edit mirror Archive-mirror2...
109 >>> browser.getControl(name='field.country_dns_mirror').value = False
110 >>> browser.getControl('Save').click()
111 >>> print find_tags_by_class(browser.contents, 'informational message')[0]
112 <div class="informational message">The mirror Archive-mirror2 has been
113 unset as the country DNS mirror for Antarctica.</div>
114
115Unofficial/pending-review mirrors may not be set as country mirrors:
116
117 >>> browser = setupBrowser(auth='Basic karl@canonical.com:test')
118 >>> browser.open('https://launchpad.dev/ubuntu/+mirror/invalid-mirror/+edit')
119 >>> browser.getControl(
120 ... name='field.country_dns_mirror').value = True
121 Traceback (most recent call last):
122 ...
123 LookupError: name 'field.country_dns_mirror'
124
125Mirror registrants may not make changes to country DNS mirrors settings:
126
127 >>> browser_mirror_registrant = setupBrowser(
128 ... auth='Basic test@canonical.com:test')
129 >>> browser_mirror_registrant.open(
130 ... 'https://launchpad.dev/ubuntu/+mirror/ubuntu.mirror.tudos.de-archive/+edit')
131 >>> browser_mirror_registrant.getControl(
132 ... name='field.country_dns_mirror').value = True
133 Traceback (most recent call last):
134 ...
135 LookupError: name 'field.country_dns_mirror'
136
137A country may not have more than one country content-type mirror, for example,
138archive mirrors:
139
140 >>> browser = setupBrowser(auth='Basic karl@canonical.com:test')
141 >>> browser.open(
142 ... 'https://launchpad.dev/ubuntu/+mirror/mirror.davis.antarctica.org-archive')
143 >>> browser.getLink("Change details").click()
144 >>> print browser.title
145 Edit mirror Davis Station...
146 >>> browser.getControl(name='field.country_dns_mirror').value = True
147 >>> browser.getControl('Save').click()
148 >>> print find_tags_by_class(browser.contents, 'informational message')[0]
149 <div class="informational message">The mirror Davis Station has been set
150 as the country DNS mirror for Antarctica.</div>
151 >>> browser.open('https://launchpad.dev/ubuntu/+mirror/archive-mirror2')
152 >>> browser.getLink("Change details").click()
153 >>> print browser.title
154 Edit mirror Archive-mirror2...
155 >>> browser.getControl(name='field.country_dns_mirror').value = True
156 >>> browser.getControl('Save').click()
157 >>> print find_tags_by_class(browser.contents, 'message')
158 [<p class="error message">There is 1 error.</p>,
159 <div class="message">Antarctica already has a country DNS mirror set, please
160 unset the country DNS mirror status of Davis Station before
161 continuing.</div>]
162
163We can however set a country archive mirror for another country in the
164mean-time, just fine:
165
166 >>> browser = setupBrowser(auth='Basic karl@canonical.com:test')
167 >>> browser.open(
168 ... 'https://launchpad.dev/ubuntu/+mirror/ubuntu.mirror.tudos.de-archive')
169 >>> browser.getLink("Change details").click()
170 >>> print browser.title
171 Edit mirror Technische Universität Dresden...
172 >>> browser.getControl(name='field.country_dns_mirror').value = True
173 >>> browser.getControl('Save').click()
174 >>> print find_tags_by_class(browser.contents, 'informational message')[0]
175 <div class="informational message">The mirror Technische Universität
176 Dresden has been set as the country DNS mirror for Germany.</div>
177
178Setting releases mirrors as country releases mirrors should not conflict with
179country archive mirrors settings:
180
181 >>> browser = setupBrowser(auth='Basic karl@canonical.com:test')
182 >>> browser.open(
183 ... 'https://launchpad.dev/ubuntu/+mirror/mirror.mawson.antarctica.org-releases')
184 >>> browser.getLink("Change details").click()
185 >>> print browser.title
186 Edit mirror Mawson Station...
187 >>> browser.getControl(name='field.country_dns_mirror').value = True
188 >>> browser.getControl('Save').click()
189 >>> print find_tags_by_class(browser.contents, 'informational message')[0]
190 <div class="informational message">The mirror Mawson Station has been set
191 as the country DNS mirror for Antarctica.</div>
192
193Once a mirror has been set as a country mirror, it's content type or country
194may not be changed:
195
196 >>> browser = setupBrowser(auth='Basic karl@canonical.com:test')
197 >>> browser.open(
198 ... 'https://launchpad.dev/ubuntu/+mirror/mirror.mawson.antarctica.org-releases')
199 >>> browser.getLink("Change details").click()
200 >>> print browser.title
201 Edit mirror Mawson Station...
202 >>> browser.getControl(name='field.content').value = ["ARCHIVE"]
203 >>> browser.getControl('Save').click()
204 >>> print find_tags_by_class(browser.contents, 'message')
205 [<p class="error message">There is 1 error.</p>,
206 <div class="message">Changing the mirror content type of country DNS
207 mirrors is forbidden.</div>]
208 >>> browser.open(
209 ... 'https://launchpad.dev/ubuntu/+mirror/mirror.mawson.antarctica.org-releases')
210 >>> browser.getLink("Change details").click()
211 >>> print browser.title
212 Edit mirror Mawson Station...
213 >>> browser.getControl(name='field.country').value = ['82'] # Germany.
214 >>> browser.getControl('Save').click()
215 >>> print find_tags_by_class(browser.contents, 'message')
216 [<p class="error message">There is 1 error.</p>,
217 <div class="message">Changing the country of country DNS mirrors is
218 forbidden.</div>]
219
69220
70=== Disabled mirrors ===221=== Disabled mirrors ===
71222
@@ -105,7 +256,7 @@
105 'http://launchpad.dev/ubuntu/+unofficialmirrors'256 'http://launchpad.dev/ubuntu/+unofficialmirrors'
106257
107 >>> print_mirrors_by_countries(browser.contents)258 >>> print_mirrors_by_countries(browser.contents)
108 France: [(u'Invalid-mirror', u'http', u'2 Mbps', u'Last update unknown')]259 France: [(u'Invalid-mirror', u'', u'http', u'2 Mbps', u'Last update unknown')]
109260
110== Pending-review mirrors ==261== Pending-review mirrors ==
111262
@@ -133,8 +284,8 @@
133 >>> browser.open('http://launchpad.dev/ubuntu/+pendingreviewmirrors')284 >>> browser.open('http://launchpad.dev/ubuntu/+pendingreviewmirrors')
134 >>> print_mirrors_by_countries(browser.contents)285 >>> print_mirrors_by_countries(browser.contents)
135 Afghanistan:286 Afghanistan:
136 [(u'Kabul LUG mirror', u'ftp', u'10 Gbps',287 [(u'Kabul LUG mirror', u'', u'ftp', u'10 Gbps',
137 u'Archive')]288 u'Archive')]
138 United Kingdom:289 United Kingdom:
139 [(u'Random-releases-mirror', u'http', u'100 Mbps',290 [(u'Random-releases-mirror', u'', u'http', u'100 Mbps',
140 u'CD Image')]291 u'CD Image')]
141292
=== modified file 'lib/lp/registry/templates/distributionmirror-index.pt'
--- lib/lp/registry/templates/distributionmirror-index.pt 2009-12-08 15:51:34 +0000
+++ lib/lp/registry/templates/distributionmirror-index.pt 2010-01-05 15:14:20 +0000
@@ -76,6 +76,15 @@
76 </div>76 </div>
77 </div>77 </div>
78 </div>78 </div>
79 <div tal:condition="context/country_dns_mirror">
80 <div class="portlet">
81 <h2>Country DNS mirror</h2>
82 <p>
83 This mirror is the country DNS mirror for <span
84 tal:content="context/country/name" />.
85 </p>
86 </div>
87 </div>
7988
80 <div class="portlet"89 <div class="portlet"
81 id="last-probe"90 id="last-probe"
8291
=== modified file 'lib/lp/registry/templates/distributionmirror-macros.pt'
--- lib/lp/registry/templates/distributionmirror-macros.pt 2009-12-09 19:41:23 +0000
+++ lib/lp/registry/templates/distributionmirror-macros.pt 2010-01-05 15:14:20 +0000
@@ -19,6 +19,7 @@
19 <tr class="highlighted">19 <tr class="highlighted">
20 <th colspan="2" style="text-align: left"20 <th colspan="2" style="text-align: left"
21 tal:content="country_and_mirrors/country" />21 tal:content="country_and_mirrors/country" />
22 <th align="right" />
22 <th style="text-align: left"23 <th style="text-align: left"
23 tal:content="country_and_mirrors/throughput"/>24 tal:content="country_and_mirrors/throughput"/>
24 <th style="text-align: left" tal:condition="show_mirror_type">25 <th style="text-align: left" tal:condition="show_mirror_type">
@@ -36,6 +37,10 @@
36 <a tal:attributes="href mirror/fmt:url"37 <a tal:attributes="href mirror/fmt:url"
37 tal:content="mirror/title">Mirror Name</a>38 tal:content="mirror/title">Mirror Name</a>
38 </td>39 </td>
40 <td align="right">
41 <img tal:condition="mirror/country_dns_mirror" src="/@@/favourite-yes"
42 title="Country DNS mirror" />
43 </td>
39 <td>44 <td>
40 <a tal:condition="mirror/http_base_url"45 <a tal:condition="mirror/http_base_url"
41 tal:attributes="href mirror/http_base_url">http</a>46 tal:attributes="href mirror/http_base_url">http</a>
@@ -65,10 +70,11 @@
6570
66 </tal:country_and_mirrors>71 </tal:country_and_mirrors>
67 <tr class="highlighted">72 <tr class="highlighted">
68 <th colspan="5" style="text-align: left; font-weight: bold;">Total</th>73 <th colspan="6" style="text-align: left; font-weight: bold;">Total</th>
69 </tr>74 </tr>
70 <tr>75 <tr>
71 <td colspan="2" />76 <td colspan="2" />
77 <td />
72 <td style="text-align: left; font-weight: bold;"78 <td style="text-align: left; font-weight: bold;"
73 tal:content="total_throughput" />79 tal:content="total_throughput" />
74 <td tal:condition="show_mirror_type"></td>80 <td tal:condition="show_mirror_type"></td>