Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
79.63% covered (warning)
79.63%
43 / 54
81.25% covered (warning)
81.25%
13 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 1
ServerInfo
79.63% covered (warning)
79.63%
43 / 54
81.25% covered (warning)
81.25%
13 / 16
40.66
0.00% covered (danger)
0.00%
0 / 1
 addServer
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getServerMaxLag
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getServerDriver
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getServerType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getServerName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getServerInfo
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getServerCount
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasServerIndex
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getLagTimes
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 getServerInfoStrict
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
5.07
 getStreamingReplicaIndexes
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 hasStreamingReplicaServers
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 reconfigureServers
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 normalizeServerMaps
80.00% covered (warning)
80.00%
12 / 15
0.00% covered (danger)
0.00%
0 / 1
6.29
 getPrimaryServerName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasReplicaServers
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace Wikimedia\Rdbms;
4
5use InvalidArgumentException;
6use UnexpectedValueException;
7
8/**
9 * Information about an individual database host
10 *
11 * @internal
12 * @ingroup Database
13 */
14class ServerInfo {
15    /**
16     * Default 'maxLag' when unspecified
17     * @internal Only for use within LoadBalancer/LoadMonitor
18     */
19    public const MAX_LAG_DEFAULT = 6;
20
21    public const WRITER_INDEX = 0;
22
23    /** @var array[] Map of (server index => server config array) */
24    private $servers;
25
26    public function addServer( $i, $server ) {
27        $this->servers[$i] = $server;
28    }
29
30    public function getServerMaxLag( $i ) {
31        return $this->servers[$i]['max lag'] ?? self::MAX_LAG_DEFAULT;
32    }
33
34    public function getServerDriver( $i ) {
35        return $this->servers[$i]['driver'] ?? null;
36    }
37
38    public function getServerType( $i ) {
39        return $this->servers[$i]['type'] ?? 'unknown';
40    }
41
42    public function getServerName( $i ): string {
43        return $this->servers[$i]['serverName'] ?? 'localhost';
44    }
45
46    public function getServerInfo( $i ) {
47        return $this->servers[$i] ?? false;
48    }
49
50    public function getServerCount() {
51        return count( $this->servers );
52    }
53
54    public function hasServerIndex( $i ) {
55        return isset( $this->servers[$i] );
56    }
57
58    public function getLagTimes() {
59        $knownLagTimes = []; // map of (server index => 0 seconds)
60        $indexesWithLag = [];
61        foreach ( $this->servers as $i => $server ) {
62            if ( empty( $server['is static'] ) ) {
63                $indexesWithLag[] = $i; // DB server might have replication lag
64            } else {
65                $knownLagTimes[$i] = 0; // DB server is a non-replicating and read-only archive
66            }
67        }
68
69        return [ $indexesWithLag, $knownLagTimes ];
70    }
71
72    /**
73     * @param int $i Server index
74     * @param string|null $field Server index field [optional]
75     * @return mixed
76     * @throws InvalidArgumentException
77     */
78    public function getServerInfoStrict( $i, $field = null ) {
79        if ( !isset( $this->servers[$i] ) || !is_array( $this->servers[$i] ) ) {
80            throw new InvalidArgumentException( "No server with index '$i'" );
81        }
82
83        if ( $field !== null ) {
84            if ( !array_key_exists( $field, $this->servers[$i] ) ) {
85                throw new InvalidArgumentException( "No field '$field' in server index '$i'" );
86            }
87
88            return $this->servers[$i][$field];
89        }
90
91        return $this->servers[$i];
92    }
93
94    /**
95     * @return int[] List of replica server indexes
96     */
97    public function getStreamingReplicaIndexes() {
98        $indexes = [];
99        foreach ( $this->servers as $i => $server ) {
100            if ( $i !== self::WRITER_INDEX && empty( $server['is static'] ) ) {
101                $indexes[] = $i;
102            }
103        }
104
105        return $indexes;
106    }
107
108    public function hasStreamingReplicaServers() {
109        return (bool)$this->getStreamingReplicaIndexes();
110    }
111
112    public function reconfigureServers( $paramServers ) {
113        $newIndexBySrvName = [];
114        $this->normalizeServerMaps( $paramServers, $newIndexBySrvName );
115
116        // Map of (existing server index => corresponding index in new config or null)
117        $newIndexByServerIndex = [];
118        // Remove servers that no longer exist in the new config and preserve those that
119        // still exist, even if they switched replication roles (e.g. primary/secondary).
120        // Note that if the primary server is depooled and a replica server is promoted
121        // to primary, then DB_PRIMARY handles will fail with server index errors. Note
122        // that if the primary server swaps roles with a replica server, then write queries
123        // to DB_PRIMARY handles will fail with read-only errors.
124        foreach ( $this->servers as $i => $server ) {
125            $srvName = $this->getServerName( $i );
126            // Since pooling or depooling of servers causes the remaining servers to be
127            // assigned different indexes, find the corresponding index by server name.
128            // Also, note that the primary can be reconfigured as a replica (moved from
129            // the writer index) and vice versa (moved to the writer index).
130            $newIndex = $newIndexByServerIndex[$i] = $newIndexBySrvName[$srvName] ?? null;
131            if ( $newIndex === null ) {
132                unset( $this->servers[$i] );
133            }
134        }
135
136        return $newIndexByServerIndex;
137    }
138
139    public function normalizeServerMaps( array $servers, array &$indexBySrvName = null ) {
140        if ( !$servers ) {
141            throw new InvalidArgumentException( 'Missing or empty "servers" parameter' );
142        }
143
144        $listKey = -1;
145        $indexBySrvName = [];
146        foreach ( $servers as $i => $server ) {
147            if ( ++$listKey !== $i ) {
148                throw new UnexpectedValueException( 'List expected for "servers" parameter' );
149            }
150            $srvName = $server['serverName'] ?? $server['host'] ?? '';
151            $srvName = ( $srvName !== '' ) ? $srvName : 'localhost';
152            if ( isset( $indexBySrvName[$srvName] ) ) {
153                // Duplicate server names confuse caching, logging, and reconfigure()
154                throw new UnexpectedValueException( 'Duplicate server name "' . $srvName . '"' );
155            }
156            $indexBySrvName[$srvName] = $i;
157            $servers[$i]['serverName'] = $srvName;
158            $servers[$i]['groupLoads'] ??= [];
159        }
160        return $servers;
161    }
162
163    /**
164     * @return string Name of the primary DB server of the relevant DB cluster (e.g. "db1052")
165     */
166    public function getPrimaryServerName() {
167        return $this->getServerName( self::WRITER_INDEX );
168    }
169
170    public function hasReplicaServers() {
171        return ( $this->getServerCount() > 1 );
172    }
173}