X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;ds=sidebyside;f=includes%2Fconfig%2FEtcdConfig.php;h=7020159fd7d72cfcc0a278a00b4ec265ca82e445;hb=8269ed4dfd5e4395e25945b1fa2ed391684606ed;hp=0ec21cb9b71275f6055631755dd3b92e85ad227b;hpb=426719108b86bba70e5b321e3386f40849471426;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/config/EtcdConfig.php b/includes/config/EtcdConfig.php index 0ec21cb9b7..7020159fd7 100644 --- a/includes/config/EtcdConfig.php +++ b/includes/config/EtcdConfig.php @@ -20,6 +20,7 @@ use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerInterface; +use Wikimedia\ObjectFactory; use Wikimedia\WaitConditionLoop; /** @@ -118,6 +119,11 @@ class EtcdConfig implements Config, LoggerAwareInterface { return $this->procCache['config'][$name]; } + public function getModifiedIndex() { + $this->load(); + return $this->procCache['modifiedIndex']; + } + /** * @throws ConfigException */ @@ -150,13 +156,17 @@ class EtcdConfig implements Config, LoggerAwareInterface { // refresh the cache from etcd, using a mutex to reduce stampedes... if ( $this->srvCache->lock( $key, 0, $this->baseCacheTTL ) ) { try { - list( $config, $error, $retry ) = $this->fetchAllFromEtcd(); - if ( is_array( $config ) ) { + $etcdResponse = $this->fetchAllFromEtcd(); + $error = $etcdResponse['error']; + if ( is_array( $etcdResponse['config'] ) ) { // Avoid having all servers expire cache keys at the same time $expiry = microtime( true ) + $this->baseCacheTTL; $expiry += mt_rand( 0, 1e6 ) / 1e6 * $this->skewCacheTTL; - - $data = [ 'config' => $config, 'expires' => $expiry ]; + $data = [ + 'config' => $etcdResponse['config'], + 'expires' => $expiry, + 'modifiedIndex' => $etcdResponse['modifiedIndex'] + ]; $this->srvCache->set( $key, $data, BagOStuff::TTL_INDEFINITE ); $this->logger->info( "Refreshed stale etcd configuration cache." ); @@ -164,7 +174,7 @@ class EtcdConfig implements Config, LoggerAwareInterface { return WaitConditionLoop::CONDITION_REACHED; } else { $this->logger->error( "Failed to fetch configuration: $error" ); - if ( !$retry ) { + if ( !$etcdResponse['retry'] ) { // Fail fast since the error is likely to keep happening return WaitConditionLoop::CONDITION_FAILED; } @@ -194,9 +204,10 @@ class EtcdConfig implements Config, LoggerAwareInterface { } /** - * @return array (config array or null, error string, allow retries) + * @return array (containing the keys config, error, retry, modifiedIndex) */ public function fetchAllFromEtcd() { + // TODO: inject DnsSrvDiscoverer in order to be able to test this method $dsd = new DnsSrvDiscoverer( $this->host ); $servers = $dsd->getServers(); if ( !$servers ) { @@ -208,8 +219,8 @@ class EtcdConfig implements Config, LoggerAwareInterface { $server = $dsd->pickServer( $servers ); $host = IP::combineHostAndPort( $server['target'], $server['port'] ); // Try to load the config from this particular server - list( $config, $error, $retry ) = $this->fetchAllFromEtcdServer( $host ); - if ( is_array( $config ) || !$retry ) { + $response = $this->fetchAllFromEtcdServer( $host ); + if ( is_array( $response['config'] ) || $response['retry'] ) { break; } @@ -217,12 +228,12 @@ class EtcdConfig implements Config, LoggerAwareInterface { $servers = $dsd->removeServer( $server, $servers ); } while ( $servers ); - return [ $config, $error, $retry ]; + return $response; } /** * @param string $address Host and port - * @return array (config array or null, error string, whether to allow retries) + * @return array (containing the keys config, error, retry, modifiedIndex) */ protected function fetchAllFromEtcdServer( $address ) { // Retrieve all the values under the MediaWiki config directory @@ -232,19 +243,21 @@ class EtcdConfig implements Config, LoggerAwareInterface { 'headers' => [ 'content-type' => 'application/json' ] ] ); + $response = [ 'config' => null, 'error' => null, 'retry' => false, 'modifiedIndex' => 0 ]; + static $terminalCodes = [ 404 => true ]; if ( $rcode < 200 || $rcode > 399 ) { - return [ - null, - strlen( $rerr ) ? $rerr : "HTTP $rcode ($rdesc)", - empty( $terminalCodes[$rcode] ) - ]; + $response['error'] = strlen( $rerr ) ? $rerr : "HTTP $rcode ($rdesc)"; + $response['retry'] = empty( $terminalCodes[$rcode] ); + return $response; } + try { - return [ $this->parseResponse( $rbody ), null, false ]; + $parsedResponse = $this->parseResponse( $rbody ); } catch ( EtcdConfigParseError $e ) { - return [ null, $e->getMessage(), false ]; + $parsedResponse = [ 'error' => $e->getMessage() ]; } + return array_merge( $response, $parsedResponse ); } /** @@ -263,8 +276,8 @@ class EtcdConfig implements Config, LoggerAwareInterface { "Unexpected JSON response: Missing or invalid node at top level." ); } $config = []; - $this->parseDirectory( '', $info['node'], $config ); - return $config; + $lastModifiedIndex = $this->parseDirectory( '', $info['node'], $config ); + return [ 'modifiedIndex' => $lastModifiedIndex, 'config' => $config ]; } /** @@ -274,8 +287,10 @@ class EtcdConfig implements Config, LoggerAwareInterface { * @param string $dirName The relative directory name * @param array $dirNode The decoded directory node * @param array &$config The output array + * @return int lastModifiedIndex The maximum last modified index across all keys in the directory */ protected function parseDirectory( $dirName, $dirNode, &$config ) { + $lastModifiedIndex = 0; if ( !isset( $dirNode['nodes'] ) ) { throw new EtcdConfigParseError( "Unexpected JSON response in dir '$dirName'; missing 'nodes' list." ); @@ -289,16 +304,19 @@ class EtcdConfig implements Config, LoggerAwareInterface { $baseName = basename( $node['key'] ); $fullName = $dirName === '' ? $baseName : "$dirName/$baseName"; if ( !empty( $node['dir'] ) ) { - $this->parseDirectory( $fullName, $node, $config ); + $lastModifiedIndex = max( + $this->parseDirectory( $fullName, $node, $config ), + $lastModifiedIndex ); } else { $value = $this->unserialize( $node['value'] ); if ( !is_array( $value ) || !array_key_exists( 'val', $value ) ) { throw new EtcdConfigParseError( "Failed to parse value for '$fullName'." ); } - + $lastModifiedIndex = max( $node['modifiedIndex'], $lastModifiedIndex ); $config[$fullName] = $value['val']; } } + return $lastModifiedIndex; } /**