Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
54.35% |
25 / 46 |
|
45.45% |
5 / 11 |
CRAP | |
0.00% |
0 / 1 |
CentralAuthDatabaseManager | |
54.35% |
25 / 46 |
|
45.45% |
5 / 11 |
62.96 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
getLoadBalancer | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
assertNotReadOnly | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
isReadOnly | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
getCentralReadOnlyReason | |
75.00% |
6 / 8 |
|
0.00% |
0 / 1 |
3.14 | |||
waitForReplication | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getCentralPrimaryDB | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
getCentralReplicaDB | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
getCentralDB | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
3 | |||
getLocalDB | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
20 | |||
centralLBHasRecentPrimaryChanges | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\CentralAuth; |
4 | |
5 | use InvalidArgumentException; |
6 | use MediaWiki\Config\ServiceOptions; |
7 | use MediaWiki\WikiMap\WikiMap; |
8 | use ReadOnlyError; |
9 | use Wikimedia\Rdbms\IDatabase; |
10 | use Wikimedia\Rdbms\ILoadBalancer; |
11 | use Wikimedia\Rdbms\IReadableDatabase; |
12 | use Wikimedia\Rdbms\LBFactory; |
13 | use Wikimedia\Rdbms\ReadOnlyMode; |
14 | |
15 | /** |
16 | * Service providing access to the CentralAuth internal database. |
17 | * |
18 | * @since 1.37 |
19 | * @author Taavi "Majavah" Väänänen <hi@taavi.wtf> |
20 | */ |
21 | class CentralAuthDatabaseManager { |
22 | /** @internal Only public for service wiring use */ |
23 | public const CONSTRUCTOR_OPTIONS = [ |
24 | 'CentralAuthDatabase', |
25 | 'CentralAuthReadOnly', |
26 | ]; |
27 | |
28 | /** @var ServiceOptions */ |
29 | private $options; |
30 | |
31 | /** @var LBFactory */ |
32 | private $lbFactory; |
33 | |
34 | /** @var ReadOnlyMode */ |
35 | private $readOnlyMode; |
36 | |
37 | /** |
38 | * @param ServiceOptions $options |
39 | * @param LBFactory $lbFactory |
40 | * @param ReadOnlyMode $readOnlyMode |
41 | */ |
42 | public function __construct( ServiceOptions $options, LBFactory $lbFactory, ReadOnlyMode $readOnlyMode ) { |
43 | $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS ); |
44 | $this->options = $options; |
45 | $this->lbFactory = $lbFactory; |
46 | $this->readOnlyMode = $readOnlyMode; |
47 | } |
48 | |
49 | /** |
50 | * Returns a database load balancer that can be used to access the shared CentralAuth database. |
51 | * @return ILoadBalancer |
52 | */ |
53 | public function getLoadBalancer(): ILoadBalancer { |
54 | $database = $this->options->get( 'CentralAuthDatabase' ); |
55 | return $this->lbFactory->getMainLB( $database ); |
56 | } |
57 | |
58 | /** |
59 | * Throw an exception if the database is read-only. |
60 | * |
61 | * @throws CentralAuthReadOnlyError |
62 | */ |
63 | public function assertNotReadOnly() { |
64 | if ( $this->readOnlyMode->isReadOnly() ) { |
65 | // ReadOnlyError gets its reason text from the global ReadOnlyMode |
66 | throw new ReadOnlyError; |
67 | } |
68 | $reason = $this->getCentralReadOnlyReason(); |
69 | if ( $reason ) { |
70 | throw new CentralAuthReadOnlyError( $reason ); |
71 | } |
72 | } |
73 | |
74 | /** |
75 | * Determine if either the local or the shared CentralAuth database is |
76 | * read only. This should determine whether assertNotReadOnly() would |
77 | * throw. |
78 | * |
79 | * @return bool |
80 | */ |
81 | public function isReadOnly(): bool { |
82 | return $this->readOnlyMode->isReadOnly() |
83 | || ( $this->getCentralReadOnlyReason() !== false ); |
84 | } |
85 | |
86 | /** |
87 | * Return the reason why either the shared CentralAuth database is read |
88 | * only, false otherwise. |
89 | * |
90 | * @return bool|string |
91 | */ |
92 | private function getCentralReadOnlyReason() { |
93 | $configReason = $this->options->get( 'CentralAuthReadOnly' ); |
94 | if ( $configReason === true ) { |
95 | return '(no reason given)'; |
96 | } elseif ( $configReason ) { |
97 | return $configReason; |
98 | } |
99 | |
100 | $database = $this->options->get( 'CentralAuthDatabase' ); |
101 | $lb = $this->getLoadBalancer(); |
102 | |
103 | return $lb->getReadOnlyReason( $database ); |
104 | } |
105 | |
106 | /** |
107 | * Wait for the CentralAuth DB replicas to catch up |
108 | */ |
109 | public function waitForReplication(): void { |
110 | $this->lbFactory->waitForReplication( [ 'domain' => $this->options->get( 'CentralAuthDatabase' ) ] ); |
111 | } |
112 | |
113 | /** |
114 | * @return IDatabase a connection to the CentralAuth database primary. |
115 | */ |
116 | public function getCentralPrimaryDB(): IDatabase { |
117 | $this->assertNotReadOnly(); |
118 | return $this->lbFactory->getPrimaryDatabase( |
119 | $this->options->get( 'CentralAuthDatabase' ) |
120 | ); |
121 | } |
122 | |
123 | /** |
124 | * @return IReadableDatabase a connection to a CentralAuth database replica |
125 | */ |
126 | public function getCentralReplicaDB(): IReadableDatabase { |
127 | return $this->lbFactory->getReplicaDatabase( |
128 | $this->options->get( 'CentralAuthDatabase' ) |
129 | ); |
130 | } |
131 | |
132 | /** |
133 | * Gets a database connection to the CentralAuth database. |
134 | * |
135 | * @param int $index DB_PRIMARY or DB_REPLICA |
136 | * @deprecated use {@link ::getCentralPrimaryDB} |
137 | * or {@link ::getCentralReplicaDB} instead |
138 | * |
139 | * @return IDatabase |
140 | * @throws CentralAuthReadOnlyError |
141 | * @throws InvalidArgumentException |
142 | */ |
143 | public function getCentralDB( int $index ): IDatabase { |
144 | if ( $index === DB_PRIMARY ) { |
145 | return $this->getCentralPrimaryDB(); |
146 | } |
147 | |
148 | if ( $index === DB_REPLICA ) { |
149 | return $this->getLoadBalancer() |
150 | ->getConnection( |
151 | DB_REPLICA, |
152 | [], |
153 | $this->options->get( 'CentralAuthDatabase' ) |
154 | ); |
155 | } |
156 | |
157 | throw new InvalidArgumentException( "Unknown index $index, expected DB_PRIMARY or DB_REPLICA" ); |
158 | } |
159 | |
160 | /** |
161 | * Gets a database connection to the local database based on a wikiId |
162 | * |
163 | * @param int $index DB_PRIMARY or DB_REPLICA |
164 | * @param string $wikiId |
165 | * |
166 | * @todo Split to two for IReadableDatabase support or drop entirely |
167 | * |
168 | * @return IDatabase |
169 | * @throws CentralAuthReadOnlyError |
170 | * @throws InvalidArgumentException |
171 | */ |
172 | public function getLocalDB( int $index, string $wikiId ): IDatabase { |
173 | if ( $index !== DB_PRIMARY && $index !== DB_REPLICA ) { |
174 | throw new InvalidArgumentException( "Unknown index $index, expected DB_PRIMARY or DB_REPLICA" ); |
175 | } |
176 | |
177 | if ( WikiMap::isCurrentWikiId( $wikiId ) ) { |
178 | $wikiId = false; |
179 | } |
180 | |
181 | return $this->lbFactory->getMainLB( $wikiId ) |
182 | ->getConnection( $index, [], $wikiId ); |
183 | } |
184 | |
185 | /** |
186 | * Check hasOrMadeRecentPrimaryChanges() on the CentralAuth load balancer |
187 | * |
188 | * @return bool |
189 | */ |
190 | public function centralLBHasRecentPrimaryChanges() { |
191 | return $this->getLoadBalancer()->hasOrMadeRecentPrimaryChanges(); |
192 | } |
193 | } |