
multiple API verstions for each service. This included updating the directory structure for the tests and moving the Common files to a Common namespace. Implements blueprint multiple-api-versions Change-Id: I9f9dfc4ef8f4172243519772a9af86dd92690fcf
1515 lines
50 KiB
PHP
1515 lines
50 KiB
PHP
<?php
|
|
/* ============================================================================
|
|
(c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
============================================================================ */
|
|
/**
|
|
* Contains the stream wrapper for `swift://` URLs.
|
|
*/
|
|
|
|
namespace OpenStack\ObjectStore\v1\Resource;
|
|
|
|
use \OpenStack\Bootstrap;
|
|
use \OpenStack\ObjectStore\v1\ObjectStorage;
|
|
|
|
/**
|
|
* Provides stream wrapping for Swift.
|
|
*
|
|
* This provides a full stream wrapper to expose `swift://` URLs to the
|
|
* PHP stream system.
|
|
*
|
|
* Swift streams provide authenticated and priviledged access to the
|
|
* swift data store. These URLs are not generally used for granting
|
|
* unauthenticated access to files (which can be done using the HTTP
|
|
* stream wrapper -- no need for swift-specific logic).
|
|
*
|
|
* URL Structure
|
|
*
|
|
* This takes URLs of the following form:
|
|
*
|
|
* `swift://CONTAINER/FILE`
|
|
*
|
|
* Example:
|
|
*
|
|
* `swift://public/example.txt`
|
|
*
|
|
* The example above would access the `public` container and attempt to
|
|
* retrieve the file named `example.txt`.
|
|
*
|
|
* Slashes are legal in Swift filenames, so a pathlike URL can be constructed
|
|
* like this:
|
|
*
|
|
* `swift://public/path/like/file/name.txt`
|
|
*
|
|
* The above would attempt to find a file in object storage named
|
|
* `path/like/file/name.txt`.
|
|
*
|
|
* A note on UTF-8 and URLs: PHP does not yet natively support many UTF-8
|
|
* characters in URLs. Thus, you ought to rawurlencode() your container name
|
|
* and object name (path) if there is any possibility that it will contain
|
|
* UTF-8 characters.
|
|
*
|
|
* Locking
|
|
*
|
|
* This library does not support locking (e.g. flock()). This is because the
|
|
* OpenStack Object Storage implementation does not support locking. But there
|
|
* are a few related things you should keep in mind:
|
|
*
|
|
* - Working with a stream is essentially working with a COPY OF a remote file.
|
|
* Local locking is not an issue.
|
|
* - If you open two streams for the same object, you will be working with
|
|
* TWO COPIES of the object. This can, of course, lead to nasty race
|
|
* conditions if each copy is modified.
|
|
*
|
|
* Usage
|
|
*
|
|
* The principle purpose of this wrapper is to make it easy to access and
|
|
* manipulate objects on a remote object storage instance. Managing
|
|
* containers is a secondary concern (and can often better be managed using
|
|
* the OpenStack API). Consequently, almost all actions done through the
|
|
* stream wrapper are focused on objects, not containers, servers, etc.
|
|
*
|
|
* Retrieving an Existing Object
|
|
*
|
|
* Retrieving an object is done by opening a file handle to that object.
|
|
*
|
|
* Writing an Object
|
|
*
|
|
* Nothing is written to the remote storage until the file is closed. This
|
|
* keeps network traffic at a minimum, and respects the more-or-less stateless
|
|
* nature of ObjectStorage.
|
|
*
|
|
* USING FILE/STREAM RESOURCES
|
|
*
|
|
* In general, you should access files like this:
|
|
*
|
|
* <?php
|
|
* \OpenStack\Bootstrap::useStreamWrappers();
|
|
* // Set up the context.
|
|
* $context = stream_context_create(
|
|
* array('swift' => array(
|
|
* 'username' => USERNAME,
|
|
* 'password' => PASSWORD,
|
|
* 'tenantid' => TENANT_ID,
|
|
* 'tenantname' => TENANT_NAME, // Optional instead of tenantid.
|
|
* 'endpoint' => AUTH_ENDPOINT_URL,
|
|
* )
|
|
* )
|
|
* );
|
|
* // Open the file.
|
|
* $handle = fopen('swift://mycontainer/myobject.txt', 'r+', false, $context);
|
|
*
|
|
* // You can get the entire file, or use fread() to loop through the file.
|
|
* $contents = stream_get_contents($handle);
|
|
*
|
|
* fclose($handle);
|
|
* ?>
|
|
*
|
|
* Remarks
|
|
* - file_get_contents() works fine.
|
|
* - You can write to a stream, too. Nothing is pushed to the server until
|
|
* fflush() or fclose() is called.
|
|
* - Mode strings (w, r, w+, r+, c, c+, a, a+, x, x+) all work, though certain
|
|
* constraints are slightly relaxed to accomodate efficient local buffering.
|
|
* - Files are buffered locally.
|
|
*
|
|
* USING FILE-LEVEL FUNCTIONS
|
|
*
|
|
* PHP provides a number of file-level functions that stream wrappers can
|
|
* optionally support. Here are a few such functions:
|
|
*
|
|
* - file_exists()
|
|
* - is_readable()
|
|
* - stat()
|
|
* - filesize()
|
|
* - fileperms()
|
|
*
|
|
* The OpenStack stream wrapper provides support for these file-level functions.
|
|
* But there are a few things you should know:
|
|
*
|
|
* - Each call to one of these functions generates at least one request. It may
|
|
* be as many as three:
|
|
* * An auth request
|
|
* * A request for the container (to get container permissions)
|
|
* * A request for the object
|
|
* - IMPORTANT: Unlike the fopen()/fclose()... functions NONE of these functions
|
|
* retrieves the body of the file. If you are working with large files, using
|
|
* these functions may be orders of magnitude faster than using fopen(), etc.
|
|
* (The crucial detail: These kick off a HEAD request, will fopen() does a
|
|
* GET request).
|
|
* - You must use Bootstrap::setConfiguration() to pass in all of the values you
|
|
* would normally pass into a stream context:
|
|
* * endpoint
|
|
* * username
|
|
* * password
|
|
* - Most of the information from this family of calls can also be obtained using
|
|
* fstat(). If you were going to open a stream anyway, you might as well use
|
|
* fopen()/fstat().
|
|
* - stat() and fstat() fake the permissions and ownership as follows:
|
|
* * uid/gid are always sset to the current user. This basically assumes that if
|
|
* the current user can access the object, the current user has ownership over
|
|
* the file. As the OpenStack ACL system developers, this may change.
|
|
* * Mode is faked. Swift uses ACLs, not UNIX mode strings. So we fake the string:
|
|
* - 770: The ACL has the object marked as private.
|
|
* - 775: The ACL has the object marked as public.
|
|
* - ACLs are actually set on the container, so every file in a public container
|
|
* will return 775.
|
|
* - stat/fstat provide only one timestamp. Swift only tracks mtime, so mtime, atime,
|
|
* and ctime are all set to the last modified time.
|
|
*
|
|
* DIRECTORIES
|
|
*
|
|
* OpenStack Swift does not really have directories. Rather, it allows
|
|
* characters such as '/' to be used to designate namespaces on object
|
|
* names. (For simplicity, this library uses only '/' as a separator).
|
|
*
|
|
* This allows for simulated directory listings. Requesting
|
|
* `scandir('swift://foo/bar/')` is really a request to "find all of the items
|
|
* in the 'foo' container whose names start with 'bar/'".
|
|
*
|
|
* Because of this...
|
|
*
|
|
* - Directory reading functions like scandir(), opendir(), readdir()
|
|
* and so forth are supported.
|
|
* - Functions to create or remove directories (mkdir() and rmdir()) are
|
|
* meaningless, and thus not supported.
|
|
*
|
|
* Swift still has support for "directory markers" (special zero-byte files
|
|
* that act like directories). However, since there are no standards for how
|
|
* said markers ought to be created, they are not supported by the stream
|
|
* wrapper.
|
|
*
|
|
* As usual, the underlying \OpenStack\ObjectStore\v1\Resource\Container class
|
|
* supports the full range of Swift features.
|
|
*
|
|
* SUPPORTED CONTEXT PARAMETERS
|
|
*
|
|
* This section details paramters that can be passed either
|
|
* through a stream context or through
|
|
* \OpenStack\Bootstrap\setConfiguration().
|
|
*
|
|
* PHP functions that do not allow you to pass a context may still be supported
|
|
* here IF you have set options using Bootstrap::setConfiguration().
|
|
*
|
|
* You are required to pass in authentication information. This
|
|
* comes in one of three forms:
|
|
*
|
|
* -# User login: username, password, tenantid, endpoint
|
|
* -# Existing (valid) token: token, swift_endpoint
|
|
*
|
|
* As of 1.0.0-beta6, you may use `tenantname` instead of `tenantid`.
|
|
*
|
|
* The third method (token) can be used when the application has already
|
|
* authenticated. In this case, a token has been generated and assigned
|
|
* to an user and tenant.
|
|
*
|
|
* The following parameters may be set either in the stream context
|
|
* or through OpenStack\Bootstrap::setConfiguration():
|
|
*
|
|
* - token: An auth token. If this is supplied, authentication is skipped and
|
|
* this token is used. NOTE: You MUST set swift_endpoint if using this
|
|
* option.
|
|
* - swift_endpoint: The URL to the swift instance. This is only necessary if
|
|
* 'token' is set. Otherwise it is ignored.
|
|
* - username: A username. MUST be accompanied by 'password' and 'tenantid' (or 'tenantname').
|
|
* - password: A password. MUST be accompanied by 'username' and 'tenantid' (or 'tenantname').
|
|
* - endpoint: The URL to the authentication endpoint. Necessary if you are not
|
|
* using a 'token' and 'swift_endpoint'.
|
|
* - use_swift_auth: If this is set to true, it will force the app to use
|
|
* the deprecated swiftAuth instead of IdentityService authentication.
|
|
* In general, you should avoid using this.
|
|
* - content_type: This is effective only when writing files. It will
|
|
* set the Content-Type of the file during upload.
|
|
* - tenantid: The tenant ID for the services you will use. (A user may
|
|
* have multiple tenancies associated.)
|
|
* - tenantname: The tenant name for the services you will use. You may use
|
|
* this in lieu of tenant ID.
|
|
*
|
|
* @see http://us3.php.net/manual/en/class.streamwrapper.php
|
|
*
|
|
* @todo The service catalog should be cached in the context like the token so that
|
|
* it can be retrieved later.
|
|
*/
|
|
class StreamWrapper
|
|
{
|
|
const DEFAULT_SCHEME = 'swift';
|
|
|
|
/**
|
|
* Cache of auth token -> service catalog.
|
|
*
|
|
* This will eventually be replaced by a better system, but for a system of
|
|
* moderate complexity, many, many file operations may be run during the
|
|
* course of a request. Caching the catalog can prevent numerous calls
|
|
* to identity services.
|
|
*/
|
|
protected static $serviceCatalogCache = array();
|
|
|
|
/**
|
|
* The stream context.
|
|
*
|
|
* This is set automatically when the stream wrapper is created by
|
|
* PHP. Note that it is not set through a constructor.
|
|
*/
|
|
public $context;
|
|
protected $contextArray = array();
|
|
|
|
protected $schemeName = self::DEFAULT_SCHEME;
|
|
protected $authToken;
|
|
|
|
// File flags. These should probably be replaced by O_ const's at some point.
|
|
protected $isBinary = false;
|
|
protected $isText = true;
|
|
protected $isWriting = false;
|
|
protected $isReading = false;
|
|
protected $isTruncating = false;
|
|
protected $isAppending = false;
|
|
protected $noOverwrite = false;
|
|
protected $createIfNotFound = true;
|
|
|
|
/**
|
|
* If this is true, no data is ever sent to the remote server.
|
|
*/
|
|
protected $isNeverDirty = false;
|
|
|
|
protected $triggerErrors = false;
|
|
|
|
/**
|
|
* Indicate whether the local differs from remote.
|
|
*
|
|
* When the file is modified in such a way that
|
|
* it needs to be written remotely, the isDirty flag
|
|
* is set to true.
|
|
*/
|
|
protected $isDirty = false;
|
|
|
|
/**
|
|
* Object storage instance.
|
|
*/
|
|
protected $store;
|
|
|
|
/**
|
|
* The Container.
|
|
*/
|
|
protected $container;
|
|
|
|
/**
|
|
* The Object.
|
|
*/
|
|
protected $obj;
|
|
|
|
/**
|
|
* The IO stream for the Object.
|
|
*/
|
|
protected $objStream;
|
|
|
|
/**
|
|
* Directory listing.
|
|
*
|
|
* Used for directory methods.
|
|
*/
|
|
protected $dirListing = array();
|
|
protected $dirIndex = 0;
|
|
protected $dirPrefix = '';
|
|
|
|
/**
|
|
* Close a directory.
|
|
*
|
|
* This closes a directory handle, freeing up the resources.
|
|
*
|
|
* <?php
|
|
*
|
|
* // Assuming a valid context in $cxt...
|
|
*
|
|
* // Get the container as if it were a directory.
|
|
* $dir = opendir('swift://mycontainer', $cxt);
|
|
*
|
|
* // Do something with $dir
|
|
*
|
|
* closedir($dir);
|
|
* ?>
|
|
*
|
|
* NB: Some versions of PHP 5.3 don't clear all buffers when
|
|
* closing, and the handle can occasionally remain accessible for
|
|
* some period of time.
|
|
*/
|
|
public function dir_closedir()
|
|
{
|
|
$this->dirIndex = 0;
|
|
$this->dirListing = array();
|
|
|
|
//syslog(LOG_WARNING, "CLOSEDIR called.");
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Open a directory for reading.
|
|
*
|
|
* <?php
|
|
*
|
|
* // Assuming a valid context in $cxt...
|
|
*
|
|
* // Get the container as if it were a directory.
|
|
* $dir = opendir('swift://mycontainer', $cxt);
|
|
*
|
|
* // Do something with $dir
|
|
*
|
|
* closedir($dir);
|
|
* ?>
|
|
*
|
|
* See opendir() and scandir().
|
|
*
|
|
* @param string $path The URL to open.
|
|
* @param int $options Unused.
|
|
*
|
|
* @return boolean true if the directory is opened, false otherwise.
|
|
*/
|
|
public function dir_opendir($path, $options)
|
|
{
|
|
$url = $this->parseUrl($path);
|
|
|
|
if (empty($url['host'])) {
|
|
trigger_error('Container name is required.' , E_USER_WARNING);
|
|
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
$this->initializeObjectStorage();
|
|
$container = $this->store->container($url['host']);
|
|
|
|
if (empty($url['path'])) {
|
|
$this->dirPrefix = '';
|
|
} else {
|
|
$this->dirPrefix = $url['path'];
|
|
}
|
|
|
|
$sep = '/';
|
|
|
|
$this->dirListing = $container->objectsWithPrefix($this->dirPrefix, $sep);
|
|
} catch (\OpenStack\Common\Exception $e) {
|
|
trigger_error('Directory could not be opened: ' . $e->getMessage(), E_USER_WARNING);
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Read an entry from the directory.
|
|
*
|
|
* This gets a single line from the directory.
|
|
*
|
|
* <?php
|
|
*
|
|
* // Assuming a valid context in $cxt...
|
|
*
|
|
* // Get the container as if it were a directory.
|
|
* $dir = opendir('swift://mycontainer', $cxt);
|
|
*
|
|
* while (($entry = readdir($dir)) !== false) {
|
|
* print $entry . PHP_EOL;
|
|
* }
|
|
*
|
|
* closedir($dir);
|
|
* ?>
|
|
*
|
|
* @return string The name of the resource or false when the directory has no
|
|
* more entries.
|
|
*/
|
|
public function dir_readdir()
|
|
{
|
|
// If we are at the end of the listing, return false.
|
|
if (count($this->dirListing) <= $this->dirIndex) {
|
|
return false;
|
|
}
|
|
|
|
$curr = $this->dirListing[$this->dirIndex];
|
|
$this->dirIndex++;
|
|
|
|
if ($curr instanceof \OpenStack\ObjectStore\v1\Resource\Subdir) {
|
|
$fullpath = $curr->path();
|
|
} else {
|
|
$fullpath = $curr->name();
|
|
}
|
|
|
|
if (!empty($this->dirPrefix)) {
|
|
$len = strlen($this->dirPrefix);
|
|
$fullpath = substr($fullpath, $len);
|
|
}
|
|
|
|
return $fullpath;
|
|
}
|
|
|
|
/**
|
|
* Rewind to the beginning of the listing.
|
|
*
|
|
* This repositions the read pointer at the first entry in the directory.
|
|
*
|
|
* <?php
|
|
*
|
|
* // Assuming a valid context in $cxt...
|
|
*
|
|
* // Get the container as if it were a directory.
|
|
* $dir = opendir('swift://mycontainer', $cxt);
|
|
*
|
|
* while (($entry = readdir($dir)) !== false) {
|
|
* print $entry . PHP_EOL;
|
|
* }
|
|
*
|
|
* rewinddir($dir);
|
|
*
|
|
* $first = readdir($dir);
|
|
*
|
|
* closedir($dir);
|
|
* ?>
|
|
*/
|
|
public function dir_rewinddir()
|
|
{
|
|
$this->dirIndex = 0;
|
|
}
|
|
|
|
/*
|
|
public function mkdir($path, $mode, $options)
|
|
{
|
|
}
|
|
|
|
public function rmdir($path, $options)
|
|
{
|
|
}
|
|
*/
|
|
|
|
/**
|
|
* Rename a swift object.
|
|
*
|
|
* This works by copying the object (metadata) and
|
|
* then removing the original version.
|
|
*
|
|
* This DOES support cross-container renaming.
|
|
*
|
|
* @see Container::copy().
|
|
*
|
|
* <?php
|
|
* Bootstrap::setConfiguration(array(
|
|
* 'tenantname' => 'foo@example.com',
|
|
* // 'tenantid' => '1234', // You can use this instead of tenantname
|
|
* 'username' => 'foobar',
|
|
* 'password' => 'baz',
|
|
* 'endpoint' => 'https://auth.example.com',
|
|
* ));
|
|
*
|
|
* $from = 'swift://containerOne/file.txt';
|
|
* $to = 'swift://containerTwo/file.txt';
|
|
*
|
|
* // Rename can also take a context as a third param.
|
|
* rename($from, $to);
|
|
*
|
|
* ?>
|
|
*
|
|
* @param string $path_from A swift URL that exists on the remote.
|
|
* @param string $path_to A swift URL to another path.
|
|
*
|
|
* @return boolean true on success, false otherwise.
|
|
*/
|
|
public function rename($path_from, $path_to)
|
|
{
|
|
$this->initializeObjectStorage();
|
|
$src = $this->parseUrl($path_from);
|
|
$dest = $this->parseUrl($path_to);
|
|
|
|
if ($src['scheme'] != $dest['scheme']) {
|
|
trigger_error("I'm too stupid to copy across protocols.", E_USER_WARNING);
|
|
}
|
|
|
|
if ( empty($src['host']) || empty($src['path'])
|
|
|| empty($dest['host']) || empty($dest['path'])) {
|
|
trigger_error('Container and path are required for both source and destination URLs.', E_USER_WARNING);
|
|
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
$container = $this->store->container($src['host']);
|
|
|
|
$object = $container->remoteObject($src['path']);
|
|
|
|
$ret = $container->copy($object, $dest['path'], $dest['host']);
|
|
if ($ret) {
|
|
return $container->delete($src['path']);
|
|
}
|
|
} catch (\OpenStack\Common\Exception $e) {
|
|
trigger_error('Rename was not completed: ' . $e->getMessage(), E_USER_WARNING);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
public function copy($path_from, $path_to)
|
|
{
|
|
throw new \Exception("UNDOCUMENTED.");
|
|
}
|
|
*/
|
|
|
|
/**
|
|
* Cast stream into a lower-level stream.
|
|
*
|
|
* This is used for stream_select() and perhaps others.Because it exposes
|
|
* the lower-level buffer objects, this function can have unexpected
|
|
* side effects.
|
|
*
|
|
* @return resource this returns the underlying stream.
|
|
*/
|
|
public function stream_cast($cast_as)
|
|
{
|
|
return $this->objStream;
|
|
}
|
|
|
|
/**
|
|
* Close a stream, writing if necessary.
|
|
*
|
|
* <?php
|
|
*
|
|
* // Assuming $cxt has a valid context.
|
|
*
|
|
* $file = fopen('swift://container/file.txt', 'r', false, $cxt);
|
|
*
|
|
* fclose($file);
|
|
*
|
|
* ?>
|
|
*
|
|
* This will close the present stream. Importantly,
|
|
* this will also write to the remote object storage if
|
|
* any changes have been made locally.
|
|
*
|
|
* @see stream_open().
|
|
*/
|
|
public function stream_close()
|
|
{
|
|
try {
|
|
$this->writeRemote();
|
|
} catch (\OpenStack\Common\Exception $e) {
|
|
trigger_error('Error while closing: ' . $e->getMessage(), E_USER_NOTICE);
|
|
|
|
return false;
|
|
}
|
|
|
|
// Force-clear the memory hogs.
|
|
unset($this->obj);
|
|
fclose($this->objStream);
|
|
}
|
|
|
|
/**
|
|
* Check whether the stream has reached its end.
|
|
*
|
|
* This checks whether the stream has reached the
|
|
* end of the object's contents.
|
|
*
|
|
* Called when `feof()` is called on a stream.
|
|
*
|
|
* @see stream_seek().
|
|
*
|
|
* @return boolean true if it has reached the end, false otherwise.
|
|
*/
|
|
public function stream_eof()
|
|
{
|
|
return feof($this->objStream);
|
|
}
|
|
|
|
/**
|
|
* Initiate saving data on the remote object storage.
|
|
*
|
|
* If the local copy of this object has been modified,
|
|
* it is written remotely.
|
|
*
|
|
* Called when `fflush()` is called on a stream.
|
|
*/
|
|
public function stream_flush()
|
|
{
|
|
try {
|
|
$this->writeRemote();
|
|
} catch (\OpenStack\Common\Exception $e) {
|
|
syslog(LOG_WARNING, $e);
|
|
trigger_error('Error while flushing: ' . $e->getMessage(), E_USER_NOTICE);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Write data to the remote object storage.
|
|
*
|
|
* Internally, this is used by flush and close.
|
|
*/
|
|
protected function writeRemote()
|
|
{
|
|
$contentType = $this->cxt('content_type');
|
|
if (!empty($contentType)) {
|
|
$this->obj->setContentType($contentType);
|
|
}
|
|
|
|
// Skip debug streams.
|
|
if ($this->isNeverDirty) {
|
|
return;
|
|
}
|
|
|
|
// Stream is dirty and needs a write.
|
|
if ($this->isDirty) {
|
|
$position = ftell($this->objStream);
|
|
|
|
rewind($this->objStream);
|
|
$this->container->save($this->obj, $this->objStream);
|
|
|
|
fseek($this->objStream, SEEK_SET, $position);
|
|
|
|
}
|
|
$this->isDirty = false;
|
|
}
|
|
|
|
/*
|
|
* Locking is currently unsupported.
|
|
*
|
|
* There is no remote support for locking a
|
|
* file.
|
|
public function stream_lock($operation)
|
|
{
|
|
}
|
|
*/
|
|
|
|
/**
|
|
* Open a stream resource.
|
|
*
|
|
* This opens a given stream resource and prepares it for reading or writing.
|
|
*
|
|
* <?php
|
|
* $cxt = stream_context_create(array(
|
|
* 'username' => 'foobar',
|
|
* 'tenantid' => '987654321',
|
|
* 'password' => 'eieio',
|
|
* 'endpoint' => 'https://auth.example.com',
|
|
* ));
|
|
* ?>
|
|
*
|
|
* $file = fopen('swift://myContainer/myObject.csv', 'rb', false, $cxt);
|
|
* while ($bytes = fread($file, 8192)) {
|
|
* print $bytes;
|
|
* }
|
|
* fclose($file);
|
|
* ?>
|
|
*
|
|
* If a file is opened in write mode, its contents will be retrieved from the
|
|
* remote storage and cached locally for manipulation. If the file is opened
|
|
* in a write-only mode, the contents will be created locally and then pushed
|
|
* remotely as necessary.
|
|
*
|
|
* During this operation, the remote host may need to be contacted for
|
|
* authentication as well as for file retrieval.
|
|
*
|
|
* @param string $path The URL to the resource. See the class description for
|
|
* details, but typically this expects URLs in the form `swift://CONTAINER/OBJECT`.
|
|
* @param string $mode Any of the documented mode strings. See fopen(). For
|
|
* any file that is in a writing mode, the file will be saved remotely on
|
|
* flush or close. Note that there is an extra mode: 'nope'. It acts like
|
|
* 'c+' except that it is never written remotely. This is useful for
|
|
* debugging the stream locally without sending that data to object storage.
|
|
* (Note that data is still fetched -- just never written.)
|
|
* @param int $options An OR'd list of options. Only STREAM_REPORT_ERRORS has
|
|
* any meaning to this wrapper, as it is not working with local files.
|
|
* @param string $opened_path This is not used, as this wrapper deals only
|
|
* with remote objects.
|
|
*/
|
|
public function stream_open($path, $mode, $options, &$opened_path)
|
|
{
|
|
//syslog(LOG_WARNING, "I received this URL: " . $path);
|
|
|
|
// If STREAM_REPORT_ERRORS is set, we are responsible for
|
|
// all error handling while opening the stream.
|
|
if (STREAM_REPORT_ERRORS & $options) {
|
|
$this->triggerErrors = true;
|
|
}
|
|
|
|
// Using the mode string, set the internal mode.
|
|
$this->setMode($mode);
|
|
|
|
// Parse the URL.
|
|
$url = $this->parseUrl($path);
|
|
//syslog(LOG_WARNING, print_r($url, true));
|
|
|
|
// Container name is required.
|
|
if (empty($url['host'])) {
|
|
//if ($this->triggerErrors) {
|
|
trigger_error('No container name was supplied in ' . $path, E_USER_WARNING);
|
|
//}
|
|
return false;
|
|
}
|
|
|
|
// A path to an object is required.
|
|
if (empty($url['path'])) {
|
|
//if ($this->triggerErrors) {
|
|
trigger_error('No object name was supplied in ' . $path, E_USER_WARNING);
|
|
//}
|
|
return false;
|
|
}
|
|
|
|
// We set this because it is possible to bind another scheme name,
|
|
// and we need to know that name if it's changed.
|
|
//$this->schemeName = isset($url['scheme']) ? $url['scheme'] : self::DEFAULT_SCHEME;
|
|
if (isset($url['scheme'])) {
|
|
$this->schemeName == $url['scheme'];
|
|
}
|
|
|
|
// Now we find out the container name. We walk a fine line here, because we don't
|
|
// create a new container, but we don't want to incur heavy network
|
|
// traffic, either. So we have to assume that we have a valid container
|
|
// until we issue our first request.
|
|
$containerName = $url['host'];
|
|
|
|
// Object name.
|
|
$objectName = $url['path'];
|
|
|
|
// XXX: We reserve the query string for passing additional params.
|
|
|
|
try {
|
|
$this->initializeObjectStorage();
|
|
} catch (\OpenStack\Common\Exception $e) {
|
|
trigger_error('Failed to init object storage: ' . $e->getMessage(), E_USER_WARNING);
|
|
|
|
return false;
|
|
}
|
|
|
|
//syslog(LOG_WARNING, "Container: " . $containerName);
|
|
|
|
// Now we need to get the container. Doing a server round-trip here gives
|
|
// us the peace of mind that we have an actual container.
|
|
// XXX: Should we make it possible to get a container blindly, without the
|
|
// server roundtrip?
|
|
try {
|
|
$this->container = $this->store->container($containerName);
|
|
} catch (\OpenStack\Common\Transport\Exception\FileNotFoundException $e) {
|
|
trigger_error('Container not found.', E_USER_WARNING);
|
|
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
// Now we fetch the file. Only under certain circumstances do we generate
|
|
// an error if the file is not found.
|
|
// FIXME: We should probably allow a context param that can be set to
|
|
// mark the file as lazily fetched.
|
|
$this->obj = $this->container->object($objectName);
|
|
$stream = $this->obj->stream();
|
|
$streamMeta = stream_get_meta_data($stream);
|
|
|
|
// Support 'x' and 'x+' modes.
|
|
if ($this->noOverwrite) {
|
|
//if ($this->triggerErrors) {
|
|
trigger_error('File exists and cannot be overwritten.', E_USER_WARNING);
|
|
//}
|
|
return false;
|
|
}
|
|
|
|
// If we need to write to it, we need a writable
|
|
// stream. Also, if we need to block reading, this
|
|
// will require creating an alternate stream.
|
|
if ($this->isWriting && ($streamMeta['mode'] == 'r' || !$this->isReading)) {
|
|
$newMode = $this->isReading ? 'rb+' : 'wb';
|
|
$tmpStream = fopen('php://temp', $newMode);
|
|
stream_copy_to_stream($stream, $tmpStream);
|
|
|
|
// Skip rewinding if we can.
|
|
if (!$this->isAppending) {
|
|
rewind($tmpStream);
|
|
}
|
|
|
|
$this->objStream = $tmpStream;
|
|
} else {
|
|
$this->objStream = $this->obj->stream();
|
|
}
|
|
|
|
// Append mode requires seeking to the end.
|
|
if ($this->isAppending) {
|
|
fseek($this->objStream, -1, SEEK_END);
|
|
}
|
|
}
|
|
|
|
// If a 404 is thrown, we need to determine whether
|
|
// or not a new file should be created.
|
|
catch (\OpenStack\Common\Transport\Exception\FileNotFoundException $nf) {
|
|
|
|
// For many modes, we just go ahead and create.
|
|
if ($this->createIfNotFound) {
|
|
$this->obj = new Object($objectName);
|
|
$this->objStream = fopen('php://temp', 'rb+');
|
|
$this->isDirty = true;
|
|
} else {
|
|
//if ($this->triggerErrors) {
|
|
trigger_error($nf->getMessage(), E_USER_WARNING);
|
|
//}
|
|
return false;
|
|
}
|
|
|
|
}
|
|
// All other exceptions are fatal.
|
|
catch (\OpenStack\Common\Exception $e) {
|
|
//if ($this->triggerErrors) {
|
|
trigger_error('Failed to fetch object: ' . $e->getMessage(), E_USER_WARNING);
|
|
//}
|
|
return false;
|
|
}
|
|
|
|
// At this point, we have a file that may be read-only. It also may be
|
|
// reading off of a socket. It will be positioned at the beginning of
|
|
// the stream.
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Read N bytes from the stream.
|
|
*
|
|
* This will read up to the requested number of bytes. Or, upon
|
|
* hitting the end of the file, it will return null.
|
|
*
|
|
* @see fread(), fgets(), and so on for examples.
|
|
*
|
|
* <?php
|
|
* $cxt = stream_context_create(array(
|
|
* 'tenantname' => 'me@example.com',
|
|
* 'username' => 'me@example.com',
|
|
* 'password' => 'secret',
|
|
* 'endpoint' => 'https://auth.example.com',
|
|
* ));
|
|
*
|
|
* $content = file_get_contents('swift://public/myfile.txt', false, $cxt);
|
|
* ?>
|
|
*
|
|
* @param int $count The number of bytes to read (usually 8192).
|
|
*
|
|
* @return string The data read.
|
|
*/
|
|
public function stream_read($count)
|
|
{
|
|
return fread($this->objStream, $count);
|
|
}
|
|
|
|
/**
|
|
* Perform a seek.
|
|
*
|
|
* This is called whenever `fseek()` or `rewind()` is called on a
|
|
* Swift stream.
|
|
*
|
|
* IMPORTANT: Unlike the PHP core, this library
|
|
* allows you to `fseek()` inside of a file opened
|
|
* in append mode ('a' or 'a+').
|
|
*/
|
|
public function stream_seek($offset, $whence)
|
|
{
|
|
$ret = fseek($this->objStream, $offset, $whence);
|
|
|
|
// fseek returns 0 for success, -1 for failure.
|
|
// We need to return true for success, false for failure.
|
|
return $ret === 0;
|
|
}
|
|
|
|
/**
|
|
* Set options on the underlying stream.
|
|
*
|
|
* The settings here do not trickle down to the network socket, which is
|
|
* left open for only a brief period of time. Instead, they impact the middle
|
|
* buffer stream, where the file is read and written to between flush/close
|
|
* operations. Thus, tuning these will not have any impact on network
|
|
* performance.
|
|
*
|
|
* See stream_set_blocking(), stream_set_timeout(), and stream_write_buffer().
|
|
*/
|
|
public function stream_set_option($option, $arg1, $arg2)
|
|
{
|
|
switch ($option) {
|
|
case STREAM_OPTION_BLOCKING:
|
|
return stream_set_blocking($this->objStream, $arg1);
|
|
case STREAM_OPTION_READ_TIMEOUT:
|
|
// XXX: Should this have any effect on the lower-level
|
|
// socket, too? Or just the buffered tmp storage?
|
|
return stream_set_timeout($this->objStream, $arg1, $arg2);
|
|
case STREAM_OPTION_WRITE_BUFFER:
|
|
return stream_set_write_buffer($this->objStream, $arg2);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Perform stat()/lstat() operations.
|
|
*
|
|
* <?php
|
|
* $file = fopen('swift://foo/bar', 'r+', false, $cxt);
|
|
* $stats = fstat($file);
|
|
* ?>
|
|
*
|
|
* To use standard `stat()` on a Swift stream, you will
|
|
* need to set account information (tenant ID, username, password,
|
|
* etc.) through \OpenStack\Bootstrap::setConfiguration().
|
|
*
|
|
* @return array The stats array.
|
|
*/
|
|
public function stream_stat()
|
|
{
|
|
$stat = fstat($this->objStream);
|
|
|
|
// FIXME: Need to calculate the length of the $objStream.
|
|
//$contentLength = $this->obj->contentLength();
|
|
$contentLength = $stat['size'];
|
|
|
|
return $this->generateStat($this->obj, $this->container, $contentLength);
|
|
}
|
|
|
|
/**
|
|
* Get the current position in the stream.
|
|
*
|
|
* @see `ftell()` and `fseek()`.
|
|
*
|
|
* @return int The current position in the stream.
|
|
*/
|
|
public function stream_tell()
|
|
{
|
|
return ftell($this->objStream);
|
|
}
|
|
|
|
/**
|
|
* Write data to stream.
|
|
*
|
|
* This writes data to the local stream buffer. Data
|
|
* is not pushed remotely until stream_close() or
|
|
* stream_flush() is called.
|
|
*
|
|
* @param string $data Data to write to the stream.
|
|
*
|
|
* @return int The number of bytes written. 0 indicates and error.
|
|
*/
|
|
public function stream_write($data)
|
|
{
|
|
$this->isDirty = true;
|
|
|
|
return fwrite($this->objStream, $data);
|
|
}
|
|
|
|
/**
|
|
* Unlink a file.
|
|
*
|
|
* This removes the remote copy of the file. Like a normal unlink operation,
|
|
* it does not destroy the (local) file handle until the file is closed.
|
|
* Therefore you can continue accessing the object locally.
|
|
*
|
|
* Note that OpenStack Swift does not draw a distinction between file objects
|
|
* and "directory" objects (where the latter is a 0-byte object). This will
|
|
* delete either one. If you are using directory markers, not that deleting
|
|
* a marker will NOT delete the contents of the "directory".
|
|
*
|
|
* You will need to use \OpenStack\Bootstrap::setConfiguration() to set the
|
|
* necessary stream configuration, since `unlink()` does not take a context.
|
|
*
|
|
* @param string $path The URL.
|
|
*
|
|
* @return boolean true if the file was deleted, false otherwise.
|
|
*/
|
|
public function unlink($path)
|
|
{
|
|
$url = $this->parseUrl($path);
|
|
|
|
// Host is required.
|
|
if (empty($url['host'])) {
|
|
trigger_error('Container name is required.', E_USER_WARNING);
|
|
|
|
return false;
|
|
}
|
|
|
|
// I suppose we could allow deleting containers,
|
|
// but that isn't really the purpose of the
|
|
// stream wrapper.
|
|
if (empty($url['path'])) {
|
|
trigger_error('Path is required.', E_USER_WARNING);
|
|
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
$this->initializeObjectStorage();
|
|
// $container = $this->store->container($url['host']);
|
|
$name = $url['host'];
|
|
$token = $this->store->token();
|
|
$endpoint_url = $this->store->url() . '/' . rawurlencode($name);
|
|
$client = $this->cxt('transport_client', null);
|
|
$container = new \OpenStack\ObjectStore\v1\Resource\Container($name, $endpoint_url, $token, $client);
|
|
|
|
return $container->delete($url['path']);
|
|
} catch (\OpenStack\Common\Exception $e) {
|
|
trigger_error('Error during unlink: ' . $e->getMessage(), E_USER_WARNING);
|
|
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* @see stream_stat().
|
|
*/
|
|
public function url_stat($path, $flags)
|
|
{
|
|
$url = $this->parseUrl($path);
|
|
|
|
if (empty($url['host']) || empty($url['path'])) {
|
|
if ($flags & STREAM_URL_STAT_QUIET) {
|
|
trigger_error('Container name (host) and path are required.', E_USER_WARNING);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
$this->initializeObjectStorage();
|
|
|
|
// Since we are throwing the $container away without really using its
|
|
// internals, we create an unchecked container. It may not actually
|
|
// exist on the server, which will cause a 404 error.
|
|
//$container = $this->store->container($url['host']);
|
|
$name = $url['host'];
|
|
$token = $this->store->token();
|
|
$endpoint_url = $this->store->url() . '/' . rawurlencode($name);
|
|
$client = $this->cxt('transport_client', null);
|
|
$container = new \OpenStack\ObjectStore\v1\Resource\Container($name, $endpoint_url, $token, $client);
|
|
$obj = $container->remoteObject($url['path']);
|
|
} catch (\OpenStack\Common\Exception $e) {
|
|
// Apparently file_exists does not set STREAM_URL_STAT_QUIET.
|
|
//if ($flags & STREAM_URL_STAT_QUIET) {
|
|
//trigger_error('Could not stat remote file: ' . $e->getMessage(), E_USER_WARNING);
|
|
//}
|
|
return false;
|
|
}
|
|
|
|
if ($flags & STREAM_URL_STAT_QUIET) {
|
|
try {
|
|
return @$this->generateStat($obj, $container, $obj->contentLength());
|
|
} catch (\OpenStack\Common\Exception $e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return $this->generateStat($obj, $container, $obj->contentLength());
|
|
}
|
|
|
|
/**
|
|
* Get the Object.
|
|
*
|
|
* This provides low-level access to the
|
|
* \OpenStack\ObjectStore\v1\ObjectStorage::Object instance in which the content
|
|
* is stored.
|
|
*
|
|
* Accessing the object's payload (Object::content()) is strongly
|
|
* discouraged, as it will modify the pointers in the stream that the
|
|
* stream wrapper is using.
|
|
*
|
|
* HOWEVER, accessing the Object's metadata properties, content type,
|
|
* and so on is okay. Changes to this data will be written on the next
|
|
* flush, provided that the file stream data has also been changed.
|
|
*
|
|
* To access this:
|
|
*
|
|
* <?php
|
|
* $handle = fopen('swift://container/test.txt', 'rb', $cxt);
|
|
* $md = stream_get_meta_data($handle);
|
|
* $obj = $md['wrapper_data']->object();
|
|
* ?>
|
|
*/
|
|
public function object()
|
|
{
|
|
return $this->obj;
|
|
}
|
|
|
|
/**
|
|
* EXPERT: Get the ObjectStorage for this wrapper.
|
|
*
|
|
* @return object \OpenStack\ObjectStorage An ObjectStorage object.
|
|
* @see object()
|
|
*/
|
|
public function objectStorage()
|
|
{
|
|
return $this->store;
|
|
}
|
|
|
|
/**
|
|
* EXPERT: Get the auth token for this wrapper.
|
|
*
|
|
* @return string A token.
|
|
* @see object()
|
|
*/
|
|
public function token()
|
|
{
|
|
return $this->store->token();
|
|
}
|
|
|
|
/**
|
|
* EXPERT: Get the service catalog (IdentityService) for this wrapper.
|
|
*
|
|
* This is only available when a file is opened via fopen().
|
|
*
|
|
* @return array A service catalog.
|
|
* @see object()
|
|
*/
|
|
public function serviceCatalog()
|
|
{
|
|
return self::$serviceCatalogCache[$this->token()];
|
|
}
|
|
|
|
/**
|
|
* Generate a reasonably accurate STAT array.
|
|
*
|
|
* Notes on mode:
|
|
* - All modes are of the (octal) form 100XXX, where
|
|
* XXX is replaced by the permission string. Thus,
|
|
* this always reports that the type is "file" (100).
|
|
* - Currently, only two permission sets are generated:
|
|
* - 770: Represents the ACL::makePrivate() perm.
|
|
* - 775: Represents the ACL::makePublic() perm.
|
|
*
|
|
* Notes on mtime/atime/ctime:
|
|
* - For whatever reason, Swift only stores one timestamp.
|
|
* We use that for mtime, atime, and ctime.
|
|
*
|
|
* Notes on size:
|
|
* - Size must be calculated externally, as it will sometimes
|
|
* be the remote's Content-Length, and it will sometimes be
|
|
* the cached stat['size'] for the underlying buffer.
|
|
*/
|
|
protected function generateStat($object, $container, $size)
|
|
{
|
|
// This is not entirely accurate. Basically, if the
|
|
// file is marked public, it gets 100775, and if
|
|
// it is private, it gets 100770.
|
|
//
|
|
// Mode is always set to file (100XXX) because there
|
|
// is no alternative that is more sensible. PHP docs
|
|
// do not recommend an alternative.
|
|
//
|
|
// octdec(100770) == 33272
|
|
// octdec(100775) == 33277
|
|
$mode = $container->acl()->isPublic() ? 33277 : 33272;
|
|
|
|
// We have to fake the UID value in order for is_readible()/is_writable()
|
|
// to work. Note that on Windows systems, stat does not look for a UID.
|
|
if (function_exists('posix_geteuid')) {
|
|
$uid = posix_geteuid();
|
|
$gid = posix_getegid();
|
|
} else {
|
|
$uid = 0;
|
|
$gid = 0;
|
|
}
|
|
|
|
if ($object instanceof \OpenStack\ObjectStore\v1\Resource\RemoteObject) {
|
|
$modTime = $object->lastModified();
|
|
} else {
|
|
$modTime = 0;
|
|
}
|
|
$values = array(
|
|
'dev' => 0,
|
|
'ino' => 0,
|
|
'mode' => $mode,
|
|
'nlink' => 0,
|
|
'uid' => $uid,
|
|
'gid' => $gid,
|
|
'rdev' => 0,
|
|
'size' => $size,
|
|
'atime' => $modTime,
|
|
'mtime' => $modTime,
|
|
'ctime' => $modTime,
|
|
'blksize' => -1,
|
|
'blocks' => -1,
|
|
);
|
|
|
|
$final = array_values($values) + $values;
|
|
|
|
return $final;
|
|
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////
|
|
// INTERNAL METHODS
|
|
// All methods beneath this line are not part of the Stream API.
|
|
///////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* Set the fopen mode.
|
|
*
|
|
* @param string $mode The mode string, e.g. `r+` or `wb`.
|
|
*
|
|
* @return \OpenStack\ObjectStore\v1\Resource\StreamWrapper $this so the method
|
|
* can be used in chaining.
|
|
*/
|
|
protected function setMode($mode)
|
|
{
|
|
$mode = strtolower($mode);
|
|
|
|
// These are largely ignored, as the remote
|
|
// object storage does not distinguish between
|
|
// text and binary files. Per the PHP recommendation
|
|
// files are treated as binary.
|
|
$this->isBinary = strpos($mode, 'b') !== false;
|
|
$this->isText = strpos($mode, 't') !== false;
|
|
|
|
// Rewrite mode to remove b or t:
|
|
$mode = preg_replace('/[bt]?/', '', $mode);
|
|
|
|
switch ($mode) {
|
|
case 'r+':
|
|
$this->isWriting = true;
|
|
case 'r':
|
|
$this->isReading = true;
|
|
$this->createIfNotFound = false;
|
|
break;
|
|
|
|
|
|
case 'w+':
|
|
$this->isReading = true;
|
|
case 'w':
|
|
$this->isTruncating = true;
|
|
$this->isWriting = true;
|
|
break;
|
|
|
|
|
|
case 'a+':
|
|
$this->isReading = true;
|
|
case 'a':
|
|
$this->isAppending = true;
|
|
$this->isWriting = true;
|
|
break;
|
|
|
|
|
|
case 'x+':
|
|
$this->isReading = true;
|
|
case 'x':
|
|
$this->isWriting = true;
|
|
$this->noOverwrite = true;
|
|
break;
|
|
|
|
case 'c+':
|
|
$this->isReading = true;
|
|
case 'c':
|
|
$this->isWriting = true;
|
|
break;
|
|
|
|
// nope mode: Mock read/write support,
|
|
// but never write to the remote server.
|
|
// (This is accomplished by never marking
|
|
// the stream as dirty.)
|
|
case 'nope':
|
|
$this->isReading = true;
|
|
$this->isWriting = true;
|
|
$this->isNeverDirty = true;
|
|
break;
|
|
|
|
// Default case is read/write
|
|
// like c+.
|
|
default:
|
|
$this->isReading = true;
|
|
$this->isWriting = true;
|
|
break;
|
|
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Get an item out of the context.
|
|
*
|
|
* @todo Should there be an option to NOT query the Bootstrap::conf()?
|
|
*
|
|
* @param string $name The name to look up. First look it up in the context,
|
|
* then look it up in the Bootstrap config.
|
|
* @param mixed $default The default value to return if no config param was
|
|
* found.
|
|
*
|
|
* @return mixed The discovered result, or $default if specified, or null if
|
|
* no $default is specified.
|
|
*/
|
|
protected function cxt($name, $default = null)
|
|
{
|
|
// Lazilly populate the context array.
|
|
if (is_resource($this->context) && empty($this->contextArray)) {
|
|
$cxt = stream_context_get_options($this->context);
|
|
|
|
// If a custom scheme name has been set, use that.
|
|
if (!empty($cxt[$this->schemeName])) {
|
|
$this->contextArray = $cxt[$this->schemeName];
|
|
}
|
|
// We fall back to this just in case.
|
|
elseif (!empty($cxt[self::DEFAULT_SCHEME])) {
|
|
$this->contextArray = $cxt[self::DEFAULT_SCHEME];
|
|
}
|
|
}
|
|
|
|
// Should this be array_key_exists()?
|
|
if (isset($this->contextArray[$name])) {
|
|
return $this->contextArray[$name];
|
|
}
|
|
|
|
// Check to see if the value can be gotten from
|
|
// \OpenStack\Bootstrap.
|
|
$val = \OpenStack\Bootstrap::config($name, null);
|
|
if (isset($val)) {
|
|
return $val;
|
|
}
|
|
|
|
return $default;
|
|
}
|
|
|
|
/**
|
|
* Parse a URL.
|
|
*
|
|
* In order to provide full UTF-8 support, URLs must be
|
|
* rawurlencoded before they are passed into the stream wrapper.
|
|
*
|
|
* This parses the URL and urldecodes the container name and
|
|
* the object name.
|
|
*
|
|
* @param string $url A Swift URL.
|
|
*
|
|
* @return array An array as documented in parse_url().
|
|
*/
|
|
protected function parseUrl($url)
|
|
{
|
|
$res = parse_url($url);
|
|
|
|
|
|
// These have to be decode because they will later
|
|
// be encoded.
|
|
foreach ($res as $key => $val) {
|
|
if ($key == 'host') {
|
|
$res[$key] = urldecode($val);
|
|
} elseif ($key == 'path') {
|
|
if (strpos($val, '/') === 0) {
|
|
$val = substr($val, 1);
|
|
}
|
|
$res[$key] = urldecode($val);
|
|
|
|
}
|
|
}
|
|
|
|
return $res;
|
|
}
|
|
|
|
/**
|
|
* Based on the context, initialize the ObjectStorage.
|
|
*
|
|
* The following parameters may be set either in the stream context
|
|
* or through \OpenStack\Bootstrap::setConfiguration():
|
|
*
|
|
* - token: An auth token. If this is supplied, authentication is skipped and
|
|
* this token is used. NOTE: You MUST set swift_endpoint if using this
|
|
* option.
|
|
* - swift_endpoint: The URL to the swift instance. This is only necessary if
|
|
* 'token' is set. Otherwise it is ignored.
|
|
* - username: A username. MUST be accompanied by 'password' and 'tenantname'.
|
|
* - password: A password. MUST be accompanied by 'username' and 'tenantname'.
|
|
* - endpoint: The URL to the authentication endpoint. Necessary if you are not
|
|
* using a 'token' and 'swift_endpoint'.
|
|
* - use_swift_auth: If this is set to true, it will force the app to use
|
|
* the deprecated swiftAuth instead of IdentityService authentication.
|
|
* In general, you should avoid using this.
|
|
* - transport_client: A transport client for the HTTP requests.
|
|
*
|
|
* To find these params, the method first checks the supplied context. If the
|
|
* key is not found there, it checks the Bootstrap::conf().
|
|
*
|
|
* @fixme This should be rewritten to use \ObjectStorage::newFromServiceCatalog().
|
|
*/
|
|
protected function initializeObjectStorage()
|
|
{
|
|
$token = $this->cxt('token');
|
|
|
|
$tenantId = $this->cxt('tenantid');
|
|
$tenantName = $this->cxt('tenantname');
|
|
$authUrl = $this->cxt('endpoint');
|
|
$endpoint = $this->cxt('swift_endpoint');
|
|
$client = $this->cxt('transport_client', null);
|
|
|
|
$serviceCatalog = null;
|
|
|
|
if (!empty($token) && isset(self::$serviceCatalogCache[$token])) {
|
|
$serviceCatalog = self::$serviceCatalogCache[$token];
|
|
}
|
|
|
|
// FIXME: If a token is invalidated, we should try to re-authenticate.
|
|
// If context has the info we need, start from there.
|
|
if (!empty($token) && !empty($endpoint)) {
|
|
$this->store = new \OpenStack\ObjectStore\v1\ObjectStorage($token, $endpoint, $client);
|
|
}
|
|
// If we get here and tenant ID is not set, we can't get a container.
|
|
elseif (empty($tenantId) && empty($tenantName)) {
|
|
throw new \OpenStack\Common\Exception('Either Tenant ID (tenantid) or Tenant Name (tenantname) is required.');
|
|
} elseif (empty($authUrl)) {
|
|
throw new \OpenStack\Common\Exception('An Identity Service Endpoint (endpoint) is required.');
|
|
}
|
|
// Try to authenticate and get a new token.
|
|
else {
|
|
$ident = $this->authenticate();
|
|
|
|
// Update token and service catalog. The old pair may be out of date.
|
|
$token = $ident->token();
|
|
$serviceCatalog = $ident->serviceCatalog();
|
|
self::$serviceCatalogCache[$token] = $serviceCatalog;
|
|
|
|
$this->store = ObjectStorage::newFromServiceCatalog($serviceCatalog, $token, \OpenStack\ObjectStore\v1\ObjectStorage::DEFAULT_REGION, $client);
|
|
|
|
/*
|
|
$catalog = $ident->serviceCatalog(ObjectStorage::SERVICE_TYPE);
|
|
if (empty($catalog) || empty($catalog[0]['endpoints'][0]['publicURL'])) {
|
|
//throw new \OpenStack\Common\Exception('No object storage services could be found for this tenant ID.' . print_r($catalog, true));
|
|
throw new \OpenStack\Common\Exception('No object storage services could be found for this tenant ID.');
|
|
}
|
|
$serviceURL = $catalog[0]['endpoints'][0]['publicURL'];
|
|
|
|
$this->store = new ObjectStorage($token, $serviceURL);
|
|
*/
|
|
}
|
|
|
|
return !empty($this->store);
|
|
|
|
}
|
|
|
|
protected function authenticate()
|
|
{
|
|
$username = $this->cxt('username');
|
|
$password = $this->cxt('password');
|
|
|
|
$tenantId = $this->cxt('tenantid');
|
|
$tenantName = $this->cxt('tenantname');
|
|
$authUrl = $this->cxt('endpoint');
|
|
|
|
$client = $this->cxt('transport_client', null);
|
|
|
|
$ident = new \OpenStack\Identity\v2\IdentityService($authUrl, $client);
|
|
|
|
// Frustrated? Go burninate. http://www.homestarrunner.com/trogdor.html
|
|
|
|
if (!empty($username) && !empty($password)) {
|
|
$token = $ident->authenticateAsUser($username, $password, $tenantId, $tenantName);
|
|
} else {
|
|
throw new \OpenStack\Common\Exception('Username/password must be provided.');
|
|
}
|
|
// Cache the service catalog.
|
|
self::$serviceCatalogCache[$token] = $ident->serviceCatalog();
|
|
|
|
return $ident;
|
|
}
|
|
|
|
}
|