/** @var int UNIX timestamp */
protected $authErrorTimestamp = null;
+ /** @var bool Whether the server is an Ceph RGW */
+ protected $isRGW = false;
+
/**
* @see FileBackendStore::__construct()
* Additional $config params include:
}
protected function doGetFileStat( array $params ) {
- list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
- if ( $srcRel === null ) {
- return false; // invalid storage path
- }
-
- $auth = $this->getAuthentication();
- if ( !$auth ) {
- return null;
- }
-
- // (a) Check the container
- $cstat = $this->getContainerStat( $srcCont, true );
- if ( $cstat === false ) {
- return false; // ok, nothing to do
- } elseif ( !is_array( $cstat ) ) {
- return null;
- }
-
- // (b) Check the file
- list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( array(
- 'method' => 'HEAD',
- 'url' => $this->storageUrl( $auth, $srcCont, $srcRel ),
- 'headers' => $this->authTokenHeaders( $auth ) + $this->headersFromParams( $params )
- ) );
- if ( $rcode === 200 || $rcode === 204 ) {
- // Update the object if it is missing some headers
- $rhdrs = $this->addMissingMetadata( $rhdrs, $params['src'] );
- // Fetch all of the custom metadata headers
- $metadata = array();
- foreach ( $rhdrs as $name => $value ) {
- if ( strpos( $name, 'x-object-meta-' ) === 0 ) {
- $metadata[substr( $name, strlen( 'x-object-meta-' ) )] = $value;
- }
- }
- // Fetch all of the custom raw HTTP headers
- $headers = $this->sanitizeHdrs( array( 'headers' => $rhdrs ) );
- $stat = array(
- // Convert various random Swift dates to TS_MW
- 'mtime' => $this->convertSwiftDate( $rhdrs['last-modified'], TS_MW ),
- // Empty objects actually return no content-length header in Ceph
- 'size' => isset( $rhdrs['content-length'] ) ? (int)$rhdrs['content-length'] : 0,
- 'sha1' => $rhdrs['x-object-meta-sha1base36'],
- 'md5' => ctype_xdigit( $rhdrs['etag'] ) ? $rhdrs['etag'] : null,
- 'xattr' => array( 'metadata' => $metadata, 'headers' => $headers )
- );
- } elseif ( $rcode === 404 ) {
- $stat = false;
- } else {
- $stat = null;
- $this->onError( null, __METHOD__, $params, $rerr, $rcode, $rdesc );
- }
+ $params = array( 'srcs' => array( $params['src'] ), 'concurrency' => 1 ) + $params;
+ unset( $params['src'] );
+ $stats = $this->doGetFileStatMulti( $params );
- return $stat;
+ return reset( $stats );
}
/**
return $objHdrs; // nothing to do
}
- $section = new ProfileSection( __METHOD__ );
+ $section = new ProfileSection( __METHOD__ . '-' . $this->name );
trigger_error( "$path was not stored with SHA-1 metadata.", E_USER_WARNING );
$auth = $this->getAuthentication();
$prefix = ( $dir == '' ) ? null : "{$dir}/";
$status = $this->objectListing( $fullCont, 'names', 1, null, $prefix );
if ( $status->isOk() ) {
- return ( count( $status->value ) );
+ return ( count( $status->value ) ) > 0;
}
return null; // error
}
$stat = array(
// Convert various random Swift dates to TS_MW
- 'mtime' => $this->convertSwiftDate( $object->last_modified, TS_MW ),
- 'size' => (int)$object->bytes,
- 'md5' => ctype_xdigit( $object->hash ) ? $object->hash : null,
+ 'mtime' => $this->convertSwiftDate( $object->last_modified, TS_MW ),
+ 'size' => (int)$object->bytes,
+ // Note: manifiest ETags are not an MD5 of the file
+ 'md5' => ctype_xdigit( $object->hash ) ? $object->hash : null,
'latest' => false // eventually consistent
);
$names[] = array( $object->name, $stat );
* @return array|bool|null False on 404, null on failure
*/
protected function getContainerStat( $container, $bypassCache = false ) {
+ $section = new ProfileSection( __METHOD__ . '-' . $this->name );
+
if ( $bypassCache ) { // purge cache
$this->containerStatCache->clear( $container );
} elseif ( !$this->containerStatCache->has( $container, 'stat' ) ) {
return null;
}
+ wfProfileIn( __METHOD__ . "-{$this->name}-miss" );
list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( array(
'method' => 'HEAD',
'url' => $this->storageUrl( $auth, $container ),
'headers' => $this->authTokenHeaders( $auth )
) );
+ wfProfileOut( __METHOD__ . "-{$this->name}-miss" );
if ( $rcode === 204 ) {
$stat = array(
}
}
+ protected function doGetFileStatMulti( array $params ) {
+ $stats = array();
+
+ $auth = $this->getAuthentication();
+
+ $reqs = array();
+ foreach ( $params['srcs'] as $path ) {
+ list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $path );
+ if ( $srcRel === null ) {
+ $stats[$path] = false;
+ continue; // invalid storage path
+ } elseif ( !$auth ) {
+ $stats[$path] = null;
+ continue;
+ }
+
+ // (a) Check the container
+ $cstat = $this->getContainerStat( $srcCont );
+ if ( $cstat === false ) {
+ $stats[$path] = false;
+ continue; // ok, nothing to do
+ } elseif ( !is_array( $cstat ) ) {
+ $stats[$path] = null;
+ continue;
+ }
+
+ $reqs[$path] = array(
+ 'method' => 'HEAD',
+ 'url' => $this->storageUrl( $auth, $srcCont, $srcRel ),
+ 'headers' => $this->authTokenHeaders( $auth ) + $this->headersFromParams( $params )
+ );
+ }
+
+ $opts = array( 'maxConnsPerHost' => $params['concurrency'] );
+ $reqs = $this->http->runMulti( $reqs, $opts );
+
+ foreach ( $params['srcs'] as $path ) {
+ if ( array_key_exists( $path, $stats ) ) {
+ continue; // some sort of failure above
+ }
+ // (b) Check the file
+ list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $reqs[$path]['response'];
+ if ( $rcode === 200 || $rcode === 204 ) {
+ // Update the object if it is missing some headers
+ $rhdrs = $this->addMissingMetadata( $rhdrs, $path );
+ // Fetch all of the custom metadata headers
+ $metadata = array();
+ foreach ( $rhdrs as $name => $value ) {
+ if ( strpos( $name, 'x-object-meta-' ) === 0 ) {
+ $metadata[substr( $name, strlen( 'x-object-meta-' ) )] = $value;
+ }
+ }
+ // Fetch all of the custom raw HTTP headers
+ $headers = $this->sanitizeHdrs( array( 'headers' => $rhdrs ) );
+ $stat = array(
+ // Convert various random Swift dates to TS_MW
+ 'mtime' => $this->convertSwiftDate( $rhdrs['last-modified'], TS_MW ),
+ // Empty objects actually return no content-length header in Ceph
+ 'size' => isset( $rhdrs['content-length'] ) ? (int)$rhdrs['content-length'] : 0,
+ 'sha1' => $rhdrs[ 'x-object-meta-sha1base36'],
+ // Note: manifiest ETags are not an MD5 of the file
+ 'md5' => ctype_xdigit( $rhdrs['etag'] ) ? $rhdrs['etag'] : null,
+ 'xattr' => array( 'metadata' => $metadata, 'headers' => $headers )
+ );
+ if ( $this->isRGW ) {
+ $stat['latest'] = true; // strong consistency
+ }
+ } elseif ( $rcode === 404 ) {
+ $stat = false;
+ } else {
+ $stat = null;
+ $this->onError( null, __METHOD__, $params, $rerr, $rcode, $rdesc );
+ }
+ $stats[$path] = $stat;
+ }
+
+ return $stats;
+ }
+
/**
* @return array|null Credential map
*/
return null;
}
}
+ // Ceph RGW does not use <account> in URLs (OpenStack Swift uses "/v1/<account>")
+ if ( substr( $this->authCreds['storage_url'], -3 ) === '/v1' ) {
+ $this->isRGW = true; // take advantage of strong consistency
+ }
}
return $this->authCreds;