/** @var CF_Connection */
protected $conn; // Swift connection handle
- protected $connStarted = 0; // integer UNIX timestamp
- protected $connException; // CloudFiles exception
+ protected $sessionStarted = 0; // integer UNIX timestamp
+
+ /** @var CloudFilesException */
+ protected $connException;
+ protected $connErrorTime = 0; // UNIX timestamp
+
+ /** @var BagOStuff */
+ protected $srvCache;
/** @var ProcessCacheLRU */
protected $connContainerCache; // container object cache
* - levels : the number of hash levels (and digits)
* - repeat : hash subdirectories are prefixed with all the
* parent hash directory names (e.g. "a/ab/abc")
+ * - cacheAuthInfo : Whether to cache authentication tokens in APC/XCache.
+ * This is probably insecure in shared hosting environments.
*/
public function __construct( array $config ) {
parent::__construct( $config );
: false;
$this->swiftCDNExpiry = isset( $config['swiftCDNExpiry'] )
? $config['swiftCDNExpiry']
- : 3600; // hour
+ : 12*3600; // 12 hours is safe (tokens last 24 hours per http://docs.openstack.org)
$this->swiftCDNPurgable = isset( $config['swiftCDNPurgable'] )
? $config['swiftCDNPurgable']
: true;
- // Cache container info to mask latency
+ // Cache container information to mask latency
$this->memCache = wfGetMainCache();
// Process cache for container info
$this->connContainerCache = new ProcessCacheLRU( 300 );
+ // Cache auth token information to avoid RTTs
+ if ( !empty( $config['cacheAuthInfo'] ) ) {
+ try { // look for APC, XCache, WinCache, ect...
+ $this->srvCache = ObjectCache::newAccelerator( array() );
+ } catch ( Exception $e ) {}
+ }
+ $this->srvCache = $this->srvCache ? $this->srvCache : new EmptyBagOStuff();
}
/**
* @return null
*/
protected function resolveContainerPath( $container, $relStoragePath ) {
- if ( strlen( urlencode( $relStoragePath ) ) > 1024 ) {
+ if ( !mb_check_encoding( $relStoragePath, 'UTF-8' ) ) { // mb_string required by CF
+ return null; // not UTF-8, makes it hard to use CF and the swift HTTP API
+ } elseif ( strlen( urlencode( $relStoragePath ) ) > 1024 ) {
return null; // too long for Swift
}
return $relStoragePath;
if ( isset( $obj->metadata['Sha1base36'] ) ) {
return true; // nothing to do
}
+ wfProfileIn( __METHOD__ );
$status = Status::newGood();
$scopeLockS = $this->getScopedFileLocks( array( $path ), LockManager::LOCK_UW, $status );
if ( $status->isOK() ) {
if ( $hash !== false ) {
$obj->metadata['Sha1base36'] = $hash;
$obj->sync_metadata(); // save to Swift
+ wfProfileOut( __METHOD__ );
return true; // success
}
}
}
$obj->metadata['Sha1base36'] = false;
+ wfProfileOut( __METHOD__ );
return false; // failed
}
// See function "create_container_table" in common/db.py.
// If a directory is not "greater" than the last one,
// then it was already listed by the calling iterator.
- if ( $objectDir > $lastDir ) {
+ if ( strcmp( $objectDir, $lastDir ) > 0 ) {
$pDir = $objectDir;
do { // add dir and all its parent dirs
$dirs[] = "{$pDir}/";
$pDir = $this->getParentDir( $pDir );
} while ( $pDir !== false // sanity
- && $pDir > $lastDir // not done already
+ && strcmp( $pDir, $lastDir ) > 0 // not done already
&& strlen( $pDir ) > strlen( $dir ) // within $dir
);
}
}
/**
- * Get a connection to the Swift proxy
+ * Get an authenticated connection handle to the Swift proxy
*
* @return CF_Connection|bool False on failure
* @throws CloudFilesException
*/
protected function getConnection() {
- if ( $this->connException instanceof Exception ) {
- throw $this->connException; // failed last attempt
+ if ( $this->connException instanceof CloudFilesException ) {
+ if ( ( time() - $this->connErrorTime ) < 60 ) {
+ throw $this->connException; // failed last attempt; don't bother
+ } else { // actually retry this time
+ $this->connException = null;
+ $this->connErrorTime = 0;
+ }
}
// Session keys expire after a while, so we renew them periodically
- if ( $this->conn && ( time() - $this->connStarted ) > $this->authTTL ) {
- $this->conn->close(); // close active cURL connections
- $this->conn = null;
- }
+ $reAuth = ( ( time() - $this->sessionStarted ) > $this->authTTL );
// Authenticate with proxy and get a session key...
- if ( !$this->conn ) {
- $this->connStarted = 0;
+ if ( !$this->conn || $reAuth ) {
+ $this->sessionStarted = 0;
$this->connContainerCache->clear();
- try {
- $this->auth->authenticate();
- $this->conn = new CF_Connection( $this->auth );
- $this->connStarted = time();
- } catch ( CloudFilesException $e ) {
- $this->connException = $e; // don't keep re-trying
- throw $e; // throw it back
+ $cacheKey = $this->getCredsCacheKey( $this->auth->username );
+ $creds = $this->srvCache->get( $cacheKey ); // credentials
+ if ( is_array( $creds ) ) { // cache hit
+ $this->auth->load_cached_credentials(
+ $creds['auth_token'], $creds['storage_url'], $creds['cdnm_url'] );
+ $this->sessionStarted = time() - ceil( $this->authTTL/2 ); // skew for worst case
+ } else { // cache miss
+ try {
+ $this->auth->authenticate();
+ $creds = $this->auth->export_credentials();
+ $this->srvCache->add( $cacheKey, $creds, ceil( $this->authTTL/2 ) ); // cache
+ $this->sessionStarted = time();
+ } catch ( CloudFilesException $e ) {
+ $this->connException = $e; // don't keep re-trying
+ $this->connErrorTime = time();
+ throw $e; // throw it back
+ }
+ }
+ if ( $this->conn ) { // re-authorizing?
+ $this->conn->close(); // close active cURL handles in CF_Http object
}
+ $this->conn = new CF_Connection( $this->auth );
}
return $this->conn;
}
+ /**
+ * Get the cache key for a container
+ *
+ * @param $username string
+ * @return string
+ */
+ private function getCredsCacheKey( $username ) {
+ return wfMemcKey( 'backend', $this->getName(), 'usercreds', $username );
+ }
+
/**
* @see FileBackendStore::doClearCache()
*/