Merge lp:~brian-murray/launchpad/api-export-messages-count into lp:launchpad/db-devel

Proposed by Brian Murray
Status: Merged
Merged at revision: not available
Proposed branch: lp:~brian-murray/launchpad/api-export-messages-count
Merge into: lp:launchpad/db-devel
Diff against target: 1855 lines (+581/-358)
42 files modified
configs/development/launchpad-lazr.conf (+0/-2)
configs/replicated-development/launchpad-lazr.conf (+0/-3)
configs/test-playground/launchpad-lazr.conf (+0/-2)
configs/testrunner/launchpad-lazr.conf (+0/-2)
database/schema/security.cfg (+6/-2)
lib/canonical/config/__init__.py (+2/-2)
lib/canonical/config/schema-lazr.conf (+0/-8)
lib/canonical/configure.zcml (+0/-2)
lib/canonical/database/harness.py (+1/-2)
lib/canonical/database/sqlbase.py (+2/-4)
lib/canonical/database/tests/test_zopeless_transaction_manager.py (+0/-31)
lib/canonical/launchpad/database/tests/test_oauth.py (+1/-1)
lib/canonical/launchpad/doc/account.txt (+1/-26)
lib/canonical/launchpad/doc/storm.txt (+51/-71)
lib/canonical/launchpad/pagetests/standalone/xx-opstats.txt (+0/-2)
lib/canonical/launchpad/scripts/garbo.py (+5/-13)
lib/canonical/launchpad/scripts/tests/test_garbo.py (+7/-9)
lib/canonical/launchpad/webapp/adapter.py (+5/-16)
lib/canonical/launchpad/webapp/ftests/test_adapter_permissions.txt (+1/-24)
lib/canonical/launchpad/webapp/interfaces.py (+1/-3)
lib/canonical/launchpad/webapp/tests/test_dbpolicy.py (+1/-4)
lib/lp/bugs/doc/checkwatches.txt (+4/-0)
lib/lp/bugs/doc/externalbugtracker-comment-imports.txt (+3/-3)
lib/lp/bugs/interfaces/bug.py (+3/-3)
lib/lp/bugs/stories/webservice/xx-bug.txt (+1/-0)
lib/lp/code/doc/branch-karma.txt (+1/-0)
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/model/person.py (+2/-17)
lib/lp/registry/stories/distributionmirror/xx-distribution-mirrors.txt (+11/-11)
lib/lp/registry/stories/person/xx-admin-person-review.txt (+1/-1)
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/registry/tests/test_personset.py (+6/-10)
lib/lp/testing/factory.py (+16/-2)
lib/lp/testopenid/browser/server.py (+6/-1)
utilities/sourcedeps.conf (+0/-1)
To merge this branch: bzr merge lp:~brian-murray/launchpad/api-export-messages-count
Reviewer Review Type Date Requested Status
Eleanor Berger (community) Approve
Review via email: mp+17697@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Brian Murray (brian-murray) wrote :

This branch exports a bug's message_count in the API.

Revision history for this message
Eleanor Berger (intellectronica) wrote :

