78 $this->host = $params[
'host'];
79 $this->protocol = $params[
'protocol'];
80 $this->directory = trim( $params[
'directory'],
'/' );
81 $this->encoding = $params[
'encoding'];
82 $this->skewCacheTTL = $params[
'skewTTL'];
83 $this->baseCacheTTL = max( $params[
'cacheTTL'] - $this->skewCacheTTL, 0 );
84 $this->timeout = $params[
'timeout'];
86 if ( !isset( $params[
'cache'] ) ) {
88 } elseif ( $params[
'cache'] instanceof
BagOStuff ) {
89 $this->srvCache = $params[
'cache'];
91 $this->srvCache = ObjectFactory::getObjectFromSpec( $params[
'cache'] );
94 $this->logger =
new Psr\Log\NullLogger();
96 'connTimeout' => $this->timeout,
97 'reqTimeout' => $this->timeout,
98 'logger' => $this->logger
132 if ( $this->procCache !==
null ) {
136 $now = microtime(
true );
137 $key = $this->srvCache->makeGlobalKey(
146 $loop =
new WaitConditionLoop(
147 function () use ( $key, $now, &$data, &$error ) {
149 $data = $this->srvCache->get( $key );
150 if ( is_array( $data ) && $data[
'expires'] > $now ) {
151 $this->logger->debug(
"Found up-to-date etcd configuration cache." );
153 return WaitConditionLoop::CONDITION_REACHED;
158 if ( $this->srvCache->lock( $key, 0, $this->baseCacheTTL ) ) {
160 $etcdResponse = $this->fetchAllFromEtcd();
161 $error = $etcdResponse[
'error'];
162 if ( is_array( $etcdResponse[
'config'] ) ) {
164 $expiry = microtime( true ) + $this->baseCacheTTL;
166 $expiry += mt_rand( 0, 1e6 ) / 1e6 * $this->skewCacheTTL;
168 'config' => $etcdResponse[
'config'],
169 'expires' => $expiry,
170 'modifiedIndex' => $etcdResponse[
'modifiedIndex']
172 $this->srvCache->set( $key, $data, BagOStuff::TTL_INDEFINITE );
174 $this->logger->info(
"Refreshed stale etcd configuration cache." );
176 return WaitConditionLoop::CONDITION_REACHED;
178 $this->logger->error(
"Failed to fetch configuration: $error" );
179 if ( !$etcdResponse[
'retry'] ) {
181 return WaitConditionLoop::CONDITION_FAILED;
185 $this->srvCache->unlock( $key );
189 if ( is_array( $data ) ) {
190 $this->logger->info(
"Using stale etcd configuration cache." );
192 return WaitConditionLoop::CONDITION_REACHED;
195 return WaitConditionLoop::CONDITION_CONTINUE;
200 if ( $loop->invoke() !== WaitConditionLoop::CONDITION_REACHED ) {
202 throw new ConfigException(
"Failed to load configuration from etcd: $error" );
205 $this->procCache = $data;
214 $servers = $dsd->getServers();
216 return $this->fetchAllFromEtcdServer( $this->host );
221 $server = $dsd->pickServer( $servers );
222 $host = IPUtils::combineHostAndPort( $server[
'target'], $server[
'port'] );
224 $response = $this->fetchAllFromEtcdServer( $host );
225 if ( is_array( $response[
'config'] ) || $response[
'retry'] ) {
230 $servers = $dsd->removeServer( $server, $servers );
231 }
while ( $servers );
242 list( $rcode, $rdesc, , $rbody, $rerr ) = $this->http->run( [
244 'url' =>
"{$this->protocol}://{$address}/v2/keys/{$this->directory}/?recursive=true",
245 'headers' => [
'content-type' =>
'application/json' ]
248 $response = [
'config' =>
null,
'error' =>
null,
'retry' =>
false,
'modifiedIndex' => 0 ];
250 static $terminalCodes = [ 404 =>
true ];
251 if ( $rcode < 200 || $rcode > 399 ) {
252 $response[
'error'] = strlen( $rerr ??
'' ) ? $rerr :
"HTTP $rcode ($rdesc)";
253 $response[
'retry'] = empty( $terminalCodes[$rcode] );
258 $parsedResponse = $this->parseResponse( $rbody );
260 $parsedResponse = [
'error' => $e->getMessage() ];
262 return array_merge( $response, $parsedResponse );
295 $lastModifiedIndex = 0;
296 if ( !isset( $dirNode[
'nodes'] ) ) {
298 "Unexpected JSON response in dir '$dirName'; missing 'nodes' list." );
300 if ( !is_array( $dirNode[
'nodes'] ) ) {
302 "Unexpected JSON response in dir '$dirName'; 'nodes' is not an array." );
305 foreach ( $dirNode[
'nodes'] as $node ) {
306 '@phan-var array $node';
307 $baseName = basename( $node[
'key'] );
308 $fullName = $dirName ===
'' ? $baseName :
"$dirName/$baseName";
309 if ( !empty( $node[
'dir'] ) ) {
310 $lastModifiedIndex = max(
311 $this->parseDirectory( $fullName, $node, $config ),
312 $lastModifiedIndex );
315 if ( !is_array( $value ) || !array_key_exists(
'val', $value ) ) {
318 $lastModifiedIndex = max( $node[
'modifiedIndex'], $lastModifiedIndex );
319 $config[$fullName] = $value[
'val'];
322 return $lastModifiedIndex;