r=me

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'configs/development/launchpad-lazr.conf'
--- configs/development/launchpad-lazr.conf 2010-03-05 20:49:31 +0000
+++ configs/development/launchpad-lazr.conf 2010-04-05 20:44:34 +0000
@@ -97,8 +97,6 @@
97# the rw_* configs.97# the rw_* configs.
98ro_main_master: dbname=launchpad_dev_template98ro_main_master: dbname=launchpad_dev_template
99ro_main_slave: dbname=launchpad_dev_template99ro_main_slave: dbname=launchpad_dev_template
100auth_master: dbname=launchpad_dev
101auth_slave: dbname=launchpad_dev
102100
103[distributionmirrorprober]101[distributionmirrorprober]
104use_proxy: False102use_proxy: False
105103
=== modified file 'configs/replicated-development/launchpad-lazr.conf'
--- configs/replicated-development/launchpad-lazr.conf 2010-01-05 19:09:58 +0000
+++ configs/replicated-development/launchpad-lazr.conf 2010-04-05 20:44:34 +0000
@@ -10,6 +10,3 @@
10rw_main_slave: dbname=launchpad_dev_slave10rw_main_slave: dbname=launchpad_dev_slave
11ro_main_master: dbname=launchpad_dev11ro_main_master: dbname=launchpad_dev
12ro_main_slave: dbname=launchpad_dev_slave12ro_main_slave: dbname=launchpad_dev_slave
13auth_master: dbname=launchpad_dev
14auth_slave: dbname=launchpad_dev_slave
15
1613
=== modified file 'configs/test-playground/launchpad-lazr.conf'
--- configs/test-playground/launchpad-lazr.conf 2010-01-05 19:09:58 +0000
+++ configs/test-playground/launchpad-lazr.conf 2010-04-05 20:44:34 +0000
@@ -10,5 +10,3 @@
10rw_main_slave: dbname=launchpad_ftest_playground10rw_main_slave: dbname=launchpad_ftest_playground
11ro_main_master: dbname=launchpad_ftest_playground11ro_main_master: dbname=launchpad_ftest_playground
12ro_main_slave: dbname=launchpad_ftest_playground12ro_main_slave: dbname=launchpad_ftest_playground
13auth_master: dbname=launchpad_ftest_playground
14auth_slave: dbname=launchpad_ftest_playground
1513
=== modified file 'configs/testrunner/launchpad-lazr.conf'
--- configs/testrunner/launchpad-lazr.conf 2010-03-11 01:39:25 +0000
+++ configs/testrunner/launchpad-lazr.conf 2010-04-05 20:44:34 +0000
@@ -51,8 +51,6 @@
51# the rw_* configs.51# the rw_* configs.
52ro_main_master: dbname=launchpad_ftest_template52ro_main_master: dbname=launchpad_ftest_template
53ro_main_slave: dbname=launchpad_ftest_template53ro_main_slave: dbname=launchpad_ftest_template
54auth_master: dbname=launchpad_ftest
55auth_slave: dbname=launchpad_ftest
56randomise_select_results: true54randomise_select_results: true
5755
58[error_reports]56[error_reports]
5957
=== modified file 'database/schema/security.cfg'
--- database/schema/security.cfg 2010-04-02 17:29:13 +0000
+++ database/schema/security.cfg 2010-04-05 20:44:34 +0000
@@ -118,7 +118,8 @@
118# lpmain replication set access from the main Z3 application.118# lpmain replication set access from the main Z3 application.
119type=user119type=user
120groups=write,script120groups=write,script
121public.account = SELECT121public.account = SELECT, INSERT, UPDATE, DELETE
122public.accountpassword = SELECT, INSERT, UPDATE, DELETE
122public.announcement = SELECT, INSERT, UPDATE, DELETE123public.announcement = SELECT, INSERT, UPDATE, DELETE
123public.answercontact = SELECT, INSERT, UPDATE, DELETE124public.answercontact = SELECT, INSERT, UPDATE, DELETE
124public.apportjob = SELECT, INSERT, UPDATE, DELETE125public.apportjob = SELECT, INSERT, UPDATE, DELETE
@@ -176,7 +177,7 @@
176public.distributionsourcepackagecache = SELECT177public.distributionsourcepackagecache = SELECT
177public.distroserieslanguage = SELECT, INSERT, UPDATE178public.distroserieslanguage = SELECT, INSERT, UPDATE
178public.distroseriespackagecache = SELECT179public.distroseriespackagecache = SELECT
179public.emailaddress = SELECT180public.emailaddress = SELECT, INSERT, UPDATE, DELETE
180public.entitlement = SELECT, INSERT, UPDATE, DELETE181public.entitlement = SELECT, INSERT, UPDATE, DELETE
181public.faq = SELECT, INSERT, UPDATE, DELETE182public.faq = SELECT, INSERT, UPDATE, DELETE
182public.featuredproject = SELECT, INSERT, DELETE183public.featuredproject = SELECT, INSERT, DELETE
@@ -916,6 +917,8 @@
916# Full access except for tables that are exclusively updated by917# Full access except for tables that are exclusively updated by
917# certain processes, such as the librarian tables. This group is deprecated -918# certain processes, such as the librarian tables. This group is deprecated -
918# access should be explicitly granted to users.919# access should be explicitly granted to users.
920public.account = SELECT, INSERT, UPDATE
921public.accountpassword = SELECT, INSERT
919public.archive = SELECT, INSERT, UPDATE922public.archive = SELECT, INSERT, UPDATE
920public.archivearch = SELECT, INSERT, UPDATE, DELETE923public.archivearch = SELECT, INSERT, UPDATE, DELETE
921public.binarypackagerelease = SELECT, INSERT, UPDATE924public.binarypackagerelease = SELECT, INSERT, UPDATE
@@ -949,6 +952,7 @@
949public.distribution = SELECT, INSERT, UPDATE952public.distribution = SELECT, INSERT, UPDATE
950public.distroarchseries = SELECT, INSERT, UPDATE953public.distroarchseries = SELECT, INSERT, UPDATE
951public.distroseries = SELECT, INSERT, UPDATE954public.distroseries = SELECT, INSERT, UPDATE
955public.openidrpsummary = SELECT, INSERT, UPDATE
952public.packageupload = SELECT, INSERT, UPDATE956public.packageupload = SELECT, INSERT, UPDATE
953public.packageuploadbuild = SELECT, INSERT, UPDATE957public.packageuploadbuild = SELECT, INSERT, UPDATE
954public.packageuploadsource = SELECT, INSERT, UPDATE958public.packageuploadsource = SELECT, INSERT, UPDATE
955959
=== modified file 'lib/canonical/config/__init__.py'
--- lib/canonical/config/__init__.py 2010-01-14 16:39:18 +0000
+++ lib/canonical/config/__init__.py 2010-04-05 20:44:34 +0000
@@ -374,13 +374,13 @@
374 _db_config_attrs = frozenset([374 _db_config_attrs = frozenset([
375 'dbuser', 'auth_dbuser',375 'dbuser', 'auth_dbuser',
376 'rw_main_master', 'rw_main_slave',376 'rw_main_master', 'rw_main_slave',
377 'ro_main_master', 'ro_main_slave', 'auth_master', 'auth_slave',377 'ro_main_master', 'ro_main_slave',
378 'db_statement_timeout', 'db_statement_timeout_precision',378 'db_statement_timeout', 'db_statement_timeout_precision',
379 'isolation_level', 'randomise_select_results',379 'isolation_level', 'randomise_select_results',
380 'soft_request_timeout', 'storm_cache', 'storm_cache_size'])380 'soft_request_timeout', 'storm_cache', 'storm_cache_size'])
381 _db_config_required_attrs = frozenset([381 _db_config_required_attrs = frozenset([
382 'dbuser', 'rw_main_master', 'rw_main_slave', 'ro_main_master',382 'dbuser', 'rw_main_master', 'rw_main_slave', 'ro_main_master',
383 'ro_main_slave', 'auth_master', 'auth_slave'])383 'ro_main_slave'])
384384
385 @property385 @property
386 def main_master(self):386 def main_master(self):
387387
=== modified file 'lib/canonical/config/schema-lazr.conf'
--- lib/canonical/config/schema-lazr.conf 2010-03-24 02:37:55 +0000
+++ lib/canonical/config/schema-lazr.conf 2010-04-05 20:44:34 +0000
@@ -559,8 +559,6 @@
559rw_main_slave: dbname=launchpad_prod_2 host=chokecherry.canonical.com559rw_main_slave: dbname=launchpad_prod_2 host=chokecherry.canonical.com
560ro_main_master: dbname=launchpad_standalone_1 host=chokecherry.canonical.com560ro_main_master: dbname=launchpad_standalone_1 host=chokecherry.canonical.com
561ro_main_slave: dbname=launchpad_standalone_1 host=chokecherry.canonical.com561ro_main_slave: dbname=launchpad_standalone_1 host=chokecherry.canonical.com
562auth_master: dbname=launchpad_prod_3 host=wildcherry.canonical.com
563auth_slave: dbname=launchpad_prod_2 host=chokecherry.canonical.com
564562
565# If the replication lag is more than this many seconds, slave databases563# If the replication lag is more than this many seconds, slave databases
566# will not be used.564# will not be used.
@@ -867,16 +865,10 @@
867# datatype: integer865# datatype: integer
868max_scaling: 500866max_scaling: 500
869867
870[sso]
871dbuser: sso_main
872auth_dbuser: sso_auth
873
874
875[launchpad]868[launchpad]
876# The database user which will be used by this process.869# The database user which will be used by this process.
877# datatype: string870# datatype: string
878dbuser: launchpad_main871dbuser: launchpad_main
879auth_dbuser: launchpad_auth
880storm_cache: generational872storm_cache: generational
881storm_cache_size: 10000873storm_cache_size: 10000
882874
883875
=== modified file 'lib/canonical/configure.zcml'
--- lib/canonical/configure.zcml 2010-01-08 21:23:15 +0000
+++ lib/canonical/configure.zcml 2010-04-05 20:44:34 +0000
@@ -154,7 +154,5 @@
154 <include package="canonical.lazr" />154 <include package="canonical.lazr" />
155 <include zcml:condition="installed canonical.shipit"155 <include zcml:condition="installed canonical.shipit"
156 package="canonical.shipit" />156 package="canonical.shipit" />
157 <include zcml:condition="installed canonical.signon"
158 package="canonical.signon" />
159157
160</configure>158</configure>
161159
=== modified file 'lib/canonical/database/harness.py'
--- lib/canonical/database/harness.py 2009-11-05 03:52:51 +0000
+++ lib/canonical/database/harness.py 2010-04-05 20:44:34 +0000
@@ -48,8 +48,7 @@
48from storm.locals import *48from storm.locals import *
49from storm.expr import *49from storm.expr import *
50from canonical.launchpad.webapp.interfaces import (50from canonical.launchpad.webapp.interfaces import (
51 IStoreSelector, MAIN_STORE, AUTH_STORE, MASTER_FLAVOR,51 IStoreSelector, MAIN_STORE, MASTER_FLAVOR, SLAVE_FLAVOR, DEFAULT_FLAVOR)
52 SLAVE_FLAVOR, DEFAULT_FLAVOR)
5352
5453
55def switch_db_user(dbuser, commit_first=True):54def switch_db_user(dbuser, commit_first=True):
5655
=== modified file 'lib/canonical/database/sqlbase.py'
--- lib/canonical/database/sqlbase.py 2010-03-23 15:04:41 +0000
+++ lib/canonical/database/sqlbase.py 2010-04-05 20:44:34 +0000
@@ -271,7 +271,6 @@
271 # This is only used by scripts, so we must connect to the read-write271 # This is only used by scripts, so we must connect to the read-write
272 # DB here -- that's why we use rw_main_master directly.272 # DB here -- that's why we use rw_main_master directly.
273 main_connection_string = dbconfig.rw_main_master273 main_connection_string = dbconfig.rw_main_master
274 auth_connection_string = dbconfig.auth_master
275274
276 # Override dbname and dbhost in the connection string if they275 # Override dbname and dbhost in the connection string if they
277 # have been passed in.276 # have been passed in.
@@ -290,7 +289,7 @@
290 match = re.search(r'host=(\S*)', main_connection_string)289 match = re.search(r'host=(\S*)', main_connection_string)
291 if match is not None:290 if match is not None:
292 dbhost = match.group(1)291 dbhost = match.group(1)
293 return main_connection_string, auth_connection_string, dbname, dbhost292 return main_connection_string, dbname, dbhost
294293
295 @classmethod294 @classmethod
296 def initZopeless(cls, dbname=None, dbhost=None, dbuser=None,295 def initZopeless(cls, dbname=None, dbhost=None, dbuser=None,
@@ -298,7 +297,7 @@
298 # Connect to the auth master store as well, as some scripts might need297 # Connect to the auth master store as well, as some scripts might need
299 # to create EmailAddresses and Accounts.298 # to create EmailAddresses and Accounts.
300299
301 main_connection_string, auth_connection_string, dbname, dbhost = (300 main_connection_string, dbname, dbhost = (
302 cls._get_zopeless_connection_config(dbname, dbhost))301 cls._get_zopeless_connection_config(dbname, dbhost))
303302
304 assert dbuser is not None, '''303 assert dbuser is not None, '''
@@ -315,7 +314,6 @@
315 overlay = dedent("""\314 overlay = dedent("""\
316 [database]315 [database]
317 rw_main_master: %(main_connection_string)s316 rw_main_master: %(main_connection_string)s
318 auth_master: %(auth_connection_string)s
319 isolation_level: %(isolation_level)s317 isolation_level: %(isolation_level)s
320 """ % vars())318 """ % vars())
321319
322320
=== modified file 'lib/canonical/database/tests/test_zopeless_transaction_manager.py'
--- lib/canonical/database/tests/test_zopeless_transaction_manager.py 2009-12-14 17:38:34 +0000
+++ lib/canonical/database/tests/test_zopeless_transaction_manager.py 2010-04-05 20:44:34 +0000
@@ -1,48 +1,17 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009 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
4from textwrap import dedent
5import unittest4import unittest
65
7from zope.component import getUtility6from zope.component import getUtility
87
9from storm.zope.interfaces import IZStorm8from storm.zope.interfaces import IZStorm
109
11from canonical.config import config
12from canonical.database.sqlbase import ZopelessTransactionManager10from canonical.database.sqlbase import ZopelessTransactionManager
13from canonical.testing.layers import LaunchpadZopelessLayer11from canonical.testing.layers import LaunchpadZopelessLayer
14from lp.testing import TestCase12from lp.testing import TestCase
1513
1614
17class TestZopelessTransactionManagerNoLayer(TestCase):
18
19 def test_initZopeless_connects_to_auth_master_db(self):
20 # Some scripts might create EmailAddress and Account entries, so
21 # initZopeless has to connect to the auth master db. This is a
22 # bugfix test. The error that this test detects is that the
23 # script used to use the main_master database for the
24 # auth_master. In this test, we make sure that the auth_master
25 # and main_master have different values in the config, and then
26 # show that they are honored. Prior to the fix, ``auth_master``
27 # would have been changed to the same value as ``main_master``.
28 # Now we set up our test data and push it on the config.
29 auth_master = "dbname=example_launchpad_auth_does_not_exist"
30 overlay = dedent("""
31 [database]
32 main_master: dbname=launchpad_dev
33 auth_master: %s
34 """ % (auth_master,))
35 config.push('new-db', overlay)
36 try:
37 main_connection_string, auth_connection_string, dbname, dbhost = (
38 ZopelessTransactionManager._get_zopeless_connection_config(
39 None, None))
40 self.assertEqual(auth_connection_string, auth_master)
41 finally:
42 # Clean up the configuration
43 config.pop('new-db')
44
45
46class TestZopelessTransactionManager(TestCase):15class TestZopelessTransactionManager(TestCase):
47 layer = LaunchpadZopelessLayer16 layer = LaunchpadZopelessLayer
4817
4918
=== modified file 'lib/canonical/launchpad/database/tests/test_oauth.py'
--- lib/canonical/launchpad/database/tests/test_oauth.py 2009-06-25 05:30:52 +0000
+++ lib/canonical/launchpad/database/tests/test_oauth.py 2010-04-05 20:44:34 +0000
@@ -22,7 +22,7 @@
22 """Base tests for the OAuth database classes."""22 """Base tests for the OAuth database classes."""
23 layer = DatabaseFunctionalLayer23 layer = DatabaseFunctionalLayer
2424
25 def test__get_store_should_return_the_auth_master_store(self):25 def test__get_store_should_return_the_main_master_store(self):
26 """We want all OAuth classes to use the master store.26 """We want all OAuth classes to use the master store.
27 Otherwise, the OAuth exchanges will fail because the authorize27 Otherwise, the OAuth exchanges will fail because the authorize
28 screen won't probably find the new request token on the slave store.28 screen won't probably find the new request token on the slave store.
2929
=== modified file 'lib/canonical/launchpad/doc/account.txt'
--- lib/canonical/launchpad/doc/account.txt 2010-02-12 15:57:27 +0000
+++ lib/canonical/launchpad/doc/account.txt 2010-04-05 20:44:34 +0000
@@ -196,32 +196,6 @@
196 >>> account.status = AccountStatus.ACTIVE196 >>> account.status = AccountStatus.ACTIVE
197 >>> login('no-priv@canonical.com')197 >>> login('no-priv@canonical.com')
198198
199The Account's displayname is synced to the Person's displayname if there
200is one. If the Person.displayname is changed, the Account.displayname is
201changed too.
202
203 >>> from canonical.launchpad.interfaces import IPersonSet
204
205 >>> personset = getUtility(IPersonSet)
206 >>> person = personset.getByEmail('no-priv@canonical.com')
207 >>> person.displayname = 'Something New'
208 >>> print account.displayname
209 Something New
210
211However, the reverse is not true. If we change the Account.displayname,
212the linked Person.displayname (if there is one) is not updated
213immediately. Instead, a cron job will sync this information later. This
214allows displayname changes to happen even when the Person table is
215unavailable.
216
217 >>> account.displayname = 'No Privileges Account'
218 >>> print person.displayname
219 Something New
220
221 >>> person.displayname = 'No Privileges Person'
222 >>> print account.displayname
223 No Privileges Person
224
225An Account has an OpenID identifier used to generate the OpenID identity199An Account has an OpenID identifier used to generate the OpenID identity
226URL.200URL.
227201
@@ -242,6 +216,7 @@
242 >>> login('admin@canonical.com')216 >>> login('admin@canonical.com')
243 >>> passwordless_account = account_set.new(217 >>> passwordless_account = account_set.new(
244 ... AccountCreationRationale.USER_CREATED, 'Passwordless')218 ... AccountCreationRationale.USER_CREATED, 'Passwordless')
219 >>> transaction.commit()
245 >>> print passwordless_account.creation_rationale.name220 >>> print passwordless_account.creation_rationale.name
246 USER_CREATED221 USER_CREATED
247 >>> print passwordless_account.displayname222 >>> print passwordless_account.displayname
248223
=== modified file 'lib/canonical/launchpad/doc/storm.txt'
--- lib/canonical/launchpad/doc/storm.txt 2010-02-22 10:33:10 +0000
+++ lib/canonical/launchpad/doc/storm.txt 2010-04-05 20:44:34 +0000
@@ -7,10 +7,11 @@
7specific Storm tools to cope with our master and slave store arrangement.7specific Storm tools to cope with our master and slave store arrangement.
88
9 >>> from canonical.launchpad.interfaces import (9 >>> from canonical.launchpad.interfaces import (
10 ... EmailAddressStatus, IAccountSet, IEmailAddressSet,10 ... EmailAddressStatus, IEmailAddressSet,
11 ... IMasterObject, IMasterStore, ISlaveStore, IStore)11 ... IMasterObject, IMasterStore, ISlaveStore, IStore)
12 >>> from canonical.launchpad.database import (12 >>> from canonical.launchpad.database import (
13 ... Account, AccountPassword, EmailAddress)13 ... Account, AccountPassword, EmailAddress)
14 >>> from lp.registry.interfaces.person import IPersonSet
14 >>> from lp.registry.model.person import Person15 >>> from lp.registry.model.person import Person
15 >>> from zope.security.proxy import ProxyFactory16 >>> from zope.security.proxy import ProxyFactory
1617
@@ -19,30 +20,14 @@
19a Launchpad database object. You can use adapters to20a Launchpad database object. You can use adapters to
20retrieve the correct Store.21retrieve the correct Store.
2122
22 >>> auth_master = IMasterStore(Account)
23 >>> main_master = IMasterStore(Person)23 >>> main_master = IMasterStore(Person)
24 >>> auth_master is main_master
25 False
26
27
28You can read most tables from any Store, which is required for doing
29fast joins in the database. However, when it is not necessary to
30retrieve objects from the same store as another object, it is better to
31explicitly use the explicit Store for its replication set. Some tables
32are only available from this store, such as the AccountPassword table.
33
34 >>> auth_slave = ISlaveStore(AccountPassword)
35 >>> main_slave = ISlaveStore(Person)
36 >>> auth_slave is main_slave
37 False
38
3924
40You can detect if a store is writable by checking what interfaces it25You can detect if a store is writable by checking what interfaces it
41provides.26provides.
4227
43 >>> IMasterStore.providedBy(auth_master)28 >>> IMasterStore.providedBy(main_master)
44 True29 True
45 >>> ISlaveStore.providedBy(auth_master)30 >>> ISlaveStore.providedBy(main_master)
46 False31 False
4732
4833
@@ -53,7 +38,6 @@
53Otherwise, it gives you the master. See IStoreSelector for details.38Otherwise, it gives you the master. See IStoreSelector for details.
5439
55 >>> main_default = IStore(Person)40 >>> main_default = IStore(Person)
56 >>> main_master = IMasterStore(Person)
57 >>> main_slave = ISlaveStore(Person)41 >>> main_slave = ISlaveStore(Person)
58 >>> main_default is main_master42 >>> main_default is main_master
59 True43 True
@@ -78,11 +62,10 @@
78changes to an object, just in case you have been passed an instance62changes to an object, just in case you have been passed an instance
79from a store other than the correct Master.63from a store other than the correct Master.
8064
81 >>> auth_slave = ISlaveStore(Account)65 >>> main_slave = ISlaveStore(Person)
82 >>> t = transaction.begin()66 >>> t = transaction.begin()
83 >>> account = auth_slave.find(67 >>> person = main_slave.find(Person, name='mark').one()
84 ... Account, openid_identifier='mark_oid').one()68 >>> person.displayname = 'Cannot change'
85 >>> account.displayname = 'Cannot change'
86 >>> transaction.commit()69 >>> transaction.commit()
87 Traceback (most recent call last):70 Traceback (most recent call last):
88 ...71 ...
@@ -90,9 +73,8 @@
9073
91 >>> transaction.abort()74 >>> transaction.abort()
92 >>> t = transaction.begin()75 >>> t = transaction.begin()
93 >>> account = auth_slave.find(76 >>> person = main_slave.find(Person, name='mark').one()
94 ... Account, openid_identifier='mark_oid').one()77 >>> IMasterObject(person).displayname = 'Can change'
95 >>> IMasterObject(account).displayname = 'Can change'
96 >>> transaction.commit()78 >>> transaction.commit()
9779
9880
@@ -100,61 +82,60 @@
100similarly wrapped.82similarly wrapped.
10183
102 >>> from zope.security.proxy import removeSecurityProxy84 >>> from zope.security.proxy import removeSecurityProxy
103 >>> account = getUtility(IAccountSet).getByEmail('no-priv@canonical.com')85 >>> person = getUtility(IPersonSet).getByEmail('no-priv@canonical.com')
104 >>> removeSecurityProxy(account) is account86 >>> removeSecurityProxy(person) is person
105 False87 False
106 >>> account.displayname88 >>> person.displayname
107 u'No Privileges Person'89 u'No Privileges Person'
108 >>> account.password90 >>> person.name = 'foo'
109 Traceback (most recent call last):91 Traceback (most recent call last):
110 ...92 ...
111 Unauthorized: ...93 Unauthorized: ...
11294
113 >>> account = IMasterObject(account)95 >>> person = IMasterObject(person)
114 >>> removeSecurityProxy(account) is account96 >>> removeSecurityProxy(person) is person
115 False97 False
116 >>> account.displayname98 >>> person.displayname
117 u'No Privileges Person'99 u'No Privileges Person'
118 >>> account.password100 >>> person.name = 'foo'
119 Traceback (most recent call last):101 Traceback (most recent call last):
120 ...102 ...
121 Unauthorized: ...103 Unauthorized: ...
122104
123 >>> account = IMasterObject(removeSecurityProxy(account))105 >>> person = IMasterObject(removeSecurityProxy(person))
124 >>> removeSecurityProxy(account) is account106 >>> removeSecurityProxy(person) is person
125 True107 True
126 >>> account.displayname108 >>> person.displayname
127 u'No Privileges Person'109 u'No Privileges Person'
128 >>> account.password110 >>> person.name = 'foo'
129 u'...'
130111
131Our objects may compare equal even if they have come from different112Our objects may compare equal even if they have come from different
132stores.113stores.
133114
134 >>> auth_master_email = IMasterStore(EmailAddress).find(115 >>> master_email = IMasterStore(EmailAddress).find(
135 ... EmailAddress, Person.name == 'janitor',116 ... EmailAddress, Person.name == 'janitor',
136 ... EmailAddress.person==Person.id).one()117 ... EmailAddress.person==Person.id).one()
137 >>> auth_slave_email = ISlaveStore(EmailAddress).find(118 >>> slave_email = ISlaveStore(EmailAddress).find(
138 ... EmailAddress, Person.name == 'janitor',119 ... EmailAddress, Person.name == 'janitor',
139 ... EmailAddress.person==Person.id).one()120 ... EmailAddress.person==Person.id).one()
140 >>> auth_master_email is auth_slave_email121 >>> master_email is slave_email
141 False122 False
142 >>> auth_master_email == auth_slave_email123 >>> master_email == slave_email
143 True124 True
144 >>> auth_master_email != auth_slave_email125 >>> master_email != slave_email
145 False126 False
146127
147Comparison works for security wrapped objects too.128Comparison works for security wrapped objects too.
148129
149 >>> wrapped_email = getUtility(IEmailAddressSet).getByEmail(130 >>> wrapped_email = getUtility(IEmailAddressSet).getByEmail(
150 ... auth_master_email.email)131 ... master_email.email)
151 >>> removeSecurityProxy(wrapped_email) is auth_master_email132 >>> removeSecurityProxy(wrapped_email) is master_email
152 True133 True
153 >>> wrapped_email is auth_master_email134 >>> wrapped_email is master_email
154 False135 False
155 >>> wrapped_email == auth_master_email136 >>> wrapped_email == master_email
156 True137 True
157 >>> wrapped_email != auth_master_email138 >>> wrapped_email != master_email
158 False139 False
159140
160Objects not yet flushed to the database also compare equal.141Objects not yet flushed to the database also compare equal.
@@ -176,10 +157,9 @@
176157
177Objects differing by class never compare equal.158Objects differing by class never compare equal.
178159
179 >>> account_one = IMasterStore(Account).get(Account, 1)160 >>> email_one = IMasterStore(EmailAddress).get(EmailAddress, 1)
180 >>> person_one = IMasterStore(Account).get(Person, 1)161 >>> person_one = IMasterStore(Person).get(Person, 1)
181 >>> account_one == person_one162 >>> email_one == person_one
182 False163 False
183 >>> account_one != person_one164 >>> email_one != person_one
184 True165 True
185
186166
=== modified file 'lib/canonical/launchpad/pagetests/standalone/xx-opstats.txt'
--- lib/canonical/launchpad/pagetests/standalone/xx-opstats.txt 2010-01-13 13:50:39 +0000
+++ lib/canonical/launchpad/pagetests/standalone/xx-opstats.txt 2010-04-05 20:44:34 +0000
@@ -236,8 +236,6 @@
236 ... [database]236 ... [database]
237 ... rw_main_master: dbname=nonexistant237 ... rw_main_master: dbname=nonexistant
238 ... rw_main_slave: dbname=nonexistant238 ... rw_main_slave: dbname=nonexistant
239 ... auth_master: dbname=nonexistant
240 ... auth_slave: dbname=nonexistant
241 ...239 ...
242 ... [launchpad_session]240 ... [launchpad_session]
243 ... dbname: nonexistant241 ... dbname: nonexistant
244242
=== modified file 'lib/canonical/launchpad/scripts/garbo.py'
--- lib/canonical/launchpad/scripts/garbo.py 2010-03-26 14:33:46 +0000
+++ lib/canonical/launchpad/scripts/garbo.py 2010-04-05 20:44:34 +0000
@@ -29,7 +29,7 @@
29from canonical.launchpad.utilities.looptuner import (29from canonical.launchpad.utilities.looptuner import (
30 DBLoopTuner, TunableLoop)30 DBLoopTuner, TunableLoop)
31from canonical.launchpad.webapp.interfaces import (31from canonical.launchpad.webapp.interfaces import (
32 IStoreSelector, AUTH_STORE, MAIN_STORE, MASTER_FLAVOR)32 IStoreSelector, MAIN_STORE, MASTER_FLAVOR)
33from lp.bugs.interfaces.bug import IBugSet33from lp.bugs.interfaces.bug import IBugSet
34from lp.bugs.interfaces.bugjob import ICalculateBugHeatJobSource34from lp.bugs.interfaces.bugjob import ICalculateBugHeatJobSource
35from lp.bugs.model.bugnotification import BugNotification35from lp.bugs.model.bugnotification import BugNotification
@@ -121,19 +121,17 @@
121 transaction.commit()121 transaction.commit()
122122
123123
124class OpenIDAssociationPruner(TunableLoop):124class OpenIDConsumerAssociationPruner(TunableLoop):
125 minimum_chunk_size = 3500125 minimum_chunk_size = 3500
126 maximum_chunk_size = 50000126 maximum_chunk_size = 50000
127127
128 table_name = 'OpenIDAssociation'128 table_name = 'OpenIDConsumerAssociation'
129 store_name = AUTH_STORE
130129
131 _num_removed = None130 _num_removed = None
132131
133 def __init__(self, log, abort_time=None):132 def __init__(self, log, abort_time=None):
134 super(OpenIDAssociationPruner, self).__init__(log, abort_time)133 super(OpenIDConsumerAssociationPruner, self).__init__(log, abort_time)
135 self.store = getUtility(IStoreSelector).get(134 self.store = getUtility(IStoreSelector).get(MAIN_STORE, MASTER_FLAVOR)
136 self.store_name, MASTER_FLAVOR)
137135
138 def __call__(self, chunksize):136 def __call__(self, chunksize):
139 result = self.store.execute("""137 result = self.store.execute("""
@@ -152,11 +150,6 @@
152 return self._num_removed == 0150 return self._num_removed == 0
153151
154152
155class OpenIDConsumerAssociationPruner(OpenIDAssociationPruner):
156 table_name = 'OpenIDConsumerAssociation'
157 store_name = MAIN_STORE
158
159
160class RevisionCachePruner(TunableLoop):153class RevisionCachePruner(TunableLoop):
161 """A tunable loop to remove old revisions from the cache."""154 """A tunable loop to remove old revisions from the cache."""
162155
@@ -869,7 +862,6 @@
869 tunable_loops = [862 tunable_loops = [
870 OAuthNoncePruner,863 OAuthNoncePruner,
871 OpenIDConsumerNoncePruner,864 OpenIDConsumerNoncePruner,
872 OpenIDAssociationPruner,
873 OpenIDConsumerAssociationPruner,865 OpenIDConsumerAssociationPruner,
874 RevisionCachePruner,866 RevisionCachePruner,
875 BugHeatUpdater,867 BugHeatUpdater,
876868
=== modified file 'lib/canonical/launchpad/scripts/tests/test_garbo.py'
--- lib/canonical/launchpad/scripts/tests/test_garbo.py 2010-01-22 06:03:19 +0000
+++ lib/canonical/launchpad/scripts/tests/test_garbo.py 2010-04-05 20:44:34 +0000
@@ -29,11 +29,11 @@
29from lp.testing import TestCase, TestCaseWithFactory29from lp.testing import TestCase, TestCaseWithFactory
30from canonical.launchpad.scripts.garbo import (30from canonical.launchpad.scripts.garbo import (
31 DailyDatabaseGarbageCollector, HourlyDatabaseGarbageCollector,31 DailyDatabaseGarbageCollector, HourlyDatabaseGarbageCollector,
32 OpenIDAssociationPruner, OpenIDConsumerAssociationPruner)32 OpenIDConsumerAssociationPruner)
33from canonical.launchpad.scripts.tests import run_script33from canonical.launchpad.scripts.tests import run_script
34from canonical.launchpad.scripts.logger import QuietFakeLogger34from canonical.launchpad.scripts.logger import QuietFakeLogger
35from canonical.launchpad.webapp.interfaces import (35from canonical.launchpad.webapp.interfaces import (
36 IStoreSelector, MASTER_FLAVOR)36 IStoreSelector, MAIN_STORE, MASTER_FLAVOR)
37from canonical.testing.layers import (37from canonical.testing.layers import (
38 DatabaseLayer, LaunchpadScriptLayer, LaunchpadZopelessLayer)38 DatabaseLayer, LaunchpadScriptLayer, LaunchpadZopelessLayer)
39from lp.bugs.model.bugnotification import (39from lp.bugs.model.bugnotification import (
@@ -219,12 +219,12 @@
219 Min(CodeImportResult.date_created)).one().replace(tzinfo=UTC)219 Min(CodeImportResult.date_created)).one().replace(tzinfo=UTC)
220 >= now - timedelta(days=30))220 >= now - timedelta(days=30))
221221
222 def test_OpenIDAssociationPruner(self, pruner=OpenIDAssociationPruner):222 def test_OpenIDConsumerAssociationPruner(self):
223 store_name = pruner.store_name223 pruner = OpenIDConsumerAssociationPruner
224 table_name = pruner.table_name224 table_name = pruner.table_name
225 LaunchpadZopelessLayer.switchDbUser('testadmin')225 LaunchpadZopelessLayer.switchDbUser('testadmin')
226 store_selector = getUtility(IStoreSelector)226 store_selector = getUtility(IStoreSelector)
227 store = store_selector.get(store_name, MASTER_FLAVOR)227 store = store_selector.get(MAIN_STORE, MASTER_FLAVOR)
228 now = time.time()228 now = time.time()
229 # Create some associations in the past with lifetimes229 # Create some associations in the past with lifetimes
230 for delta in range(0, 20):230 for delta in range(0, 20):
@@ -247,7 +247,7 @@
247 self.runHourly()247 self.runHourly()
248248
249 LaunchpadZopelessLayer.switchDbUser('testadmin')249 LaunchpadZopelessLayer.switchDbUser('testadmin')
250 store = store_selector.get(store_name, MASTER_FLAVOR)250 store = store_selector.get(MAIN_STORE, MASTER_FLAVOR)
251 # Confirm all the rows we know should have been expired have251 # Confirm all the rows we know should have been expired have
252 # been expired. These are the ones that would be expired using252 # been expired. These are the ones that would be expired using
253 # the test start time as 'now'.253 # the test start time as 'now'.
@@ -263,9 +263,6 @@
263 "SELECT COUNT(*) FROM %s" % table_name).get_one()[0]263 "SELECT COUNT(*) FROM %s" % table_name).get_one()[0]
264 self.failUnless(num_unexpired > 0)264 self.failUnless(num_unexpired > 0)
265265
266 def test_OpenIDConsumerAssociationPruner(self):
267 self.test_OpenIDAssociationPruner(OpenIDConsumerAssociationPruner)
268
269 def test_RevisionAuthorEmailLinker(self):266 def test_RevisionAuthorEmailLinker(self):
270 LaunchpadZopelessLayer.switchDbUser('testadmin')267 LaunchpadZopelessLayer.switchDbUser('testadmin')
271 rev1 = self.factory.makeRevision('Author 1 <author-1@Example.Org>')268 rev1 = self.factory.makeRevision('Author 1 <author-1@Example.Org>')
@@ -373,6 +370,7 @@
373370
374 # If we remove the email address that was subscribed, the371 # If we remove the email address that was subscribed, the
375 # garbage collector removes the subscription.372 # garbage collector removes the subscription.
373 LaunchpadZopelessLayer.switchDbUser('testadmin')
376 Store.of(email).remove(email)374 Store.of(email).remove(email)
377 transaction.commit()375 transaction.commit()
378 self.runDaily()376 self.runDaily()
379377
=== modified file 'lib/canonical/launchpad/webapp/adapter.py'
--- lib/canonical/launchpad/webapp/adapter.py 2010-03-25 11:58:42 +0000
+++ lib/canonical/launchpad/webapp/adapter.py 2010-04-05 20:44:34 +0000
@@ -40,14 +40,13 @@
40from canonical.launchpad.readonly import is_read_only40from canonical.launchpad.readonly import is_read_only
41from canonical.launchpad.webapp.dbpolicy import MasterDatabasePolicy41from canonical.launchpad.webapp.dbpolicy import MasterDatabasePolicy
42from canonical.launchpad.webapp.interfaces import (42from canonical.launchpad.webapp.interfaces import (
43 AUTH_STORE, DEFAULT_FLAVOR, IStoreSelector,43 DEFAULT_FLAVOR, IStoreSelector, MAIN_STORE, MASTER_FLAVOR,
44 MAIN_STORE, MASTER_FLAVOR, ReadOnlyModeViolation, SLAVE_FLAVOR)44 ReadOnlyModeViolation, SLAVE_FLAVOR)
45from canonical.launchpad.webapp.opstats import OpStats45from canonical.launchpad.webapp.opstats import OpStats
46from canonical.lazr.utils import safe_hasattr46from canonical.lazr.utils import safe_hasattr
4747
4848
49__all__ = [49__all__ = [
50 'DisconnectionError',
51 'RequestExpired',50 'RequestExpired',
52 'set_request_started',51 'set_request_started',
53 'clear_request_started',52 'clear_request_started',
@@ -327,7 +326,7 @@
327326
328 def __init__(self, uri):327 def __init__(self, uri):
329 # The uri is just a property name in the config, such as main_master328 # The uri is just a property name in the config, such as main_master
330 # or auth_slave.329 # or main_slave.
331 # We don't invoke the superclass constructor as it has a very limited330 # We don't invoke the superclass constructor as it has a very limited
332 # opinion on what uri is.331 # opinion on what uri is.
333 # pylint: disable-msg=W0231332 # pylint: disable-msg=W0231
@@ -356,7 +355,7 @@
356 'Connection uri %s does not match section-realm-flavor format'355 'Connection uri %s does not match section-realm-flavor format'
357 % repr(self._uri.database))356 % repr(self._uri.database))
358357
359 assert realm in ('main', 'auth'), 'Unknown realm %s' % realm358 assert realm == 'main', 'Unknown realm %s' % realm
360 assert flavor in ('master', 'slave'), 'Unknown flavor %s' % flavor359 assert flavor in ('master', 'slave'), 'Unknown flavor %s' % flavor
361360
362 my_dbconfig = DatabaseConfig()361 my_dbconfig = DatabaseConfig()
@@ -572,14 +571,6 @@
572 return db_policy.getStore(name, flavor)571 return db_policy.getStore(name, flavor)
573572
574573
575# There are not many tables outside of the main replication set, so we
576# can just maintain a hardcoded list of what isn't in there for now.
577_auth_store_tables = frozenset([
578 'Account', 'AccountPassword', 'AuthToken', 'EmailAddress',
579 'OpenIDAssociation', 'OpenIDAuthorization', 'OpenIDNonce',
580 'OpenIDRPSummary'])
581
582
583# We want to be able to adapt a Storm class to an IStore, IMasterStore or574# We want to be able to adapt a Storm class to an IStore, IMasterStore or
584# ISlaveStore. Unfortunately, the component architecture provides no575# ISlaveStore. Unfortunately, the component architecture provides no
585# way for us to declare that a class, and all its subclasses, provides576# way for us to declare that a class, and all its subclasses, provides
@@ -588,9 +579,7 @@
588def get_store(storm_class, flavor=DEFAULT_FLAVOR):579def get_store(storm_class, flavor=DEFAULT_FLAVOR):
589 """Return a flavored Store for the given database class."""580 """Return a flavored Store for the given database class."""
590 table = getattr(removeSecurityProxy(storm_class), '__storm_table__', None)581 table = getattr(removeSecurityProxy(storm_class), '__storm_table__', None)
591 if table in _auth_store_tables:582 if table is not None:
592 return getUtility(IStoreSelector).get(AUTH_STORE, flavor)
593 elif table is not None:
594 return getUtility(IStoreSelector).get(MAIN_STORE, flavor)583 return getUtility(IStoreSelector).get(MAIN_STORE, flavor)
595 else:584 else:
596 return None585 return None
597586
=== modified file 'lib/canonical/launchpad/webapp/ftests/test_adapter_permissions.txt'
--- lib/canonical/launchpad/webapp/ftests/test_adapter_permissions.txt 2009-06-11 01:28:55 +0000
+++ lib/canonical/launchpad/webapp/ftests/test_adapter_permissions.txt 2010-04-05 20:44:34 +0000
@@ -10,8 +10,7 @@
1010
11 >>> from lp.registry.model.person import Person11 >>> from lp.registry.model.person import Person
12 >>> from canonical.launchpad.webapp.interfaces import (12 >>> from canonical.launchpad.webapp.interfaces import (
13 ... IStoreSelector, AUTH_STORE, MAIN_STORE,13 ... IStoreSelector, MAIN_STORE, MASTER_FLAVOR, SLAVE_FLAVOR)
14 ... MASTER_FLAVOR, SLAVE_FLAVOR)
15 >>> import transaction14 >>> import transaction
16 >>> from zope.component import getUtility15 >>> from zope.component import getUtility
1716
@@ -47,25 +46,3 @@
47 >>> main_master.find(Person, name='janitor').one().displayname46 >>> main_master.find(Person, name='janitor').one().displayname
48 u'BenD'47 u'BenD'
49 >>> transaction.abort()48 >>> transaction.abort()
50
51
52A MASTER_FLAVOR Store does not allow writes to tables outside of that
53Store's replication set.
54
55 >>> t = transaction.begin()
56 >>> person = main_master.find(Person, name='no-priv').one()
57 >>> account = person.account
58 >>> account.displayname = "Ben Dover"
59 >>> main_master.flush()
60 Traceback (most recent call last):
61 ...
62 ProgrammingError: permission denied for relation account
63 >>> transaction.abort()
64
65 >>> t = transaction.begin()
66 >>> auth_master = getUtility(IStoreSelector).get(AUTH_STORE, MASTER_FLAVOR)
67 >>> person = auth_master.find(Person, name='no-priv').one()
68 >>> account = person.account
69 >>> account.displayname = "Ben Dover"
70 >>> auth_master.flush()
71 >>> transaction.abort()
7249
=== modified file 'lib/canonical/launchpad/webapp/interfaces.py'
--- lib/canonical/launchpad/webapp/interfaces.py 2010-03-26 22:57:38 +0000
+++ lib/canonical/launchpad/webapp/interfaces.py 2010-04-05 20:44:34 +0000
@@ -745,9 +745,7 @@
745#745#
746746
747MAIN_STORE = 'main' # The main database.747MAIN_STORE = 'main' # The main database.
748AUTH_STORE = 'auth' # The authentication database.748ALL_STORES = frozenset([MAIN_STORE])
749
750ALL_STORES = frozenset([MAIN_STORE, AUTH_STORE])
751749
752DEFAULT_FLAVOR = 'default' # Default flavor for current state.750DEFAULT_FLAVOR = 'default' # Default flavor for current state.
753MASTER_FLAVOR = 'master' # The master database.751MASTER_FLAVOR = 'master' # The master database.
754752
=== modified file 'lib/canonical/launchpad/webapp/tests/test_dbpolicy.py'
--- lib/canonical/launchpad/webapp/tests/test_dbpolicy.py 2010-02-24 23:18:40 +0000
+++ lib/canonical/launchpad/webapp/tests/test_dbpolicy.py 2010-04-05 20:44:34 +0000
@@ -25,7 +25,7 @@
25 ReadOnlyLaunchpadDatabasePolicy, SlaveDatabasePolicy,25 ReadOnlyLaunchpadDatabasePolicy, SlaveDatabasePolicy,
26 SlaveOnlyDatabasePolicy)26 SlaveOnlyDatabasePolicy)
27from canonical.launchpad.webapp.interfaces import (27from canonical.launchpad.webapp.interfaces import (
28 ALL_STORES, AUTH_STORE, DEFAULT_FLAVOR, DisallowedStore, IDatabasePolicy,28 ALL_STORES, DEFAULT_FLAVOR, DisallowedStore, IDatabasePolicy,
29 IStoreSelector, MAIN_STORE, MASTER_FLAVOR, ReadOnlyModeDisallowedStore,29 IStoreSelector, MAIN_STORE, MASTER_FLAVOR, ReadOnlyModeDisallowedStore,
30 SLAVE_FLAVOR)30 SLAVE_FLAVOR)
31from canonical.launchpad.webapp.servers import LaunchpadTestRequest31from canonical.launchpad.webapp.servers import LaunchpadTestRequest
@@ -47,9 +47,6 @@
47 main_store = store_selector.get(MAIN_STORE, DEFAULT_FLAVOR)47 main_store = store_selector.get(MAIN_STORE, DEFAULT_FLAVOR)
48 self.failUnlessEqual(self.getDBUser(main_store), 'launchpad_main')48 self.failUnlessEqual(self.getDBUser(main_store), 'launchpad_main')
4949
50 auth_store = store_selector.get(AUTH_STORE, DEFAULT_FLAVOR)
51 self.failUnlessEqual(self.getDBUser(auth_store), 'launchpad_auth')
52
53 def getDBUser(self, store):50 def getDBUser(self, store):
54 return store.execute(51 return store.execute(
55 'SHOW session_authorization').get_one()[0]52 'SHOW session_authorization').get_one()[0]
5653
=== removed symlink 'lib/canonical/signon'
=== target was u'../../sourcecode/canonical-identity-provider'
=== modified file 'lib/lp/bugs/doc/checkwatches.txt'
--- lib/lp/bugs/doc/checkwatches.txt 2010-03-26 15:24:59 +0000
+++ lib/lp/bugs/doc/checkwatches.txt 2010-04-05 20:44:34 +0000
@@ -337,9 +337,13 @@
337If a bug tracker doesn't have any watches to update, forceUpdateAll()337If a bug tracker doesn't have any watches to update, forceUpdateAll()
338will ignore it.338will ignore it.
339339
340 >>> transaction.commit()
341 >>> LaunchpadZopelessLayer.switchDbUser('launchpad')
340 >>> login('test@canonical.com')342 >>> login('test@canonical.com')
341 >>> empty_tracker = factory.makeBugTracker(343 >>> empty_tracker = factory.makeBugTracker(
342 ... 'http://example.com', BugTrackerType.ROUNDUP)344 ... 'http://example.com', BugTrackerType.ROUNDUP)
345 >>> transaction.commit()
346 >>> LaunchpadZopelessLayer.switchDbUser(config.checkwatches.dbuser)
343 >>> empty_tracker_name = empty_tracker.name347 >>> empty_tracker_name = empty_tracker.name
344 >>> update_all(empty_tracker_name)348 >>> update_all(empty_tracker_name)
345 INFO Bug tracker 'auto-example.com' doesn't have any watches. Ignoring.349 INFO Bug tracker 'auto-example.com' doesn't have any watches. Ignoring.
346350
=== modified file 'lib/lp/bugs/doc/externalbugtracker-comment-imports.txt'
--- lib/lp/bugs/doc/externalbugtracker-comment-imports.txt 2010-03-25 14:28:33 +0000
+++ lib/lp/bugs/doc/externalbugtracker-comment-imports.txt 2010-04-05 20:44:34 +0000
@@ -373,8 +373,10 @@
373since they aren't a valid Launchpad user, having been created during the373since they aren't a valid Launchpad user, having been created during the
374import process.374import process.
375375
376We'll add a listener to check for Karma events.376We'll create a bug watch and add a listener to check for Karma events.
377377
378 >>> LaunchpadZopelessLayer.switchDbUser('launchpad')
379 >>> bug_watch = factory.makeBugWatch('123456')
378 >>> from lp.registry.tests.karma import KarmaAssignedEventListener380 >>> from lp.registry.tests.karma import KarmaAssignedEventListener
379 >>> karma_helper = KarmaAssignedEventListener()381 >>> karma_helper = KarmaAssignedEventListener()
380 >>> karma_helper.register_listener()382 >>> karma_helper.register_listener()
@@ -382,8 +384,6 @@
382Importing a comment with a CVE reference will produce a CVE link in384Importing a comment with a CVE reference will produce a CVE link in
383Launchpad but will result in no Karma records being created.385Launchpad but will result in no Karma records being created.
384386
385 >>> LaunchpadZopelessLayer.switchDbUser('launchpad')
386 >>> bug_watch = factory.makeBugWatch('123456')
387 >>> transaction.commit()387 >>> transaction.commit()
388 >>> LaunchpadZopelessLayer.switchDbUser(config.checkwatches.dbuser)388 >>> LaunchpadZopelessLayer.switchDbUser(config.checkwatches.dbuser)
389 >>> external_bugtracker.remote_comments = {389 >>> external_bugtracker.remote_comments = {
390390
=== modified file 'lib/lp/bugs/interfaces/bug.py'
--- lib/lp/bugs/interfaces/bug.py 2010-03-12 06:24:59 +0000
+++ lib/lp/bugs/interfaces/bug.py 2010-04-05 20:44:34 +0000
@@ -288,9 +288,9 @@
288 number_of_duplicates = exported(288 number_of_duplicates = exported(
289 Int(title=_('The number of bugs marked as duplicates of this bug'),289 Int(title=_('The number of bugs marked as duplicates of this bug'),
290 required=True, readonly=True))290 required=True, readonly=True))
291 message_count = Int(291 message_count = exported(
292 title=_('The number of comments on this bug'),292 Int(title=_('The number of comments on this bug'),
293 required=True, readonly=True)293 required=True, readonly=True))
294 users_affected_count = exported(294 users_affected_count = exported(
295 Int(title=_('The number of users affected by this bug '295 Int(title=_('The number of users affected by this bug '
296 '(not including duplicates)'),296 '(not including duplicates)'),
297297
=== modified file 'lib/lp/bugs/stories/webservice/xx-bug.txt'
--- lib/lp/bugs/stories/webservice/xx-bug.txt 2010-03-24 21:59:44 +0000
+++ lib/lp/bugs/stories/webservice/xx-bug.txt 2010-04-05 20:44:34 +0000
@@ -34,6 +34,7 @@
34 id: 1134 id: 11
35 latest_patch_uploaded: None35 latest_patch_uploaded: None
36 linked_branches_collection_link: u'http://.../bugs/11/linked_branches'36 linked_branches_collection_link: u'http://.../bugs/11/linked_branches'
37 message_count: 7
37 messages_collection_link: u'http://.../bugs/11/messages'38 messages_collection_link: u'http://.../bugs/11/messages'
38 name: None39 name: None
39 number_of_duplicates: 040 number_of_duplicates: 0
4041
=== modified file 'lib/lp/code/doc/branch-karma.txt'
--- lib/lp/code/doc/branch-karma.txt 2010-02-16 20:36:48 +0000
+++ lib/lp/code/doc/branch-karma.txt 2010-04-05 20:44:34 +0000
@@ -49,6 +49,7 @@
49You get karma for linking a bug to a branch.49You get karma for linking a bug to a branch.
5050
51 >>> bug = factory.makeBug(product=fooix)51 >>> bug = factory.makeBug(product=fooix)
52 Karma added: action=bugcreated, product=fooix, person=person-name11
52 >>> branch_link = bug.linkBranch(branch, eric)53 >>> branch_link = bug.linkBranch(branch, eric)
53 Karma added: action=bugbranchcreated, product=fooix, person=eric54 Karma added: action=bugbranchcreated, product=fooix, person=eric
5455
5556
=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml 2010-03-19 11:33:44 +0000
+++ lib/lp/registry/configure.zcml 2010-04-05 20:44:34 +0000
@@ -1644,18 +1644,60 @@
1644 <!-- DistributionMirror -->1644 <!-- DistributionMirror -->
1645 <class class="lp.registry.model.distributionmirror.DistributionMirror">1645 <class class="lp.registry.model.distributionmirror.DistributionMirror">
1646 <allow1646 <allow
1647 interface="lp.registry.interfaces.distributionmirror.IDistributionMirrorPublic" />1647 attributes="
1648 id
1649 name
1650 displayname
1651 description
1652 distribution
1653 http_base_url
1654 ftp_base_url
1655 rsync_base_url
1656 enabled
1657 speed
1658 status
1659 country
1660 content
1661 owner
1662 title
1663 cdimage_series
1664 source_series
1665 arch_series
1666 last_probe_record
1667 all_probe_records
1668 has_ftp_or_rsync_base_url
1669 base_url
1670 date_created
1671 country_dns_mirror
1672 mirrorMustHaveHTTPOrFTPURL
1673 getSummarizedMirroredSourceSeries
1674 getSummarizedMirroredArchSeries
1675 getOverallFreshness
1676 isOfficial
1677 shouldDisable
1678 disable
1679 newProbeRecord
1680 deleteMirrorDistroArchSeries
1681 ensureMirrorDistroArchSeries
1682 ensureMirrorDistroSeriesSource
1683 deleteMirrorDistroSeriesSource
1684 ensureMirrorCDImageSeries
1685 deleteMirrorCDImageSeries
1686 deleteAllMirrorCDImageSeries
1687 getExpectedPackagesPaths
1688 getExpectedSourcesPaths
1689 canTransitionToCountryMirror" />
1648 <require1690 <require
1649 permission="launchpad.Edit"1691 permission="launchpad.Edit"
1650 interface="lp.registry.interfaces.distributionmirror.IDistributionMirrorEditRestricted"
1651 set_attributes="name displayname description whiteboard1692 set_attributes="name displayname description whiteboard
1652 http_base_url ftp_base_url rsync_base_url enabled1693 http_base_url ftp_base_url rsync_base_url enabled
1653 speed country content official_candidate owner" />1694 speed country content official_candidate owner"
1695 attributes="official_candidate whiteboard" />
1654 <require1696 <require
1655 permission="launchpad.Admin"1697 permission="launchpad.Admin"
1656 interface="lp.registry.interfaces.distributionmirror.IDistributionMirrorAdminRestricted"
1657 set_attributes="status reviewer date_reviewed"1698 set_attributes="status reviewer date_reviewed"
1658 attributes="destroySelf" />1699 attributes="reviewer date_reviewed destroySelf
1700 transitionToCountryMirror" />
1659 </class>1701 </class>
16601702
1661 <adapter1703 <adapter
16621704
=== 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-04-05 20:44:34 +0000
@@ -8,7 +8,7 @@
8 >>> from canonical.launchpad.interfaces import (8 >>> from canonical.launchpad.interfaces import (
9 ... ICountrySet, IDistributionSet, IDistributionMirrorSet,9 ... ICountrySet, IDistributionSet, IDistributionMirrorSet,
10 ... IDistroArchSeriesSet, IDistroSeriesSet, ILibraryFileAliasSet,10 ... IDistroArchSeriesSet, IDistroSeriesSet, ILibraryFileAliasSet,
11 ... IPersonSet, MirrorContent, MirrorSpeed)11 ... IPersonSet, MirrorContent, MirrorSpeed, MirrorStatus)
12 >>> from lp.registry.interfaces.pocket import PackagePublishingPocket12 >>> from lp.registry.interfaces.pocket import PackagePublishingPocket
13 >>> mirrorset = getUtility(IDistributionMirrorSet)13 >>> mirrorset = getUtility(IDistributionMirrorSet)
14 >>> distroset = getUtility(IDistributionSet)14 >>> distroset = getUtility(IDistributionSet)
@@ -814,3 +814,121 @@
814 >>> mirrorset.getByName('invalid-mirror') is None814 >>> mirrorset.getByName('invalid-mirror') is None
815 True815 True
816816
817Country DNS mirrors
818-------------------
819
820Country DNS mirrors are mirrors which have been assigned
821$CC.archive.ubuntu.com or $CC.releases.ubuntu.com. These assignments are
822tracked in Launchpad.
823
824 >>> login('admin@canonical.com')
825 >>> ubuntu_distro = getUtility(IDistributionSet).getByName('ubuntu')
826 >>> de_archive_mirror = factory.makeMirror(ubuntu_distro,
827 ... "Technische Universitaet Dresden", country=82,
828 ... http_url="http://ubuntu.mirror.tudos.de/ubuntu/",
829 ... official_candidate=True)
830 >>> davis_station_archive = factory.makeMirror(ubuntu_distro,
831 ... "Davis Station", country=9,
832 ... http_url="http://mirror.davis.antarctica.org/ubuntu",
833 ... official_candidate=True)
834 >>> de_archive_mirror.status = MirrorStatus.OFFICIAL
835 >>> de_archive_prober_log = factory.makeMirrorProbeRecord(de_archive_mirror)
836 >>> logout()
837
838Normal users can access country_dns_mirror, can see if a mirror is eligible
839for the status, however, they may not change it:
840
841 >>> login('test@canonical.com')
842 >>> de_archive_mirror.canTransitionToCountryMirror()
843 True
844 >>> de_archive_mirror.transitionToCountryMirror(True)
845 Traceback (most recent call last):
846 ...
847 Unauthorized: (<DistributionMirror at ...>, 'transitionToCountryMirror',
848 'launchpad.Admin')
849 >>> logout()
850
851Mirror listing administrators may change the status however:
852
853 >>> login('karl@canonical.com')
854 >>> de_archive_mirror.transitionToCountryMirror(True)
855
856Mirrors which are already set as country mirrors can't be 'set' as such
857again:
858
859 >>> de_archive_mirror.canTransitionToCountryMirror()
860 False
861 >>> de_archive_mirror.transitionToCountryMirror(True)
862 >>> logout()
863
864There cannot be multiple country mirrors of one type for one country:
865
866 >>> login('karl@canonical.com')
867 >>> proberecord = factory.makeMirrorProbeRecord(davis_station_archive)
868
869 >>> print davis_station_archive.content.name
870 ARCHIVE
871 >>> print davis_station_archive.country_dns_mirror
872 False
873 >>> print davis_station_archive.country.name
874 Antarctica
875
876 >>> archive_mirror2 = getUtility(IDistributionMirrorSet).getByName(
877 ... 'archive-mirror2')
878 >>> print archive_mirror2.content.name
879 ARCHIVE
880 >>> print archive_mirror2.country_dns_mirror
881 False
882 >>> print archive_mirror2.country.name
883 Antarctica
884
885 >>> davis_station_archive.status = MirrorStatus.OFFICIAL
886
887 >>> davis_station_archive.transitionToCountryMirror(True)
888 >>> archive_mirror2.transitionToCountryMirror(True)
889 Traceback (most recent call last):
890 ...
891 CountryMirrorAlreadySet: Antarctica already has a country Archive mirror
892 set.
893
894Mirrors which have not been probed may not be marked as country mirrors:
895
896 >>> linux_au_mirror = factory.makeMirror(ubuntu_distro,
897 ... "Linux.org.au", country=14,
898 ... http_url="http://mirror.linux.org.au/ubuntu",
899 ... official_candidate=True)
900 >>> linux_au_mirror.status = MirrorStatus.OFFICIAL
901 >>> linux_au_mirror.transitionToCountryMirror(True)
902 Traceback (most recent call last):
903 ...
904 MirrorNotProbed: This mirror may not be set as a country mirror as it has
905 not been probed.
906 >>> logout()
907
908Mirrors which are not official or do not have an HTTP URL may not be set as
909country mirrors:
910
911 >>> login('admin@canonical.com')
912 >>> osuosl_mirror = factory.makeMirror(ubuntu_distro,
913 ... "OSU Open Source Lab", country=226,
914 ... ftp_url="ftp://ubuntu.osuosl.org/pub/ubuntu/",
915 ... official_candidate=True)
916 >>> osuosl_mirror.status = MirrorStatus.OFFICIAL
917 >>> print osuosl_mirror.http_base_url
918 None
919
920 >>> osuosl_mirror.canTransitionToCountryMirror()
921 False
922
923 >>> osuosl_mirror.transitionToCountryMirror(None)
924 Traceback (most recent call last):
925 ...
926 NoneError: None isn't acceptable as a value for
927 DistributionMirror.country_dns_mirror
928
929 >>> osuosl_mirror.transitionToCountryMirror(True)
930 Traceback (most recent call last):
931 ...
932 MirrorHasNoHTTPURL: This mirror may not be set as a country mirror as it
933 does not have an HTTP URL set.
934 >>> logout()
817935
=== 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-04-05 20:44:34 +0000
@@ -25,7 +25,7 @@
2525
26from lazr.restful.fields import CollectionField, Reference26from lazr.restful.fields import CollectionField, Reference
27from lazr.restful.declarations import (27from lazr.restful.declarations import (
28 collection_default_content, export_as_webservice_collection,28 collection_default_content, copy_field, export_as_webservice_collection,
29 export_as_webservice_entry, export_operation_as,29 export_as_webservice_entry, export_operation_as,
30 export_read_operation, exported, operation_parameters,30 export_read_operation, exported, operation_parameters,
31 operation_returns_collection_of, operation_returns_entry,31 operation_returns_collection_of, operation_returns_entry,
@@ -321,6 +321,14 @@
321 if it's not found.321 if it's not found.
322 """322 """
323323
324 @operation_parameters(
325 country=copy_field(IDistributionMirror['country'], required=True),
326 mirror_type=copy_field(IDistributionMirror['content'], required=True))
327 @operation_returns_entry(IDistributionMirror)
328 @export_read_operation()
329 def getCountryMirror(country, mirror_type):
330 """Return the country DNS mirror for a country and content type."""
331
324 def newMirror(owner, speed, country, content, displayname=None,332 def newMirror(owner, speed, country, content, displayname=None,
325 description=None, http_base_url=None,333 description=None, http_base_url=None,
326 ftp_base_url=None, rsync_base_url=None, enabled=False,334 ftp_base_url=None, rsync_base_url=None, enabled=False,
327335
=== 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-04-05 20:44:34 +0000
@@ -6,21 +6,24 @@
6__metaclass__ = type6__metaclass__ = type
77
8__all__ = [8__all__ = [
9'IDistributionMirror',9 'CannotTransitionToCountryMirror',
10'IDistributionMirrorAdminRestricted',10 'CountryMirrorAlreadySet',
11'IDistributionMirrorEditRestricted',11 'IDistributionMirror',
12'IDistributionMirrorPublic',12 'IMirrorDistroArchSeries',
13'IMirrorDistroArchSeries',13 'IMirrorDistroSeriesSource',
14'IMirrorDistroSeriesSource',14 'IMirrorProbeRecord',
15'IMirrorProbeRecord',15 'IDistributionMirrorSet',
16'IDistributionMirrorSet',16 'IMirrorCDImageDistroSeries',
17'IMirrorCDImageDistroSeries',17 'PROBE_INTERVAL',
18'PROBE_INTERVAL',18 'MirrorContent',
19'UnableToFetchCDImageFileList',19 'MirrorFreshness',
20'MirrorContent',20 'MirrorHasNoHTTPURL',
21'MirrorFreshness',21 'MirrorNotOfficial',
22'MirrorSpeed',22 'MirrorNotProbed',
23'MirrorStatus']23 'MirrorSpeed',
24 'MirrorStatus',
25 'UnableToFetchCDImageFileList',
26 ]
2427
25from cgi import escape28from cgi import escape
2629
@@ -31,8 +34,11 @@
31from zope.component import getUtility34from zope.component import getUtility
32from lazr.enum import DBEnumeratedType, DBItem35from lazr.enum import DBEnumeratedType, DBItem
33from lazr.restful.declarations import (36from lazr.restful.declarations import (
34 export_as_webservice_entry, export_read_operation, exported)37 export_as_webservice_entry, export_read_operation,
38 export_write_operation, exported, mutator_for, operation_parameters,
39 webservice_error)
35from lazr.restful.fields import Reference, ReferenceChoice40from lazr.restful.fields import Reference, ReferenceChoice
41from lazr.restful.interface import copy_field
3642
37from canonical.launchpad import _43from canonical.launchpad import _
38from canonical.launchpad.fields import (44from canonical.launchpad.fields import (
@@ -47,6 +53,43 @@
47PROBE_INTERVAL = 2353PROBE_INTERVAL = 23
4854
4955
56class CannotTransitionToCountryMirror(Exception):
57 """Root exception for transitions to country mirrors."""
58 webservice_error(400)
59
60
61class CountryMirrorAlreadySet(CannotTransitionToCountryMirror):
62 """Distribution mirror cannot be set as a country mirror.
63
64 Raised when a user tries to change set a distribution mirror as a country
65 mirror, however there is already one set for that country.
66 """
67
68
69class MirrorNotOfficial(CannotTransitionToCountryMirror):
70 """Distribution mirror is not permitted to become a country mirror.
71
72 Raised when a user tries to change set a distribution mirror as a country
73 mirror, however the mirror in question is not official.
74 """
75
76
77class MirrorHasNoHTTPURL(CannotTransitionToCountryMirror):
78 """Distribution mirror has no HTTP URL.
79
80 Raised when a user tries to make an official mirror a country mirror,
81 however the mirror has not HTTP URL set.
82 """
83
84
85class MirrorNotProbed(CannotTransitionToCountryMirror):
86 """Distribution mirror has not been probed.
87
88 Raised when a user tries to set an official mirror as a country mirror,
89 however the mirror has not been probed yet.
90 """
91
92
50class MirrorContent(DBEnumeratedType):93class MirrorContent(DBEnumeratedType):
51 """The content that is mirrored."""94 """The content that is mirrored."""
5295
@@ -284,33 +327,10 @@
284 def getMirrorByURI(self, url):327 def getMirrorByURI(self, url):
285 return getUtility(IDistributionMirrorSet).getByRsyncUrl(url)328 return getUtility(IDistributionMirrorSet).getByRsyncUrl(url)
286329
287class IDistributionMirrorAdminRestricted(Interface):330
288 """IDistributionMirror properties requiring launchpad.Admin permission."""331class IDistributionMirror(Interface):
289332 """A mirror of a given distribution."""
290 reviewer = exported(PublicPersonChoice(333 export_as_webservice_entry()
291 title=_('Reviewer'), required=False, readonly=True,
292 vocabulary='ValidPersonOrTeam', description=_(
293 "The person who last reviewed this mirror.")))
294 date_reviewed = exported(Datetime(
295 title=_('Date reviewed'), required=False, readonly=True,
296 description=_(
297 "The date on which this mirror was last reviewed by a mirror admin.")))
298
299
300class IDistributionMirrorEditRestricted(Interface):
301 """IDistributionMirror properties requiring launchpad.Edit permission."""
302
303 official_candidate = exported(Bool(
304 title=_('Apply to be an official mirror of this distribution'),
305 required=False, readonly=False, default=True))
306 whiteboard = exported(Whiteboard(
307 title=_('Whiteboard'), required=False, readonly=False,
308 description=_("Notes on the current status of the mirror (only "
309 "visible to admins and the mirror's registrant).")))
310
311
312class IDistributionMirrorPublic(Interface):
313 """Public IDistributionMirror properties."""
314334
315 id = Int(title=_('The unique id'), required=True, readonly=True)335 id = Int(title=_('The unique id'), required=True, readonly=True)
316 owner = exported(PublicPersonChoice(336 owner = exported(PublicPersonChoice(
@@ -386,6 +406,39 @@
386 date_created = exported(Datetime(406 date_created = exported(Datetime(
387 title=_('Date Created'), required=True, readonly=True,407 title=_('Date Created'), required=True, readonly=True,
388 description=_("The date on which this mirror was registered.")))408 description=_("The date on which this mirror was registered.")))
409 country_dns_mirror = exported(Bool(
410 title=_('Country DNS Mirror'),
411 description=_('Whether this is a country mirror in DNS.'),
412 required=False, readonly=True, default=False))
413
414 reviewer = exported(PublicPersonChoice(
415 title=_('Reviewer'), required=False, readonly=True,
416 vocabulary='ValidPersonOrTeam', description=_(
417 "The person who last reviewed this mirror.")))
418 date_reviewed = exported(Datetime(
419 title=_('Date reviewed'), required=False, readonly=True,
420 description=_(
421 "The date on which this mirror was last reviewed by a mirror "
422 "admin.")))
423
424 official_candidate = exported(Bool(
425 title=_('Apply to be an official mirror of this distribution'),
426 required=False, readonly=False, default=True))
427 whiteboard = exported(Whiteboard(
428 title=_('Whiteboard'), required=False, readonly=False,
429 description=_("Notes on the current status of the mirror (only "
430 "visible to admins and the mirror's registrant).")))
431
432 @export_read_operation()
433 def canTransitionToCountryMirror():
434 """Verify if a mirror can be set as a country mirror or return
435 False."""
436
437 @mutator_for(country_dns_mirror)
438 @operation_parameters(country_dns_mirror=copy_field(country_dns_mirror))
439 @export_write_operation()
440 def transitionToCountryMirror(country_dns_mirror):
441 """Method run on changing country_dns_mirror."""
389442
390 @invariant443 @invariant
391 def mirrorMustHaveHTTPOrFTPURL(mirror):444 def mirrorMustHaveHTTPOrFTPURL(mirror):
@@ -521,10 +574,6 @@
521 Sources.gz file refer to and the path to the file itself.574 Sources.gz file refer to and the path to the file itself.
522 """575 """
523576
524class IDistributionMirror(IDistributionMirrorAdminRestricted,
525 IDistributionMirrorEditRestricted, IDistributionMirrorPublic):
526 """A mirror of a given distribution."""
527 export_as_webservice_entry()
528577
529578
530class UnableToFetchCDImageFileList(Exception):579class UnableToFetchCDImageFileList(Exception):
531580
=== modified file 'lib/lp/registry/model/distribution.py'
--- lib/lp/registry/model/distribution.py 2010-03-24 02:53:42 +0000
+++ lib/lp/registry/model/distribution.py 2010-04-05 20:44:34 +0000
@@ -402,6 +402,17 @@
402 """See `IDistribution`."""402 """See `IDistribution`."""
403 return DistributionMirror.selectOneBy(distribution=self, name=name)403 return DistributionMirror.selectOneBy(distribution=self, name=name)
404404
405 def getCountryMirror(self, country, mirror_type):
406 """See `IDistribution`."""
407 store = Store.of(self)
408 results = store.find(
409 DistributionMirror,
410 DistributionMirror.distribution == self,
411 DistributionMirror.country == country,
412 DistributionMirror.content == mirror_type,
413 DistributionMirror.country_dns_mirror == True)
414 return results.one()
415
405 def newMirror(self, owner, speed, country, content, displayname=None,416 def newMirror(self, owner, speed, country, content, displayname=None,
406 description=None, http_base_url=None,417 description=None, http_base_url=None,
407 ftp_base_url=None, rsync_base_url=None,418 ftp_base_url=None, rsync_base_url=None,
408419
=== modified file 'lib/lp/registry/model/distributionmirror.py'
--- lib/lp/registry/model/distributionmirror.py 2010-03-23 20:42:23 +0000
+++ lib/lp/registry/model/distributionmirror.py 2010-04-05 20:44:34 +0000
@@ -45,9 +45,11 @@
45from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities45from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
46from lp.soyuz.interfaces.publishing import PackagePublishingStatus46from lp.soyuz.interfaces.publishing import PackagePublishingStatus
47from lp.registry.interfaces.distributionmirror import (47from lp.registry.interfaces.distributionmirror import (
48 CannotTransitionToCountryMirror, CountryMirrorAlreadySet,
48 IDistributionMirror, IDistributionMirrorSet, IMirrorCDImageDistroSeries,49 IDistributionMirror, IDistributionMirrorSet, IMirrorCDImageDistroSeries,
49 IMirrorDistroArchSeries, IMirrorDistroSeriesSource, IMirrorProbeRecord,50 IMirrorDistroArchSeries, IMirrorDistroSeriesSource, IMirrorProbeRecord,
50 MirrorContent, MirrorFreshness, MirrorSpeed, MirrorStatus, PROBE_INTERVAL)51 MirrorContent, MirrorFreshness, MirrorHasNoHTTPURL, MirrorNotOfficial,
52 MirrorNotProbed, MirrorSpeed, MirrorStatus, PROBE_INTERVAL)
51from lp.registry.interfaces.distroseries import IDistroSeries53from lp.registry.interfaces.distroseries import IDistroSeries
52from lp.registry.interfaces.sourcepackage import SourcePackageFileType54from lp.registry.interfaces.sourcepackage import SourcePackageFileType
53from canonical.launchpad.mail import simple_sendmail, format_address55from canonical.launchpad.mail import simple_sendmail, format_address
@@ -100,6 +102,8 @@
100 date_reviewed = UtcDateTimeCol(default=None)102 date_reviewed = UtcDateTimeCol(default=None)
101 whiteboard = StringCol(103 whiteboard = StringCol(
102 notNull=False, default=None)104 notNull=False, default=None)
105 country_dns_mirror = BoolCol(
106 notNull=True, default=False)
103107
104 @property108 @property
105 def base_url(self):109 def base_url(self):
@@ -145,6 +149,64 @@
145 "This mirror has been probed and thus can't be removed.")149 "This mirror has been probed and thus can't be removed.")
146 SQLBase.destroySelf(self)150 SQLBase.destroySelf(self)
147151
152 def verifyTransitionToCountryMirror(self):
153 """Verify that a mirror can be set as a country mirror.
154
155 Return True if valid, otherwise raise a subclass of
156 CannotTransitionToCountryMirror.
157 """
158
159 current_country_mirror = self.distribution.getCountryMirror(
160 self.country, self.content)
161
162 if current_country_mirror is not None:
163 # Country already has a country mirror.
164 raise CountryMirrorAlreadySet(
165 "%s already has a country %s mirror set." % (
166 self.country.name, self.content))
167
168 if not self.isOfficial():
169 # Only official mirrors may be set as country mirrors.
170 raise MirrorNotOfficial(
171 "This mirror may not be set as a country mirror as it is not "
172 "an official mirror.")
173
174 if self.http_base_url is None:
175 # Country mirrors must have HTTP URLs set.
176 raise MirrorHasNoHTTPURL(
177 "This mirror may not be set as a country mirror as it does "
178 "not have an HTTP URL set.")
179
180 if not self.last_probe_record:
181 # Only mirrors which have been probed may be set as country
182 # mirrors.
183 raise MirrorNotProbed(
184 "This mirror may not be set as a country mirror as it has "
185 "not been probed.")
186
187 # Verification done.
188 return True
189
190 def canTransitionToCountryMirror(self):
191 """See `IDistributionMirror`."""
192 try:
193 return self.verifyTransitionToCountryMirror()
194 except CannotTransitionToCountryMirror:
195 return False
196
197 def transitionToCountryMirror(self, country_dns_mirror):
198 """See `IDistributionMirror`."""
199
200 # country_dns_mirror has not been changed, do nothing.
201 if self.country_dns_mirror == country_dns_mirror:
202 return
203
204 # Environment sanity checks.
205 if country_dns_mirror:
206 self.verifyTransitionToCountryMirror()
207
208 self.country_dns_mirror = country_dns_mirror
209
148 def getOverallFreshness(self):210 def getOverallFreshness(self):
149 """See IDistributionMirror"""211 """See IDistributionMirror"""
150 # XXX Guilherme Salgado 2006-08-16:212 # XXX Guilherme Salgado 2006-08-16:
151213
=== modified file 'lib/lp/registry/model/person.py'
--- lib/lp/registry/model/person.py 2010-03-24 23:19:52 +0000
+++ lib/lp/registry/model/person.py 2010-04-05 20:44:34 +0000
@@ -124,7 +124,7 @@
124 TeamMembershipStatus)124 TeamMembershipStatus)
125from lp.registry.interfaces.wikiname import IWikiName, IWikiNameSet125from lp.registry.interfaces.wikiname import IWikiName, IWikiNameSet
126from canonical.launchpad.webapp.interfaces import (126from canonical.launchpad.webapp.interfaces import (
127 AUTH_STORE, ILaunchBag, IStoreSelector, MASTER_FLAVOR)127 ILaunchBag, IStoreSelector, MASTER_FLAVOR)
128128
129from lp.soyuz.model.archive import Archive129from lp.soyuz.model.archive import Archive
130from lp.registry.model.codeofconduct import SignedCodeOfConduct130from lp.registry.model.codeofconduct import SignedCodeOfConduct
@@ -263,22 +263,7 @@
263 return '<Person at 0x%x %s (%s)>' % (263 return '<Person at 0x%x %s (%s)>' % (
264 id(self), self.name, self.displayname)264 id(self), self.name, self.displayname)
265265
266 def _sync_displayname(self, attr, value):266 displayname = StringCol(dbName='displayname', notNull=True)
267 """Update any related Account.displayname.
268
269 We can't do this in a DB trigger as soon the Account table will
270 in a separate database to the Person table.
271 """
272 if self.accountID is not None:
273 auth_store = getUtility(IStoreSelector).get(
274 AUTH_STORE, MASTER_FLAVOR)
275 account = auth_store.get(Account, self.accountID)
276 if account.displayname != value:
277 account.displayname = value
278 return value
279
280 displayname = StringCol(dbName='displayname', notNull=True,
281 storm_validator=_sync_displayname)
282267
283 teamdescription = StringCol(dbName='teamdescription', default=None)268 teamdescription = StringCol(dbName='teamdescription', default=None)
284 homepage_content = StringCol(default=None)269 homepage_content = StringCol(default=None)
285270
=== 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-04-05 20:44:34 +0000
@@ -40,13 +40,14 @@
40 >>> print browser.title40 >>> print browser.title
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: [(u'Archive-mirror2', u'http', u'128 Kbps',
44 [(u'Archive-mirror2', u'http', u'128 Kbps', u'Six hours behind')]44 u'Six hours behind')]
45 France:45 France:
46 [(u'Archive-404-mirror', u'http', u'512 Kbps', u'Last update unknown'),46 [(u'Archive-404-mirror', u'http', u'512 Kbps', u'Last update unknown'),
47 (u'Archive-mirror', u'http', u'128 Kbps', u'Last update unknown')]47 (u'Archive-mirror', u'http', u'128 Kbps', u'Last update unknown')]
48 United Kingdom:48 United Kingdom: [(u'Canonical-archive', u'http', u'100 Mbps',
49 [(u'Canonical-archive', u'http', u'100 Mbps', u'Last update unknown')]49 u'Last update unknown')]
50
50 >>> find_tags_by_class(browser.contents, 'distromirrorstatusSIXHOURSBEHIND')51 >>> find_tags_by_class(browser.contents, 'distromirrorstatusSIXHOURSBEHIND')
51 [<span class="distromirrorstatusSIXHOURSBEHIND">Six hours behind</span>]52 [<span class="distromirrorstatusSIXHOURSBEHIND">Six hours behind</span>]
52 >>> find_tags_by_class(browser.contents, 'distromirrorstatusUNKNOWN')[0]53 >>> find_tags_by_class(browser.contents, 'distromirrorstatusUNKNOWN')[0]
@@ -59,13 +60,12 @@
59 >>> browser.url60 >>> browser.url
60 'http://launchpad.dev/ubuntu/+cdmirrors'61 'http://launchpad.dev/ubuntu/+cdmirrors'
61 >>> print_mirrors_by_countries(browser.contents)62 >>> print_mirrors_by_countries(browser.contents)
62 France:63 France:
63 [(u'Releases-mirror', u'http', u'2 Mbps'),64 [(u'Releases-mirror', u'http', u'2 Mbps'),
64 (u'Unreachable-mirror', u'http', u'512 Kbps')]65 (u'Unreachable-mirror', u'http', u'512 Kbps')]
65 Germany:66 Germany: [(u'Releases-mirror2', u'http', u'2 Mbps')]
66 [(u'Releases-mirror2', u'http', u'2 Mbps')]67 United Kingdom: [(u'Canonical-releases', u'http', u'100 Mbps')]
67 United Kingdom:68
68 [(u'Canonical-releases', u'http', u'100 Mbps')]
6969
70=== Disabled mirrors ===70=== Disabled mirrors ===
7171
7272
=== modified file 'lib/lp/registry/stories/person/xx-admin-person-review.txt'
--- lib/lp/registry/stories/person/xx-admin-person-review.txt 2010-01-30 22:39:54 +0000
+++ lib/lp/registry/stories/person/xx-admin-person-review.txt 2010-04-05 20:44:34 +0000
@@ -63,7 +63,7 @@
63 >>> print admin_browser.title63 >>> print admin_browser.title
64 The one and only Salgado does not use Launchpad64 The one and only Salgado does not use Launchpad
65 >>> print get_feedback_messages(admin_browser.contents)[0]65 >>> print get_feedback_messages(admin_browser.contents)[0]
66 The account "The one and only Salgado" has been suspended.66 The account "Guilherme Salgado" has been suspended.
6767
68The admin can see the account information of a user that does not use68The admin can see the account information of a user that does not use
69Launchpad, and can change the account too. Note that all pages that belong69Launchpad, and can change the account too. Note that all pages that belong
7070
=== modified file 'lib/lp/registry/stories/webservice/xx-distribution-mirror.txt'
--- lib/lp/registry/stories/webservice/xx-distribution-mirror.txt 2010-02-23 19:40:45 +0000
+++ lib/lp/registry/stories/webservice/xx-distribution-mirror.txt 2010-04-05 20:44:34 +0000
@@ -8,10 +8,12 @@
8 >>> distro = distros['entries'][0]8 >>> distro = distros['entries'][0]
9 >>> ubuntu = webservice.get(distro['self_link']).jsonBody()9 >>> ubuntu = webservice.get(distro['self_link']).jsonBody()
10 >>> ubuntu_archive_mirrors = webservice.get(ubuntu['archive_mirrors_collection_link']).jsonBody()10 >>> ubuntu_archive_mirrors = webservice.get(ubuntu['archive_mirrors_collection_link']).jsonBody()
11 >>> canonical_archive = ubuntu_archive_mirrors['entries'][0]11 >>> canonical_archive = webservice.named_get(
12 >>> canonical_archive_json = webservice.get(canonical_archive['self_link']).jsonBody()12 ... ubuntu['self_link'], 'getMirrorByName',
13 >>> pprint_entry(canonical_archive_json)13 ... name='canonical-archive').jsonBody()
14 >>> pprint_entry(canonical_archive)
14 content: u'Archive'15 content: u'Archive'
16 country_dns_mirror: False
15 country_link: u'http://.../+countries/GB'17 country_link: u'http://.../+countries/GB'
16 date_created: u'2006-10-16T18:31:43.434567+00:00'18 date_created: u'2006-10-16T18:31:43.434567+00:00'
17 date_reviewed: None19 date_reviewed: None
@@ -39,6 +41,7 @@
39 >>> canonical_releases_json = webservice.get(canonical_releases['self_link']).jsonBody()41 >>> canonical_releases_json = webservice.get(canonical_releases['self_link']).jsonBody()
40 >>> pprint_entry(canonical_releases_json)42 >>> pprint_entry(canonical_releases_json)
41 content: u'CD Image'43 content: u'CD Image'
44 country_dns_mirror: False
42 country_link: u'http://.../+countries/GB'45 country_link: u'http://.../+countries/GB'
43 date_created: u'2006-10-16T18:31:43.434567+00:00'46 date_created: u'2006-10-16T18:31:43.434567+00:00'
44 date_reviewed: None47 date_reviewed: None
@@ -73,12 +76,12 @@
73 >>> karl_db = getUtility(IPersonSet).getByName('karl')76 >>> karl_db = getUtility(IPersonSet).getByName('karl')
74 >>> test_db = getUtility(IPersonSet).getByName('name12')77 >>> test_db = getUtility(IPersonSet).getByName('name12')
75 >>> no_priv_db = getUtility(IPersonSet).getByName('no-priv')78 >>> no_priv_db = getUtility(IPersonSet).getByName('no-priv')
76 >>> karl_webservice = webservice_for_person(karl_db,79 >>> karl_webservice = webservice_for_person(
77 ... permission=OAuthPermission.WRITE_PUBLIC)80 ... karl_db, permission=OAuthPermission.WRITE_PUBLIC)
78 >>> test_webservice = webservice_for_person(test_db,81 >>> test_webservice = webservice_for_person(
79 ... permission=OAuthPermission.WRITE_PUBLIC)82 ... test_db, permission=OAuthPermission.WRITE_PUBLIC)
80 >>> no_priv_webservice = webservice_for_person(no_priv_db,83 >>> no_priv_webservice = webservice_for_person(
81 ... permission=OAuthPermission.READ_PUBLIC)84 ... no_priv_db, permission=OAuthPermission.READ_PUBLIC)
82 >>> logout()85 >>> logout()
8386
84Ensure that anonymous API sessions can view mirror listings; archive/releases.87Ensure that anonymous API sessions can view mirror listings; archive/releases.
@@ -97,7 +100,9 @@
97100
98One must have special permissions to access certain attributes:101One must have special permissions to access certain attributes:
99102
100 >>> archive_404_mirror = ubuntu_archive_mirrors['entries'][1]103 >>> archive_404_mirror = webservice.named_get(
104 ... ubuntu['self_link'], 'getMirrorByName',
105 ... name="archive-404-mirror").jsonBody()
101 >>> response = no_priv_webservice.get(106 >>> response = no_priv_webservice.get(
102 ... archive_404_mirror['self_link']).jsonBody()107 ... archive_404_mirror['self_link']).jsonBody()
103 >>> pprint_entry(response)108 >>> pprint_entry(response)
@@ -128,6 +133,7 @@
128 ... archive_404_mirror['self_link']).jsonBody()133 ... archive_404_mirror['self_link']).jsonBody()
129 >>> pprint_entry(response)134 >>> pprint_entry(response)
130 content: u'Archive'135 content: u'Archive'
136 country_dns_mirror: False
131 country_link: u'http://.../+countries/FR'137 country_link: u'http://.../+countries/FR'
132 date_created: u'2006-10-16T18:31:43.438573+00:00'138 date_created: u'2006-10-16T18:31:43.438573+00:00'
133 date_reviewed: None139 date_reviewed: None
@@ -209,6 +215,7 @@
209 ... canonical_releases['self_link'], 'application/json', dumps(patch)).jsonBody()215 ... canonical_releases['self_link'], 'application/json', dumps(patch)).jsonBody()
210 >>> pprint_entry(response)216 >>> pprint_entry(response)
211 content: u'CD Image'217 content: u'CD Image'
218 country_dns_mirror: False
212 country_link: u'http://.../+countries/GL'219 country_link: u'http://.../+countries/GL'
213 date_created: u'2006-10-16T18:31:43.434567+00:00'220 date_created: u'2006-10-16T18:31:43.434567+00:00'
214 date_reviewed: None221 date_reviewed: None
@@ -244,7 +251,9 @@
244"getOverallFreshness" returns the freshness of the mirror determined by the251"getOverallFreshness" returns the freshness of the mirror determined by the
245mirror prober from the mirror's last probe.252mirror prober from the mirror's last probe.
246253
247 >>> releases_mirror2 = ubuntu_cd_mirrors['entries'][2]254 >>> releases_mirror2 = webservice.named_get(
255 ... ubuntu['self_link'], 'getMirrorByName',
256 ... name='releases-mirror2').jsonBody()
248 >>> freshness = webservice.named_get(releases_mirror2['self_link'],257 >>> freshness = webservice.named_get(releases_mirror2['self_link'],
249 ... 'getOverallFreshness').jsonBody()258 ... 'getOverallFreshness').jsonBody()
250 >>> print freshness259 >>> print freshness
251260
=== modified file 'lib/lp/registry/stories/webservice/xx-distribution.txt'
--- lib/lp/registry/stories/webservice/xx-distribution.txt 2010-02-23 17:36:27 +0000
+++ lib/lp/registry/stories/webservice/xx-distribution.txt 2010-04-05 20:44:34 +0000
@@ -123,6 +123,7 @@
123 ... name='canonical-releases').jsonBody()123 ... name='canonical-releases').jsonBody()
124 >>> pprint_entry(canonical_releases)124 >>> pprint_entry(canonical_releases)
125 content: u'CD Image'125 content: u'CD Image'
126 country_dns_mirror: False
126 country_link: u'http://.../+countries/GB'127 country_link: u'http://.../+countries/GB'
127 date_created: u'2006-10-16T18:31:43.434567+00:00'128 date_created: u'2006-10-16T18:31:43.434567+00:00'
128 date_reviewed: None129 date_reviewed: None
@@ -142,3 +143,76 @@
142 speed: u'100 Mbps'143 speed: u'100 Mbps'
143 status: u'Official'144 status: u'Official'
144 whiteboard: None145 whiteboard: None
146
147"getCountryMirror" returns the country DNS mirror for a given country;
148returning None if there isn't one.
149
150 >>> # Prepare stuff.
151 >>> from lp.registry.interfaces.distribution import IDistributionSet
152 >>> from zope.component import getUtility
153 >>> from canonical.launchpad.testing.pages import webservice_for_person
154 >>> from canonical.launchpad.webapp.interfaces import OAuthPermission
155 >>> from lp.registry.interfaces.person import IPersonSet
156 >>> from simplejson import dumps
157
158 >>> login('admin@canonical.com')
159 >>> ubuntu_distro = getUtility(IDistributionSet).getByName('ubuntu')
160 >>> showa_station = factory.makeMirror(ubuntu_distro,
161 ... "Showa Station", country=9,
162 ... http_url="http://mirror.showa.antarctica.org/ubuntu",
163 ... official_candidate=True)
164 >>> showa_station_log = factory.makeMirrorProbeRecord(showa_station)
165 >>> logout()
166
167 >>> login(ANONYMOUS)
168 >>> karl_db = getUtility(IPersonSet).getByName('karl')
169 >>> karl_webservice = webservice_for_person(karl_db,
170 ... permission=OAuthPermission.WRITE_PUBLIC)
171 >>> logout()
172
173 >>> # Mark new mirror as official and a country mirror.
174 >>> patch = {
175 ... u'status': 'Official',
176 ... u'country_dns_mirror': True
177 ... }
178
179 >>> antarctica_patch_target = webservice.named_get(
180 ... ubuntu['self_link'], 'getMirrorByName',
181 ... name='mirror.showa.antarctica.org-archive').jsonBody()
182 ... )
183
184 >>> response = karl_webservice.patch(
185 ... antarctica_patch_target['self_link'], 'application/json',
186 ... dumps(patch))
187
188 >>> antarctica = webservice.get("/+countries/AQ").jsonBody()
189 >>> antarctica_country_mirror_archive = webservice.named_get(
190 ... ubuntu['self_link'], 'getCountryMirror',
191 ... country=antarctica['self_link'],
192 ... mirror_type="Archive").jsonBody()
193 >>> pprint_entry(antarctica_country_mirror_archive)
194 content: u'Archive'
195 country_dns_mirror: True
196 country_link: u'http://.../+countries/AQ'
197 ...
198
199 >>> uk = webservice.get("/+countries/GB").jsonBody()
200 >>> uk_country_mirror_archive = webservice.named_get(
201 ... ubuntu['self_link'], 'getCountryMirror',
202 ... country=uk['self_link'],
203 ... mirror_type="Archive")
204 >>> print uk_country_mirror_archive.jsonBody()
205 None
206
207For "getCountryMirror", the mirror_type parameter must be "Archive" or
208"CD Images":
209
210 >>> uk_country_mirror_archive = webservice.named_get(
211 ... ubuntu['self_link'], 'getCountryMirror',
212 ... country=uk['self_link'],
213 ... mirror_type="Bogus")
214 >>> print uk_country_mirror_archive.jsonBody()
215 Traceback (most recent call last):
216 ...
217 ValueError: mirror_type: Invalid value "Bogus". Acceptable values are:
218 Archive, CD Image
145219
=== modified file 'lib/lp/registry/tests/test_distributionmirror.py'
--- lib/lp/registry/tests/test_distributionmirror.py 2009-10-26 18:40:04 +0000
+++ lib/lp/registry/tests/test_distributionmirror.py 2010-04-05 20:44:34 +0000
@@ -3,7 +3,6 @@
33
4__metaclass__ = type4__metaclass__ = type
55
6from StringIO import StringIO
7import unittest6import unittest
87
9import transaction8import transaction
@@ -17,19 +16,19 @@
17from lp.registry.interfaces.distributionmirror import (16from lp.registry.interfaces.distributionmirror import (
18 IDistributionMirrorSet, MirrorContent, MirrorFreshness)17 IDistributionMirrorSet, MirrorContent, MirrorFreshness)
19from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities18from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
20from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet
21from lp.registry.interfaces.pocket import PackagePublishingPocket19from lp.registry.interfaces.pocket import PackagePublishingPocket
22from lp.registry.interfaces.distribution import IDistributionSet20from lp.registry.interfaces.distribution import IDistributionSet
23from lp.services.mail import stub21from lp.services.mail import stub
22from lp.testing.factory import LaunchpadObjectFactory
2423
25from canonical.testing import LaunchpadFunctionalLayer24from canonical.testing import LaunchpadFunctionalLayer
2625
27
28class TestDistributionMirror(unittest.TestCase):26class TestDistributionMirror(unittest.TestCase):
29 layer = LaunchpadFunctionalLayer27 layer = LaunchpadFunctionalLayer
3028
31 def setUp(self):29 def setUp(self):
32 login('test@canonical.com')30 login('test@canonical.com')
31 self.factory = LaunchpadObjectFactory()
33 mirrorset = getUtility(IDistributionMirrorSet)32 mirrorset = getUtility(IDistributionMirrorSet)
34 self.cdimage_mirror = getUtility(IDistributionMirrorSet).getByName(33 self.cdimage_mirror = getUtility(IDistributionMirrorSet).getByName(
35 'releases-mirror')34 'releases-mirror')
@@ -132,15 +131,6 @@
132 self.archive_mirror.getOverallFreshness(),131 self.archive_mirror.getOverallFreshness(),
133 MirrorFreshness.TWODAYSBEHIND)132 MirrorFreshness.TWODAYSBEHIND)
134133
135 def _create_probe_record(self, mirror):
136 log_file = StringIO()
137 log_file.write("Fake probe, nothing useful here.")
138 log_file.seek(0)
139 library_alias = getUtility(ILibraryFileAliasSet).create(
140 name='foo', size=len(log_file.getvalue()),
141 file=log_file, contentType='text/plain')
142 proberecord = mirror.newProbeRecord(library_alias)
143
144 def test_disabling_mirror_and_notifying_owner(self):134 def test_disabling_mirror_and_notifying_owner(self):
145 login('karl@canonical.com')135 login('karl@canonical.com')
146136
@@ -148,7 +138,7 @@
148 # If a mirror has been probed only once, the owner will always be138 # If a mirror has been probed only once, the owner will always be
149 # notified when it's disabled --it doesn't matter whether it was139 # notified when it's disabled --it doesn't matter whether it was
150 # previously enabled or disabled.140 # previously enabled or disabled.
151 self._create_probe_record(mirror)141 self.factory.makeMirrorProbeRecord(mirror)
152 self.failUnless(mirror.enabled)142 self.failUnless(mirror.enabled)
153 log = 'Got a 404 on http://foo/baz'143 log = 'Got a 404 on http://foo/baz'
154 mirror.disable(notify_owner=True, log=log)144 mirror.disable(notify_owner=True, log=log)
@@ -166,7 +156,7 @@
166156
167 # For mirrors that have been probed more than once, we'll only notify157 # For mirrors that have been probed more than once, we'll only notify
168 # the owner if the mirror was previously enabled.158 # the owner if the mirror was previously enabled.
169 self._create_probe_record(mirror)159 self.factory.makeMirrorProbeRecord(mirror)
170 mirror.enabled = True160 mirror.enabled = True
171 mirror.disable(notify_owner=True, log=log)161 mirror.disable(notify_owner=True, log=log)
172 # A notification was sent to the owner and other to the mirror admins.162 # A notification was sent to the owner and other to the mirror admins.
173163
=== modified file 'lib/lp/registry/tests/test_personset.py'
--- lib/lp/registry/tests/test_personset.py 2010-03-11 20:54:36 +0000
+++ lib/lp/registry/tests/test_personset.py 2010-04-05 20:44:34 +0000
@@ -99,23 +99,19 @@
99 # Person in question.99 # Person in question.
100100
101 # Create a testing `Account` and a testing `Person` directly,101 # Create a testing `Account` and a testing `Person` directly,
102 # linked. However the `Account` email is not linked to the102 # linked.
103 # `Person`.
104 testing_account = self.factory.makeAccount(103 testing_account = self.factory.makeAccount(
105 self.displayname, email=self.email_address)104 self.displayname, email=self.email_address)
106 testing_person = removeSecurityProxy(105 testing_person = removeSecurityProxy(
107 testing_account).createPerson(self.rationale)106 testing_account).createPerson(self.rationale)
108 self.assertIs(None, testing_account.preferredemail.person)107 self.assertEqual(
109 self.assertIs(None, testing_person.preferredemail)108 testing_person, testing_account.preferredemail.person)
110109
110 # Since there's an existing Person for the given email address,
111 # IPersonSet.ensurePerson() will just return it.
111 ensured_person = self.person_set.ensurePerson(112 ensured_person = self.person_set.ensurePerson(
112 self.email_address, self.displayname, self.rationale)113 self.email_address, self.displayname, self.rationale)
113114 self.assertEqual(testing_person, ensured_person)
114 # The existing Person was retrieved and the Account
115 # 'preferredemail' is also bound to the existing Person.
116 self.assertEquals(testing_person.id, ensured_person.id)
117 self.assertEquals(testing_account.preferredemail.id,
118 ensured_person.preferredemail.id)
119115
120116
121class TestPersonSetMerge(TestCaseWithFactory):117class TestPersonSetMerge(TestCaseWithFactory):
122118
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2010-03-26 21:03:30 +0000
+++ lib/lp/testing/factory.py 2010-04-05 20:44:34 +0000
@@ -1992,8 +1992,22 @@
1992 team_list = self.makeMailingList(team, owner)1992 team_list = self.makeMailingList(team, owner)
1993 return team, team_list1993 return team, team_list
19941994
1995 def makeMirrorProbeRecord(self, mirror):
1996 """Create a probe record for a mirror of a distribution."""
1997 log_file = StringIO()
1998 log_file.write("Fake probe, nothing useful here.")
1999 log_file.seek(0)
2000
2001 library_alias = getUtility(ILibraryFileAliasSet).create(
2002 name='foo', size=len(log_file.getvalue()),
2003 file=log_file, contentType='text/plain')
2004
2005 proberecord = mirror.newProbeRecord(library_alias)
2006 return proberecord
2007
1995 def makeMirror(self, distribution, displayname, country=None,2008 def makeMirror(self, distribution, displayname, country=None,
1996 http_url=None, ftp_url=None, rsync_url=None):2009 http_url=None, ftp_url=None, rsync_url=None,
2010 official_candidate=False):
1997 """Create a mirror for the distribution."""2011 """Create a mirror for the distribution."""
1998 # If no URL is specified create an HTTP URL.2012 # If no URL is specified create an HTTP URL.
1999 if http_url is None and ftp_url is None and rsync_url is None:2013 if http_url is None and ftp_url is None and rsync_url is None:
@@ -2012,7 +2026,7 @@
2012 http_base_url=http_url,2026 http_base_url=http_url,
2013 ftp_base_url=ftp_url,2027 ftp_base_url=ftp_url,
2014 rsync_base_url=rsync_url,2028 rsync_base_url=rsync_url,
2015 official_candidate=False)2029 official_candidate=official_candidate)
2016 return mirror2030 return mirror
20172031
2018 def makeUniqueRFC822MsgId(self):2032 def makeUniqueRFC822MsgId(self):
20192033
=== modified file 'lib/lp/testopenid/browser/server.py'
--- lib/lp/testopenid/browser/server.py 2010-02-24 12:52:08 +0000
+++ lib/lp/testopenid/browser/server.py 2010-04-05 20:44:34 +0000
@@ -6,7 +6,7 @@
6__all__ = [6__all__ = [
7 'PersistentIdentityView',7 'PersistentIdentityView',
8 'TestOpenIDApplicationNavigation',8 'TestOpenIDApplicationNavigation',
9 'TestOpenIDIndexView'9 'TestOpenIDIndexView',
10 'TestOpenIDLoginView',10 'TestOpenIDLoginView',
11 'TestOpenIDRootUrlData',11 'TestOpenIDRootUrlData',
12 'TestOpenIDView',12 'TestOpenIDView',
@@ -21,6 +21,7 @@
21from zope.security.proxy import isinstance as zisinstance21from zope.security.proxy import isinstance as zisinstance
22from zope.session.interfaces import ISession22from zope.session.interfaces import ISession
2323
24from openid import oidutil
24from openid.server.server import CheckIDRequest, Server25from openid.server.server import CheckIDRequest, Server
25from openid.store.memstore import MemoryStore26from openid.store.memstore import MemoryStore
2627
@@ -47,6 +48,10 @@
47openid_store = MemoryStore()48openid_store = MemoryStore()
4849
4950
51# Shut up noisy OpenID library
52oidutil.log = lambda message, level=0: None
53
54
50class TestOpenIDRootUrlData:55class TestOpenIDRootUrlData:
51 """`ICanonicalUrlData` for the test OpenID provider."""56 """`ICanonicalUrlData` for the test OpenID provider."""
5257
5358
=== modified file 'utilities/sourcedeps.conf'
--- utilities/sourcedeps.conf 2010-03-26 17:53:41 +0000
+++ utilities/sourcedeps.conf 2010-04-05 20:44:34 +0000
@@ -15,5 +15,4 @@
15subunit lp:~launchpad-pqm/subunit/trunk;revno=6115subunit lp:~launchpad-pqm/subunit/trunk;revno=61
16subvertpy lp:~launchpad-pqm/subvertpy/trunk;revno=204016subvertpy lp:~launchpad-pqm/subvertpy/trunk;revno=2040
17testresources lp:~launchpad-pqm/testresources/dev;revno=1617testresources lp:~launchpad-pqm/testresources/dev;revno=16
18canonical-identity-provider lp:~launchpad-pqm/canonical-identity-provider/trunk;revno=8903 optional
19shipit lp:~launchpad-pqm/shipit/trunk;revno=8903 optional18shipit lp:~launchpad-pqm/shipit/trunk;revno=8903 optional

Subscribers

People subscribed via source and target branches

to status/vote changes: