From 9fb2743fc4d022bfa602cfd8d1e31672de4ad0d0 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Sat, 17 Oct 2015 16:04:03 -0400 Subject: [PATCH] Retire stackforge/openstack-sdk-php --- .gitignore | 9 - .gitreview | 4 - CHANGELOG.rst | 4 - LICENSE.txt | 176 -- README.rst | 106 +- composer.json | 27 - doc/documentation.php | 227 --- doc/introduction.md | 74 - doc/oo-tutorial-code.php | 36 - doc/oo-tutorial.md | 480 ------ doc/streams-tutorial-example.php | 40 - doc/streams-tutorial.md | 236 --- doc/style.md | 120 -- phpunit.xml.dist | 21 - src/OpenStack/Bootstrap.php | 298 ---- src/OpenStack/Common/Exception.php | 27 - .../Common/Transport/AbstractClient.php | 53 - .../Common/Transport/ClientInterface.php | 138 -- .../Transport/Exception/ConflictException.php | 31 - .../Exception/ForbiddenException.php | 32 - .../Exception/LengthRequiredException.php | 31 - .../Exception/MethodNotAllowedException.php | 30 - .../Transport/Exception/RequestException.php | 111 -- .../Exception/ResourceNotFoundException.php | 30 - .../Transport/Exception/ServerException.php | 31 - .../Exception/UnauthorizedException.php | 30 - .../UnprocessableEntityException.php | 30 - .../Common/Transport/Guzzle/GuzzleAdapter.php | 159 -- .../Common/Transport/Guzzle/HttpError.php | 57 - .../Transport/Guzzle/MessageAdapter.php | 118 -- .../Transport/Guzzle/RequestAdapter.php | 55 - .../Transport/Guzzle/ResponseAdapter.php | 50 - .../Common/Transport/MessageInterface.php | 160 -- .../Common/Transport/RequestInterface.php | 66 - .../Common/Transport/ResponseInterface.php | 50 - src/OpenStack/Common/Transport/Url.php | 321 ---- src/OpenStack/Identity/v2/IdentityService.php | 740 -------- .../Exception/ContainerNotEmptyException.php | 29 - .../ContentVerificationException.php | 28 - .../v1/Exception/ReadOnlyObjectException.php | 23 - .../ObjectStore/v1/ObjectStorage.php | 493 ------ src/OpenStack/ObjectStore/v1/Resource/ACL.php | 567 ------- .../ObjectStore/v1/Resource/Container.php | 1084 ------------ .../ObjectStore/v1/Resource/Object.php | 524 ------ .../ObjectStore/v1/Resource/RemoteObject.php | 669 -------- .../ObjectStore/v1/Resource/StreamWrapper.php | 1495 ----------------- .../v1/Resource/StreamWrapperFS.php | 218 --- .../ObjectStore/v1/Resource/Subdir.php | 75 - tests/README.md | 105 -- tests/Tests/BootstrapTest.php | 30 - .../Common/Transport/AbstractClientTest.php | 94 -- .../Transport/Guzzle/GuzzleAdapterTest.php | 92 - .../Common/Transport/Guzzle/HttpErrorTest.php | 128 -- .../Transport/Guzzle/MessageAdapterTest.php | 139 -- .../Transport/Guzzle/RequestAdapterTest.php | 69 - .../Transport/Guzzle/ResponseAdapterTest.php | 55 - tests/Tests/Common/Transport/UrlTest.php | 113 -- .../Identity/v2/IdentityServicesTest.php | 439 ----- .../ObjectStore/v1/ObjectStorageTest.php | 287 ---- .../Tests/ObjectStore/v1/Resource/ACLTest.php | 215 --- .../ObjectStore/v1/Resource/ContainerTest.php | 393 ----- .../ObjectStore/v1/Resource/ObjectTest.php | 150 -- .../v1/Resource/RemoteObjectTest.php | 330 ---- .../v1/Resource/StreamWrapperFSTest.php | 353 ---- .../v1/Resource/StreamWrapperTest.php | 314 ---- .../v1/Resource/StreamWrapperTestCase.php | 135 -- tests/Tests/TestCase.php | 240 --- tests/bootstrap.php | 19 - tests/example.settings.ini | 53 - 69 files changed, 5 insertions(+), 13161 deletions(-) delete mode 100644 .gitignore delete mode 100644 .gitreview delete mode 100644 CHANGELOG.rst delete mode 100644 LICENSE.txt delete mode 100644 composer.json delete mode 100644 doc/documentation.php delete mode 100644 doc/introduction.md delete mode 100644 doc/oo-tutorial-code.php delete mode 100644 doc/oo-tutorial.md delete mode 100644 doc/streams-tutorial-example.php delete mode 100644 doc/streams-tutorial.md delete mode 100644 doc/style.md delete mode 100644 phpunit.xml.dist delete mode 100644 src/OpenStack/Bootstrap.php delete mode 100644 src/OpenStack/Common/Exception.php delete mode 100644 src/OpenStack/Common/Transport/AbstractClient.php delete mode 100644 src/OpenStack/Common/Transport/ClientInterface.php delete mode 100644 src/OpenStack/Common/Transport/Exception/ConflictException.php delete mode 100644 src/OpenStack/Common/Transport/Exception/ForbiddenException.php delete mode 100644 src/OpenStack/Common/Transport/Exception/LengthRequiredException.php delete mode 100644 src/OpenStack/Common/Transport/Exception/MethodNotAllowedException.php delete mode 100644 src/OpenStack/Common/Transport/Exception/RequestException.php delete mode 100644 src/OpenStack/Common/Transport/Exception/ResourceNotFoundException.php delete mode 100644 src/OpenStack/Common/Transport/Exception/ServerException.php delete mode 100644 src/OpenStack/Common/Transport/Exception/UnauthorizedException.php delete mode 100644 src/OpenStack/Common/Transport/Exception/UnprocessableEntityException.php delete mode 100644 src/OpenStack/Common/Transport/Guzzle/GuzzleAdapter.php delete mode 100644 src/OpenStack/Common/Transport/Guzzle/HttpError.php delete mode 100644 src/OpenStack/Common/Transport/Guzzle/MessageAdapter.php delete mode 100644 src/OpenStack/Common/Transport/Guzzle/RequestAdapter.php delete mode 100644 src/OpenStack/Common/Transport/Guzzle/ResponseAdapter.php delete mode 100644 src/OpenStack/Common/Transport/MessageInterface.php delete mode 100644 src/OpenStack/Common/Transport/RequestInterface.php delete mode 100644 src/OpenStack/Common/Transport/ResponseInterface.php delete mode 100644 src/OpenStack/Common/Transport/Url.php delete mode 100644 src/OpenStack/Identity/v2/IdentityService.php delete mode 100644 src/OpenStack/ObjectStore/v1/Exception/ContainerNotEmptyException.php delete mode 100644 src/OpenStack/ObjectStore/v1/Exception/ContentVerificationException.php delete mode 100644 src/OpenStack/ObjectStore/v1/Exception/ReadOnlyObjectException.php delete mode 100644 src/OpenStack/ObjectStore/v1/ObjectStorage.php delete mode 100644 src/OpenStack/ObjectStore/v1/Resource/ACL.php delete mode 100644 src/OpenStack/ObjectStore/v1/Resource/Container.php delete mode 100644 src/OpenStack/ObjectStore/v1/Resource/Object.php delete mode 100644 src/OpenStack/ObjectStore/v1/Resource/RemoteObject.php delete mode 100644 src/OpenStack/ObjectStore/v1/Resource/StreamWrapper.php delete mode 100644 src/OpenStack/ObjectStore/v1/Resource/StreamWrapperFS.php delete mode 100644 src/OpenStack/ObjectStore/v1/Resource/Subdir.php delete mode 100644 tests/README.md delete mode 100644 tests/Tests/BootstrapTest.php delete mode 100644 tests/Tests/Common/Transport/AbstractClientTest.php delete mode 100644 tests/Tests/Common/Transport/Guzzle/GuzzleAdapterTest.php delete mode 100644 tests/Tests/Common/Transport/Guzzle/HttpErrorTest.php delete mode 100644 tests/Tests/Common/Transport/Guzzle/MessageAdapterTest.php delete mode 100644 tests/Tests/Common/Transport/Guzzle/RequestAdapterTest.php delete mode 100644 tests/Tests/Common/Transport/Guzzle/ResponseAdapterTest.php delete mode 100644 tests/Tests/Common/Transport/UrlTest.php delete mode 100644 tests/Tests/Identity/v2/IdentityServicesTest.php delete mode 100644 tests/Tests/ObjectStore/v1/ObjectStorageTest.php delete mode 100644 tests/Tests/ObjectStore/v1/Resource/ACLTest.php delete mode 100644 tests/Tests/ObjectStore/v1/Resource/ContainerTest.php delete mode 100644 tests/Tests/ObjectStore/v1/Resource/ObjectTest.php delete mode 100644 tests/Tests/ObjectStore/v1/Resource/RemoteObjectTest.php delete mode 100644 tests/Tests/ObjectStore/v1/Resource/StreamWrapperFSTest.php delete mode 100644 tests/Tests/ObjectStore/v1/Resource/StreamWrapperTest.php delete mode 100644 tests/Tests/ObjectStore/v1/Resource/StreamWrapperTestCase.php delete mode 100644 tests/Tests/TestCase.php delete mode 100644 tests/bootstrap.php delete mode 100644 tests/example.settings.ini diff --git a/.gitignore b/.gitignore deleted file mode 100644 index a4a4cc9..0000000 --- a/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -.idea/ -build/ -vendor/ - -.DS_Store -composer.lock -composer.phar -phpunit.xml -tests/settings.ini* \ No newline at end of file diff --git a/.gitreview b/.gitreview deleted file mode 100644 index d63960b..0000000 --- a/.gitreview +++ /dev/null @@ -1,4 +0,0 @@ -[gerrit] -host=review.openstack.org -port=29418 -project=stackforge/openstack-sdk-php.git diff --git a/CHANGELOG.rst b/CHANGELOG.rst deleted file mode 100644 index 8952345..0000000 --- a/CHANGELOG.rst +++ /dev/null @@ -1,4 +0,0 @@ -Release Notes -============= - -This changelog contains the relevant feature additions and bug fixes. diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index 68c771a..0000000 --- a/LICENSE.txt +++ /dev/null @@ -1,176 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - diff --git a/README.rst b/README.rst index 5a0d327..9006052 100644 --- a/README.rst +++ b/README.rst @@ -1,103 +1,7 @@ -OpenStack PHP-Client -==================== +This project is no longer maintained. -This package provides PHP OpenStack bindings. +The contents of this repository are still available in the Git source code +management system. To see the contents of this repository before it reached +its end of life, please check out the previous commit with +"git checkout HEAD^1". -You can use this library to: - -- Authenticate your application to OpenStack. -- Interact with Object Storage (aka Swift). - -Coming soon: - -- Intect with the Compute (Nova) manager. -- Interact with other OpenStack services - -Requirements ------------- - -- PHP 5.3 -- An active OpenStack account with the desired services. - -Suggestions -~~~~~~~~~~~ - -- Enable the cURL extension for full protocol support. - -We also have support for using PHP's native HTTP stream wrapper, but it -is not as reliable. We recommend cURL. - -Versioning ----------- - -We have a goal to be as consistent as possible with `Semantic -Versioning `__. For released HP Cloud services this -is what you can expect. - -Installation ------------- - -There are currently two methods of installation. We've been considering -PEAR and Phar releases, but have currently limited to only Composer and -builds because these cover our needs. - -Method #1: -~~~~~~~~~~ - -Use `Composer `__ to download and install the -latest version of OpenStack. - -Method #2: -~~~~~~~~~~ - -Download a tagged release and include it in your project. - -Features --------- - -Identity Services -~~~~~~~~~~~~~~~~~ - -Authenticate, authorize service usage, and retrieve account information. - -Object Storage -~~~~~~~~~~~~~~ - -Store files or other data objects in containers on your OpenStack object -storage instance. Create, modify and delete containers. Manage ACLs. -Read, write, and delete objects. Expose objects in your object storage -to other services. - -With full stream wrapper support, you can use built-in PHP functions -like ``file_get_contents()``, ``fopen()``, and ``stat()`` for reading -and writing files into object storage. - -Autoloading -^^^^^^^^^^^ - -OpenStack SDK for PHP is `PSR-4 -compliant `__, -which means that it should work with any PSR-4 autoloader. However, it -also comes with its own autoloader for apps that don't yet make use of a -standard autoloader. - -Composer Support -^^^^^^^^^^^^^^^^ - -OpenStack PHP-Client is available as part of the Packagist archive, -which means you can use Composer to automatically download, install, and -manage revisions to OpenStack from within your project. - -We're big fans of `Composer `__. - -More information ----------------- - -`OpenStack `__ is a cloud computing platform that -provides many services, inlcuding compute installs, object and block -storage, and a host of hosted services. - -This library provides access to those services. - -The best source of documentation is the official API documentation, -which is available at http://FIXME diff --git a/composer.json b/composer.json deleted file mode 100644 index 2ef9227..0000000 --- a/composer.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "openstack/openstack-sdk-php", - "description": "Access OpenStack services in PHP.", - "type": "library", - "keywords": ["openstack","keystone","cloud","swift","nova"], - "license": "Apache-2.0", - "require": { - "php": ">=5.4.0", - "guzzlehttp/guzzle": "~4.1" - }, - "require-dev": { - "phpunit/phpunit": "~4.1" - }, - "support": { - "irc": "irc://irc.freenode.org/openstack-sdks", - "issues": "https://bugs.launchpad.net/openstack-sdk-php", - "forum": "https://ask.openstack.org/", - "wiki": "https://wiki.openstack.org/wiki/OpenStack-SDK-PHP", - "source": "http://git.openstack.org/cgit/stackforge/openstack-sdk-php" - }, - "autoload": { - "psr-4": { - "OpenStack\\": "src/OpenStack", - "OpenStack\\Tests\\": "tests/Tests" - } - } -} diff --git a/doc/documentation.php b/doc/documentation.php deleted file mode 100644 index 8b28486..0000000 --- a/doc/documentation.php +++ /dev/null @@ -1,227 +0,0 @@ - 'foo@example.com', - * 'password' => 'secret', - * 'tenantid' => '123456', - * 'endpoint' => 'http://url.from.hpcloud.com/', - * )); - * - * - * // Get an object from the remote object storage and read it as a string - * // right into $myObject. - * $myObject = file_get_contents('swift://mycontainer/foo.txt', FALSE, $cxt); - * - * ?> - * - * With stream wrapper support, you can transparently read and write files to the - * ObjectStorage service without using any fancy API at all. Use the - * normal file methods like this: - * - *- fopen()/fclose() - *- fread()/fwrite() - *- file_get_contents(), stream_get_contents() - *- stat()/fstat() - *- is_readable()/is_writable() - *- And so on - * @see http://us3.php.net/manual/en/ref.filesystem.php - * - * Learn more about this at \OpenStack\ObjectStore\v1\Resource\StreamWrapper. - * - * Basic Example: Identity Service - * - * Stream wrappers are nice and all, but - * some of us love fancy APIs. So here's an example using the full API - * to log in and then dump a list of services that are available to you: - * - * token(). - * $token = $identity->authenticateAsUser($username, $password, $tenantId); - * - * // Get a listing of all of the services you currently have configured in - * // OpenStack. - * $catalog = $identity->serviceCatalog(); - * - * var_dump($catalog); - * - * ?> - * - *- Our classes use PHP namespaces to organize components. If you've never used - * them before, don't worry. They're easy to get the hang of. - *- The Bootstrap class handles setting up OpenStack services. Read about it at \OpenStack\Bootstrap. - *- The IdentityServices class handles authenticating to OpenStack, discovering services, and providing - * access to your account. \OpenStack\Identity\v1\IdentityService explains the details, but here are - * a few functions you'll want to know: - * - \OpenStack\Identity\v1\IdentityService::__construct() tells the object where to connect. - * - \OpenStack\Identity\v1\IdentityService::authenticateAsUser() lets you log - * in with username and password. - * - \OpenStack\Identity\v1\IdentityService::serviceCatalog() tells you about - * the services you have activated on this account. - * - * Basic Example: Object Storage - * - * Assuming you have an object storage instance available in your service - * catalog, we could continue on with something like this: - * - * serviceCatalog('object-storage'); - * // $objectStorageUrl = storageList[0]['endpoints'][0]['publicURL']; - * - * // Create a new ObjectStorage instance: - * // $objectStore = new \OpenStack\ObjectStore\v1\ObjectStorage($token, $objectStorageUrl); - * - * // Or let ObjectStorage figure out which instance to use: - * $objectStore = \OpenStack\ObjectStore\v1\ObjectStorage::newFromIdentity($identity); - * - * // List containers: - * print_r($objectStore->containers()); - * - * // Get a container named 'stuff': - * $container = $objectStore->container('stuff'); - * - * // List all of the objects in that container: - * print_r($container->objects()); - * - * // Get an object named 'example.txt' - * $obj = $container->object('example.txt'); - * - * // Print that object's contents: - * print $obj->content(); - * - * // Actually, since it implements __tostring, we could do this: - * print $obj; - * ?> - * - * This shows you a few methods for accessing objects and containers on your - * \OpenStack\ObjectStore\v1\ObjectStorage account. There are many functions for - * creating and modifying containers and objects, too. - * - *- \OpenStack\ObjectStore\v1\ObjectStorage is where you will start. - *- Container services are in \OpenStack\ObjectStore\v1\ObjectStorage\Container - *- There are two classes for objects: - * - \OpenStack\ObjectStore\v1\ObjectStorage\Object is for creating new objects. - * - \OpenStack\ObjectStore\v1\ObjectStorage\RemoteObject provides better network - * performance when reading objects. - * - */ - -/** - * @package OpenStack - * The OpenStack PHP-Client library. - */ -/** - * @namespace OpenStack.Services - * OpenStack classes providing access to various services. - * - * OpenStack offers a number of services, including Compute (Nova), - * and IdentityService. - * - * This package is reserved for classes that provide access to - * services. - */ -/** - * @package OpenStack.Storage - * OpenStack classes for remote storage. - * - * Services for now and the future: - * - *- ObjectStorage - *- Others coming. - * - */ -/** - * @package OpenStack.Storage.ObjectStorage - * Classes specific to ObjectStorage. - * - * The main class is \OpenStack\ObjectStore\v1\ObjectStorage. - */ -/** - * @package OpenStack.Transport - * HTTP/REST/JSON classes. - * - * HTTP/HTTPS is the transport protocol for OpenStack's RESTful services. - * - * This library provides both CURL and PHP Streams-based HTTP support, - * and this package provides a simple REST client architecture, along - * with the minimal JSON processing necessary. - * - * - */ -?> diff --git a/doc/introduction.md b/doc/introduction.md deleted file mode 100644 index f7a8467..0000000 --- a/doc/introduction.md +++ /dev/null @@ -1,74 +0,0 @@ -# Using the OpenStack PHP-CLient API - -This tutorial explains how you can use the PHP API to connect to your OpenStack -services and interact programmatically. - -## Object Storage (Swift) - -One of the services that OpenStack offers is called "Object Storage". -This service provides a useful means of storing objects (usually files) -on a service that you control, but that is available to other services -in your cloud (and optionally is availably publically). - -This section of the tutorial describes how you can write PHP code to -interact with the Object Storage service. - -## Authenticating to Object Storage - -There are two ways to authenticate to Object Storage: - -- Legacy Swift authentication -- Control Services authentication - -For legacy swift authentication, you will need to use your Tenant ID, your username, -and your password, along with the URL to the Object Storage endpoint. - -### Using Stream Wrappers - -There are two main methods for accessing OpenStack through this library. -The first is through PHP *stream wrappers*. In PHP, stream wrappers -provide a facility with which you can access various data streams (like -a webpage, the data in a ZIP file, or an OpenStack object store) as if -they were local files on your file system. - -Stream wrappers have a huge advantage for you: You can use the normal -file system functions (`fread()`, `mkdir()`, `file_get_contents()`, etc) -to access things not necessarily on your local filesystem. The PHP-Client -library integrates with this facility of PHP. - - -### Using the PHP-Client Classes - -While the stream wrappers are a fantastic way to accomplish many common -tasks, sometimes you need a finer level of control, or you wish to use -an Object-Oriented API. We provide you with the classes you need to work -this way. - -(Deep dark secret: Actually, these are the classes that underly the -stream wrappers.) - -In this section of the tutorial, we focus on using this API as a -data-access layer. - -#### Main Classes - -- \OpenStack\Bootstrap: Provides services for bootstrapping the library. - It's not necessary, but it can be helpful. -- \OpenStack\ObjectStorage: The main interface to the OpenStack object - storage. - -## Slightly Irreverant Glossary - -*Tenant ID:* You service provider will provide you with -an account ID and a secret key, along with a URL, that can be used to -access the cloud APIs. - -*Container:* A namespace extension useful for differentiating object -space with pseudo-containment logical units. Or, a directory. (see -_Object Storage_) - -*Object Storage:* A service provided by OpenStack that allows you to store -entire files on the cloud. Files can be organized into _containers_, which are -rough analogs to file system directories (or folders). - - diff --git a/doc/oo-tutorial-code.php b/doc/oo-tutorial-code.php deleted file mode 100644 index ea98e0c..0000000 --- a/doc/oo-tutorial-code.php +++ /dev/null @@ -1,36 +0,0 @@ -authenticateAsUser($username, $password, $tenantId); - -$catalog = $idService->serviceCatalog(); - -$store = ObjectStorage::newFromServiceCatalog($catalog, $token); - -$store->createContainer('Example'); -$container = $store->container('Example'); - -$name = 'hello.txt'; -$content = 'Hello World'; -$mime = 'text/plain'; - -$localObject = new Object($name, $content, $mime); -$container->save($localObject); - -$object = $container->object('hello.txt'); -printf("Name: %s \n", $object->name()); -printf("Size: %d \n", $object->contentLength()); -printf("Type: %s \n", $object->contentType()); -print $object->content() . PHP_EOL; diff --git a/doc/oo-tutorial.md b/doc/oo-tutorial.md deleted file mode 100644 index 7d0f572..0000000 --- a/doc/oo-tutorial.md +++ /dev/null @@ -1,480 +0,0 @@ -Tutorial: Using OpenStack PHP-Client -================= - -PHP-Client provides PHP language bindings for the OpenStack APIs. - -In this tutorial, we will walk through the process of creating a simple -tool that interacts with OpenStack's Object Storage. The emphasis in this -article is on getting started and learning the concepts, not building a -polished product. - -**This tutorial focuses on the object-oriented API.** The other way to -work with this library is through the stream wrapper. That topic is -covered in [another tutorial](@ref streams-tutorial). - -## Pre-flight Check - -PHP-Client has been developed to require PHP 5.3 or later. You are -strongly encouraged to also install the CURL PHP extension. Many -distributions of PHP come with this enabled. Sometimes, though, you may -need to do something like `apt-get php5-curl` or similar. (Don't take -our word for it -- check your system's documentation.) - -You can check for both of these conditions by checking the output of -`php --info` (on the commandline) or ``. - -### Check the pilot, too! - -In our pre-flight check, we would be remiss if we didn't point out that -there are some requirements for the pilot (that's you), too. - -The PHP-Client library is composed of two parts: - -1. The Object-Oriented part, which is the subject of this tutorial. -2. The Stream Wrapper, which is the subject of another tutorial. - -The object-oriented library makes ample use of PHP namespaces. If you've -never seen these before, they look like this: - - - -The namespace above is read like this: "The RemoteObject class is part -of the ObjectStorage package in the Storage package in the base OpenStack -package." Those familiar with Java, Python, and other languages will -recognize this way of talking (though the backslash is an unfortunate -symbol choice). - -For our library, we followed the recommendation of SPR-0, which means -that the class above can be found in the file at: - - src/OpenStack/ObjectStore/v1/Resource/RemoteObject.php - -The pattern of matching namespace to file name should (we hope) make it -easier for you to navigate our code. - -If this namespace stuff continues to confuse you, you may want to take a -look at [the PHP documentation](http://us3.php.net/manual/en/language.namespaces.php), -or you may just prefer to keep on reading and learn by example. We don't -do anything really fancy with namespaces. - -**In this document, we sometimes replace the backslash (\\) with double -colons (`::`) so that links are automatically generated.** So -`\OpenStack\Bootstrap` may appear as OpenStack::Bootstrap. The reason for -this is [explained elsewhere](@ref styleguide). - -## Step 1: Getting the Library - -You can get the OpenStack PHP-CLient library at the [OpenStack PHP-Client -Repository](https://FIXME). The latest code is always -available there. - -The project also uses [Composer](http://packagist.org/), and this is the -best method for adding PHP-Client to your PHP project. - -For our example, we will assume that the library is accessible in the -default include path, so the following line will include the -`Bootstrap.php` file: - - include 'OpenStack/Bootstrap.php'; - -## Step 2: Bootstrap the Library - -The first thing to do in your application is make sure the OpenStack -library is bootstrapped. When we say "bootstrap", what we really mean is -letting the library initialize itself. - -The only thing you will need to do is require Composer's PSR-compliant -autoloader, like so: - - authenticateAsUser($username, $password, $tenantId); - ?> - -Assuming the variables above have been set to include valid data, this -script can connect to OpenStack and authenticate. - -When we construct a new OpenStack::Services::IdentityService object, we must pass it the -endpoint URL for OpenStack Identity Service. Typically, that URL will -look something like this: - -~~~ -https://region-a.geo-1.identity.hpcloudsvc.com:35357 -~~~ - -The `authenticateAsUser()` method will authenticate to the -Identity Services endpoint. For convenience, it returns the -authorization token (`$token`), though we can also get the token from -`$idService->token()`. - -Note that the `IdentityService` object may throw various exceptions -(all subclasses of OpenStack\Common\Exception) during authentication. Failed -authentication results in an \OpenStack\Common\Transport\AuthorizationException, while -a network failure may result in an \OpenStack\Common\Transport\ServerException. - -Earlier, we talked about the service catalog. Once we've authenticated, -we can get the service catalog from `$idService->serviceCatalog()`. It -is an associative array, and you can get an idea of what it contains by -dumping it with `var_dump()`, should you so desire. - -At this point, we have what we need from Identity Service. It's time to -look at Object Storage. - -### IdentityService in a Nutshell - -Instances of `OpenStack\Identity\v2\IdentityService` are responsible for: - -- Authentication -- Accessing the service catalog -- Accessing account info -- Associating tenant IDs with accounts (advanced) - -## Step 4: Connecting to Object Storage - -The Object Storage system is concerned with two classes of things: - -- An Object: A self-contained bundle of data (back in my day, we called - them "files"). -- A Container: A storage container (bucket) for objects. - -Your object storage can have any number of containers, and each -container can have any number of objects. - -In the object model for the OpenStack PHP-Client library, a top-level object -called OpenStack::Storage::ObjectStorage provides access to the Object -Storage service. In this step, we will be working with that object. - -### Getting an ObjectStorage Instance - -Earlier, we created an `IdentityService` instance called `$idService`. -We will use that here to get the service catalog. Once we have the -catalog, we can have a new `ObjectStorage` instance created for us, -configured to talk to our account's Object Storage instance in -OpenStack. Along with the service catalog, we also need our token that -shows the Object Storage endpoint that we have already authenticated to -Identity Services. Earlier, we captured that value in the `$token` -variable. - -Now we can get a new `\OpenStack\ObjectStore\v1\ObjectStorage` instance: - - serviceCatalog(); - - $store = ObjectStorage::newFromServiceCatalog($catalog, $token); - - // UPDATE: As of Beta 6, you can use newFromIdentity(): - // $store = ObjectStorage::newFromIdentity($idService); - ?> - -First we get the service catalog (`$catalog`), and then we use the -`ObjectStorage::newFromServiceCatalog()` static method to create the new -Object Storage instance. - -The pattern of using a constructor-like static function is used -throughout the OpenStack PHP-Client library. Inspired by Objective-C constructors -and the Factory design pattern, it makes it possible for a single class -to have multiple constructors. - -In particular, many top-level classes provide a -`newFromServiceCatalog()` constructor function, since these classes know -how to construct instances from a service catalog, thus freeing the -developer up from knowing the details of a service catalog entry. - -Now we have an `ObjectStorage` instance that is already configured to -talk to our OpenStack object storage service. Next, we can create a -container. - -### ObjectStorage in a Nutshell - -Instances of OpenStack::Storage::ObjectStorage are responsbile for: - -- Providing high-level information about the Object Storage service -- Creating, deleting, loading, and listing Containers -- Modifying Container ACLs - -## Step 5: Adding a Container - -Before we can start putting objects (files) into our Object Storage -service, we need a place to put them. An Object Storage service can hold -numerous containers (and each container can have different access -controls -- a topic we won't get into here). - -Containers are represented in the library by the -OpenStack::Storage::ObjectStorage::Container class. And creating a -container is done by a method on the `ObjectStorage` object that we -created above: - - createContainer('Example'); - $container = $store->container('Example'); - ?> - -Recall that `$store` is the name of our `ObjectStorage` instance. In the -first of the two lines above, we create a new container named `Example`. -Then in the second line, we get that container. - -Why is this two steps? The answer is that the OpenStack PHP-Client library mimics -the architecture of the underlying API. This is two operations (which -means it requires two network requests to the remote host), and so we -must perform two operations. - -The `createContainer()` call actually creates the new container on the -cloud's Object Storage. The second call connects to the remote object -storage, and gets the new container. The container that is returned will -have some additional information, such as the amount of space it takes -up on the remote storage, and the access control rules for that -container. All of this information will be available on the `$container` -instance. - -Our `$container` instance is an instance of -OpenStack::Storage::ObjectStorage::Container. This object can be used not -only to find out about a container, but also to get information about -the objects in that container. - -Now that we have a `Container`, we can add an object. - -### The Container in a Nutshell - -(Yes, we realize the irony of that title.) - -A `\OpenStack\ObjectStore\v1\Resource\Container` instance is responsible for the following: - -- Accessing information about the container -- Creating, saving, deleting, and listing objects in the container -- Providing path-like traversal of objects -- Copying objects across containers -- Loading a lightweight representation of an object without fetching the - entire object (more on this later). - -Among the features of a Container, it can act like an `Iterator` and is -`Countable`. That means you can loop through a Container in a `foreach` -loop and also use `count($container)` to find out the number of objects -in a Container. - -## Step 6: Storing an Object - -Now we are ready to create an object, and then store it in our -container. - -Before diving too deeply, it is important to point out a detail: When -working with a remote data storage service, we are typically working -with a local copy and a remote copy. If our code isn't constructed -correctly, it is possible for these two to get out of sync. - -Earlier, we created a container directly on the remote side, and then -fetched the container. As we create an object, we are going to do the -opposite: We will create a local object, and then save it to the remote -storage. Later, we will fetch the remote object. - - save($localObject); - ?> - -In the code above, we create `$localObject` with a `$name`, some -`$content`, and a `$mime` type. Strictly speaking, only `$name` is -required. - -The OpenStack::Storage::ObjectStorage::Object class is used primarily to -describe a locally created object. Once we have our new `Object`, we can -save it remotely using the `save()` method on our `$container` object. -This will push the object to the remote object storage service. - -While we can continue manipulating `$localObject`, we are working with -the local version, not the latest version of what's on the server. This -is fine if what we are doing is writing more data. However, when -examining the content of the object, remember that we are working with -the local copy, and its properties may differ from the remote copy's. - -### What if I call save() twice with the same Object? - -Objects, like files on a file system, are referenced by name. Any time -you `save()` an object, it will be pushed to the remote object storage -server, which will happily replace the old content with your newly -submitted content. - -Next let's turn to loading objects from the remote object storage. - -### The Object in a Nutshell - -The `\OpenStack\ObjectStore\v1\Resource\Object` instances are used for: - -- Creating a local object to be stored remotely - -This class is also the base class for the `RemoteObject` class that we -will look at later. - -The API is generally constructed so that a developer needn't worry about -the differences between an `Object` and a `RemoteObject`. But in all but -the edgiest of edge cases, you would only create an instance of -`Object`, never of `RemoteObject`. - -## Step 7: Loading an Object - -Containers not only provide the methods for saving objects, but also for -loading objects. Thus, we can fetch the object that we just created: - - object('hello.txt'); - - printf("Name: %s \n", $object->name()); - printf("Size: %d \n", $object->contentLength()); - printf("Type: %s \n", $object->contentType()); - print $object->content() . PHP_EOL; - ?> - -The `$object` variable now references an instance of a -`\OpenStack\ObjectStore\v1\Resource\RemoteObject` that contains the entire -object. `RemoteObject` represents an object that was loaded from the -remote server. Along with providing the features of the `Object` class -we saw earlier, it also provides numerous optimizations for working over -the network. - -Now that we have the object, we print out several pieces of information --- `name()`, `size()`, amd `type()`. Then, using `content()`, we fetch -the content of the object. - -### Lazily Loading an Object - -The method we used above to fetch the object is perfect for our needs. -It pulls the entire object down in a single request. But imagine this -scenario: Our object storage has large media files, and we don't know -at loading time whether or not we need to access the body content, or -just the other data about the object. - -It would be a time-consuming task to download the entire body of a large -media file if we don't actually use the body. On the other hand, from an -API standpoint it is great to be able to pass around a single object, -and not require the application to know whether or not the body has been -retrieved. - -The `RemoteObject` solves this problem using a technique known as "lazy -loading". That is, it can pull some of the data right away, but defer -fetching the rest of the data until that data is actually needed. - -To fetch an object this way, we can just swap out one line in the -example above: - - proxyObject('hello.txt'); - - printf("Name: %s \n", $object->name()); - printf("Size: %d \n", $object->contentLength()); - printf("Type: %s \n", $object->contentType()); - print $object->content() . PHP_EOL; - ?> - -Instead of using `object()`, we now use `proxyObject()`. This method -immediately loads the core data about the remote object, but defers -fetching the content until the content is requested. - -In the example above, then, one network request is issued by -`proxyObject()`, but another is initiated when `$object->content()` is -called. - -### The RemoteObject in a Nutshell - -Instances of a `\OpenStack\ObjectStore\v1\Resource\RemoteObject` offer the following features: - -- Access to an object stored on the remote object storage -- A proxying mechanism for lazily loading objects -- A stream-based API for using stream and file-based PHP functions -- Automatic tempfile-based caching for large objects (using - `php://temp`). - -`RemoteObject` instances can be updated and then passed to -`Container::save()` to update the copy on the server, too. - -## Summary - -At this point we have created a very basic script that connects to -OpenStack and works with object storage. Clearly, this only scratches the -surface of what the OpenStack PHP-Client library does. But hopefully this is -enough to get you started with the library. - -\see oo-tutorial-code.php diff --git a/doc/streams-tutorial-example.php b/doc/streams-tutorial-example.php deleted file mode 100644 index 5aeddb1..0000000 --- a/doc/streams-tutorial-example.php +++ /dev/null @@ -1,40 +0,0 @@ - $ini['account'], - 'key' => $ini['secret'], - 'tenantid' => $ini['tenantId'], - 'endpoint' => $ini['url'], -]; -Bootstrap::setConfiguration($settings); - -// Create a new file and write it to the object store. -$newfile = fopen('swift://Example/my_file.txt', 'w'); -fwrite($newfile, "Good Morning!"); -fclose($newfile); - -// Check for an object: -if (file_exists('swift://Example/my_file.txt')) { - print "Found my_file.txt." . PHP_EOL; -} - -// Get an entire object at once: -$file = file_get_contents('swift://Example/my_file.txt'); -print 'File: ' . $file . PHP_EOL; - -$cxt = stream_context_create([ - 'swift' => [ - 'account' => $ini['account'], - 'key' => $ini['secret'], - 'tenantid' => $ini['tenantId'], - 'endpoint' => $ini['url'], - ], -]); - -print file_get_contents('swift://Example/my_file.txt', FALSE, $cxt); diff --git a/doc/streams-tutorial.md b/doc/streams-tutorial.md deleted file mode 100644 index fe5ea5e..0000000 --- a/doc/streams-tutorial.md +++ /dev/null @@ -1,236 +0,0 @@ -Tutorial: Using Stream Wrappers {#streams-tutorial} -=============================== - -This is an introduction to the OpenStack PHP-Client library. While the library is -large and feature-rich, this tutorial focuses on the Stream Wrapper -feature. (There is also a [tutorial about the object-oriented -library](@ref 00-tutorial).) - -## TL;DR - -With a few lines of setup code, you can fetch objects from OpenStack's -object storage using built-in PHP functions like this: - - - -In fact, the vast majority of file and stream functions work with -OpenStack's `swift://` URLs. - -The rest of this tutorial explains how they work. - -## The Setup - -The example above does not show the code necessary for initializing the -OpenStack PHP-Client stream wrapper. In this section, we will look at the necessary -setup code. - -### Loading Classes - -The OpenStack PHP-Client library is structured following PSR-4 recommendations. -Practically speaking, what this means is that applications that use an -PSR-4 autoloader may be able to automatically load the OpenStack PHP-Client. - -However, we'll assume that that is not the case. We'll assume that the -library needs to be initialized manually. - -What we will do is first load the PHP-Client Bootstrap.php file, and then -use the autoloader in that file to load the rest of the library: - - YOUR_USERNAME, - 'password' => YOUR_PASSWORD, - 'tenantid' => YOUR_TENANT_ID, - 'endpoint' => IDENTITY_SERVICES_URL, - ); - Bootstrap::setConfiguration($settings); - -Basically, what we do above is declare an associative array of -configuration parameters and then tell OpenStack::Bootstrap to set these -as the default configuration. - -Once the above is done, all of those PHP stream and file functions will -just work. All you need to do is pass them `swift://` URLs, and they -will do the rest. - -## The Format of Swift URLs - -Early in the tutorial we saw some swift URLs like this: -`swift://Example/my_file.txt` . What is this URL referencing? - -The URL above has three important parts, in the form -`swift://CONTAINER/OBJECT_NAME`. - -- *swift://*: This is the schema. This part of the URL tells PHP to pass - the request to the OpenStack stream wrapper. (Swift, by the way, is the - [OpenStack name for object storage](http://openstack.org/projects/storage/). -- *Example*: This is the *container name*. In Object Storage parlance, a - container is a place to store documents. One account can have lots of - containers, and each container can have lots of objects. -- *my_file.txt*: This is the object name. An object is basically the - same as a file. - -Swift does not support directories, but it does allow slashes in object -names. So `swift://Example/this/is/my/file.png' checks the container -*Example* for the object named `this/is/my/file.png`. - -(For power users, there are some fancy operations you can do to treat -Swift filename parts as if they were directories. Check out -`\OpenStack\ObjectStore\v1\Resource\Container`.) - -## Using Stream Contexts for Authentication - -Sometimes it is better to pass authentication information directly to -the stream or file function, instead of relying upon a global -configuration. PHP provides for this with **stream contexts**. - -Stream contexts have one major downside: Not all PHP functions accept -stream contexts. Here are some notable examples: - -- file_exists() -- is_readable() -- stat() - -(Basically, anything that calls the underlying `stat(3)`.) - -The advantage, though, is that each call can have its own authentication -data. This is good for supporting multiple accounts, and can also be -used to optimize long-term performance (e.g. by saving authentication -tokens in a database and re-using them). - -Here's how a stream context is used: - - array( - 'username' => YOUR_USERNAME, - 'password' => YOUR_PASSWORD, - 'tenantid' => YOUR_TENANT_ID, - 'endpoint' => IDENTITY_SERVICES_URL, - ), - )); - - print file_get_contents('swift://Example/my_file.txt', FALSE, $cxt); - ?> - -The main difference is the creation of `$cxt` using PHP's -`stream_context_create()`. To fully understand this, you may want to -take a look at the [PHP documentation](http://us3.php.net/manual/en/book.stream.php) -for streams. - -## Stream Wrapper As A File System -As it was noted earlier in this tutorial, swift does not support directories. -Instead the names of a file can be path like with a separator. For example, -`swiftfs://Example/path/to/my_file.txt` has a name of `path/to/my_file.txt`. - -To enable applications to use swift in a more directory like manner there is a -second stream wrapper with a prefix `swiftfs://`. swiftfs stands for swift file -system. It works in a similar manner to to the standard stream wrappers with a -few key differences: - -- mkdir will return TRUE is no objects start with the directory you are trying - to crate. Otherwise it will return FALSE. -- rmdir will return FALSE if any objects start with the directory prefix you are - trying to remove. rmdir does not allow you to remove directories with files - in them. -- Running stat on a directory that is a prefix for some objects (e.g., - `swiftfs://Example/path/to/`) will see this is a prefix for a file and treat - it as if it were a directory. - -To use this stream wrapper instead of the standard swift one simple replace the -usage of `swift://` with `swiftfs://`. - -## Summary - -This tutorial is focused on using stream wrappers to interact with your -OpenStack Object Storage service. We focused on configuring the -environment for transparently using PHP functions like `fopen()` and -`file_get_contents()` to work with objects in OpenStack's object storage. - -This is just one way of interoperating with the OpenStack PHP-Client library. For -more detail-oriented work, you may find the Object Oriented facilities -better suited. You can read [the OO tutorial](@ref oo-tutorial) to learn -more about that. - -Addidtionally, you may wish to learn more about the internals of the -stream wrapper, the main class, -`\OpenStack\ObjectStore\v1\Resource\StreamWrapper`, is well-documented. diff --git a/doc/style.md b/doc/style.md deleted file mode 100644 index a155ab2..0000000 --- a/doc/style.md +++ /dev/null @@ -1,120 +0,0 @@ -Coding and Documentation Style Guide {#styleguide} -==================================== - -This guide explain coding style, coding structure, and documentation -style. - -## TL;DR - -- Read the [coding standards](https://github.com/mattfarina/Coding-Standards) - to learn why we code the way we do. -- Read about [PHPDoc](http://www.phpdoc.org/) - if you're curious about our source code documentation. -- Two spaces, no tabs. -- WE LOVE GITHUB ISSUES AND PULL REQUESTS - -## Code Style - -The code in this package rigidly conforms to a given coding standard. -The standard we use is published here and is based -on the Drupal coding standards, the PEAR coding standards, and several -other popular standards. - -Important highlights: - -- Indentation uses *two space characters* and no tabs. -- Variables and class names use CamelCasing (details above). - -Please do your best to follow coding standards when submitting patches. - -### Object Orientation - -We have chosen to give the library a strongly object-oriented flavor. -However, the stream wrapper integration provides a procudural interface -to some of the services. - -### Design Patterns and Coding Practices - -Where applicable, we use established coding patterns and practices. Some -are PHP specific (like stream wrappers), while most enjoy broader -industry support. - -There are a few things a developer should be aware of: - -**Accessors and Mutators**: The naming convention for methods on an -object are as follows: - -- A function that accesses an object's data is a *noun*. Thus, the color - of a fictional `Pillow` object may be accessed using - `Pillow::color()`. -- A function that performs an action is verbal, and this includes - mutator functions (so-called "setters"). Thus, - `Pillow::changeColor()`, `Pillow::setColor()`, and `Pillow::fight()` may - all be appropriate mutator names. -- Unless a contract (interface or superclass) requires it, we do not ever - define "getters". - -**Constructor Functions**: PHP does not support method overloading -(that is, declaring multiple methods with the same name, but different -signatures). While some languages (viz. Java, C++, C#) allow more than -one constructor, PHP limits you to just one constructor. - -One strategy for working around this is to create constructors that take -vague or generic parameters, and then perform various inspection tasks -to figure out what the parameters are: - -~~~{.php} - -~~~ - -The above quickly degenerates into code that is both slow -(because of the inspection tasks) and difficult to read and use. - -Another option, following Objective-C and Vala, is to create constructor -functions. These are static functions (in PHP, at least) that can build -instances. Constructor functions have signatures like -`Pillow::newFromJSON()` and `Pillow::newFromXML()`. - -*This library uses constructor functions.* Generally, a very basic -constructor is provided for cases where it is needed, but more complex -cases are handled with specialized constructor functions. - -**Namespaces**: The library has been divided up into namespaces -according to the following principles: - -- Each broad service category should have a namespace. Currently, the - service categories are *Services* and *Storage*. - * Services handle computing tasks on behalf of the client. - * Storage handles data storage and retrieval -- Individual services and storage services may have their own namespace - if the number of supporting classes requires this. -- The *Transport* namespace deals with lower-level details that are - shared across all services. - -Otherwise, we have attempted to keep the namespace relatively shallow. - -### Balancing Performance and Elegance - -Any network-based library must struggle with the inefficiencies of -working over a network. This library is no exception. We've done our -best to straddle the line between keeping load times down and making the -code simple and elegant. Doubtless we have sometimes failed. Please feel -free to suggest improvements on either side of the scales. - -## Documentation Style - -This project is documented with PHPDoc. diff --git a/phpunit.xml.dist b/phpunit.xml.dist deleted file mode 100644 index 2cc04ad..0000000 --- a/phpunit.xml.dist +++ /dev/null @@ -1,21 +0,0 @@ - - - - - tests/Tests/ - - - - - - - \ No newline at end of file diff --git a/src/OpenStack/Bootstrap.php b/src/OpenStack/Bootstrap.php deleted file mode 100644 index 9b9d83d..0000000 --- a/src/OpenStack/Bootstrap.php +++ /dev/null @@ -1,298 +0,0 @@ - 'OpenStack\Common\Transport\Guzzle\GuzzleAdapter', - * // Set the HTTP max wait time to 500 seconds. - * 'transport.timeout' => 500, - * ); - * Bootstrap::setConfiguration($config); - * - * // Check and get params. - * if (Bootstrap::hasConf('transport.timeout') { - * $to = Bootstrap::conf('transport.timeout'); - * } - * - * // Or get a param with a default value: - * $val = Bootstrap::conf('someval', 'default value'); - * - * // $val will be set to 'default value' because there - * // is no 'someval' configuration param. - * - * ?> - * - * STREAM WRAPPERS - * - * Stream wrappers allow you to use the built-in file manipulation - * functions in PHP to interact with other services. Specifically, - * the OpenStack stream wrappers allow you to use built-in file commands - * to access Object Storage (Swift) and other OpenStack services using - * commands like file_get_contents() and fopen(). - * - * It's awesome. Trust me. - */ -class Bootstrap -{ - const VERSION = '0.0.1'; - - public static $config = [ - // The transport implementation. By default, we use the Guzzle Client - 'transport' => 'OpenStack\Common\Transport\Guzzle\GuzzleAdapter', - ]; - - /** - * @var \OpenStack\Identity\v2\IdentityService An identity services object - * created from the global settings. - */ - public static $identity = null; - - /** - * @var \OpenStack\Common\Transport\ClientInterface A transport client for requests. - */ - public static $transport = null; - - /** - * Register stream wrappers for OpenStack. - * - * This registers the ObjectStorage stream wrappers, which allow you to access - * ObjectStorage through standard file access mechanisms. - * - * // Enable stream wrapper. - * Bootstrap::useStreamWrappers(); - * - * // Create a context resource. - * $cxt = stream_context_create(array( - * 'tenantid' => '12de21', - * 'username' => 'foobar', - * 'password' => 'f78saf7hhlll', - * 'endpoint' => 'https://identity.hpcloud.com' // <-- not real URL! - * )); - * - * // Get the contents of a Swift object. - * $content = file_get_contents('swift://public/notes.txt', 'r', false, $cxt); - */ - public static function useStreamWrappers() - { - self::enableStreamWrapper( - StreamWrapper::DEFAULT_SCHEME, - 'OpenStack\ObjectStore\v1\Resource\StreamWrapper' - ); - self::enableStreamWrapper( - StreamWrapperFS::DEFAULT_SCHEME, - 'OpenStack\ObjectStore\v1\Resource\StreamWrapperFS' - ); - } - - /** - * Register a stream wrapper according to its scheme and class - * - * @param $scheme Stream wrapper's scheme - * @param $class The class that contains stream wrapper functionality - */ - private static function enableStreamWrapper($scheme, $class) - { - if (in_array($scheme, stream_get_wrappers())) { - stream_wrapper_unregister($scheme); - } - - stream_wrapper_register($scheme, $class); - } - - /** - * Set configuration directives for OpenStack. - * - * This merges the provided associative array into the existing - * configuration parameters (Bootstrap::$config). - * - * All of the OpenStack classes share the same configuration. This - * ensures that a stable runtime environment is maintained. - * - * Common configuration directives: - * - * - 'transport': The namespaced classname for the transport that - * should be used. Example: \OpenStack\Common\Transport\Guzzle\GuzzleAdapter - * - 'transport.debug': The integer 1 for enabling debug, 0 for - * disabling. Enabling will turn on verbose debugging output - * for any transport that supports it. - * - 'transport.timeout': An integer value indicating how long - * the transport layer should wait for an HTTP request. A - * transport MAY ignore this parameter, but the ones included - * with the library honor it. - * - 'transport.ssl_verify': Set this to false to turn off SSL certificate - * verification. This is NOT recommended, but is sometimes necessary for - * certain proxy configurations. - * - 'transport.proxy': Set the proxy as a string. - * - 'username' and 'password' - * - 'tenantid' - * - 'endpoint': The full URL to identity services. This is used by stream - * wrappers. - * - * @param array $array An associative array of configuration directives. - */ - public static function setConfiguration($array) - { - self::$config = $array + self::$config; - } - - /** - * Get a configuration option. - * - * Get a configuration option by name, with an optional default. - * - * @param string $name The name of the configuration option to get. - * @param mixed $default The default value to return if the name is not found. - * - * @return mixed The value, if found; or the default, if set; or null. - */ - public static function config($name = null, $default = null) - { - // If no name is specified, return the entire config array. - if (empty($name)) { - return self::$config; - } - - // If the config value exists, return that. - if (isset(self::$config[$name])) { - return self::$config[$name]; - } - - // Otherwise, just return the default value. - return $default; - } - - /** - * Check whether the given configuration option is set. - * - * if (Bootstrap::hasConfig('transport')) { - * syslog(LOG_INFO, 'An alternate transport is supplied.'); - * } - * - * @param string $name The name of the item to check for. - * - * @return boolean true if the named option is set, false otherwise. Note that - * the value may be falsey (false, 0, etc.), but if the value is null, this - * will return false. - */ - public static function hasConfig($name) - { - return isset(self::$config[$name]); - } - - /** - * Get a \OpenStack\Identity\v2\IdentityService object from the bootstrap config. - * - * A factory helper function that uses the bootstrap configuration to create - * a ready to use \OpenStack\Identity\v2\IdentityService object. - * - * @param bool $force Whether to force the generation of a new object even if - * one is already cached. - * - * @return \OpenStack\Identity\v2\IdentityService An authenticated ready to use - * \OpenStack\Identity\v2\IdentityService object. - * @throws \OpenStack\Common\Exception When the needed configuration to authenticate - * is not available. - */ - public static function identity($force = false) - { - $transport = self::transport(); - - // If we already have an identity make sure the token is not expired. - if ($force || is_null(self::$identity) || self::$identity->isExpired()) { - - // Make sure we have an endpoint to use - if (!self::hasConfig('endpoint')) { - throw new Exception('Unable to authenticate. No endpoint supplied.'); - } - - // User cannot be an empty string, so we need - // to do more checking than self::hasConfig(), which returns true - // if an item exists and is an empty string. - $user = self::config('username', null); - - // Check if we have a username/password - if (!empty($user) && self::hasConfig('password')) { - $is = new IdentityService(self::config('endpoint'), $transport); - $is->authenticateAsUser($user, self::config('password'), self::config('tenantid', null), self::config('tenantname', null)); - self::$identity = $is; - } else { - throw new Exception('Unable to authenticate. No user credentials supplied.'); - } - } - - return self::$identity; - } - - /** - * Get a transport client. - * - * @param boolean $reset Whether to recreate the transport client if one already exists. - * - * @return \OpenStack\Common\Transport\ClientInterface A transport client. - */ - public static function transport($reset = false) - { - if (is_null(self::$transport) || $reset == true) { - $options = [ - 'ssl_verify' => self::config('ssl_verify', true), - 'timeout' => self::config('timeout', 0), // 0 is no timeout. - 'debug' => self::config('debug', 0), - ]; - $proxy = self::config('proxy', false); - if ($proxy) { - $options['proxy'] = $proxy; - } - - $class = self::config('transport'); - self::$transport = $class::create($options); - } - - return self::$transport; - } -} diff --git a/src/OpenStack/Common/Exception.php b/src/OpenStack/Common/Exception.php deleted file mode 100644 index 731827a..0000000 --- a/src/OpenStack/Common/Exception.php +++ /dev/null @@ -1,27 +0,0 @@ -send($this->createRequest('GET', $uri, null, $options)); - } - - public function head($uri, array $options = []) - { - return $this->send($this->createRequest('HEAD', $uri, null, $options)); - } - - public function post($uri, $body = null, array $options = []) - { - return $this->send($this->createRequest('POST', $uri, $body, $options)); - } - - public function put($uri, $body = null, array $options = []) - { - return $this->send($this->createRequest('PUT', $uri, $body, $options)); - } - - public function delete($uri, array $options = []) - { - return $this->send($this->createRequest('DELETE', $uri, null, $options)); - } -} \ No newline at end of file diff --git a/src/OpenStack/Common/Transport/ClientInterface.php b/src/OpenStack/Common/Transport/ClientInterface.php deleted file mode 100644 index 2768061..0000000 --- a/src/OpenStack/Common/Transport/ClientInterface.php +++ /dev/null @@ -1,138 +0,0 @@ -getStatusCode()); - - $this->request = $request; - $this->response = $response; - } - - /** - * Factory method that creates an appropriate Exception object based on the - * Response's status code. The message is constructed here also. - * - * @param \OpenStack\Common\Transport\RequestInterface $request The failed request - * @param \OpenStack\Common\Transport\ResponseInterface $response The API's response - * @return self - */ - public static function create(RequestInterface $request, ResponseInterface $response) - { - $label = 'A HTTP error occurred'; - - $status = $response->getStatusCode(); - - $exceptions = [ - 401 => 'UnauthorizedException', - 403 => 'ForbiddenException', - 404 => 'ResourceNotFoundException', - 405 => 'MethodNotAllowedException', - 409 => 'ConflictException', - 411 => 'LengthRequiredException', - 422 => 'UnprocessableEntityException', - 500 => 'ServerException' - ]; - - $message = sprintf( - "%s\n[Status] %s (%s)\n[URL] %s\n[Message] %s\n", $label, - (string) $request->getUrl(), - $status, $response->getReasonPhrase(), - (string) $response->getBody() - ); - - // Find custom exception class or use default - $exceptionClass = isset($exceptions[$status]) - ? sprintf("%s\\%s", __NAMESPACE__, $exceptions[$status]) - : __CLASS__; - - return new $exceptionClass($message, $request, $response); - } - - /** - * Returns the server response. - * - * @return \OpenStack\Common\Transport\ResponseInterface - */ - public function getResponse() - { - return $this->response; - } - - /** - * Returns the request that caused error. - * - * @return \OpenStack\Common\Transport\RequestInterface - */ - public function getRequest() - { - return $this->request; - } -} \ No newline at end of file diff --git a/src/OpenStack/Common/Transport/Exception/ResourceNotFoundException.php b/src/OpenStack/Common/Transport/Exception/ResourceNotFoundException.php deleted file mode 100644 index f4aacda..0000000 --- a/src/OpenStack/Common/Transport/Exception/ResourceNotFoundException.php +++ /dev/null @@ -1,30 +0,0 @@ - false, - 'subscribers' => [new HttpError()], - 'headers' => ['User-Agent' => self::getDefaultUserAgent()] - ]; - - // Inject client and pass in options for adapter - return new self(new Client($options)); - } - - /** - * Instantiate a new Adapter which wraps a Guzzle client. - * - * @param \GuzzleHttp\ClientInterface $guzzle The Client being wrapped - */ - public function __construct(GuzzleClientInterface $guzzle) - { - $this->client = $guzzle; - } - - public function createRequest($method, $uri = null, $body = null, array $options = []) - { - $headers = isset($options['headers']) ? $options['headers'] : []; - - $request = $this->client->createRequest($method, $uri, [ - 'headers' => $headers, - 'body' => $body, - ]); - - return new RequestAdapter($request); - } - - /** - * @inheritDoc - * @param \OpenStack\Common\Transport\RequestInterface $adapter - * @return \OpenStack\Common\Transport\ResponseInterface - * @throws \OpenStack\Common\Transport\Exception\RequestException - * @throws \GuzzleHttp\Exception\RequestException - */ - public function send(RequestInterface $adapter) - { - try { - $guzzleResponse = $this->client->send($adapter->getMessage()); - return new ResponseAdapter($guzzleResponse); - } catch (GuzzleRequestException $e) { - // In order to satisfy {@see GuzzleHttp\ClientInterface}, Guzzle - // wraps all exceptions in its own RequestException class. This is - // not useful for our end-users, so we need to make sure our own - // versions are returned (Guzzle buffers them). - $previous = $e->getPrevious(); - if ($previous instanceof Exception\RequestException) { - throw $previous; - } - throw $e; - } - } - - /** - * Guzzle handles options using the defaults/ prefix. So if a key is passed - * in to be set, or got, that contains this prefix - assume that its a - * Guzzle option, not an adapter one. - * - * @inheritDoc - */ - public function setOption($key, $value) - { - $this->client->setDefaultOption($key, $value); - } - - /** - * Guzzle handles options using the defaults/ prefix. So if a key is passed - * in to be set, or got, that contains this prefix - assume that its a - * Guzzle option, not an adapter one. - * - * @inheritDoc - */ - public function getOption($key) - { - if ($key == 'base_url') { - return $this->getBaseUrl(); - } else { - return $this->client->getDefaultOption($key); - } - } - - public function getBaseUrl() - { - return $this->client->getBaseUrl(); - } - - /** - * Prepends the SDK's version number to the standard Guzzle string. - * - * @return string - */ - public static function getDefaultUserAgent() - { - return sprintf("OpenStack/%f %s", Bootstrap::VERSION, Client::getDefaultUserAgent()); - } -} \ No newline at end of file diff --git a/src/OpenStack/Common/Transport/Guzzle/HttpError.php b/src/OpenStack/Common/Transport/Guzzle/HttpError.php deleted file mode 100644 index f279187..0000000 --- a/src/OpenStack/Common/Transport/Guzzle/HttpError.php +++ /dev/null @@ -1,57 +0,0 @@ - ['onComplete', RequestEvents::VERIFY_RESPONSE]]; - } - - /** - * When a request completes, this method is executed. Because this class - * checks for HTTP errors and handles them, this method checks the HTTP - * status code and invokes {@see RequestException} if necessary. - * - * @param CompleteEvent $event - * @throws \OpenStack\Common\Transport\Exception\RequestException - */ - public function onComplete(CompleteEvent $event) - { - $status = (int) $event->getResponse()->getStatusCode(); - - // Has an error occurred (4xx or 5xx status)? - if ($status >= 400 && $status <= 505) { - $request = new RequestAdapter($event->getRequest()); - $response = new ResponseAdapter($event->getResponse()); - throw RequestException::create($request, $response); - } - } -} \ No newline at end of file diff --git a/src/OpenStack/Common/Transport/Guzzle/MessageAdapter.php b/src/OpenStack/Common/Transport/Guzzle/MessageAdapter.php deleted file mode 100644 index 266792c..0000000 --- a/src/OpenStack/Common/Transport/Guzzle/MessageAdapter.php +++ /dev/null @@ -1,118 +0,0 @@ -setMessage($guzzleMessage); - } - - /** - * This sets the Guzzle object being wrapped. - * - * @param \GuzzleHttp\Message\MessageInterface $guzzleMessage The object being wrapped. - */ - public function setMessage(GuzzleMessageInterface $guzzleMessage) - { - $this->message = $guzzleMessage; - } - - /** - * @return \GuzzleHttp\Message\MessageInterface - */ - public function getMessage() - { - return $this->message; - } - - public function getProtocolVersion() - { - return $this->message->getProtocolVersion(); - } - - public function getBody() - { - return $this->message->getBody(); - } - - public function setBody(/* StreamInterface */ $body = null) - { - $this->message->setBody($body); - } - - public function getHeaders() - { - return $this->message->getHeaders(); - } - - public function hasHeader($header) - { - return $this->message->hasHeader($header); - } - - public function getHeader($header, $asArray = false) - { - return $this->message->getHeader($header, $asArray); - } - - public function setHeader($header, $value) - { - $this->message->setHeader($header, $value); - } - - public function setHeaders(array $headers) - { - $this->message->setHeaders($headers); - } - - public function addHeader($header, $value) - { - $this->message->addHeader($header, $value); - } - - public function addHeaders(array $headers) - { - $this->message->addHeaders($headers); - } - - public function removeHeader($header) - { - $this->message->removeHeader($header); - } -} \ No newline at end of file diff --git a/src/OpenStack/Common/Transport/Guzzle/RequestAdapter.php b/src/OpenStack/Common/Transport/Guzzle/RequestAdapter.php deleted file mode 100644 index 098f381..0000000 --- a/src/OpenStack/Common/Transport/Guzzle/RequestAdapter.php +++ /dev/null @@ -1,55 +0,0 @@ -setMessage($guzzleRequest); - } - - public function getMethod() - { - return $this->message->getMethod(); - } - - public function setMethod($method) - { - $this->message->setMethod($method); - } - - public function getUrl() - { - return $this->message->getUrl(); - } - - public function setUrl($url) - { - $this->message->setUrl($url); - } -} \ No newline at end of file diff --git a/src/OpenStack/Common/Transport/Guzzle/ResponseAdapter.php b/src/OpenStack/Common/Transport/Guzzle/ResponseAdapter.php deleted file mode 100644 index 39db781..0000000 --- a/src/OpenStack/Common/Transport/Guzzle/ResponseAdapter.php +++ /dev/null @@ -1,50 +0,0 @@ -setMessage($guzzleResponse); - } - - public function getStatusCode() - { - return $this->message->getStatusCode(); - } - - public function getReasonPhrase() - { - return $this->message->getReasonPhrase(); - } - - public function json() - { - return $this->message->json(); - } -} \ No newline at end of file diff --git a/src/OpenStack/Common/Transport/MessageInterface.php b/src/OpenStack/Common/Transport/MessageInterface.php deleted file mode 100644 index c82a233..0000000 --- a/src/OpenStack/Common/Transport/MessageInterface.php +++ /dev/null @@ -1,160 +0,0 @@ -getHeaders() as $name => $values) { - * echo $name . ": " . implode(", ", $values); - * } - * - * @return array Returns an associative array of the message's headers. - */ - public function getHeaders(); - - /** - * Checks if a header exists by the given case-insensitive name. - * - * @param string $header Case-insensitive header name. - * - * @return bool Returns true if any header names match the given header - * name using a case-insensitive string comparison. Returns false if - * no matching header name is found in the message. - */ - public function hasHeader($header); - - /** - * Retrieve a header by the given case-insensitive name. - * - * By default, this method returns all of the header values of the given - * case-insensitive header name as a string concatenated together using - * a comma. Because some header should not be concatenated together using a - * comma, this method provides a Boolean argument that can be used to - * retrieve the associated header values as an array of strings. - * - * @param string $header Case-insensitive header name. - * @param bool $asArray Set to true to retrieve the header value as an - * array of strings. - * - * @return array|string - */ - public function getHeader($header, $asArray = false); - - /** - * Sets a header, replacing any existing values of any headers with the - * same case-insensitive name. - * - * The header values MUST be a string or an array of strings. - * - * @param string $header Header name - * @param string|array $value Header value(s) - * - * @return self Returns the message. - */ - public function setHeader($header, $value); - - /** - * Sets headers, replacing any headers that have already been set on the - * message. - * - * The array keys MUST be a string. The array values must be either a - * string or an array of strings. - * - * @param array $headers Headers to set. - * - * @return self Returns the message. - */ - public function setHeaders(array $headers); - - /** - * Appends a header value to any existing values associated with the - * given header name. - * - * @param string $header Header name to add - * @param string $value Value of the header - * - * @return self - */ - public function addHeader($header, $value); - - /** - * Merges in an associative array of headers. - * - * Each array key MUST be a string representing the case-insensitive name - * of a header. Each value MUST be either a string or an array of strings. - * For each value, the value is appended to any existing header of the same - * name, or, if a header does not already exist by the given name, then the - * header is added. - * - * @param array $headers Associative array of headers to add to the message - * - * @return self - */ - public function addHeaders(array $headers); - - /** - * Remove a specific header by case-insensitive name. - * - * @param string $header HTTP header to remove - * - * @return self - */ - public function removeHeader($header); -} \ No newline at end of file diff --git a/src/OpenStack/Common/Transport/RequestInterface.php b/src/OpenStack/Common/Transport/RequestInterface.php deleted file mode 100644 index 6033f29..0000000 --- a/src/OpenStack/Common/Transport/RequestInterface.php +++ /dev/null @@ -1,66 +0,0 @@ -populateFromArray($value); - } - - /** - * Internal method that allows for the hydration of this object with an - * array. It iterates through each element and calls the necessary setter - * method if it exists. - * - * @param array $array The input array - */ - private function populateFromArray(array $array) - { - foreach ($array as $key => $val) { - if ($key == 'pass') { - $key = 'password'; - } - $method = 'set' . $key; - if ($val && method_exists($this, $method)) { - $this->$method($val); - } - } - } - - /** - * @param string $scheme - */ - public function setScheme($scheme) - { - $this->scheme = (string)$scheme; - } - - /** - * @return string - */ - public function getScheme() - { - return $this->scheme; - } - - /** - * @param string $host - */ - public function setHost($host) - { - $this->host = (string)$host; - } - - /** - * @return string - */ - public function getHost() - { - return $this->host; - } - - /** - * @param int $port - */ - public function setPort($port) - { - $this->port = (int)$port; - } - - /** - * @return int|null - */ - public function getPort() - { - return $this->port; - } - - /** - * @param string $user - */ - public function setUser($user) - { - $this->user = (string)$user; - } - - /** - * @return string - */ - public function getUser() - { - return $this->user; - } - - /** - * @param string $password - */ - public function setPassword($password) - { - $this->password = (string)$password; - } - - /** - * @return string - */ - public function getPassword() - { - return $this->password; - } - - /** - * Sets the path to a string value, ensuring that a trailing slash is always - * added. - * - * @param string $path - */ - public function setPath($path) - { - $this->path = rtrim((string)$path, '/'); - } - - /** - * Adds a string path to the existing path value. - * - * @param string $path - */ - public function addPath($path) - { - $path = '/' . ltrim((string)$path, '/'); - $this->setPath($this->path . $path); - } - - /** - * @return string - */ - public function getPath() - { - return $this->path; - } - - /** - * Sets the query value. If a string is provided, it is expanded according - * to conventional key=pair representation, where `&' is a delimeter. An - * array can also be provided. - * - * @param string|array $query - * - * @throws \InvalidArgumentException - */ - public function setQuery($query) - { - if (is_string($query)) { - $query = $this->expandQueryString($query); - } elseif (!is_array($query)) { - throw new \InvalidArgumentException("Query must be an array"); - } - - $this->query = $query; - } - - /** - * Internal method for expanding a string representation of a query into an - * array. The return value should be a simple key/value pair. Query arrays - * are also supported. - * - * @param string $value A string based query representation, in the form of - * ?foo=val&bar=val&baz[]=val_1&baz[]=val_2 - * - * @return array - */ - private function expandQueryString($value) - { - $parts = explode('&', $value); - $array = []; - foreach ($parts as $partArray) { - $inner = explode('=', $partArray); - $key = str_replace('[]', '', $inner[0]); - $val = $inner[1]; - - if (isset($array[$key])) { - $array[$key] = [$array[$key], $val]; - } else { - $array[$key] = $val; - } - } - - return $array; - } - - /** - * @param array $query - */ - public function addQuery(array $query) - { - $this->setQuery((array)$this->query + $query); - } - - /** - * @return array - */ - public function getQuery() - { - return $this->query; - } - - /** - * @param string $fragment - */ - public function setFragment($fragment) - { - $this->fragment = (string)$fragment; - } - - /** - * @return string - */ - public function getFragment() - { - return $this->fragment; - } - - /** - * Shrinks the query array and returns as a string representation. - * - * @return string - */ - private function shrinkQueryArray() - { - $url = '?'; - foreach ($this->query as $key => $val) { - if (is_array($val)) { - foreach ($val as $subVal) { - $url .= $key . '[]=' . $subVal . '&'; - } - } else { - $url .= $key . '=' . $val . '&'; - } - } - - return rtrim($url, '&'); - } - - /** - * Cast this URL object into a string representation - * - * @return string - */ - public function __toString() - { - $url = ($this->scheme) ? $this->scheme . '://' : '//'; - - if ($this->user && $this->password) { - $url .= sprintf("%s:%s@", $this->user, $this->password); - } - - $url .= $this->host; - - if ($this->port) { - $url .= ':' . (int)$this->port; - } - - $url .= $this->path; - - if (!empty($this->query)) { - $url .= $this->shrinkQueryArray(); - } - - if ($this->fragment) { - $url .= '#' . $this->fragment; - } - - return $url; - } -} \ No newline at end of file diff --git a/src/OpenStack/Identity/v2/IdentityService.php b/src/OpenStack/Identity/v2/IdentityService.php deleted file mode 100644 index 5f19ca3..0000000 --- a/src/OpenStack/Identity/v2/IdentityService.php +++ /dev/null @@ -1,740 +0,0 @@ -authenticateAsUser('me@example.com', 'password', '1234567'); - * - * // The token to use when connecting to other services: - * $token = $ident->token(); - * - * // The tenant ID. - * $tenant = $ident->tenantId(); - * - * // Details about what services this token can access. - * $services = $ident->serviceCatalog(); - * - * // List all available tenants. - * $tenants = $ident->tenants(); - * - * // Switch to a different tenant. - * $ident->rescopeUsingTenantId($tenants[0]['id']); - * - * ?> - * - * PERFORMANCE CONSIDERATIONS - * - * The following methods require network requests: - * - * - authenticate() - * - authenticateAsUser() - * - tenants() - * - rescopeUsingTenantId() - * - rescopeUsingTenantName() - * - * Serializing - * - * IdentityService has been intentionally built to serialize well. - * This allows implementors to cache IdentityService objects rather - * than make repeated requests for identity information. - * - */ -class IdentityService -{ - /** - * The version of the API currently supported. - */ - const API_VERSION = '2.0'; - - /** - * The full OpenStack accept type. - */ - const ACCEPT_TYPE = 'application/json'; - - // This is no longer supported. - //const ACCEPT_TYPE = 'application/vnd.openstack.identity+json;version=2.0'; - - /** - * The URL to the CS endpoint. - */ - protected $endpoint; - - /** - * The details sent with the token. - * - * The exact details of this array will differ depending on what type of - * authentication is used. For example, authenticating by username and - * password will set tenant information. Authenticating by username and - * password, however, will leave the tenant section empty. - * - * This is an associative array looking like this: - * - * 'auth_123abc321defef99', - * // Only non-empty for username/password auth. - * 'tenant' => array( - * 'id' => '123456', - * 'name' => 'matt.butcher@hp.com', - * ), - * 'expires' => '2012-01-24T12:46:01.682Z' - * ); - */ - protected $tokenDetails; - - /** - * The service catalog. - */ - protected $catalog = []; - - protected $userDetails; - - /** - * The HTTP Client - */ - protected $client; - - /** - * Build a new IdentityService object. - * - * Each object is bound to a particular identity services endpoint. - * - * For the URL, you are advised to use the version without a - * version number at the end, e.g. http://cs.example.com/ rather - * than http://cs.example.com/v2.0. The version number must be - * controlled by the library. - * - * If a version is included in the URI, the library will attempt to use - * that URI. - * - * authenticateAsUser($username, $password); - * ?> - * - * @param string $url An URL pointing to the Identity Service endpoint. - * Note that you do not need the version identifier in the URL, as version - * information is sent in the HTTP headers rather than in the URL. The URL - * should always be to an SSL/TLS encrypted endpoint. - * - * @param \OpenStack\Common\Transport\ClientInterface $client An optional HTTP client to use when making the requests. - */ - public function __construct($url, ClientInterface $client = null) - { - $parts = parse_url($url); - - if (!empty($parts['path'])) { - $this->endpoint = rtrim($url, '/'); - } else { - $this->endpoint = rtrim($url, '/') . '/v' . self::API_VERSION; - } - - // Guzzle is the default client to use. - if (is_null($client)) { - $this->client = GuzzleAdapter::create(); - } else { - $this->client = $client; - } - } - - /** - * Get the endpoint URL. - * - * This includes version number, so in that regard it is not an identical - * URL to the one passed into the constructor. - * - * @return string The complete URL to the identity services endpoint. - */ - public function url() - { - return $this->endpoint; - } - - /** - * Send an authentication request. - * - * EXPERT: This allows authentication requests at a low level. For simple - * authentication requests using a username, see the - * authenticateAsUser() method. - * - * Here is an example of username/password-based authentication done with - * the authenticate() method: - * - * array( - * 'username' => $username, - * 'password' => $password, - * ), - * 'tenantId' => $tenantId, - * ); - * $token = $cs->authenticate($ops); - * ?> - * - * Note that the same authentication can be done by authenticateAsUser(). - * - * @param array $ops An associative array of authentication operations and - * their respective parameters. - * - * @return string The token. This is returned for simplicity. The full - * response is used to populate this object's service catalog, etc. The - * token is also retrievable with token(). - * - * @throws \OpenStack\Common\Transport\Exception\AuthorizationException If authentication failed. - * @throws \OpenStack\Common\Exception For abnormal network conditions. The message - * will give an indication as to the underlying problem. - */ - public function authenticate(array $ops) - { - $url = $this->url() . '/tokens'; - $envelope = [ - 'auth' => $ops, - ]; - - $body = json_encode($envelope); - - $headers = [ - 'Content-Type' => 'application/json', - 'Accept' => self::ACCEPT_TYPE, - 'Content-Length' => strlen($body), - ]; - - $response = $this->client->post($url, $body, ['headers' => $headers]); - - $this->handleResponse($response); - - return $this->token(); - } - - /** - * Authenticate to Identity Services with username, password, and either - * tenant ID or tenant Name. - * - * Given a OpenStack username and password, authenticate to Identity Services. - * Identity Services will then issue a token that can be used to access other - * OpenStack services. - * - * If a tenant ID is provided, this will also associate the user with the - * given tenant ID. If a tenant Name is provided, this will associate the user - * with the given tenant Name. Only the tenant ID or tenant Name needs to be - * given, not both. - * - * If no tenant ID or tenant Name is given, it will likely be necessary to - * rescopeUsingTenantId() the request (See also tenants()). - * - * Other authentication methods: - * - authenticate() - * - * @param string $username A valid username. - * @param string $password A password string. - * @param string $tenantId The tenant ID. This can be obtained through the - * OpenStack console. - * @param string $tenantName The tenant Name. This can be obtained through the - * OpenStack console. - * - * @throws \OpenStack\Common\Transport\Exception\AuthorizationException If authentication failed. - * @throws \OpenStack\Common\Exception For abnormal network conditions. The message will give an - * indication as to the underlying problem. - */ - public function authenticateAsUser($username, $password, $tenantId = null, $tenantName = null) - { - $ops = [ - 'passwordCredentials' => [ - 'username' => $username, - 'password' => $password, - ] - ]; - - // If a tenant ID is provided, added it to the auth array. - if (!empty($tenantId)) { - $ops['tenantId'] = $tenantId; - } elseif (!empty($tenantName)) { - $ops['tenantName'] = $tenantName; - } - - return $this->authenticate($ops); - } - - /** - * Get the token. - * - * This will not be populated until after one of the authentication - * methods has been run. - * - * @return string The token ID to be used in subsequent calls. - */ - public function token() - { - return $this->tokenDetails['id']; - } - - /** - * Get the tenant ID associated with this token. - * - * If this token has a tenant ID, the ID will be returned. Otherwise, this - * will return null. - * - * This will not be populated until after an authentication method has been - * run. - * - * @return string The tenant ID if available, or null. - */ - public function tenantId() - { - if (!empty($this->tokenDetails['tenant']['id'])) { - return $this->tokenDetails['tenant']['id']; - } - } - - /** - * Get the tenant name associated with this token. - * - * If this token has a tenant name, the name will be returned. Otherwise, this - * will return null. - * - * This will not be populated until after an authentication method has been - * run. - * - * @return string The tenant name if available, or null. - */ - public function tenantName() - { - if (!empty($this->tokenDetails['tenant']['name'])) { - return $this->tokenDetails['tenant']['name']; - } - } - - /** - * Get the token details. - * - * This returns an associative array with several pieces of information - * about the token, including: - * - * - id: The token itself - * - expires: When the token expires - * - tenant_id: The tenant ID of the authenticated user. - * - tenant_name: The username of the authenticated user. - * - * 'auth_123abc321defef99', - * 'tenant' => array( - * 'id' => '123456', - * 'name' => 'matt.butcher@hp.com', - * ), - * 'expires' => '2012-01-24T12:46:01.682Z' - * ); - * - * This will not be populated until after authentication has been done. - * - * @return array An associative array of details. - */ - public function tokenDetails() - { - return $this->tokenDetails; - } - - /** - * Check whether the current identity has an expired token. - * - * This does not perform a round-trip to the server. Instead, it compares the - * machine's local timestamp with the server's expiration time stamp. A - * mis-configured machine timestamp could give spurious results. - * - * @return boolean This will return false if there is a current token and it - * has not yet expired (according to the date info). In all - * other cases it returns true. - */ - public function isExpired() - { - $details = $this->tokenDetails(); - - if (empty($details['expires'])) { - return true; - } - - $currentDateTime = new \DateTime('now'); - $expireDateTime = new \DateTime($details['expires']); - - return $currentDateTime > $expireDateTime; - } - - /** - * Get the service catalog, optionaly filtering by type. - * - * This returns the service catalog (largely unprocessed) that - * is returned during an authentication request. If a type is passed in, - * only entries of that type are returned. If no type is passed in, the - * entire service catalog is returned. - * - * The service catalog contains information about what services (if any) are - * available for the present user. Object storage (Swift) Compute instances - * (Nova) and other services will each be listed here if they are enabled - * for your user in the current tenant. Only services that have been turned on - * for the user on the tenant will be available. (That is, even if you *can* - * create a compute instance, until you have actually created one, it will not - * show up in this list.) - * - * One of the authentication methods MUST be run before obtaining the service - * catalog. - * - * The return value is an indexed array of associative arrays, where each assoc - * array describes an individual service. - * - * 'object-store', - * 'endpoints' => array( - * 'tenantId' => '123456', - * 'adminURL' => 'https://example.hpcloud.net/1.0', - * 'publicURL' => 'https://example.hpcloud.net/1.0/123456', - * 'region' => 'region-a.geo-1', - * 'id' => '1.0', - * ), - * ), - * array( - * 'name' => 'Identity', - * 'type' => 'identity' - * 'endpoints' => array( - * 'publicURL' => 'https://example.hpcloud.net/1.0/123456', - * 'region' => 'region-a.geo-1', - * 'id' => '2.0', - * 'list' => 'http://example.hpcloud.net/extension', - * ), - * ) - * - * ); - * ?> - * - * This will not be populated until after authentication has been done. - * - * Types: - * - * While this is by no means an exhaustive list, here are a few types that - * might appear in a service catalog (and upon which you can filter): - * - * - identity: Identity Services (i.e. Keystone) - * - compute: Compute instance (Nova) - * - object-store: Object Storage (Swift) - * - * Other services will be added. - * - * @todo Paging on the service catalog is not yet implemented. - * - * @return array An associative array representing the service catalog. - */ - public function serviceCatalog($type = null) - { - // If no type is specified, return the entire - // catalog. - if (empty($type)) { - return $this->serviceCatalog; - } - - $list = []; - foreach ($this->serviceCatalog as $entry) { - if ($entry['type'] == $type) { - $list[] = $entry; - } - } - - return $list; - } - - /** - * Get information about the currently authenticated user. - * - * This returns an associative array of information about the authenticated - * user, including the user's username and roles. - * - * The returned data is structured like this: - * - * 'matthew.butcher@hp.com', - * 'id' => '1234567890' - * 'roles' => array( - * array( - * 'name' => 'domainuser', - * 'serviceId' => '100', - * 'id' => '000100400010011', - * ), - * // One array for each role... - * ), - * ) - * ?> - * - * This will not have data until after authentication has been done. - * - * @return array An associative array, as described above. - */ - public function user() - { - return $this->userDetails; - } - - /** - * Get a list of all tenants associated with this account. - * - * If a valid token is passed into this object, the method can be invoked - * before authentication. However, if no token is supplied, this attempts - * to use the one returned by an authentication call. - * - * Returned data will follow this format: - * - * "395I91234514446", - * "name" => "Banking Tenant Services", - * "description" => "Banking Tenant Services for TimeWarner", - * "enabled" => true, - * "created" => "2011-11-29T16:59:52.635Z", - * "updated" => "2011-11-29T16:59:52.635Z", - * ), - * ); - * ?> - * - * Note that this method invokes a new request against the remote server. - * - * @return array An indexed array of tenant info. Each entry will be an - * associative array containing tenant details. - * - * @throws \OpenStack\Common\Transport\Exception\AuthorizationException If authentication failed. - * @throws \OpenStack\Common\Exception For abnormal network conditions. The message will give an - * indication as to the underlying problem. - */ - public function tenants($token = null) - { - $url = $this->url() . '/tenants'; - - if (empty($token)) { - $token = $this->token(); - } - - $headers = [ - 'X-Auth-Token' => $token, - 'Accept' => 'application/json' - ]; - - $response = $this->client->get($url, ['headers' => $headers]); - - return $response->json()['tenants']; - } - - /** - * Rescope the authentication token to a different tenant. - * - * Note that this will rebuild the service catalog and user information for - * the current object, since this information is sensitive to tenant info. - * - * An authentication token can be in one of two states: - * - * - unscoped: It has no associated tenant ID. - * - scoped: It has a tenant ID, and can thus access that tenant's services. - * - * This method allows you to do any of the following: - * - * - Begin with an unscoped token, and assign it a tenant ID. - * - Change a token from one tenant ID to another (re-scoping). - * - Remove the tenant ID from a scoped token (unscoping). - * - * @param string $tenantId The tenant ID that this present token should be - * bound to. If this is the empty string (`''`), the - * present token will be "unscoped" and its tenant - * ID will be removed. - * - * @return string The authentication token. - * - * @throws \OpenStack\Common\Transport\Exception\AuthorizationException If authentication failed. - * @throws \OpenStack\Common\Exception For abnormal network conditions. The message will give an - * indication as to the underlying problem. - */ - public function rescopeUsingTenantId($tenantId) - { - $url = $this->url() . '/tokens'; - - $body = json_encode([ - 'auth' => [ - 'tenantId' => $tenantId, - 'token' => [ - 'id' => $this->token(), - ] - ] - ]); - - $headers = [ - 'Accept' => self::ACCEPT_TYPE, - 'Content-Type' => 'application/json', - 'Content-Length' => strlen($body) - ]; - - $response = $this->client->post($url, $body, ['headers' => $headers]); - - $this->handleResponse($response); - - return $this->token(); - } - - /** - * Rescope the authentication token to a different tenant. - * - * Note that this will rebuild the service catalog and user information for - * the current object, since this information is sensitive to tenant info. - * - * An authentication token can be in one of two states: - * - * - unscoped: It has no associated tenant ID. - * - scoped: It has a tenant ID, and can thus access that tenant's services. - * - * This method allows you to do any of the following: - * - * - Begin with an unscoped token, and assign it a tenant ID. - * - Change a token from one tenant ID to another (re-scoping). - * - Remove the tenant ID from a scoped token (unscoping). - * - * @param string $tenantName The tenant name that this present token should be - * bound to. If this is the empty string (`''`), the - * present token will be "unscoped" and its tenant - * name will be removed. - * - * @return string The authentication token. - * - * @throws \OpenStack\Common\Transport\Exception\AuthorizationException If authentication failed. - * @throws \OpenStack\Common\Exception For abnormal network conditions. The message will - * give an indication as to the underlying problem. - */ - public function rescopeUsingTenantName($tenantName) - { - $url = $this->url() . '/tokens'; - - $body = json_encode([ - 'auth' => [ - 'tenantName' => $tenantName, - 'token' => [ - 'id' => $this->token() - ] - ] - ]); - - $headers = [ - 'Accept' => self::ACCEPT_TYPE, - 'Content-Type' => 'application/json', - 'Content-Length' => strlen($body) - ]; - - $response = $this->client->post($url, $body, ['headers' => $headers]); - - $this->handleResponse($response); - - return $this->token(); - } - - /** - * Given a response object, populate this object. - * - * This parses the JSON data and parcels out the data to the appropriate - * fields. - * - * @param \OpenStack\Common\Transport\ResponseInterface $response A response object. - * - * @return \OpenStack\Identity\v2\IdentityService $this for the current object so - * it can be used in chaining. - */ - protected function handleResponse($response) - { - $json = $response->json(); - - $this->tokenDetails = $json['access']['token']; - $this->userDetails = $json['access']['user']; - $this->serviceCatalog = $json['access']['serviceCatalog']; - - return $this; - } -} diff --git a/src/OpenStack/ObjectStore/v1/Exception/ContainerNotEmptyException.php b/src/OpenStack/ObjectStore/v1/Exception/ContainerNotEmptyException.php deleted file mode 100644 index ce254b8..0000000 --- a/src/OpenStack/ObjectStore/v1/Exception/ContainerNotEmptyException.php +++ /dev/null @@ -1,29 +0,0 @@ -serviceCatalog(); - $tok = $identity->token(); - - return self::newFromServiceCatalog($cat, $tok, $region, $client); - } - - /** - * Given a service catalog and a token, create an ObjectStorage instance. - * - * The IdentityService object contains a service catalog listing all of the - * services to which the present user has access. - * - * This builder can scan the catalog and generate a new ObjectStorage - * instance pointed to the first object storage endpoint in the catalog - * that matches the specified parameters. - * - * @param array $catalog The service catalog from IdentityService::serviceCatalog(). - * This can be either the entire catalog or a catalog - * filtered to just ObjectStorage::SERVICE_TYPE. - * @param string $authToken The auth token returned by IdentityService. - * @param string $region The Object Storage region - * @param \OpenStack\Common\Transport\ClientInterface $client The HTTP client - * - * - * @return \OpenStack\ObjectStore\v1\ObjectStorage A new ObjectStorage instance. - */ - public static function newFromServiceCatalog($catalog, $authToken, $region, \OpenStack\Common\Transport\ClientInterface $client = null) - { - $c = count($catalog); - for ($i = 0; $i < $c; ++$i) { - if ($catalog[$i]['type'] == self::SERVICE_TYPE) { - foreach ($catalog[$i]['endpoints'] as $endpoint) { - if (isset($endpoint['publicURL']) && $endpoint['region'] == $region) { - return new ObjectStorage($authToken, $endpoint['publicURL'], $client); - } - } - } - } - - return false; - - } - - /** - * Construct a new ObjectStorage object. - * - * Use this if newFromServiceCatalog() does not meet your needs. - * - * @param string $authToken A token that will be included in subsequent - * requests to validate that this client has authenticated - * correctly. - * @param string $url The URL to the endpoint. This typically is returned - * after authentication. - * @param \OpenStack\Common\Transport\ClientInterface $client The HTTP client - */ - public function __construct($authToken, $url, ClientInterface $client = null) - { - $this->token = $authToken; - $this->url = $url; - - // Guzzle is the default client to use. - if (is_null($client)) { - $this->client = GuzzleAdapter::create(); - } else { - $this->client = $client; - } - } - - /** - * Get the authentication token. - * - * @return string The authentication token. - */ - public function token() - { - return $this->token; - } - - /** - * Get the URL endpoint. - * - * @return string The URL that is the endpoint for this service. - */ - public function url() - { - return $this->url; - } - - /** - * Fetch a list of containers for this user. - * - * By default, this fetches the entire list of containers for the - * given user. If you have more than 10,000 containers (who - * wouldn't?), you will need to use $marker for paging. - * - * If you want more controlled paging, you can use $limit to indicate - * the number of containers returned per page, and $marker to indicate - * the last container retrieved. - * - * Containers are ordered. That is, they will always come back in the - * same order. For that reason, the pager takes $marker (the name of - * the last container) as a paging parameter, rather than an offset - * number. - * - * @todo For some reason, ACL information does not seem to be returned - * in the JSON data. Need to determine how to get that. As a - * stop-gap, when a container object returned from here has its ACL - * requested, it makes an additional round-trip to the server to - * fetch that data. - * - * @param int $limit The maximum number to return at a time. The default is - * -- brace yourself -- 10,000 (as determined by OpenStack. Implementations - * may vary). - * @param string $marker The name of the last object seen. Used when paging. - * - * @return array An associative array of containers, where the key is the - * container's name and the value is an \OpenStack\ObjectStore\v1\ObjectStorage\Container - * object. Results are ordered in server order (the order that the remote - * host puts them in). - */ - public function containers($limit = 0, $marker = null) - { - $url = $this->url() . '?format=json'; - - if ($limit > 0) { - $url .= sprintf('&limit=%d', $limit); - } - if (!empty($marker)) { - $url .= sprintf('&marker=%d', $marker); - } - - $headers = ['X-Auth-Token' => $this->token]; - $response = $this->client->get($url, ['headers' => $headers]); - $containers = $response->json(); - - $containerList = []; - foreach ($containers as $container) { - $cname = $container['name']; - $containerList[$cname] = Container::newFromJSON($container, $this->token(), $this->url(), $this->client); - } - - return $containerList; - } - - /** - * Get a single specific container. - * - * This loads only the named container from the remote server. - * - * @param string $name The name of the container to load. - * - * @return \OpenStack\ObjectStore\v1\Resource\Container A container. - * - * @throws \OpenStack\Common\Transport\Exception\ResourceNotFoundException if the named container is not - * found on the remote server. - */ - public function container($name) - { - $url = $this->url() . '/' . rawurlencode($name); - - $headers = ['X-Auth-Token' => $this->token()]; - $response = $this->client->head($url, ['headers' => $headers]); - - $status = $response->getStatusCode(); - - if ($status == 204) { - return Container::newFromResponse($name, $response, $this->token(), $this->url()); - } - - // If we get here, it's not a 404 and it's not a 204. - throw new Exception(sprintf("Unknown status: %d", $status)); - } - - /** - * Check to see if this container name exists. - * - * This method directly checks the remote server. Calling container() - * or containers() might be more efficient if you plan to work with - * the resulting container. - * - * @param string $name The name of the container to test. - * - * @return boolean true if the container exists, false if it does not. - * - * @throws \OpenStack\Common\Exception If an unexpected network error occurs. - */ - public function hasContainer($name) - { - try { - $container = $this->container($name); - } catch (ResourceNotFoundException $e) { - return false; - } - - return true; - } - - /** - * Create a container with the given name. - * - * This creates a new container on the ObjectStorage - * server with the name provided in $name. - * - * A boolean is returned when the operation did not generate an error - * condition. - * - * - true means that the container was created. - * - false means that the container was not created because it already - * exists. - * - * Any actual error will cause an exception to be thrown. These will - * be the HTTP-level exceptions. - * - * ACLs - * - * Swift supports an ACL stream that allows for specifying (with - * certain caveats) various levels of read and write access. However, - * there are two standard settings that cover the vast majority of - * cases. - * - * - Make the resource private: This grants read and write access to - * ONLY the creating user tenant. This is the default; it can also be - * specified with ACL::makeNonPublic(). - * - Make the resource public: This grants READ permission to any - * requesting host, yet only allows the creator to WRITE to the - * object. This level can be granted by ACL::makePublic(). - * - * Note that ACLs operate at a container level. Thus, marking a - * container public will allow access to ALL objects inside of the - * container. - * - * To find out whether an existing container is public, you can - * write something like this: - * - * container('my_container'); - * - * //Check the permission on the ACL: - * $boolean = $container->acl()->isPublic(); - * ?> - * - * For details on ACLs, see \OpenStack\ObjectStore\v1\Resource\ACL. - * - * @param string $name The name of the container. - * @param object $acl \OpenStack\ObjectStore\v1\Resource\ACL An access control - * list object. By default, a container is non-public - * (private). To change this behavior, you can add a - * custom ACL. To make the container publically - * readable, you can use this: \OpenStack\ObjectStore\v1\Resource\ACL::makePublic(). - * @param array $metadata An associative array of metadata to attach to the - * container. - * - * @return boolean true if the container was created, false if the container - * was not created because it already exists. - */ - public function createContainer($name, ACL $acl = null, $metadata = []) - { - $url = $this->url() . '/' . rawurlencode($name); - $headers = ['X-Auth-Token' => $this->token()]; - - if (!empty($metadata)) { - $prefix = Container::CONTAINER_METADATA_HEADER_PREFIX; - $headers += Container::generateMetadataHeaders($metadata, $prefix); - } - - // Add ACLs to header. - if (!empty($acl)) { - $headers += $acl->headers(); - } - - $data = $this->client->put($url, null, ['headers' => $headers]); - - $status = $data->getStatusCode(); - - if ($status == 201) { - return true; - } elseif ($status == 202) { - return false; - } else { - // According to the OpenStack docs, there are no other return codes. - throw new Exception('Server returned unexpected code: ' . $status); - } - } - - /** - * Alias of createContainer(). - * - * At present, there is no distinction in the Swift REST API between - * creating an updating a container. In the future this may change, so - * you are encouraged to use this alias in cases where you clearly intend - * to update an existing container. - */ - public function updateContainer($name, ACL $acl = null, $metadata = []) - { - return $this->createContainer($name, $acl, $metadata); - } - - /** - * Change the container's ACL. - * - * This will attempt to change the ACL on a container. If the - * container does not already exist, it will be created first, and - * then the ACL will be set. (This is a relic of the OpenStack Swift - * implementation, which uses the same HTTP verb to create a container - * and to set the ACL.) - * - * @param string $name The name of the container. - * @param object $acl \OpenStack\ObjectStore\v1\Resource\ACL An ACL. To make the - * container publically readable, use ACL::makePublic(). - * - * @return boolean true if the cointainer was created, false otherwise. - */ - public function changeContainerACL($name, ACL $acl) - { - // Oddly, the way to change an ACL is to issue the - // same request as is used to create a container. - return $this->createContainer($name, $acl); - } - - /** - * Delete an empty container. - * - * Given a container name, this attempts to delete the container in - * the object storage. - * - * The container MUST be empty before it can be deleted. If it is not, - * an \OpenStack\ObjectStore\v1\Exception\ContainerNotEmptyException will - * be thrown. - * - * @param string $name The name of the container. - * - * @return boolean true if the container was deleted, false if the container - * was not found (and hence, was not deleted). - * - * @throws \OpenStack\ObjectStore\v1\Exception\ContainerNotEmptyException if the container is not empty. - * - * @throws \OpenStack\Common\Exception if an unexpected response code is returned. While this should never happen on - * OpenStack servers, forks of OpenStack may choose to extend object storage in a way - * that results in a non-standard code. - */ - public function deleteContainer($name) - { - $url = $this->url() . '/' . rawurlencode($name); - - try { - $headers = ['X-Auth-Token' => $this->token()]; - $data = $this->client->delete($url, ['headers' => $headers]); - } catch (ResourceNotFoundException $e) { - return false; - } catch (ConflictException $e) { - // XXX: I'm not terribly sure about this. Why not just throw the - // ConflictException? - throw new ContainerNotEmptyException( - "Non-empty container cannot be deleted", - $e->getRequest(), - $e->getResponse() - ); - } - - $status = $data->getStatusCode(); - - // 204 indicates that the container has been deleted. - if ($status == 204) { - return true; - } else { - // OpenStacks documentation doesn't suggest any other return codes. - throw new Exception('Server returned unexpected code: ' . $status); - } - } - - /** - * Retrieve account info. - * - * This returns information about: - * - * - The total bytes used by this Object Storage instance (`bytes`). - * - The number of containers (`count`). - * - * @return array An associative array of account info. Typical keys are: - * - bytes: Bytes consumed by existing content. - * - containers: Number of containers. - * - objects: Number of objects. - * - * @throws \OpenStack\Common\Transport\Exception\AuthorizationException if the user credentials - * are invalid or have expired. - */ - public function accountInfo() - { - $headers = ['X-Auth-Token' => $this->token()]; - $response = $this->client->head($this->url(), ['headers' => $headers]); - - return [ - 'bytes' => $response->getHeader('X-Account-Bytes-Used', 0), - 'containers' => $response->getHeader('X-Account-Container-Count', 0), - 'objects' => $response->getHeader('X-Account-Container-Count', 0) - ]; - } -} diff --git a/src/OpenStack/ObjectStore/v1/Resource/ACL.php b/src/OpenStack/ObjectStore/v1/Resource/ACL.php deleted file mode 100644 index 774307b..0000000 --- a/src/OpenStack/ObjectStore/v1/Resource/ACL.php +++ /dev/null @@ -1,567 +0,0 @@ - - * - * Public read access is granted like this: - * - * - * - * (Note that in both cases, what is returned is an instance of an ACL with - * all of the necessary configuration done.) - * - * Sometimes you will need more sophisticated access control rules. The - * following grants READ access to anyone coming from an `example.com` - * domain, but grants WRITE access only to the account `admins:` - * - * addReferrer(ACL::READ, '*.example.com'); - * - * // Allow only people in the account 'admins' access to - * // write. - * $acl->addAccount(ACL::WRITE, 'admins'); - * - * // Allow example.com users to view the container - * // listings: - * $acl->allowListings(); - * - * ?> - * - * Notes - * - * - The current implementation does not do any validation of rules. - * This will likely change in the future. - * - There is discussion in OpenStack about providing a different or - * drastically improved ACL mechanism. This class would then be - * replaced by a new mechanism. - * - * For a detailed description of the rules for ACL creation, - * @see http://swift.openstack.org/misc.html#acls - */ -class ACL -{ - /** - * Read flag. - * - * This is for an ACL of the READ type. - */ - const READ = 1; - /** - * Write flag. - * - * This is for an ACL of the WRITE type. - */ - const WRITE = 2; - /** - * Flag for READ and WRITE. - * - * This is equivalent to `ACL::READ | ACL::WRITE` - */ - const READ_WRITE = 3; // self::READ | self::WRITE; - - /** - * Header string for a read flag. - */ - const HEADER_READ = 'X-Container-Read'; - /** - * Header string for a write flag. - */ - const HEADER_WRITE = 'X-Container-Write'; - - protected $rules = []; - - /** - * Allow READ access to the public. - * - * This grants the following: - * - * - READ to any host, with container listings. - * - * @return \OpenStack\ObjectStore\v1\Resource\ACL an ACL object with the - * appopriate permissions set. - */ - public static function makePublic() - { - $acl = new ACL(); - $acl->addReferrer(self::READ, '*'); - $acl->allowListings(); - - return $acl; - } - - /** - * Disallow all public access. - * - * Non-public is the same as private. Private, however, is a reserved - * word in PHP. - * - * This does not grant any permissions. OpenStack interprets an object - * with no permissions as a private object. - * - * @return \OpenStack\ObjectStore\v1\Resource\ACL an ACL object with the - * appopriate permissions set. - */ - public static function makeNonPublic() - { - // Default ACL is private. - return new ACL(); - } - - /** - * Alias of ACL::makeNonPublic(). - */ - public static function makePrivate() - { - return self::makeNonPublic(); - } - - /** - * Given a list of headers, get the ACL info. - * - * This is a utility for processing headers and discovering any ACLs embedded - * inside the headers. - * - * @param array $headers An associative array of headers. - * - * @return \OpenStack\ObjectStore\v1\Resource\ACL A new ACL. - */ - public static function newFromHeaders($headers) - { - $acl = new ACL(); - - // READ rules. - $rules = []; - if (!empty($headers[self::HEADER_READ])) { - $read = $headers[self::HEADER_READ]; - $rules = explode(',', $read); - foreach ($rules as $rule) { - $ruleArray = self::parseRule(self::READ, $rule); - if (!empty($ruleArray)) { - $acl->rules[] = $ruleArray; - } - } - } - - // WRITE rules. - $rules = []; - if (!empty($headers[self::HEADER_WRITE])) { - $write = $headers[self::HEADER_WRITE]; - $rules = explode(',', $write); - foreach ($rules as $rule) { - $ruleArray = self::parseRule(self::WRITE, $rule); - if (!empty($ruleArray)) { - $acl->rules[] = $ruleArray; - } - } - } - - //throw new \Exception(print_r($acl->rules(), true)); - return $acl; - } - - /** - * Parse a rule. - * - * This attempts to parse an ACL rule. It is not particularly - * fault-tolerant. - * - * @param int $perm The permission (ACL::READ, ACL::WRITE). - * @param string $rule The string rule to parse. - * - * @return array The rule as an array. - */ - public static function parseRule($perm, $rule) - { - // This regular expression generates the following: - // - // array( - // 0 => ENTIRE RULE - // 1 => WHOLE EXPRESSION, no whitespace - // 2 => domain compontent - // 3 => 'rlistings', set if .rincludes is the directive - // 4 => account name - // 5 => :username - // 6 => username - // ); - $exp = '/^\s*(.r:([a-zA-Z0-9\*\-\.]+)|\.(rlistings)|([a-zA-Z0-9]+)(\:([a-zA-Z0-9]+))?)\s*$/'; - - $matches = []; - preg_match($exp, $rule, $matches); - - $entry = ['mask' => $perm]; - if (!empty($matches[2])) { - $entry['host'] = $matches[2]; - } elseif (!empty($matches[3])) { - $entry['rlistings'] = true; - } elseif (!empty($matches[4])) { - $entry['account'] = $matches[4]; - if (!empty($matches[6])) { - $entry['user'] = $matches[6]; - } - } - - return $entry; - } - - /** - * Create a new ACL. - * - * This creates an empty ACL with no permissions granted. When no - * permissions are granted, the file is effectively private - * (nonPublic()). - * - * Use add* methods to add permissions. - */ - public function __construct() {} - - /** - * Grant ACL access to an account. - * - * Optionally, a user may be given to further limit access. - * - * This is used to restrict access to a particular account and, if so - * specified, a specific user on that account. - * - * If just an account is given, any user on that account will be - * automatically granted access. - * - * If an account and a user is given, only that user of the account is - * granted access. - * - * If $user is an array, every user in the array will be granted - * access under the provided account. That is, for each user in the - * array, an entry of the form `account:user` will be generated in the - * final ACL. - * - * At this time there does not seem to be a way to grant global write - * access to an object. - * - * @param int $perm ACL::READ, ACL::WRITE or ACL::READ_WRITE (which is the - * same as ACL::READ|ACL::WRITE). - * @param string $account The name of the account. - * @param mixed $user The name of the user, or optionally an indexed array of - * user names. - * - * @return \OpenStack\ObjectStore\v1\Resource\ACL $this for current object so - * the method can be used in chaining. - */ - public function addAccount($perm, $account, $user = null) - { - $rule = ['account' => $account]; - - if (!empty($user)) { - $rule['user'] = $user; - } - - $this->addRule($perm, $rule); - - return $this; - } - - /** - * Allow (or deny) a hostname or host pattern. - * - * In current Swift implementations, only READ rules can have host - * patterns. WRITE permissions cannot be granted to hostnames. - * - * Formats: - * - Allow any host: '*' - * - Allow exact host: 'www.example.com' - * - Allow hosts in domain: '.example.com' - * - Disallow exact host: '-www.example.com' - * - Disallow hosts in domain: '-.example.com' - * - * Note that a simple minus sign ('-') is illegal, though it seems it - * should be "disallow all hosts." - * - * @param string $perm The permission being granted. One of ACL:READ, - * ACL::WRITE, or ACL::READ_WRITE. - * @param string $host A host specification string as described above. - * - * @return \OpenStack\ObjectStore\v1\Resource\ACL $this for current object so - * the method can be used in chaining. - */ - public function addReferrer($perm, $host = '*') - { - $this->addRule($perm, ['host' => $host]); - - return $this; - } - - /** - * Add a rule to the appropriate stack of rules. - * - * @param int $perm One of the predefined permission constants. - * @param array $rule A rule array. - * - * @return \OpenStack\ObjectStore\v1\Resource\ACL $this for current object so - * the method can be used in chaining. - */ - protected function addRule($perm, $rule) - { - $rule['mask'] = $perm; - - $this->rules[] = $rule; - - return $this; - } - - /** - * Allow hosts with READ permissions to list a container's content. - * - * By default, granting READ permission on a container does not grant - * permission to list the contents of a container. Setting the - * ACL::allowListings() permission will allow matching hosts to also list - * the contents of a container. - * - * In the current Swift implementation, there is no mechanism for - * allowing some hosts to get listings, while denying others. - * - * @return \OpenStack\ObjectStore\v1\Resource\ACL $this for current object so - * the method can be used in chaining. - */ - public function allowListings() - { - $this->rules[] = [ - 'mask' => self::READ, - 'rlistings' => true, - ]; - - return $this; - } - - /** - * Get the rules array for this ACL. - * - * @return array An array of associative arrays of rules. - */ - public function rules() - { - return $this->rules; - } - - /** - * Generate HTTP headers for this ACL. - * - * If this is called on an empty object, an empty set of headers is - * returned. - * - * @return array Array of headers - */ - public function headers() - { - $headers = []; - $readers = []; - $writers = []; - - // Create the rule strings. We need two copies, one for READ and - // one for WRITE. - foreach ($this->rules as $rule) { - // We generate read and write rules separately so that the - // generation logic has a chance to respond to the differences - // allowances for READ and WRITE ACLs. - if (self::READ & $rule['mask']) { - $ruleStr = $this->ruleToString(self::READ, $rule); - if (!empty($ruleStr)) { - $readers[] = $ruleStr; - } - } - if (self::WRITE & $rule['mask']) { - $ruleStr = $this->ruleToString(self::WRITE, $rule); - if (!empty($ruleStr)) { - $writers[] = $ruleStr; - } - } - } - - // Create the HTTP headers. - if (!empty($readers)) { - $headers[self::HEADER_READ] = implode(',', $readers); - } - if (!empty($writers)) { - $headers[self::HEADER_WRITE] = implode(',', $writers); - } - - return $headers; - } - - /** - * Convert a rule to a string. - * - * @param int $perm The permission for which to generate the rule. - * @param array $rule A rule array. - */ - protected function ruleToString($perm, $rule) - { - // Some rules only apply to READ. - if (self::READ & $perm) { - - // Host rule. - if (!empty($rule['host'])) { - return '.r:' . $rule['host']; - } - - // Listing rule. - if (!empty($rule['rlistings'])) { - return '.rlistings'; - } - } - - // READ and WRITE both allow account/user rules. - if (!empty($rule['account'])) { - - // Just an account name. - if (empty($rule['user'])) { - return $rule['account']; - } - - // Account + multiple users. - elseif (is_array($rule['user'])) { - $buffer = []; - foreach ($rule['user'] as $user) { - $buffer[] = $rule['account'] . ':' . $user; - } - - return implode(',', $buffer); - - } - - // Account + one user. - else { - return $rule['account'] . ':' . $rule['user']; - } - } - } - - /** - * Check if the ACL marks this private. - * - * This returns true only if this ACL does not grant any permissions - * at all. - * - * @return boolean true if this is private (non-public), false if any - * permissions are granted via this ACL. - */ - public function isNonPublic() - { - return empty($this->rules); - } - - /** - * Alias of isNonPublic(). - */ - public function isPrivate() - { - return $this->isNonPublic(); - } - - /** - * Check whether this object allows public reading. - * - * This will return true the ACL allows (a) any host to access - * the item, and (b) it allows container listings. - * - * This checks whether the object allows public reading, - * not whether it is ONLY allowing public reads. - * - * @see ACL::makePublic(). - * - * @return boolean Whether or not the object allows public reading. - */ - public function isPublic() - { - $allowsAllHosts = false; - $allowsRListings = false; - foreach ($this->rules as $rule) { - if (self::READ & $rule['mask']) { - if (!empty($rule['rlistings'])) { - $allowsRListings = true; - } elseif (!empty($rule['host']) && trim($rule['host']) == '*') { - $allowsAllHosts = true; - } - } - } - - return $allowsAllHosts && $allowsRListings; - } - - /** - * Implements the magic `__toString()` PHP function. - * - * This allows you to `print $acl` and get back - * a pretty string. - * - * @return string The ACL represented as a string. - */ - public function __toString() - { - $headers = $this->headers(); - - $buffer = []; - foreach ($headers as $k => $v) { - $buffer[] = $k . ': ' . $v; - } - - return implode("\t", $buffer); - } - -} diff --git a/src/OpenStack/ObjectStore/v1/Resource/Container.php b/src/OpenStack/ObjectStore/v1/Resource/Container.php deleted file mode 100644 index 59b2155..0000000 --- a/src/OpenStack/ObjectStore/v1/Resource/Container.php +++ /dev/null @@ -1,1084 +0,0 @@ -container('foo'); - * - * // Create an object. - * $obj = new Object('bar.txt'); - * $obj->setContent('Example content.', 'text/plain'); - * - * // Save the new object in the container. - * $container->save($obj); - * - * ?> - * - * Once you have a Container, you manipulate objects inside of the - * container. - * - * @todo Add support for container metadata. - */ -class Container implements \Countable, \IteratorAggregate -{ - /** - * The prefix for any piece of metadata passed in HTTP headers. - */ - const METADATA_HEADER_PREFIX = 'X-Object-Meta-'; - const CONTAINER_METADATA_HEADER_PREFIX = 'X-Container-Meta-'; - - //protected $properties = array(); - protected $name = null; - - // These were both changed from 0 to null to allow lazy loading. - protected $count = null; - protected $bytes = null; - - protected $token; - protected $url; - protected $baseUrl; - protected $acl; - protected $metadata; - - /** - * The HTTP Client - */ - protected $client; - - /** - * Transform a metadata array into headers. - * - * This is used when storing an object in a container. - * - * @param array $metadata An associative array of metadata. Metadata is not - * escaped in any way (there is no codified spec by which to escape), so - * make sure that keys are alphanumeric (dashes allowed) and values are - * ASCII-armored with no newlines. - * @param string $prefix A prefix for the metadata headers. - * - * @return array An array of headers. - * - * @see http://docs.openstack.org/bexar/openstack-object-storage/developer/content/ch03s03.html#d5e635 - * @see http://docs.openstack.org/bexar/openstack-object-storage/developer/content/ch03s03.html#d5e700 - */ - public static function generateMetadataHeaders(array $metadata, $prefix = null) - { - if (empty($prefix)) { - $prefix = Container::METADATA_HEADER_PREFIX; - } - $headers = []; - foreach ($metadata as $key => $val) { - $headers[$prefix . $key] = $val; - } - - return $headers; - } - /** - * Create an object URL. - * - * Given a base URL and an object name, create an object URL. - * - * This is useful because object names can contain certain characters - * (namely slashes (`/`)) that are normally URLencoded when they appear - * inside of path sequences. - * - * Swift does not distinguish between `%2F` and a slash character, so - * this is not strictly necessary. - * - * @param string $base The base URL. This is not altered; it is just prepended - * to the returned string. - * @param string $oname The name of the object. - * - * @return string The URL to the object. Characters that need escaping will be - * escaped, while slash characters are not. Thus, the URL will - * look pathy. - */ - public static function objectUrl($base, $oname) - { - if (strpos($oname, '/') === false) { - return $base . '/' . rawurlencode($oname); - } - - $oParts = explode('/', $oname); - $buffer = []; - foreach ($oParts as $part) { - $buffer[] = rawurlencode($part); - } - $newname = implode('/', $buffer); - - return $base . '/' . $newname; - } - - /** - * Extract object attributes from HTTP headers. - * - * When OpenStack sends object attributes, it sometimes embeds them in - * HTTP headers with a prefix. This function parses the headers and - * returns the attributes as name/value pairs. - * - * Note that no decoding (other than the minimum amount necessary) is - * done to the attribute names or values. The Open Stack Swift - * documentation does not prescribe encoding standards for name or - * value data, so it is left up to implementors to choose their own - * strategy. - * - * @param array $headers An associative array of HTTP headers. - * @param string $prefix The prefix on metadata headers. - * - * @return array An associative array of name/value attribute pairs. - */ - public static function extractHeaderAttributes($headers, $prefix = null) - { - if (empty($prefix)) { - $prefix = Container::METADATA_HEADER_PREFIX; - } - $attributes = []; - $offset = strlen($prefix); - foreach ($headers as $header => $value) { - - $index = strpos($header, $prefix); - if ($index === 0) { - $key = substr($header, $offset); - $attributes[$key] = $value; - } - } - - return $attributes; - } - - /** - * Create a new Container from JSON data. - * - * This is used in lieue of a standard constructor when - * fetching containers from ObjectStorage. - * - * @param array $jsonArray An associative array as returned by - * json_decode($foo, true); - * @param string $token The auth token. - * @param string $url The base URL. The container name is automatically - * appended to this at construction time. - * @param \OpenStack\Common\Transport\ClientInterface $client A HTTP transport client. - * - * @return \OpenStack\ObjectStore\v1\Resource\Container A new container object. - */ - public static function newFromJSON($jsonArray, $token, $url, ClientInterface $client = null) - { - $container = new Container($jsonArray['name'], null, null, $client); - - $container->baseUrl = $url; - - $container->url = $url . '/' . rawurlencode($jsonArray['name']); - $container->token = $token; - - // Access to count and bytes is basically controlled. This is is to - // prevent a local copy of the object from getting out of sync with - // the remote copy. - if (!empty($jsonArray['count'])) { - $container->count = $jsonArray['count']; - } - - if (!empty($jsonArray['bytes'])) { - $container->bytes = $jsonArray['bytes']; - } - - //syslog(LOG_WARNING, print_r($jsonArray, true)); - return $container; - } - - /** - * Given an OpenStack HTTP response, build a Container. - * - * This factory is intended for use by low-level libraries. In most - * cases, the standard constructor is preferred for client-size - * Container initialization. - * - * @param string $name The name of the container. - * @param object $response \OpenStack\Common\Transport\Response The HTTP response object from the Transporter layer - * @param string $token The auth token. - * @param string $url The base URL. The container name is automatically - * appended to this at construction time. - * @param \OpenStack\Common\Transport\ClientInterface $client A HTTP transport client. - * - * @return \OpenStack\ObjectStore\v1\Resource\Container The Container object, initialized and ready for use. - */ - public static function newFromResponse($name, $response, $token, $url, ClientInterface $client = null) - { - $container = new Container($name, null, null, $client); - $container->bytes = $response->getHeader('X-Container-Bytes-Used', 0); - $container->count = $response->getHeader('X-Container-Object-Count', 0); - $container->baseUrl = $url; - $container->url = $url . '/' . rawurlencode($name); - $container->token = $token; - - $headers = self::reformatHeaders($response->getHeaders()); - - $container->acl = ACL::newFromHeaders($headers); - - $prefix = Container::CONTAINER_METADATA_HEADER_PREFIX; - $metadata = Container::extractHeaderAttributes($headers, $prefix); - $container->setMetadata($metadata); - - return $container; - } - - /** - * Construct a new Container. - * - * Typically a container should be created by ObjectStorage::createContainer(). - * Get existing containers with ObjectStorage::container() or - * ObjectStorage::containers(). Using the constructor directly has some - * side effects of which you should be aware. - * - * Simply creating a container does not save the container remotely. - * - * Also, this does no checking of the underlying container. That is, simply - * constructing a Container in no way guarantees that such a container exists - * on the origin object store. - * - * The constructor involves a selective lazy loading. If a new container is created, - * and one of its accessors is called before the accessed values are initialized, then - * this will make a network round-trip to get the container from the remote server. - * - * Containers loaded from ObjectStorage::container() or Container::newFromRemote() - * will have all of the necessary values set, and thus will not require an extra network - * transaction to fetch properties. - * - * The practical result of this: - * - * - If you are creating a new container, it is best to do so with - * ObjectStorage::createContainer(). - * - If you are manipulating an existing container, it is best to load the - * container with ObjectStorage::container(). - * - If you are simply using the container to fetch resources from the - * container, you may wish to use `new Container($name, $url, $token)` - * and then load objects from that container. Note, however, that - * manipulating the container directly will likely involve an extra HTTP - * transaction to load the container data. - * - When in doubt, use the ObjectStorage methods. That is always the safer - * option. - * - * @param string $name The name. - * @param string $url The full URL to the container. - * @param string $token The auth token. - * @param \OpenStack\Common\Transport\ClientInterface $client A HTTP transport client. - */ - public function __construct($name , $url = null, $token = null, ClientInterface $client = null) - { - $this->name = $name; - $this->url = $url; - $this->token = $token; - - // Guzzle is the default client to use. - if (is_null($client)) { - $this->client = GuzzleAdapter::create(); - } else { - $this->client = $client; - } - } - - /** - * Get the name of this container. - * - * @return string The name of the container. - */ - public function name() - { - return $this->name; - } - - /** - * Get the number of bytes in this container. - * - * @return int The number of bytes in this container. - */ - public function bytes() - { - if (is_null($this->bytes)) { - $this->loadExtraData(); - } - - return $this->bytes; - } - - /** - * Get the container metadata. - * - * Metadata (also called tags) are name/value pairs that can be - * attached to a container. - * - * Names can be no longer than 128 characters, and values can be no - * more than 256. UTF-8 or ASCII characters are allowed, though ASCII - * seems to be preferred. - * - * If the container was loaded from a container listing, the metadata - * will be fetched in a new HTTP request. This is because container - * listings do not supply the metadata, while loading a container - * directly does. - * - * @return array An array of metadata name/value pairs. - */ - public function metadata() - { - // If created from JSON, metadata does not get fetched. - if (!isset($this->metadata)) { - $this->loadExtraData(); - } - - return $this->metadata; - } - - /** - * Set the tags on the container. - * - * Container metadata (sometimes called "tags") provides a way of - * storing arbitrary name/value pairs on a container. - * - * Since saving a container is a function of the ObjectStorage - * itself, if you change the metadta, you will need to call - * ObjectStorage::updateContainer() to save the new container metadata - * on the remote object storage. - * - * (Similarly, when it comes to objects, an object's metdata is saved - * by the container.) - * - * Names can be no longer than 128 characters, and values can be no - * more than 256. UTF-8 or ASCII characters are allowed, though ASCII - * seems to be preferred. - * - * @return \OpenStack\ObjectStore\v1\Resource\Container $this so the method can - * be used in chaining. - */ - public function setMetadata($metadata) - { - $this->metadata = $metadata; - - return $this; - } - - /** - * Get the number of items in this container. - * - * Since Container implements Countable, the PHP builtin count() can be used - * on a Container instance: - * - * count(); - * ?> - * - * @return int The number of items in this container. - */ - public function count() - { - if (is_null($this->count)) { - $this->loadExtraData(); - } - - return $this->count; - } - - /** - * Save an Object into Object Storage. - * - * This takes an \OpenStack\ObjectStore\v1\Resource\Object - * and stores it in the given container in the present - * container on the remote object store. - * - * @param object $obj \OpenStack\ObjectStore\v1\Resource\Object The object to - * store. - * @param resource $file An optional file argument that, if set, will be - * treated as the contents of the object. - * - * @return boolean true if the object was saved. - * - * @throws \OpenStack\Common\Transport\Exception\LengthRequiredException if the Content-Length could not be - * determined and chunked encoding was - * not enabled. This should not occur for - * this class, which always automatically - * generates Content-Length headers. - * However, subclasses could generate - * this error. - * @throws \OpenStack\Common\Transport\Exception\UnprocessableEntityException if the checksum passed here does not - * match the checksum calculated remotely. - * @throws \OpenStack\Common\Exception when an unexpected (usually - * network-related) error condition arises. - */ - public function save(Object $obj, $file = null) - { - if (empty($this->token)) { - throw new Exception('Container does not have an auth token.'); - } - if (empty($this->url)) { - throw new Exception('Container does not have a URL to send data.'); - } - - //$url = $this->url . '/' . rawurlencode($obj->name()); - $url = self::objectUrl($this->url, $obj->name()); - - // See if we have any metadata. - $headers = []; - $md = $obj->metadata(); - if (!empty($md)) { - $headers = self::generateMetadataHeaders($md, Container::METADATA_HEADER_PREFIX); - } - - // Set the content type. - $headers['Content-Type'] = $obj->contentType(); - - - // Add content encoding, if necessary. - $encoding = $obj->encoding(); - if (!empty($encoding)) { - $headers['Content-Encoding'] = rawurlencode($encoding); - } - - // Add content disposition, if necessary. - $disposition = $obj->disposition(); - if (!empty($disposition)) { - $headers['Content-Disposition'] = $disposition; - } - - // Auth token. - $headers['X-Auth-Token'] = $this->token; - - // Add any custom headers: - $moreHeaders = $obj->additionalHeaders(); - if (!empty($moreHeaders)) { - $headers += $moreHeaders; - } - - if (empty($file)) { - // Now build up the rest of the headers: - $headers['Etag'] = $obj->eTag(); - - // If chunked, we set transfer encoding; else - // we set the content length. - if ($obj->isChunked()) { - // How do we handle this? Does the underlying - // stream wrapper pay any attention to this? - $headers['Transfer-Encoding'] = 'chunked'; - } else { - $headers['Content-Length'] = $obj->contentLength(); - } - $response = $this->client->put($url, $obj->content(), ['headers' => $headers]); - } else { - // Rewind the file. - rewind($file); - - // XXX: What do we do about Content-Length header? - //$headers['Transfer-Encoding'] = 'chunked'; - $stat = fstat($file); - $headers['Content-Length'] = $stat['size']; - - // Generate an eTag: - $hash = hash_init('md5'); - hash_update_stream($hash, $file); - $etag = hash_final($hash); - $headers['Etag'] = $etag; - - // Not sure if this is necessary: - rewind($file); - - $response = $this->client->put($url, $file, ['headers' => $headers]); - } - - if ($response->getStatusCode() != 201) { - throw new Exception('An unknown error occurred while saving: ' . $response->status()); - } - - return true; - } - - /** - * Update an object's metadata. - * - * This updates the metadata on an object without modifying anything - * else. This is a convenient way to set additional metadata without - * having to re-upload a potentially large object. - * - * Swift's behavior during this operation is sometimes unpredictable, - * particularly in cases where custom headers have been set. - * Use with caution. - * - * @param object $obj \OpenStack\ObjectStore\v1\Resource\Object The object to update. - * - * @return boolean true if the metadata was updated. - * - * @throws \OpenStack\Common\Transport\Exception\ResourceNotFoundException if the object does not already - * exist on the object storage. - */ - public function updateMetadata(Object $obj) - { - $url = self::objectUrl($this->url, $obj->name()); - $headers = ['X-Auth-Token' => $this->token]; - - // See if we have any metadata. We post this even if there - // is no metadata. - $metadata = $obj->metadata(); - if (!empty($metadata)) { - $headers += self::generateMetadataHeaders($metadata, Container::METADATA_HEADER_PREFIX); - } - - // In spite of the documentation's claim to the contrary, - // content type IS reset during this operation. - $headers['Content-Type'] = $obj->contentType(); - - // The POST verb is for updating headers. - - $response = $this->client->post($url, $obj->content(), ['headers' => $headers]); - - if ($response->getStatusCode() != 202) { - throw new Exception(sprintf( - "An unknown error occurred while saving: %d", $response->status() - )); - } - - return true; - } - - /** - * Copy an object to another place in object storage. - * - * An object can be copied within a container. Essentially, this will - * give you duplicates of the file, each with a new name. - * - * An object can be copied to another container if the name of the - * other container is specified, and if that container already exists. - * - * Note that there is no MOVE operation. You must copy and then DELETE - * in order to achieve that. - * - * @param object $obj \OpenStack\ObjectStore\v1\Resource\Object The object to - * copy. This object MUST already be saved on the remote server. The body of - * the object is not sent. Instead, the copy operation is performed on the - * remote server. You can, and probably should, use a RemoteObject here. - * @param string $newName The new name of this object. If you are copying a - * cross containers, the name can be the same. If you are copying within - * the same container, though, you will need to supply a new name. - * @param string $container The name of the alternate container. If this is - * set, the object will be saved into this container. If this is not sent, - * the copy will be performed inside of the original container. - */ - public function copy(Object $obj, $newName, $container = null) - { - $sourceUrl = self::objectUrl($this->url, $obj->name()); - - if (empty($newName)) { - throw new Exception("An object name is required to copy the object."); - } - - // Figure out what container we store in. - if (empty($container)) { - $container = $this->name; - } - $container = rawurlencode($container); - $destUrl = self::objectUrl('/' . $container, $newName); - - $headers = [ - 'X-Auth-Token' => $this->token, - 'Destination' => $destUrl, - 'Content-Type' => $obj->contentType(), - ]; - - $response = $this->client->send( - $this->client->createRequest('COPY', $sourceUrl, null, ['headers' => $headers]) - ); - - if ($response->getStatusCode() != 201) { - throw new Exception("An unknown condition occurred during copy. " . $response->getStatusCode()); - } - - return true; - } - - /** - * Get the object with the given name. - * - * This fetches a single object with the given name. It downloads the - * entire object at once. This is useful if the object is small (under - * a few megabytes) and the content of the object will be used. For - * example, this is the right operation for accessing a text file - * whose contents will be processed. - * - * For larger files or files whose content may never be accessed, use - * proxyObject(), which delays loading the content until one of its - * content methods (e.g. RemoteObject::content()) is called. - * - * This does not yet support the following features of Swift: - * - * - Byte range queries. - * - If-Modified-Since/If-Unmodified-Since - * - If-Match/If-None-Match - * - * @param string $name The name of the object to load. - * - * @return \OpenStack\ObjectStore\v1\Resource\RemoteObject A remote object with the content already stored locally. - */ - public function object($name) - { - $url = self::objectUrl($this->url, $name); - $headers = ['X-Auth-Token' => $this->token]; - - $response = $this->client->get($url, ['headers' => $headers]); - - if ($response->getStatusCode() != 200) { - throw new Exception('An unknown error occurred while saving: ' . $response->status()); - } - - $remoteObject = RemoteObject::newFromHeaders($name, self::reformatHeaders($response->getHeaders()), $this->token, $url, $this->client); - $remoteObject->setContent($response->getBody()); - - return $remoteObject; - } - - /** - * Fetch an object, but delay fetching its contents. - * - * This retrieves all of the information about an object except for - * its contents. Size, hash, metadata, and modification date - * information are all retrieved and wrapped. - * - * The data comes back as a RemoteObject, which can be used to - * transparently fetch the object's content, too. - * - * Why Use This? - * - * The regular object() call will fetch an entire object, including - * its content. This may not be desireable for cases where the object - * is large. - * - * This method can fetch the relevant metadata, but delay fetching - * the content until it is actually needed. - * - * Since RemoteObject extends Object, all of the calls that can be - * made to an Object can also be made to a RemoteObject. Be aware, - * though, that calling RemoteObject::content() will initiate another - * network operation. - * - * @param string $name The name of the object to fetch. - * - * @return \OpenStack\ObjectStore\v1\Resource\RemoteObject A remote object ready for use. - */ - public function proxyObject($name) - { - $url = self::objectUrl($this->url, $name); - $headers = ['X-Auth-Token' => $this->token]; - - $response = $this->client->head($url, ['headers' => $headers]); - - if ($response->getStatusCode() != 200) { - throw new Exception('An unknown error occurred while saving: ' . $response->status()); - } - - $headers = self::reformatHeaders($response->getHeaders()); - - return RemoteObject::newFromHeaders($name, $headers, $this->token, $url, $this->client); - } - - /** - * Get a list of objects in this container. - * - * This will return a list of objects in the container. With no parameters, it - * will attempt to return a listing of all objects in the container. However, - * by setting contraints, you can retrieve only a specific subset of objects. - * - * Note that OpenStacks Swift will return no more than 10,000 objects - * per request. When dealing with large datasets, you are encouraged - * to use paging. - * - * Paging - * - * Paging is done with a combination of a limit and a marker. The - * limit is an integer indicating the maximum number of items to - * return. The marker is the string name of an object. Typically, this - * is the last object in the previously returned set. The next batch - * will begin with the next item after the marker (assuming the marker - * is found.) - * - * @param int $limit An integer indicating the maximum number of items to - * return. This cannot be greater than the Swift maximum (10k). - * @param string $marker The name of the object to start with. The query will - * begin with the next object AFTER this one. - * - * @return array List of RemoteObject or Subdir instances. - */ - public function objects($limit = null, $marker = null) - { - return $this->objectQuery([], $limit, $marker); - } - - /** - * Retrieve a list of Objects with the given prefix. - * - * Object Storage containers support directory-like organization. To - * get a list of items inside of a particular "subdirectory", provide - * the directory name as a "prefix". This will return only objects - * that begin with that prefix. - * - * (Directory-like behavior is also supported by using "directory - * markers". See objectsByPath().) - * - * Prefixes - * - * Prefixes are basically substring patterns that are matched against - * files on the remote object storage. - * - * When a prefix is used, object storage will begin to return not just - * Object instsances, but also Subdir instances. A Subdir is simply a - * container for a "path name". - * - * Delimiters - * - * Object Storage (OpenStack Swift) does not have a native concept of - * files and directories when it comes to paths. Instead, it merely - * represents them and simulates their behavior under specific - * circumstances. - * - * The default behavior (when prefixes are used) is to treat the '/' - * character as a delimiter. Thus, when it encounters a name like - * this: `foo/bar/baz.txt` and the prefix is `foo/`, it will - * parse return a Subdir called `foo/bar`. - * - * Similarly, if you store a file called `foo:bar:baz.txt` and then - * set the delimiter to `:` and the prefix to `foo:`, it will return - * the Subdir `foo:bar`. However, merely setting the delimiter back to - * `/` will not allow you to query `foo/bar` and get the contents of - * `foo:bar`. - * - * Setting $delimiter will tell the Object Storage server which - * character to parse the filenames on. This means that if you use - * delimiters other than '/', you need to be very consistent with your - * usage or else you may get surprising results. - * - * @param string $prefix The leading prefix. - * @param string $delimiter The character used to delimit names. By default, - * this is '/'. - * @param int $limit An integer indicating the maximum number of items to - * return. This cannot be greater than the Swift maximum (10k). - * @param string $marker The name of the object to start with. The query will - * begin with the next object AFTER this one. - * - * @return array List of RemoteObject or Subdir instances. - */ - public function objectsWithPrefix($prefix, $delimiter = '/', $limit = null, $marker = null) - { - $params = [ - 'prefix' => $prefix, - 'delimiter' => $delimiter - ]; - - return $this->objectQuery($params, $limit, $marker); - } - - /** - * Specify a path (subdirectory) to traverse. - * - * OpenStack Swift provides two basic ways to handle directory-like - * structures. The first is using a prefix (see objectsWithPrefix()). - * The second is to create directory markers and use a path. - * - * A directory marker is just a file with a name that is - * directory-like. You create it exactly as you create any other file. - * Typically, it is 0 bytes long. - * - * save($dir); - * ?> - * - * Using objectsByPath() with directory markers will return a list of - * Object instances, some of which are regular files, and some of - * which are just empty directory marker files. When creating - * directory markers, you may wish to set metadata or content-type - * information indicating that they are directory markers. - * - * At one point, the OpenStack documentation suggested that the path - * method was legacy. More recent versions of the documentation no - * longer indicate this. - * - * @param string $path The path prefix. - * @param string $delimiter The character used to delimit names. By default, - * this is '/'. - * @param int $limit An integer indicating the maximum number of items to - * return. This cannot be greater than the Swift maximum (10k). - * @param string $marker The name of the object to start with. The query will - * begin with the next object AFTER this one. - */ - public function objectsByPath($path, $delimiter = '/', $limit = null, $marker = null) - { - $params = [ - 'path' => $path, - 'delimiter' => $delimiter, - ]; - - return $this->objectQuery($params, $limit, $marker); - } - - /** - * Get the URL to this container. - * - * Any container that has been created will have a valid URL. If the - * Container was set to be public (See - * ObjectStorage::createContainer()) will be accessible by this URL. - * - * @return string The URL. - */ - public function url() - { - return $this->url; - } - - /** - * Get the ACL. - * - * Currently, if the ACL wasn't added during object construction, - * calling acl() will trigger a request to the remote server to fetch - * the ACL. Since only some Swift calls return ACL data, this is an - * unavoidable artifact. - * - * Calling this on a Container that has not been stored on the remote - * ObjectStorage will produce an error. However, this should not be an - * issue, since containers should always come from one of the - * ObjectStorage methods. - * - * @todo Determine how to get the ACL from JSON data. - * - * @return \OpenStack\ObjectStore\v1\Resource\ACL An ACL, or null if the ACL could not be retrieved. - */ - public function acl() - { - if (!isset($this->acl)) { - $this->loadExtraData(); - } - - return $this->acl; - } - - /** - * Get missing fields. - * - * Not all containers come fully instantiated. This method is sometimes - * called to "fill in" missing fields. - * - * @return \OpenStack\ObjectStore\v1\Resource\Container - */ - protected function loadExtraData() - { - // If URL and token are empty, we are dealing with a local item that - // has not been saved, and was not created with Container::createContainer(). - // We treat this as an error condition. - if (empty($this->url) || empty($this->token)) { - throw new Exception('Remote data cannot be fetched. A Token and endpoint URL are required.'); - } - - // Do a GET on $url to fetch headers. - $headers = ['X-Auth-Token' => $this->token]; - $response = $this->client->get($this->url, ['headers' => $headers]); - - $headers = self::reformatHeaders($response->getHeaders()); - // Get ACL. - $this->acl = ACL::newFromHeaders($headers); - - // Update size and count. - $this->bytes = $response->getHeader('X-Container-Bytes-Used', 0); - $this->count = $response->getHeader('X-Container-Object-Count', 0); - - // Get metadata. - $prefix = Container::CONTAINER_METADATA_HEADER_PREFIX; - $this->setMetadata(Container::extractHeaderAttributes($headers, $prefix)); - - return $this; - } - - /** - * Perform the HTTP query for a list of objects and de-serialize the - * results. - */ - protected function objectQuery($params = [], $limit = null, $marker = null) - { - if (isset($limit)) { - $params['limit'] = (int) $limit; - if (!empty($marker)) { - $params['marker'] = (string) $marker; - } - } - - // We always want JSON. - $params['format'] = 'json'; - - $query = http_build_query($params); - $query = str_replace('%2F', '/', $query); - $url = $this->url . '?' . $query; - - $headers = ['X-Auth-Token' => $this->token]; - - $response = $this->client->get($url, ['headers' => $headers]); - - // The only codes that should be returned are 200 and the ones - // already thrown by GET. - if ($response->getStatusCode() != 200) { - throw new Exception('An unknown exception occurred while processing the request.'); - } - - $json = $response->json(); - - // Turn the array into a list of RemoteObject instances. - $list = []; - foreach ($json as $item) { - if (!empty($item['subdir'])) { - $list[] = new Subdir($item['subdir'], $params['delimiter']); - } elseif (empty($item['name'])) { - throw new Exception('Unexpected entity returned.'); - } else { - //$url = $this->url . '/' . rawurlencode($item['name']); - $url = self::objectUrl($this->url, $item['name']); - $list[] = RemoteObject::newFromJSON($item, $this->token, $url, $this->client); - } - } - - return $list; - } - - /** - * Return the iterator of contents. - * - * A Container is Iterable. This means that you can use a container in - * a `foreach` loop directly: - * - * name(); - * } - * ?> - * - * The above is equivalent to doing the following: - * - * objects(); - * foreach ($objects as $object) { - * print $object->name(); - * } - * ?> - * - * Note that there is no way to pass any constraints into an iterator. - * You cannot limit the number of items, set an marker, or add a - * prefix. - */ - public function getIterator() - { - return new \ArrayIterator($this->objects()); - } - - /** - * Remove the named object from storage. - * - * @param string $name The name of the object to remove. - * - * @return boolean true if the file was deleted, false if no such file is - * found. - */ - public function delete($name) - { - $url = self::objectUrl($this->url, $name); - $headers = [ - 'X-Auth-Token' => $this->token, - ]; - - try { - $response = $this->client->delete($url, ['headers' => $headers]); - } catch (ResourceNotFoundException $e) { - return false; - } - - if ($response->getStatusCode() != 204) { - throw new Exception(sprintf( - "An unknown exception occured while deleting %s", $name - )); - } - - return true; - } - - /** - * Reformat the headers array to remove a nested array. - * - * For example, headers coming in could be in the format: - * - * $headers = [ - * 'Content-Type' => [ - * [0] => 'Foo', - * ], - * ]; - * - * This method would reformat the array into: - * - * $headers = [ - * 'Content-Type' => 'Foo', - * ]; - * - * Note, for cases where multiple values for a header are needed this method - * should not be used. - * - * @param array $headers A headers array from the response. - * - * @return array A new shallower array. - */ - public static function reformatHeaders(array $headers) - { - $newHeaders = []; - - foreach ($headers as $name => $header) { - $newHeaders[$name] = $header[0]; - } - - return $newHeaders; - } -} diff --git a/src/OpenStack/ObjectStore/v1/Resource/Object.php b/src/OpenStack/ObjectStore/v1/Resource/Object.php deleted file mode 100644 index 1efc0c3..0000000 --- a/src/OpenStack/ObjectStore/v1/Resource/Object.php +++ /dev/null @@ -1,524 +0,0 @@ -name = $name; - - if (!is_null($content)) { - $this->content = $content; - } - if (!empty($type)) { - $this->contentType = $type; - } - } - - /** - * Set the metadata. - * - * OpenStack allows you to specify metadata for a file. Metadata items - * must follow these conventions: - * - * - names must contain only letters, numbers, and short dashes. Since - * OpenStack normalizes the name to begin with uppercase, it is - * suggested that you follow this convetion: Foo, not foo. Or you - * can do your own normalizing (such as converting all to lowercase. - * OpenStack limits the name length to 126 unicode chars. - * - values must be encoded if they contain newlines or binary data. - * While the exact encoding is up to you, Base-64 encoding is probably - * your best bet. OpenStack limits the value to 256 unicode chars. - * - * (The docs are ambiguous -- they say chars, but they may mean - * bytes.) - * - * This library does only minimal processing of metadata, and does no - * error checking, escaping, etc. This is up to the implementor. The - * OpenStack Swift implementation does not dictate what encoding is - * used, though it suggests url encoding of both name and values. - * - * Currently, no length checking is performed in the library, nor is - * any encoding of the data performed. - * - * IMPORTANT: Current versions of OpenStack Swift normalize metadata - * names so that the name is always given an initial capital leter. - * That is, `foo` becomes `Foo`. - * - * @param array $array An associative array of metadata names to values. - * - * @return \OpenStack\ObjectStore\v1\Resource\Object $this so the method can be - * used in chaining. - */ - public function setMetadata(array $array) - { - $this->metadata = $array; - - return $this; - } - - /** - * Get any associated metadata. - * - * This returns an associative array of all metadata for this object. - * - * @return array An associative array of metadata. This may be empty. - */ - public function metadata() - { - return $this->metadata; - } - - /** - * Override (change) the name of an object. - * - * Note that this changes only the local copy of an object. It - * does not rename the remote copy. In fact, changing the local name - * and then saving it will result in a new object being created in the - * object store. - * - * To copy an object: - * @see \OpenStack\ObjectStore\v1\Resource\Container::copyObject(). - * - * @param string $name A file or object name. - * - * @return \OpenStack\ObjectStore\v1\Resource\Object $this so the method can be - * used in chaining. - */ - public function setName($name) - { - $this->name = $name; - - return $this; - } - - /** - * Get the name. - * - * Returns the name of an object. If the name has been overwritten - * using setName(), this will return the latest (overwritten) name. - * - * @return string The name of the object. - */ - public function name() - { - return $this->name; - } - - /** - * Set the content type (MIME type) for the object. - * - * Object storage is, to a certain degree, content-type aware. For - * that reason, a content type is mandatory. - * - * The default MIME type used is `application/octet-stream`, which is - * the generic content type for a byte stream. Where possible, you - * should set a more accurate content type. - * - * All HTTP type options are allowed. So, for example, you can add a - * charset to a text type: - * - * setContentType('text/html; charset=iso-8859-13'); - * ?> - * - * Content type is not parsed or verified locally (though it is - * remotely). It can be dangerous, too, to allow users to specify a - * content type. - * - * @param string $type A valid content type. - * - * @return \OpenStack\ObjectStore\v1\Resource\Object $this so the method can be - * used in chaining. - */ - public function setContentType($type) - { - $this->contentType = $type; - - return $this; - } - - /** - * Get the content type. - * - * This returns the currently set content type. - * - * @return string The content type, including any additional options. - */ - public function contentType() - { - return $this->contentType; - } - - /** - * Set the content for this object. - * - * Place the content into the object. Typically, this is string - * content that will be stored remotely. - * - * PHP's string is backed by a robust system that can accomodate - * moderately sized files. However, it is best to keep strings short - * (<2MB, for example -- test for your own system's sweet spot). - * Larger data may be better handled with file system entries or - * database storage. - * - * Note that the OpenStack will not allow files larger than 5G, and - * PHP will likely croak well before that marker. So use discretion. - * - * @param string $content The content of the object. - * @param string $type The content type (MIME type). This can be set here for - * convenience, or you can call setContentType() directly. - * - * @return \OpenStack\ObjectStore\v1\Resource\Object $this so the method can be - * used in chaining. - */ - public function setContent($content, $type = null) - { - $this->content = $content; - if (!empty($type)) { - $this->contentType = $type; - } - - return $this; - } - - /** - * Retrieve the content. - * - * Retrieve the ENTIRE content of an object. - * - * Note that this may be binary data (depending on what the original - * content is). PHP strings are generally binary safe, but use this - * with caution if you do not know what kind of data is stored in an - * object. - * - * OpenStack does not do anything to validate that the content type is - * accurate. While contentType() is intended to provide useful - * information, poorly managed data can be written with the wrong - * content type. - * - * When extending this class, you should make sure that this function - * returns the entire contents of an object. - * - * @return string The content of the file. - */ - public function content() - { - return $this->content; - } - - /** - * Calculate the content length. - * - * This returns the number of bytes in a piece of content (not - * the number of characters). Among other things, it is used to let - * the remote object store know how big of an object to expect when - * transmitting data. - * - * When extending this class, you should make sure to calculate the - * content length appropriately. - * - * @return int The length of the content, in bytes. - */ - public function contentLength() - { - // strlen() is binary safe (or at least it seems to be). - return strlen($this->content); - } - - /** - * Generate an ETag for the ObjectStorage server. - * - * OpenStack uses ETag to pass validation data. This generates an ETag - * using an MD5 hash of the content. - * - * When extending this class, generate an ETag by creating an MD5 of - * the entire object's content (but not the metadata or name). - * - * @return string An MD5 value as a string of 32 hex digits (0-9a-f). - */ - public function eTag() - { - return md5($this->content); - } - - /** - * Set the encoding for a file. - * - * You can use content encoding on compressed content to indicate to - * the receiving agent that a file is encoded using a specific - * compression type. - * - * Typical compression types are 'gzip', 'zip', and 'compress', though - * many others exist. - * - * This allows you, for example, to save a zipped file, yet preserve - * its underlying content type. For example, for a gzipped text/plain - * file, you can set the content type to "text/plain" and the encoding - * to "gzip". This allows many user agents to receive the compressed - * data and automatically decompress them and display them correctly. - * - * @param string $encoding A valid encoding type. - * - * @return \OpenStack\ObjectStore\v1\Resource\Object $this so the method can be - * used in chaining. - */ - public function setEncoding($encoding) - { - $this->contentEncoding = $encoding; - - return $this; - } - - /** - * Get the encoding (if any) for this object. - * - * Encoding is used to indicate how a file was encoded or compressed. - * See setEncoding() for more information. - * - * @return string The encoding type. - */ - public function encoding() - { - return $this->contentEncoding; - } - - /** - * Set the content disposition. - * - * This makes it possible to have the file act like a download (in a - * browser or similar agent), even if the MIME type normally triggers - * a display. - * - * The typical value for this is: - * - * setDisposition('attachment; filename=foo.png'); - * ?> - * - * A disposition string should not include any newline characters or - * binary data. - * - * @param string $disposition A valid disposition declaration. These are - * defined in various HTTP specifications. - * - * @return \OpenStack\ObjectStore\v1\Resource\Object $this so the method can be - * used in chaining. - */ - public function setDisposition($disposition) - { - $this->contentDisposition = $disposition; - - return $this; - } - - /** - * Get the current disposition string, if any. - * - * See setDisposition() for discussion. - * - * @return string The disposition string, or null if none is set. - */ - public function disposition() - { - return $this->contentDisposition; - } - - /** - * Set additional headers for storage. - * - * EXPERT: You will need to understand OpenStack internals to use this - * effectively. - * - * Headers set here will be added to the HTTP request during save - * operations. They are not merged into existing headers until - * save-time. - * - * This provides a mechanism for adding extension headers. CORS - * headers and possibly others are stored by Swift, but have no - * semantic value to Swift or to popular user agents. - * - * There are a few things to note about this mechanism: - * - * - Existing headers cannot be overwritten. Only new headers can be - * added. - * - Headers are not merged. They are simply sent to the remote - * server. A new object must be retrieved from the server before - * these headers will be accessible. - * - Swift only stores certain headers. If you supply an unrecognized - * header to Swift, it may simply ignore it. - * - The RemoteObject::headers() method provides access to all of the - * headers returned from Swift. - * - Headers are merged in as they are, with no cleaning, encoding, or - * checking. You must ensure that the headers are in the proper - * format. - * - * @param array $headers An associative array where each name is an HTTP - * header name, and each value is the HTTP header value. No encoding or - * escaping is done. - * - * @return \OpenStack\ObjectStore\v1\Resource\Object $this so the method can be - * used in chaining. - */ - public function setAdditionalHeaders($headers) - { - $this->additionalHeaders = $headers; - - return $this; - } - - /** - * Return additional headers. - * - * Headers here have likely not been stored remotely until - * Container::save() is called on the object. - */ - public function additionalHeaders() - { - return $this->additionalHeaders; - } - - /** - * Remove headers. - * - * This takes an array of header names, and removes - * any matching headers. Typically, only headers set - * by setAdditionalHeaders() are removed from an Object. - * (RemoteObject works differently). - * - * Many headers are generated automatically, such as - * Content-Type and Content-Length. Removing these - * will simply result in their being regenerated. - * - * @param array $keys The header names to be removed. - * - * @return \OpenStack\ObjectStore\v1\Resource\Object $this for the current - * object so it can be used in chaining methods. - */ - public function removeHeaders($keys) - { - foreach ($keys as $k) { - unset($this->additionalHeaders[$k]); - } - - return $this; - } - - /** - * This object should be transmitted in chunks. - * - * Indicates whether or not this object should be transmitted as - * chunked data (in HTTP). - * - * This should be used when (a) the file size is large, or (b) the - * exact size of the file is unknown. - * - * If this returns true, it does not guarantee that the data - * will be transmitted in chunks. But it recommends that the - * underlying transport layer use chunked encoding. - * - * The contentLength() method is not called for chunked transfers. So - * if this returns true, contentLength() is ignored. - * - * @return boolean true to recommend chunked transfer, false otherwise. - */ - public function isChunked() - { - // Currently, this value is hard-coded. The default Object - // implementation does not get chunked. - return false; - } -} diff --git a/src/OpenStack/ObjectStore/v1/Resource/RemoteObject.php b/src/OpenStack/ObjectStore/v1/Resource/RemoteObject.php deleted file mode 100644 index 3670ab0..0000000 --- a/src/OpenStack/ObjectStore/v1/Resource/RemoteObject.php +++ /dev/null @@ -1,669 +0,0 @@ -setContentType($data['content_type']); - - $object->contentLength = (int) $data['bytes']; - $object->etag = (string) $data['hash']; - $object->lastModified = strtotime($data['last_modified']); - - $object->token = $token; - $object->url = $url; - - // FIXME: What do we do about HTTP header data that doesn't come - // back in JSON? - - if (is_null($client)) { - $client = GuzzleAdapter::create(); - } - $object->setClient($client); - - return $object; - } - - /** - * Create a new RemoteObject from HTTP headers. - * - * This is used to create objects from GET and HEAD requests, which - * return all of the metadata inside of the headers. - * - * @param string $name The name of the object. - * @param array $headers An associative array of HTTP headers in the exact - * format documented by OpenStack's API docs. - * @param string $token The current auth token (used for issuing subsequent - * requests). - * @param string $url The URL to the object in the object storage. Used for - * issuing subsequent requests. - * - * @return \OpenStack\ObjectStore\v1\Resource\RemoteObject A new RemoteObject. - */ - public static function newFromHeaders($name, $headers, $token, $url, ClientInterface $client = null) - { - $object = new RemoteObject($name); - - //$object->allHeaders = $headers; - $object->setHeaders($headers); - - //throw new \Exception(print_r($headers, true)); - - // Fix inconsistant header. - if (isset($headers['ETag'])) { - $headers['Etag'] = $headers['ETag']; - } - - $object->setContentType($headers['Content-Type']); - $object->contentLength = empty($headers['Content-Length']) ? 0 : (int) $headers['Content-Length']; - $object->etag = (string) $headers['Etag']; // ETag is now Etag. - $object->lastModified = strtotime($headers['Last-Modified']); - - // Set the metadata, too. - $object->setMetadata(Container::extractHeaderAttributes($headers)); - - - // If content encoding and disposition exist, set them on the - // object. - if (!empty($headers['Content-Disposition'])) { - $object->setDisposition($headers['Content-Disposition']); - - } - if (!empty($headers['Content-Encoding'])) { - $object->setEncoding($headers['Content-Encoding']); - } - - $object->token = $token; - $object->url = $url; - - if (is_null($client)) { - $client = GuzzleAdapter::create(); - } - $object->setClient($client); - - return $object; - } - - /** - * Set the HTTP Client to use. - * - * @param OpenStackTransportClientInterface $client The HTTP Client - */ - public function setClient(ClientInterface $client) - { - $this->client = $client; - } - - /** - * Get the URL to this object. - * - * If this object has been stored remotely, it will have - * a valid URL. - * - * @return string A URL to the object. The following considerations apply: - * - If the container is public, this URL can be loaded without - * authentication. You can, for example, pass the URL to a browser - * user agent. - * - If this object has never been saved remotely, then there will be - * no URL, and this will return null. - */ - public function url() - { - return $this->url; - } - - - public function contentLength() - { - if (!empty($this->content)) { - return parent::contentLength(); - } - - return $this->contentLength; - } - - public function eTag() - { - if (!empty($this->content)) { - return parent::eTag(); - } - - return $this->etag; - } - - /** - * Get the modification time, as reported by the server. - * - * This returns an integer timestamp indicating when the server's - * copy of this file was last modified. - */ - public function lastModified() - { - return $this->lastModified; - } - - public function metadata() - { - // How do we get this? - return $this->metadata; - } - - /** - * Set the headers - * - * @return \OpenStack\ObjectStore\v1\Resource\RemoteObject $this for the current object so it can be used in chaining - * methods. - */ - public function setHeaders($headers) - { - $this->allHeaders = []; - - foreach ($headers as $name => $value) { - if (strpos($name, Container::METADATA_HEADER_PREFIX) !== 0) { - $this->allHeaders[$name] = $value; - } - } - - return $this; - } - - /** - * Get the HTTP headers sent by the server. - * - * EXPERT. - * - * This returns the array of minimally processed HTTP headers that - * were sent from the server. - * - * @return array An associative array of header names and values. - */ - public function headers() - { - return $this->allHeaders; - } - - public function additionalHeaders($mergeAll = false) - { - // Any additional headers will be set. Note that $this->headers will contain - // some headers that are NOT additional. But we do not know which headers are - // additional and which are from Swift because Swift does not commit to using - // a specific set of headers. - if ($mergeAll) { - $additionalHeaders = parent::additionalHeaders() + $this->allHeaders; - $this->filterHeaders($additionalHeaders); - } else { - $additionalHeaders = parent::additionalHeaders(); - } - - return $additionalHeaders; - } - - protected $reservedHeaders = [ - 'etag' => true, 'content-length' => true, - 'x-auth-token' => true, - 'transfer-encoding' => true, - 'x-trans-id' => true, - ]; - - /** - * Filter the headers. - * - * @return \OpenStack\ObjectStore\v1\Resource\RemoteObject $this for the current object so it can be used in chaining - * methods. - */ - public function filterHeaders(&$headers) - { - $unset = []; - foreach ($headers as $name => $value) { - $lower = strtolower($name); - if (isset($this->reservedHeaders[$lower])) { - $unset[] = $name; - } - } - foreach ($unset as $u) { - unset($headers[$u]); - } - - return $this; - } - - /** - * Given an array of header names. - * - * This will remove the given headers from the existing headers. - * Both additional headers and the original headers from the - * server are affected here. - * - * Note that you cannot remove metadata through this mechanism, - * as it is managed using the metadata() methods. - * - * Many headers are generated automatically, such as - * Content-Type and Content-Length. Removing these - * will simply result in their being regenerated. - * - * @param array $keys The header names to be removed. - * - * @return \OpenStack\ObjectStore\v1\Resource\RemoteObject $this for the current object so it can be used in chaining - * methods. - */ - public function removeHeaders($keys) - { - foreach ($keys as $key) { - unset($this->allHeaders[$key]); - unset($this->additionalHeaders[$key]); - } - - return $this; - } - - /** - * Get the content of this object. - * - * Since this is a proxy object, calling content() will cause the - * object to be fetched from the remote data storage. The result will - * be delivered as one large string. - * - * The file size, content type, etag, and modification date of the - * object are all updated during this command, too. This accounts for - * the possibility that the content was modified externally between - * the time this object was constructed and the time this method was - * executed. - * - * Be wary of using this method with large files. - * - * @return string The contents of the file as a string. - * - * @throws \OpenStack\Common\Transport\Exception\ResourceNotFoundException when the requested content cannot be - * located on the remote server. - * @throws \OpenStack\Common\Exception when an unknown exception (usually an - * abnormal network condition) occurs. - */ - public function content() - { - // XXX: This allows local overwrites. Is this a good idea? - if (!empty($this->content)) { - return $this->content; - } - - // Get the object, content included. - $response = $this->fetchObject(true); - - $content = $response->getBody(); - - // Checksum the content. - // XXX: Right now the md5 is done even if checking is turned off. - // Should fix that. - $check = md5($content); - if ($this->isVerifyingContent() && $check != $this->etag()) { - throw new Exception\ContentVerificationException("Checksum $check does not match Etag " . $this->etag()); - } - - // If we are caching, set the content locally when we retrieve - // remotely. - if ($this->isCaching()) { - $this->setContent($content); - } - - return $content; - } - - /** - * Get the content of this object as a file stream. - * - * This is useful for large objects. Such objects should not be read - * into memory all at once (as content() does), but should instead be - * made available as an input stream. - * - * PHP offers low-level stream support in the form of PHP stream - * wrappers, and this mechanism is used internally whenever available. - * - * If there is a local copy of the content, the stream will be read - * out of the content as if it were a temp-file backed in-memory - * resource. To ignore the local version, pass in true for the - * $refresh parameter. - * - * If the content is coming from a remote copy, the stream will be - * read directly from the underlying IO stream. - * - * Each time stream() is called, a new stream is created. In most - * cases, this results in a new HTTP transaction (unless $refresh is - * false and the content is already stored locally). - * - * The stream is read-only. - * - * @param boolean $refresh If this is set to true, any existing local - * modifications will be ignored and the content will - * be refreshed from the server. Any local changes to - * the object will be discarded. - * - * @return resource A handle to the stream, which is already opened and - * positioned at the beginning of the stream. - */ - public function stream($refresh = false) - { - // If we're working on local content, return that content wrapped in - // a fake IO stream. - if (!$refresh && isset($this->content)) { - return $this->localFileStream(); - } - - // Otherwise, we fetch a fresh version from the remote server and - // return its stream handle. - $response = $this->fetchObject(true); - - // Write to in-mem handle backed by a temp file. - $out = fopen('php://temp', 'rb+'); - fwrite($out, $response->getBody()); - rewind($out); - - return $out; - } - - /** - * Transform a local copy of content into a file stream. - * - * This buffers the content into a stream resource and then returns - * the stream resource. The resource is not used internally, and its - * data is never written back to the remote object storage. - */ - protected function localFileStream() - { - $tmp = fopen('php://temp', 'rw'); - fwrite($tmp, $this->content(), $this->contentLength()); - rewind($tmp); - - return $tmp; - } - - /** - * Enable or disable content caching. - * - * If a RemoteObject is set to cache then the first time content() is - * called, its results will be cached locally. This is very useful for - * small files whose content is accessed repeatedly, but can be a - * cause of memory consumption for larger files. - * - * If caching settings are changed after content is retrieved, the - * already retrieved content will not be affected, though any - * subsequent requests will use the new caching settings. That is, - * existing cached content will not be removed if caching is turned - * off. - * - * @param boolean $enabled If this is true, caching will be enabled. If this - * is false, caching will be disabled. - * - * @return \OpenStack\ObjectStore\v1\Resource\RemoteObject $this so the method can be used in chaining. - */ - public function setCaching($enabled) - { - $this->caching = $enabled; - - return $this; - } - - /** - * Indicates whether this object caches content. - * - * Importantly, this indicates whether the object will cache - * its contents, not whether anything is actually cached. - * - * @return boolean true if caching is enabled, false otherwise. - */ - public function isCaching() - { - return $this->caching; - } - - /** - * Enable or disable content verification (checksum/md5). - * - * The default behavior of a RemoteObject is to verify that the MD5 - * provided by the server matches the locally generated MD5 of the - * file contents. - * - * If content verification is enabled, then whenever the content is - * fetched from the remote server, its checksum is calculated and - * tested against the ETag value. This provides a layer of assurance - * that the payload of the HTTP request was not altered during - * transmission. - * - * This featured can be turned off, which is sometimes necessary on - * systems that do not correctly produce MD5s. Turning this off might - * also provide a small performance improvement on large files, but at - * the expense of security. - * - * @param boolean $enabled If this is true, content verification is performed. - * The content is hashed and checked against a - * server-supplied MD5 hashcode. If this is false, - * no checking is done. - * - * @return \OpenStack\ObjectStore\v1\Resource\RemoteObject $this so the method can be used in chaining. - */ - public function setContentVerification($enabled) - { - $this->contentVerification = $enabled; - - return $this; - } - - /** - * Indicate whether this object verifies content (checksum). - * - * When content verification is on, RemoteObject attemts to perform a - * checksum on the object, calculating the MD5 hash of the content - * returned by the remote server, and comparing that to the server's - * supplied ETag hash. - * - * @return boolean true if this is verifying, false otherwise. - */ - public function isVerifyingContent() - { - return $this->contentVerification; - } - - /** - * Check whether there are unsaved changes. - * - * An object is marked "dirty" if it has been altered - * locally in such a way that it no longer matches the - * remote version. - * - * The practical definition of dirtiness, for us, is this: An object - * is dirty if and only if (a) it has locally buffered content AND (b) - * the checksum of the local content does not match the checksom of - * the remote content. - * - * Not that minor differences, such as altered character encoding, may - * change the checksum value, and thus (correctly) mark the object as - * dirty. - * - * The RemoteObject implementation does not internally check dirty - * markers. It is left to implementors to ensure that dirty content is - * written to the remote server when desired. - * - * To replace dirty content with a clean copy, see refresh(). - * - * @return boolean Whether or not there are unsaved changes. - */ - public function isDirty() - { - // If there is no content, the object can't be dirty. - if (!isset($this->content)) { - return false; - } - - // Content is dirty iff content is set, and it is - // different from the original content. Note that - // we are using the etag from the original headers. - if ($this->etag != md5($this->content)) { - return true; - } - - return false; - } - - /** - * Rebuild the local object from the remote. - * - * This refetches the object from the object store and then - * reconstructs the present object based on the refreshed data. - * - * WARNING: This will destroy any unsaved local changes. You can use - * isDirty() to determine whether or not a local change has been made. - * - * @param boolean $fetchContent If this is true, the content will be - * downloaded as well. - * - * @return \OpenStack\ObjectStore\v1\Resource\RemoteObject $this for the current object so it - * can be used in chaining methods. - */ - public function refresh($fetchContent = false) - { - // Kill old content. - unset($this->content); - - $response = $this->fetchObject($fetchContent); - - - if ($fetchContent) { - $this->setContent($response->getBody()); - } - - return $this; - } - - /** - * Helper function for fetching an object. - * - * @param boolean $fetchContent If this is set to true, a GET request will be - * issued, which will cause the remote host to - * return the object in the response body. The - * response body is not handled, though. If this - * is set to false, a HEAD request is sent, and - * no body is returned. - * - * @return \OpenStack\Common\Transport\Response containing the object metadata and (depending - * on the $fetchContent flag) optionally the data. - */ - protected function fetchObject($fetchContent = false) - { - $method = $fetchContent ? 'GET' : 'HEAD'; - - $headers = ['X-Auth-Token' => $this->token]; - - $response = $this->client->send( - $this->client->createRequest($method, $this->url, null, ['headers' => $headers]) - ); - - if ($response->getStatusCode() != 200) { - throw new \OpenStack\Common\Exception('An unknown exception occurred during transmission.'); - } - - $this->extractFromHeaders($response); - - return $response; - } - - /** - * Extract information from HTTP headers. - * - * This is used internally to set object properties from headers. - * - * @return \OpenStack\ObjectStore\v1\Resource\RemoteObject $this for the current object so it - * can be used in chaining methods. - */ - protected function extractFromHeaders($response) - { - $this->setContentType($response->getHeader('Content-Type') ? $response->getHeader('Content-Type') : $this->contentType()); - $this->lastModified = strtotime($response->getHeader('Last-Modified') ? $response->getHeader('Last-Modified') : 0); - $this->etag = $response->getHeader('Etag') ? $response->getHeader('Etag') : $this->etag; - $this->contentLength = (int) ($response->getHeader('Content-Length') ? $response->getHeader('Content-Length') : 0); - - $this->setDisposition($response->getHeader('Content-Disposition', null)); - $this->setEncoding($response->getHeader('Content-Encoding', null)); - - // Reset the metadata, too: - $headers = []; - foreach ($response->getHeaders() as $name => $header) { - $headers[$name] = $header[0]; - } - $this->setMetadata(Container::extractHeaderAttributes($headers)); - - return $this; - } -} \ No newline at end of file diff --git a/src/OpenStack/ObjectStore/v1/Resource/StreamWrapper.php b/src/OpenStack/ObjectStore/v1/Resource/StreamWrapper.php deleted file mode 100644 index ebefdc2..0000000 --- a/src/OpenStack/ObjectStore/v1/Resource/StreamWrapper.php +++ /dev/null @@ -1,1495 +0,0 @@ - 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'. - * - 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 = []; - - /** - * 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 = []; - - 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 = []; - protected $dirIndex = 0; - protected $dirPrefix = ''; - - /** - * Close a directory. - * - * This closes a directory handle, freeing up the resources. - * - * - * - * 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 = []; - - //syslog(LOG_WARNING, "CLOSEDIR called."); - return true; - } - - /** - * Open a directory for reading. - * - * - * - * 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. - * - * - * - * @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. - * - * - */ - 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(). - * - * '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->proxyObject($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. - * - * - * - * 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. - * - * '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 (ResourceNotFoundException $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); - } - } catch (ResourceNotFoundException $nf) { - // If a 404 is thrown, we need to determine whether - // or not a new file should be created. - - // 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; - } - - } catch (Exception $e) { - // All other exceptions are fatal. - //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. - * - * '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. - * - * - * - * 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->proxyObject($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: - * - * 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 = [ - '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'. - * - 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(). - */ - 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; - - $region = $this->cxt('openstack.swift.region'); - - $this->store = ObjectStorage::newFromServiceCatalog($serviceCatalog, $token, $region, $client); - } - - 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; - } - -} diff --git a/src/OpenStack/ObjectStore/v1/Resource/StreamWrapperFS.php b/src/OpenStack/ObjectStore/v1/Resource/StreamWrapperFS.php deleted file mode 100644 index 436d487..0000000 --- a/src/OpenStack/ObjectStore/v1/Resource/StreamWrapperFS.php +++ /dev/null @@ -1,218 +0,0 @@ -cxt('swiftfs_fake_isdir_true', false) || !($this->testDirectoryExists($uri))); - - } - - /** - * Fake Remove a directory. - * - * ObjectStorage has pathy objects not directories. If no objects with a path - * prefix exist we can pass removing it. If objects with a path prefix exist - * removing the directory will fail. - */ - public function rmdir($path, $options) - { - return !($this->testDirectoryExists($path)); - - } - - /** - * @see stream_stat(). - */ - public function url_stat($path, $flags) - { - $stat = parent::url_stat($path, $flags); - - // If the file stat setup returned anything return it. - if ($stat) { - return $stat; - } - // When false is returned there is no file to stat. So, we attempt to handle - // it like a directory. - else { - if ($this->cxt('swiftfs_fake_isdir_true', false) || $this->testDirectoryExists($path)) { - // The directory prefix exists. Fake the directory file permissions. - return $this->fakeStat(true); - } else { - // The directory does not exist as a prefix. - return false; - } - } - } - - /////////////////////////////////////////////////////////////////// - // INTERNAL METHODS - // All methods beneath this line are not part of the Stream API. - /////////////////////////////////////////////////////////////////// - - /** - * Test if a path prefix (directory like) esits. - * - * ObjectStorage has pathy objects not directories. If objects exist with a - * path prefix we can consider that the directory exists. For example, if - * we have an object at foo/bar/baz.txt and test the existance of the - * directory foo/bar/ we sould see it. - * - * @param string $path The directory path to test. - * - * @return boolean true if the directory prefix exists and false otherwise. - */ - protected function testDirectoryExists($path) - { - $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 = '/'; - - - $dirListing = $container->objectsWithPrefix($this->dirPrefix, $sep); - - return !empty($dirListing); - } catch (\OpenStack\Common\Exception $e) { - trigger_error('Path could not be opened: ' . $e->getMessage(), E_USER_WARNING); - - return false; - } - } - - /** - * Fake stat data. - * - * Under certain conditions we have to return totally trumped-up - * stats. This generates those. - */ - protected function fakeStat($dir = false) - { - $request_time = time(); - - // Set inode type to directory or file. - $type = $dir ? 040000 : 0100000; - // Fake world-readible - $mode = $type + $this->cxt('swiftfs_fake_stat_mode', 0777); - - $values = [ - 'dev' => 0, - 'ino' => 0, - 'mode' => $mode, - 'nlink' => 0, - 'uid' => posix_getuid(), - 'gid' => posix_getgid(), - 'rdev' => 0, - 'size' => 0, - 'atime' => $request_time, - 'mtime' => $request_time, - 'ctime' => $request_time, - 'blksize' => -1, - 'blocks' => -1, - ]; - - $final = array_values($values) + $values; - - return $final; - } - -} diff --git a/src/OpenStack/ObjectStore/v1/Resource/Subdir.php b/src/OpenStack/ObjectStore/v1/Resource/Subdir.php deleted file mode 100644 index 491b589..0000000 --- a/src/OpenStack/ObjectStore/v1/Resource/Subdir.php +++ /dev/null @@ -1,75 +0,0 @@ -path = $path; - $this->delimiter = $delimiter; - } - - /** - * Get the path. - * - * The path is delimited using the string returned by delimiter(). - * - * @return string The path - */ - public function path() - { - return $this->path; - } - /** - * Get the delimiter used by the server. - * - * @return string The value used as a delimiter. - */ - public function delimiter() - { - return $this->delimiter; - } -} diff --git a/tests/README.md b/tests/README.md deleted file mode 100644 index fb011af..0000000 --- a/tests/README.md +++ /dev/null @@ -1,105 +0,0 @@ -# Running Tests for the PHP-Client bindings - -This file explains how to configured your environment for running the -PHP-Client automated testing. - -The OpenStack bindings offer a few stand-alone tests for testing basic -connectivity to OpenStack services, but most tests are of the -automated variety. - -*IMPORTANT*: Make sure your settings.ini file is up-to-date! Options -have changed! - -## Stand-alone Tests - -Stand-alone tests are designed to verify that certain preconditions of -the libary are met. - -### AuthTest.php - -The AuthTest test is a simple commandline program that allows you to -verify that your PHP client can successfully connect to OpenStack. To -run this test, do the following: - -1. Begin from the root directory of this project, where you should see - the directories `tests/` and `src/`, among others. -2. Execute the following command on the commandline: - -``` -$ php tests/AuthTest.php -``` - -This will instruct you to use a more complete version of the command, -including: - -* USERNAME: The username given to you. -* PASSWORD: The password associated with the username. -* URL: The Endpoint URL. -* TENANT ID: Your users's tenant ID. - -All four pieces of information can be found by logging into the -console. From there, you can execute a command like this: - -``` -$ php tests/AuthTest.php myusername apassword https://region-a.geo-1.identity.hpcloudsvc.com:35357/v2.0/ 1234567 - -``` - -If successfull, it should return details about your username, token, and -the services in your service catalog. - -## Unit Tests - -Unit and behavioral tests are built using [PHPUnit](http://www.phpunit.de/). Before you can -test this package, you will need to [install that tool](http://www.phpunit.de/manual/3.7/en/installation.html). - -Next, you need to create your own `settings.ini` file to contain your HP -Cloud credentials, along with your preferred testing parameters. - -### Creating settings.ini - -The easiest way to do this is to copy the example settings file, and -then make the necessary changes: - - $ cd tests/ - $ cp example.settings.ini settings.ini - $ edit settings.ini - -### Running Tests - -The test suite uses PHPUnit and can generate a code coverage report if -xdebug is installed. To run the test suite make sure PHPUnit is installed -via composer by using `composer install` or `composer update`. Once PHPUnit is -installed execute the following command from the root of the project. - - $ ./vendor/bin/phpunit - -This should generate output looking something like this: - - PHPUnit 4.0.13 by Sebastian Bergmann. - - Configuration read from /path/to/openstack-sdk-php/phpunit.xml.dist - - ............................................................... 63 / 146 ( 43%) - ............................................................... 126 / 146 ( 86%) - .................... - - Time: 4.94 minutes, Memory: 17.50Mb - - OK (146 tests, 413 assertions) - - Generating code coverage report in Clover XML format ... done - - Generating code coverage report in HTML format ... done - -If the tests fail, detailed information about the failure will be -displayed. - -PHPUnit has a wide variety of commandline options. Other sorts of -reports and analyses can be done using those. - -## Writing Tests - -Tests should be written according to the PHPUnit documentation. Tests -should follow the same coding standards as all other parts of the -library. diff --git a/tests/Tests/BootstrapTest.php b/tests/Tests/BootstrapTest.php deleted file mode 100644 index 673eb5e..0000000 --- a/tests/Tests/BootstrapTest.php +++ /dev/null @@ -1,30 +0,0 @@ -assertTrue(!empty(self::$settings)); - } -} diff --git a/tests/Tests/Common/Transport/AbstractClientTest.php b/tests/Tests/Common/Transport/AbstractClientTest.php deleted file mode 100644 index 6555b23..0000000 --- a/tests/Tests/Common/Transport/AbstractClientTest.php +++ /dev/null @@ -1,94 +0,0 @@ - 'bar']; - private $body = 'baz'; - - public function setUp() - { - $this->request = $this->getMockBuilder('OpenStack\Common\Transport\RequestInterface') - ->disableOriginalConstructor() - ->getMock(); - - $this->client = $this->getMockForAbstractClass('OpenStack\Common\Transport\AbstractClient'); - - $this->client->expects($this->once()) - ->method('send') - ->with($this->request); - } - - public function testGet() - { - $this->client->expects($this->once()) - ->method('createRequest') - ->with('GET', self::URI, null, $this->options) - ->will($this->returnValue($this->request)); - - $this->client->get(self::URI, $this->options); - } - - public function testHead() - { - $this->client->expects($this->once()) - ->method('createRequest') - ->with('HEAD', self::URI, null, $this->options) - ->will($this->returnValue($this->request)); - - $this->client->head(self::URI, $this->options); - } - - public function testPost() - { - $this->client->expects($this->once()) - ->method('createRequest') - ->with('POST', self::URI, $this->body, $this->options) - ->will($this->returnValue($this->request)); - - $this->client->post(self::URI, $this->body, $this->options); - } - - public function testPut() - { - $this->client->expects($this->once()) - ->method('createRequest') - ->with('PUT', self::URI, $this->body, $this->options) - ->will($this->returnValue($this->request)); - - $this->client->put(self::URI, $this->body, $this->options); - } - - public function testDelete() - { - $this->client->expects($this->once()) - ->method('createRequest') - ->with('DELETE', self::URI, null, $this->options) - ->will($this->returnValue($this->request)); - - $this->client->delete(self::URI, $this->options); - } -} \ No newline at end of file diff --git a/tests/Tests/Common/Transport/Guzzle/GuzzleAdapterTest.php b/tests/Tests/Common/Transport/Guzzle/GuzzleAdapterTest.php deleted file mode 100644 index f98b071..0000000 --- a/tests/Tests/Common/Transport/Guzzle/GuzzleAdapterTest.php +++ /dev/null @@ -1,92 +0,0 @@ -mockClient = $this->getMockBuilder('GuzzleHttp\Client') - ->disableOriginalConstructor() - ->getMock(); - - $this->adapter = new GuzzleAdapter($this->mockClient); - } - - public function testFactoryReturnsInstance() - { - $this->assertInstanceOf( - 'OpenStack\Common\Transport\Guzzle\GuzzleAdapter', - $this->adapter - ); - } - - public function testFactoryMethod() - { - $this->assertInstanceOf( - 'OpenStack\Common\Transport\Guzzle\GuzzleAdapter', - GuzzleAdapter::create() - ); - } - - public function testCreateRequestCallsClientAndReturnsAdapter() - { - $this->mockClient - ->expects($this->once()) - ->method('createRequest') - ->with('GET') - ->will($this->returnValue( - $this->getMock('GuzzleHttp\Message\RequestInterface') - )); - - $adapter = (new GuzzleAdapter($this->mockClient))->createRequest('GET'); - $this->assertInstanceOf('OpenStack\Common\Transport\Guzzle\RequestAdapter', $adapter); - $this->assertInstanceOf('GuzzleHttp\Message\RequestInterface', $adapter->getMessage()); - } - - public function testSetOptionCallsClient() - { - $key = 'foo'; - $value = 'bar'; - $this->mockClient->expects($this->once())->method('setDefaultOption')->with($key, $value); - - (new GuzzleAdapter($this->mockClient))->setOption($key, $value); - } - - public function testGetBaseUrlWithOption() - { - $this->mockClient->expects($this->once())->method('getBaseUrl'); - (new GuzzleAdapter($this->mockClient))->getOption('base_url'); - } - - public function testGetOption() - { - $this->mockClient->expects($this->once())->method('getDefaultOption')->with('foo'); - (new GuzzleAdapter($this->mockClient))->getOption('foo'); - } -} \ No newline at end of file diff --git a/tests/Tests/Common/Transport/Guzzle/HttpErrorTest.php b/tests/Tests/Common/Transport/Guzzle/HttpErrorTest.php deleted file mode 100644 index 01bb6b1..0000000 --- a/tests/Tests/Common/Transport/Guzzle/HttpErrorTest.php +++ /dev/null @@ -1,128 +0,0 @@ -assertInstanceOf('OpenStack\Common\Transport\Guzzle\HttpError', $sub); - } - - private function getEvent() - { - return new CompleteEvent(new Transaction(new Client(), new Request('GET', '/'))); - } - - public function testSuccessfulResponsesThrowNothing() - { - $event = $this->getEvent(); - $event->intercept(new Response(200)); - (new HttpError())->onComplete($event); - } - - /** - * @expectedException \OpenStack\Common\Transport\Exception\ConflictException - */ - public function testConflictExceptionRaisedFor409Error() - { - $event = $this->getEvent(); - $event->intercept(new Response(409)); - (new HttpError())->onComplete($event); - } - - /** - * @expectedException \OpenStack\Common\Transport\Exception\ForbiddenException - */ - public function testConflictExceptionRaisedFor403Error() - { - $event = $this->getEvent(); - $event->intercept(new Response(403)); - (new HttpError())->onComplete($event); - } - - /** - * @expectedException \OpenStack\Common\Transport\Exception\LengthRequiredException - */ - public function testConflictExceptionRaisedFor411Error() - { - $event = $this->getEvent(); - $event->intercept(new Response(411)); - (new HttpError())->onComplete($event); - } - - /** - * @expectedException \OpenStack\Common\Transport\Exception\MethodNotAllowedException - */ - public function testConflictExceptionRaisedFor405Error() - { - $event = $this->getEvent(); - $event->intercept(new Response(405)); - (new HttpError())->onComplete($event); - } - - /** - * @expectedException \OpenStack\Common\Transport\Exception\ResourceNotFoundException - */ - public function testConflictExceptionRaisedFor404Error() - { - $event = $this->getEvent(); - $event->intercept(new Response(404)); - (new HttpError())->onComplete($event); - } - - /** - * @expectedException \OpenStack\Common\Transport\Exception\ServerException - */ - public function testConflictExceptionRaisedFor500Error() - { - $event = $this->getEvent(); - $event->intercept(new Response(500)); - (new HttpError())->onComplete($event); - } - - /** - * @expectedException \OpenStack\Common\Transport\Exception\UnauthorizedException - */ - public function testConflictExceptionRaisedFor401Error() - { - $event = $this->getEvent(); - $event->intercept(new Response(401)); - (new HttpError())->onComplete($event); - } - - /** - * @expectedException \OpenStack\Common\Transport\Exception\UnprocessableEntityException - */ - public function testConflictExceptionRaisedFor422Error() - { - $event = $this->getEvent(); - $event->intercept(new Response(422)); - (new HttpError())->onComplete($event); - } -} \ No newline at end of file diff --git a/tests/Tests/Common/Transport/Guzzle/MessageAdapterTest.php b/tests/Tests/Common/Transport/Guzzle/MessageAdapterTest.php deleted file mode 100644 index 2392a8b..0000000 --- a/tests/Tests/Common/Transport/Guzzle/MessageAdapterTest.php +++ /dev/null @@ -1,139 +0,0 @@ -getMockBuilder($class) - ->disableOriginalConstructor() - ->getMock(); - } - - public function setUp() - { - $this->mock = $this->getStub(self::REQUEST_CLASS); - $this->adapter = new MessageAdapter($this->mock); - } - - public function testConstructorSetsMessage() - { - $this->assertInstanceOf(self::REQUEST_CLASS, $this->adapter->getMessage()); - } - - public function testSettingMessage() - { - $this->adapter->setMessage($this->getStub(self::RESPONSE_CLASS)); - $this->assertInstanceOf(self::RESPONSE_CLASS, $this->adapter->getMessage()); - } - - public function testGetProtocol() - { - $this->mock->expects($this->once())->method('getProtocolVersion'); - $this->adapter->setMessage($this->mock); - $this->adapter->getProtocolVersion(); - } - - public function testSetBody() - { - $body = $this->getMock('GuzzleHttp\Stream\StreamInterface'); - $this->mock->expects($this->once())->method('setBody')->with($body); - $this->adapter->setMessage($this->mock); - $this->adapter->setBody($body); - } - - public function testGetBody() - { - $this->mock->expects($this->once())->method('getBody'); - $this->adapter->setMessage($this->mock); - $this->adapter->getBody(); - } - - public function testGetHeaders() - { - $this->mock->expects($this->once())->method('getHeaders'); - $this->adapter->setMessage($this->mock); - $this->adapter->getHeaders(); - } - - public function testHasHeader() - { - $this->mock->expects($this->once())->method('hasHeader')->with('foo'); - $this->adapter->setMessage($this->mock); - $this->adapter->hasHeader('foo'); - } - - public function testSetHeader() - { - $header = 'foo'; - $value = 'bar'; - $this->mock->expects($this->once())->method('setHeader')->with($header, $value); - $this->adapter->setMessage($this->mock); - $this->adapter->setHeader($header, $value); - } - - public function testGetHeader() - { - $this->mock->expects($this->once())->method('getHeader')->with('foo'); - $this->adapter->setMessage($this->mock); - $this->adapter->getHeader('foo'); - } - - public function testSetHeaders() - { - $headers = ['foo' => 'bar']; - $this->mock->expects($this->once())->method('setHeaders')->with($headers); - $this->adapter->setMessage($this->mock); - $this->adapter->setHeaders($headers); - } - - public function testAddHeader() - { - $header = 'foo'; - $value = 'bar'; - $this->mock->expects($this->once())->method('addHeader')->with($header, $value); - $this->adapter->setMessage($this->mock); - $this->adapter->addHeader($header, $value); - } - - public function testAddHeaders() - { - $headers = ['foo' => 'bar']; - $this->mock->expects($this->once())->method('addHeaders')->with($headers); - $this->adapter->setMessage($this->mock); - $this->adapter->addHeaders($headers); - } - - public function testRemoveHeader() - { - $this->mock->expects($this->once())->method('removeHeader')->with('foo'); - $this->adapter->setMessage($this->mock); - $this->adapter->removeHeader('foo'); - } -} \ No newline at end of file diff --git a/tests/Tests/Common/Transport/Guzzle/RequestAdapterTest.php b/tests/Tests/Common/Transport/Guzzle/RequestAdapterTest.php deleted file mode 100644 index c278919..0000000 --- a/tests/Tests/Common/Transport/Guzzle/RequestAdapterTest.php +++ /dev/null @@ -1,69 +0,0 @@ -getMockBuilder($class) - ->disableOriginalConstructor() - ->getMock(); - } - - public function setUp() - { - $this->mock = $this->getStub('GuzzleHttp\Message\Request'); - $this->adapter = new RequestAdapter($this->mock); - } - - public function testGetMethod() - { - $this->mock->expects($this->once())->method('getMethod'); - $this->adapter->setMessage($this->mock); - $this->adapter->getMethod(); - } - - public function testSetMethod() - { - $this->mock->expects($this->once())->method('setMethod')->with('foo'); - $this->adapter->setMessage($this->mock); - $this->adapter->setMethod('foo'); - } - - public function testGetUrl() - { - $this->mock->expects($this->once())->method('getUrl'); - $this->adapter->setMessage($this->mock); - $this->adapter->getUrl(); - } - - public function testSetUrl() - { - $this->mock->expects($this->once())->method('setUrl')->with('foo'); - $this->adapter->setMessage($this->mock); - $this->adapter->setUrl('foo'); - } -} \ No newline at end of file diff --git a/tests/Tests/Common/Transport/Guzzle/ResponseAdapterTest.php b/tests/Tests/Common/Transport/Guzzle/ResponseAdapterTest.php deleted file mode 100644 index 653ddf2..0000000 --- a/tests/Tests/Common/Transport/Guzzle/ResponseAdapterTest.php +++ /dev/null @@ -1,55 +0,0 @@ -getMockBuilder($class) - ->disableOriginalConstructor() - ->getMock(); - } - - public function setUp() - { - $this->mock = $this->getStub('GuzzleHttp\Message\Response'); - $this->adapter = new ResponseAdapter($this->mock); - } - - public function testGetStatusCode() - { - $this->mock->expects($this->once())->method('getStatusCode'); - $this->adapter->setMessage($this->mock); - $this->adapter->getStatusCode(); - } - - public function testGetReasonPhrase() - { - $this->mock->expects($this->once())->method('getReasonPhrase'); - $this->adapter->setMessage($this->mock); - $this->adapter->getReasonPhrase(); - } -} \ No newline at end of file diff --git a/tests/Tests/Common/Transport/UrlTest.php b/tests/Tests/Common/Transport/UrlTest.php deleted file mode 100644 index 3ee4515..0000000 --- a/tests/Tests/Common/Transport/UrlTest.php +++ /dev/null @@ -1,113 +0,0 @@ -url = new Url(self::URL_STRING); - } - - public function testIsConstructedWithProperties() - { - $this->assertEquals('https', $this->url->getScheme()); - $this->assertEquals('openstack.org', $this->url->getHost()); - $this->assertEquals('80', $this->url->getPort()); - $this->assertEquals('/community/members', $this->url->getPath()); - $this->assertEquals('username', $this->url->getUser()); - $this->assertEquals('password', $this->url->getPassword()); - $this->assertEquals('anchor', $this->url->getFragment()); - } - - public function testSettingStringUrlResultsInArrayBasedQuery() - { - $url = new Url('//foo.com?bar=a&baz=b'); - $this->assertEquals(['bar' => 'a', 'baz' => 'b'], $url->getQuery()); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testExceptionIsThrownWhenPopulatingWithInvalidDataType() - { - $value = (object) ['path' => 'https', 'host' => 'openstack.org']; - new Url($value); - } - - public function testSettingQueryWithString() - { - $this->url->setQuery('foo=bar&baz=boo'); - $this->assertEquals(['foo' => 'bar', 'baz' => 'boo'], $this->url->getQuery()); - } - - public function testSettingQueryWithStringArray() - { - $this->url->setQuery('foo[]=bar&foo[]=baz'); - $this->assertEquals(['foo' => ['bar', 'baz']], $this->url->getQuery()); - } - - public function testSettingQueryWithArray() - { - $query = ['foo' => 'bar']; - $this->url->setQuery($query); - $this->assertEquals($query, $this->url->getQuery()); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testExceptionIsThrownWhenSettingQueryWithInvalidDataType() - { - $this->url->setQuery(false); - } - - public function testAddPath() - { - $this->url->addPath('foo'); - $this->assertEquals('/community/members/foo', $this->url->getPath()); - } - - public function testAddQuery() - { - $this->url->setQuery(['foo' => 'bar']); - $this->url->addQuery(['baz' => 'boo']); - $this->assertEquals(['foo' => 'bar', 'baz' => 'boo'], $this->url->getQuery()); - } - - public function testCastingToString() - { - $this->assertEquals(self::URL_STRING, (string) $this->url); - } - - public function testCastingToStringForQueryArrays() - { - $url = new Url('http://openstack.org'); - $url->setQuery(['foo' => ['val1', 'val2'], 'bar' => 'val3']); - - $this->assertEquals('http://openstack.org?foo[]=val1&foo[]=val2&bar=val3', (string) $url); - } -} \ No newline at end of file diff --git a/tests/Tests/Identity/v2/IdentityServicesTest.php b/tests/Tests/Identity/v2/IdentityServicesTest.php deleted file mode 100644 index d6d9103..0000000 --- a/tests/Tests/Identity/v2/IdentityServicesTest.php +++ /dev/null @@ -1,439 +0,0 @@ -assertNotEmpty($endpoint); - - $service = new IdentityService($endpoint, $this->getTransportClient()); - - $this->assertInstanceOf('\OpenStack\Identity\v2\IdentityService', $service); - - return $service; - } - - /** - * @depends testConstructor - */ - public function testUrl() - { - $endpoint = self::conf('openstack.identity.url'); - $service = new IdentityService($endpoint, $this->getTransportClient()); - - // If there is a trailing / we remove that from the endpoint. Our calls add - // the / back where appropriate. - $this->assertStringStartsWith(rtrim($endpoint, '/'), $service->url()); - - return $service; - } - - /** - * @depends testUrl - */ - public function testAuthenticate($service) - { - // Canary: Make sure all the required params are declared. - $settings = [ - 'openstack.identity.username', - 'openstack.identity.password', - 'openstack.identity.tenantId', - ]; - foreach ($settings as $setting) { - $this->assertNotEmpty(self::conf($setting), "Required param: " . $setting); - } - - // Test username/password auth. - $auth = [ - 'passwordCredentials' => [ - 'username' => self::conf('openstack.identity.username'), - 'password' => self::conf('openstack.identity.password'), - ], - 'tenantId' => self::conf('openstack.identity.tenantId'), - ]; - $tok = $service->authenticate($auth); - $this->assertNotEmpty($tok); - - // Again with no tenant ID. - $auth = [ - 'passwordCredentials' => [ - 'username' => self::conf('openstack.identity.username'), - 'password' => self::conf('openstack.identity.password'), - ], - //'tenantId' => self::conf('openstack.identity.tenantId'), - ]; - $tok = $service->authenticate($auth); - $this->assertNotEmpty($tok); - } - - /** - * @depends testAuthenticate - */ - public function testAuthenticateAsUser() - { - $service = new IdentityService(self::conf('openstack.identity.url'), $this->getTransportClient()); - - $user = self::conf('openstack.identity.username'); - $pass = self::conf('openstack.identity.password'); - $tenantId = self::conf('openstack.identity.tenantId'); - - $tok = $service->authenticateAsUser($user, $pass, $tenantId); - $this->assertNotEmpty($tok); - - return $service; - } - - public function testAuthenticatingAsUserWithoutTenant() - { - $service = new IdentityService(self::conf('openstack.identity.url'), $this->getTransportClient()); - - $username = self::conf('openstack.identity.username'); - $password = self::conf('openstack.identity.password'); - - $this->assertNotEmpty($service->authenticateAsUser($username, $password)); - } - - /** - * @depends testAuthenticateAsUser - */ - public function testToken($service) - { - $this->assertNotEmpty($service->token()); - } - - /** - * @depends testAuthenticateAsUser - */ - public function testIsExpired($service) - { - $this->assertFalse($service->isExpired()); - - $service2 = new IdentityService(self::conf('openstack.identity.url'), $this->getTransportClient()); - $this->assertTrue($service2->isExpired()); - } - - /** - * @depends testAuthenticateAsUser - */ - public function testTenantName() - { - $user = self::conf('openstack.identity.username'); - $pass = self::conf('openstack.identity.password'); - $tenantName = self::conf('openstack.identity.tenantName'); - - $service = new IdentityService(self::conf('openstack.identity.url'), $this->getTransportClient()); - $this->assertNull($service->tenantName()); - - $service->authenticateAsUser($user, $pass); - $this->assertEmpty($service->tenantName()); - - $service = new IdentityService(self::conf('openstack.identity.url'), $this->getTransportClient()); - $ret = $service->authenticateAsUser($user, $pass, null, $tenantName); - $this->assertNotEmpty($service->tenantName()); - - $service = new IdentityService(self::conf('openstack.identity.url'), $this->getTransportClient()); - $this->assertNull($service->tenantName()); - } - - /** - * @depends testAuthenticateAsUser - */ - public function testTenantId() - { - $user = self::conf('openstack.identity.username'); - $pass = self::conf('openstack.identity.password'); - $tenantId = self::conf('openstack.identity.tenantId'); - - $service = new IdentityService(self::conf('openstack.identity.url'), $this->getTransportClient()); - $this->assertNull($service->tenantId()); - - $service->authenticateAsUser($user, $pass); - $this->assertEmpty($service->tenantId()); - - $service = new IdentityService(self::conf('openstack.identity.url'), $this->getTransportClient()); - $service->authenticateAsUser($user, $pass, $tenantId); - $this->assertNotEmpty($service->tenantId()); - } - - /** - * @depends testAuthenticateAsUser - */ - public function testTokenDetails() - { - $now = time(); - $user = self::conf('openstack.identity.username'); - $pass = self::conf('openstack.identity.password'); - $tenantId = self::conf('openstack.identity.tenantId'); - - $service = new IdentityService(self::conf('openstack.identity.url'), $this->getTransportClient()); - $service->authenticateAsUser($user, $pass); - - // Details for user auth. - $details = $service->tokenDetails(); - $this->assertNotEmpty($details['id']); - $this->assertFalse(isset($details['tenant'])); - - $ts = strtotime($details['expires']); - $this->assertGreaterThan($now, $ts); - - // Test details for username auth. - $service = new IdentityService(self::conf('openstack.identity.url'), $this->getTransportClient()); - $service->authenticateAsUser($user, $pass, $tenantId); - - $details = $service->tokenDetails(); - - $expectUser = self::conf('openstack.identity.username'); - - $this->assertStringStartsWith($expectUser, $details['tenant']['name']); - $this->assertNotEmpty($details['id']); - $this->assertNotEmpty($details['tenant']['id']); - - $this->assertEquals($tenantId, $details['tenant']['id']); - - $ts = strtotime($details['expires']); - $this->assertGreaterThan($now, $ts); - } - - /** - * @depends testAuthenticateAsUser - */ - public function testServiceCatalog($service) - { - $catalog = $service->serviceCatalog(); - - $this->assertGreaterThan(0, count($catalog)); - - $idService = null; - foreach ($catalog as $item) { - if ($item['type'] == 'identity') { - $idService = $item; - } - } - - $this->assertNotEmpty($idService['endpoints']); - $this->assertNotEmpty($idService['endpoints'][0]['publicURL']); - - // Test filters. - $justID = $service->serviceCatalog('identity'); - $this->assertEquals(1, count($justID)); - - $idService = $justID[0]; - $this->assertNotEmpty($idService['endpoints']); - $this->assertNotEmpty($idService['endpoints'][0]['publicURL']); - - // Make sure a missed filter returns an empty set. - $expectEmpty = $service->serviceCatalog('no-such-servicename'); - $this->assertEmpty($expectEmpty); - } - - /** - * @depends testAuthenticateAsUser - */ - public function testUser($service) - { - $user = $service->user(); - - $this->assertEquals(self::conf('openstack.identity.username'), $user['name']); - $this->assertNotEmpty($user['roles']); - } - - /** - * @depends testAuthenticateAsUser - * @group serialize - */ - public function testSerialization($service) - { - $ser = serialize($service); - - $this->assertNotEmpty($ser); - - $again = unserialize($ser); - - $this->assertInstanceOf('\OpenStack\Identity\v2\IdentityService', $again); - - $this->assertEquals($service->tenantId(), $again->tenantId()); - $this->assertEquals($service->serviceCatalog(), $again->serviceCatalog()); - $this->assertEquals($service->tokenDetails(), $again->tokenDetails()); - $this->assertEquals($service->user(), $again->user()); - $this->assertFalse($again->isExpired()); - - $tenantId = $again->tenantId(); - - $newTok = $again->rescopeUsingTenantId($tenantId); - - $this->assertNotEmpty($newTok); - } - - /** - * @group tenant - */ - public function testTenants() - { - $service = new IdentityService(self::conf('openstack.identity.url'), $this->getTransportClient()); - $service2 = new IdentityService(self::conf('openstack.identity.url'), $this->getTransportClient()); - $user = self::conf('openstack.identity.username'); - $pass = self::conf('openstack.identity.password'); - $tenantId = self::conf('openstack.identity.tenantId'); - $service->authenticateAsUser($user, $pass, $tenantId); - - $tenants = $service2->tenants($service->token()); - - $this->assertGreaterThan(0, count($tenants)); - $this->assertNotEmpty($tenants[0]['name']); - $this->assertNotEmpty($tenants[0]['id']); - - $tenants = $service->tenants(); - $this->assertGreaterThan(0, count($tenants)); - $this->assertNotEmpty($tenants[0]['name']); - $this->assertNotEmpty($tenants[0]['id']); - - } - - /** - * @group tenant - * @depends testTenants - */ - public function testRescopeUsingTenantId() - { - $service = new IdentityService(self::conf('openstack.identity.url'), $this->getTransportClient()); - $user = self::conf('openstack.identity.username'); - $pass = self::conf('openstack.identity.password'); - $tenantId = self::conf('openstack.identity.tenantId'); - - // Authenticate without a tenant ID. - $token = $service->authenticateAsUser($user, $pass); - - $this->assertNotEmpty($token); - - $details = $service->tokenDetails(); - $this->assertFalse(isset($details['tenant'])); - - $service->rescopeUsingTenantId($tenantId); - - $details = $service->tokenDetails(); - $this->assertEquals($tenantId, $details['tenant']['id']); - - // Test unscoping - $service->rescopeUsingTenantId(''); - $details = $service->tokenDetails(); - $this->assertFalse(isset($details['tenant'])); - } - - /** - * @group tenant - * @depends testTenants - */ - public function testRescopeByTenantName() - { - $service = new IdentityService(self::conf('openstack.identity.url'), $this->getTransportClient()); - $user = self::conf('openstack.identity.username'); - $pass = self::conf('openstack.identity.password'); - $tenantName = self::conf('openstack.identity.tenantName'); - - // Authenticate without a tenant ID. - $token = $service->authenticateAsUser($user, $pass); - - $this->assertNotEmpty($token); - - $details = $service->tokenDetails(); - $this->assertFalse(isset($details['tenant'])); - - $service->rescopeUsingTenantName($tenantName); - - $details = $service->tokenDetails(); - $this->assertEquals($tenantName, $details['tenant']['name']); - - // Test unscoping - $service->rescopeUsingTenantName(''); - $details = $service->tokenDetails(); - $this->assertFalse(isset($details['tenant'])); - } - - /** - * Test the bootstrap identity factory. - * @depends testAuthenticateAsUser - */ - public function testBootstrap() - { - // We need to save the config settings and reset the bootstrap to this. - // It does not remove the old settings. The means the identity fall through - // for different settings may not happen because of ordering. So, we cache - // and reset back to the default for each test. - $reset = Bootstrap::$config; - - // Test authenticating as a user. - $settings = [ - 'username' => self::conf('openstack.identity.username'), - 'password' => self::conf('openstack.identity.password'), - 'endpoint' => self::conf('openstack.identity.url'), - 'tenantid' => self::conf('openstack.identity.tenantId'), - 'transport' => self::conf('transport'), - 'transport.debug' => self::conf('transport.debug', false), - 'transport.ssl_verify' => self::conf('transport.ssl', true), - ]; - if (self::conf('transport.timeout')) { - $setting['transport.timeout'] = self::conf('transport.timeout'); - } - if (self::conf('transport.proxy')) { - $setting['transport.proxy'] = self::conf('transport.proxy'); - } - Bootstrap::setConfiguration($settings); - - $is = Bootstrap::identity(true); - $this->assertInstanceOf('\OpenStack\Identity\v2\IdentityService', $is); - - // Test getting a second instance from the cache. - $is2 = Bootstrap::identity(); - $this->assertEquals($is, $is2); - - // Test that forcing a refresh does so. - $is2 = Bootstrap::identity(true); - $this->assertNotEquals($is, $is2); - - Bootstrap::$config = $reset; - - // Test with tenant name - $settings = [ - 'username' => self::conf('openstack.identity.username'), - 'password' => self::conf('openstack.identity.password'), - 'endpoint' => self::conf('openstack.identity.url'), - 'tenantname' => self::conf('openstack.identity.tenantName'), - 'transport' => self::conf('transport'), - 'transport.debug' => self::conf('transport.debug', false), - 'transport.ssl_verify' => self::conf('transport.ssl', true), - ]; - if (self::conf('transport.timeout')) { - $setting['transport.timeout'] = self::conf('transport.timeout'); - } - if (self::conf('transport.proxy')) { - $setting['transport.proxy'] = self::conf('transport.proxy'); - } - Bootstrap::setConfiguration($settings); - - $is = Bootstrap::identity(true); - $this->assertInstanceOf('\OpenStack\Identity\v2\IdentityService', $is); - } -} diff --git a/tests/Tests/ObjectStore/v1/ObjectStorageTest.php b/tests/Tests/ObjectStore/v1/ObjectStorageTest.php deleted file mode 100644 index 33cd359..0000000 --- a/tests/Tests/ObjectStore/v1/ObjectStorageTest.php +++ /dev/null @@ -1,287 +0,0 @@ -assertTrue(!empty(self::$settings)); - } - - /** - * @group auth - */ - public function testConstructor() - { - $ident = $this->identity(); - - $services = $ident->serviceCatalog(\OpenStack\ObjectStore\v1\ObjectStorage::SERVICE_TYPE); - - if (empty($services)) { - throw new \Exception('No object-store service found.'); - } - - //$serviceURL = $services[0]['endpoints'][0]['adminURL']; - $serviceURL = $services[0]['endpoints'][0]['publicURL']; - - $ostore = new \OpenStack\ObjectStore\v1\ObjectStorage($ident->token(), $serviceURL, $this->getTransportClient()); - - $this->assertInstanceOf('\OpenStack\ObjectStore\v1\ObjectStorage', $ostore); - $this->assertTrue(strlen($ostore->token()) > 0); - - } - - public function testNewFromServiceCatalog() - { - $ident = $this->identity(); - $tok = $ident->token(); - $cat = $ident->serviceCatalog(); - $region = self::$settings['openstack.swift.region']; - $client = $this->getTransportClient(); - $ostore = \OpenStack\ObjectStore\v1\ObjectStorage::newFromServiceCatalog($cat, $tok, $region, $client); - $this->assertInstanceOf('\OpenStack\ObjectStore\v1\ObjectStorage', $ostore); - $this->assertTrue(strlen($ostore->token()) > 0); - } - - public function testFailedNewFromServiceCatalog() - { - $ident = $this->identity(); - $tok = $ident->token(); - $cat = $ident->serviceCatalog(); - $client = $this->getTransportClient(); - $ostore = \OpenStack\ObjectStore\v1\ObjectStorage::newFromServiceCatalog($cat, $tok, 'region-w.geo-99999.fake'); - $this->assertEmpty($ostore); - } - - public function testNewFromIdentity() - { - $ident = $this->identity(); - $region = self::$settings['openstack.swift.region']; - $client = $this->getTransportClient(); - $ostore = \OpenStack\ObjectStore\v1\ObjectStorage::newFromIdentity($ident, $region, $client); - $this->assertInstanceOf('\OpenStack\ObjectStore\v1\ObjectStorage', $ostore); - $this->assertTrue(strlen($ostore->token()) > 0); - } - - /** - * @group auth - * @group acl - */ - public function testCreateContainer() - { - $testCollection = self::$settings['openstack.swift.container']; - - $this->assertNotEmpty($testCollection, "Canary: container name must be in settings file."); - - $store = $this->objectStore(); - - $this->destroyContainerFixture(); - /* - if ($store->hasContainer($testCollection)) { - $store->deleteContainer($testCollection); - } - */ - - $md = ['Foo' => 1234]; - - $ret = $store->createContainer($testCollection, null, $md); - $this->assertTrue($ret, "Create container"); - - } - - /** - * @group auth - * @depends testCreateContainer - */ - public function testAccountInfo() - { - $store = $this->objectStore(); - - $info = $store->accountInfo(); - - $this->assertGreaterThan(0, $info['containers']); - $this->assertGreaterThanOrEqual(0, $info['bytes']); - $this->assertGreaterThanOrEqual(0, $info['objects']); - } - - /** - * @depends testCreateContainer - */ - public function testContainers() - { - $store = $this->objectStore(); - $containers = $store->containers(); - - $this->assertNotEmpty($containers); - - //$first = array_shift($containers); - - $testCollection = self::conf('openstack.swift.container'); - $testContainer = $containers[$testCollection]; - $this->assertEquals($testCollection, $testContainer->name()); - $this->assertEquals(0, $testContainer->bytes()); - $this->assertEquals(0, $testContainer->count()); - - // Make sure we get back an ACL: - $this->assertInstanceOf('\OpenStack\ObjectStore\v1\Resource\ACL', $testContainer->acl()); - } - - /** - * @depends testCreateContainer - */ - public function testContainer() - { - $testCollection = self::$settings['openstack.swift.container']; - $store = $this->objectStore(); - - $container = $store->container($testCollection); - - $this->assertEquals(0, $container->bytes()); - $this->assertEquals(0, $container->count()); - $this->assertEquals($testCollection, $container->name()); - - $md = $container->metadata(); - $this->assertEquals(1, count($md)); - $this->assertEquals('1234', $md['Foo']); - } - - /** - * @depends testCreateContainer - */ - public function testHasContainer() - { - $testCollection = self::$settings['openstack.swift.container']; - $store = $this->objectStore(); - - $this->assertTrue($store->hasContainer($testCollection)); - $this->assertFalse($store->hasContainer('nihil')); - } - - /** - * @depends testHasContainer - */ - public function testDeleteContainer() - { - $testCollection = self::$settings['openstack.swift.container']; - - $store = $this->objectStore(); - //$ret = $store->createContainer($testCollection); - //$this->assertTrue($store->hasContainer($testCollection)); - - $ret = $store->deleteContainer($testCollection); - - $this->assertTrue($ret); - - // Now we try to delete a container that does not exist. - $ret = $store->deleteContainer('nihil'); - $this->assertFalse($ret); - } - - /** - * @expectedException \OpenStack\ObjectStore\v1\Exception\ContainerNotEmptyException - */ - public function testDeleteNonEmptyContainer() - { - $testCollection = self::$settings['openstack.swift.container']; - - $this->assertNotEmpty($testCollection); - - $store = $this->objectStore(); - $store->createContainer($testCollection); - - $container = $store->container($testCollection); - $container->save(new Object('test', 'test', 'text/plain')); - - try { - $ret = $store->deleteContainer($testCollection); - } catch (\Exception $e) { - $container->delete('test'); - $store->deleteContainer($testCollection); - throw $e; - } - - try { - $container->delete('test'); - } - // Skip 404s. - catch (\Exception $e) {} - - $store->deleteContainer($testCollection); - } - - /** - * @depends testCreateContainer - * @group acl - */ - public function testCreateContainerPublic() - { - $testCollection = self::$settings['openstack.swift.container'] . 'PUBLIC'; - $store = $this->objectStore(); - if ($store->hasContainer($testCollection)) { - $store->deleteContainer($testCollection); - } - - $ret = $store->createContainer($testCollection, ACL::makePublic()); - $container = $store->container($testCollection); - - // Now test that we can get the container contents. Since there is - // no content in the container, we use the format=xml to make sure - // we get some data back. - $url = $container->url() . '?format=xml'; - - $data = file_get_contents($url); - $this->assertNotEmpty($data, $url); - - $containers = $store->containers(); - - $store->deleteContainer($testCollection); - } - - /** - * @depends testCreateContainerPublic - */ - public function testChangeContainerACL() - { - $testCollection = self::$settings['openstack.swift.container'] . 'PUBLIC'; - $store = $this->objectStore(); - if ($store->hasContainer($testCollection)) { - $store->deleteContainer($testCollection); - } - $ret = $store->createContainer($testCollection); - - $acl = ACL::makePublic(); - $ret = $store->changeContainerACL($testCollection, $acl); - - $this->assertFalse($ret); - - $container = $store->container($testCollection); - $url = $container->url() . '?format=xml'; - - $data = file_get_contents($url); - $this->assertNotEmpty($data, $url); - - $store->deleteContainer($testCollection); - } -} diff --git a/tests/Tests/ObjectStore/v1/Resource/ACLTest.php b/tests/Tests/ObjectStore/v1/Resource/ACLTest.php deleted file mode 100644 index 4ea8fa9..0000000 --- a/tests/Tests/ObjectStore/v1/Resource/ACLTest.php +++ /dev/null @@ -1,215 +0,0 @@ -assertEmpty($acl->rules()); - - } - - public function testAddAccount() - { - $acl = new ACL(); - - $acl->addAccount(ACL::READ, 'test'); - - $rules = $acl->rules(); - - $this->assertEquals(1, count($rules)); - - $rule = array_shift($rules); - - $this->assertEquals(ACL::READ, $rule['mask']); - $this->assertEquals('test', $rule['account']); - - // Test with user - $acl = new ACL(); - $acl->addAccount(ACL::WRITE, 'admin', 'earnie'); - $rules = $acl->rules(); - $rule = array_shift($rules); - - $this->assertEquals(ACL::WRITE, $rule['mask']); - $this->assertEquals('admin', $rule['account']); - $this->assertEquals('earnie', $rule['user']); - - // Test with multiple users: - $acl = new ACL(); - $acl->addAccount(ACL::WRITE, 'admin', ['earnie', 'bert']); - $rules = $acl->rules(); - $rule = array_shift($rules); - - $this->assertEquals(ACL::WRITE, $rule['mask']); - $this->assertEquals('admin', $rule['account']); - $this->assertEquals('earnie', $rule['user'][0]); - $this->assertEquals('bert', $rule['user'][1]); - - } - - public function testAddReferrer() - { - $acl = new ACL(); - $acl->addReferrer(ACL::READ, '.example.com'); - $acl->addReferrer(ACL::READ_WRITE, '-bad.example.com'); - - $rules = $acl->rules(); - - $this->assertEquals(2, count($rules)); - - $first = array_shift($rules); - $this->assertEquals(ACL::READ, $first['mask']); - $this->assertEquals('.example.com', $first['host']); - } - - public function testAllowListings() - { - $acl = new ACL(); - $acl->allowListings(); - $rules = $acl->rules(); - - $this->assertEquals(1, count($rules)); - $this->assertTrue($rules[0]['rlistings']); - $this->assertEquals(ACL::READ, $rules[0]['mask']); - } - - public function testHeaders() - { - $acl = new ACL(); - $acl->addAccount(ACL::READ_WRITE, 'test'); - - $headers = $acl->headers(); - - $this->assertEquals(2, count($headers)); - $read = $headers[ACL::HEADER_READ]; - $write = $headers[ACL::HEADER_WRITE]; - - $this->assertEquals('test', $read); - $this->assertEquals('test', $write); - - // Test hostname rules, which should only appear in READ. - $acl = new ACL(); - $acl->addReferrer(ACL::READ_WRITE, '.example.com'); - $headers = $acl->headers(); - - $this->assertEquals(1, count($headers), print_r($headers, true)); - $read = $headers[ACL::HEADER_READ]; - - $this->assertEquals('.r:.example.com', $read); - } - - public function testToString() - { - $acl = new ACL(); - $acl->addReferrer(ACL::READ_WRITE, '.example.com'); - - $str = (string) $acl; - - $this->assertEquals('X-Container-Read: .r:.example.com', $str); - } - - public function testMakePublic() - { - $acl = (string) ACL::makePublic(); - - $this->assertEquals('X-Container-Read: .r:*,.rlistings', $acl); - } - - public function testMakeNonPublic() - { - $acl = (string) ACL::makeNonPublic(); - - $this->assertEmpty($acl); - } - - public function testNewFromHeaders() - { - $headers = [ - ACL::HEADER_READ => '.r:.example.com,.rlistings,.r:-*.evil.net', - ACL::HEADER_WRITE => 'testact2, testact3:earnie, .rlistings ', - ]; - - $acl = ACL::newFromHeaders($headers); - - $rules = $acl->rules(); - - $this->assertEquals(6, count($rules)); - - // Yay, now we get to test each one. - - $this->assertEquals(ACL::READ, $rules[0]['mask']); - $this->assertEquals('.example.com', $rules[0]['host']); - $this->assertTrue($rules[1]['rlistings']); - $this->assertEquals('-*.evil.net', $rules[2]['host']); - - $this->assertEquals(ACL::WRITE, $rules[3]['mask']); - $this->assertEquals('testact2', $rules[3]['account']); - $this->assertEquals('testact3', $rules[4]['account']); - $this->assertEquals('earnie', $rules[4]['user']); - $this->assertTrue($rules[5]['rlistings']); - - // Final canary: - $headers = $acl->headers(); - $read = $headers[ACL::HEADER_READ]; - $write = $headers[ACL::HEADER_WRITE]; - - $this->assertEquals('.r:.example.com,.rlistings,.r:-*.evil.net', $read); - // Note that the spurious .rlistings was removed. - $this->assertEquals('testact2,testact3:earnie', $write); - - } - - public function testIsNonPublic() - { - $acl = new ACL(); - - $this->assertTrue($acl->isNonPublic()); - - $acl->addReferrer(ACL::READ, '*.evil.net'); - $this->assertFalse($acl->isNonPublic()); - - $acl = ACL::makeNonPublic(); - $this->assertTrue($acl->isNonPublic()); - } - - public function testIsPublic() - { - $acl = new ACL(); - - $this->assertFalse($acl->isPublic()); - $acl->allowListings(); - $acl->addReferrer(ACL::READ, '*'); - - $this->assertTrue($acl->isPublic()); - - $acl->addAccount(ACL::WRITE, 'foo', 'bar'); - $this->assertTrue($acl->isPublic()); - - $acl = ACL::makePublic(); - $this->assertTrue($acl->isPublic()); - } - -} diff --git a/tests/Tests/ObjectStore/v1/Resource/ContainerTest.php b/tests/Tests/ObjectStore/v1/Resource/ContainerTest.php deleted file mode 100644 index 30724bc..0000000 --- a/tests/Tests/ObjectStore/v1/Resource/ContainerTest.php +++ /dev/null @@ -1,393 +0,0 @@ -assertEquals('foo', $container->name()); - } - - /** - * @expectedException \OpenStack\Common\Exception - */ - public function testExceptionIsThrownWhenContainerNotFound() - { - $container = new Container('foo'); - $container->bytes(); - } - - public function testCountable() - { - // Verify that the interface Countable is properly implemented. - - $mockJSON = ['count' => 5, 'bytes' => 128, 'name' => 'foo']; - $container = Container::newFromJSON($mockJSON, 'fake', 'fake'); - $this->assertCount(5, $container); - } - - public function testSave() - { - // Clean up anything left. - $this->destroyContainerFixture(); - - $container = $this->containerFixture(); - - $object = new Object(self::FNAME, self::FCONTENT, self::FTYPE); - $object->setMetadata(['foo' => '1234']); - - $this->assertEquals(self::FCONTENT, $object->content()); - - try { - $ret = $container->save($object); - } catch (\Exception $e) { - $this->destroyContainerFixture(); - throw $e; - } - - $this->assertTrue($ret); - } - - /** - * @depends testSave - */ - public function testProxyObject() - { - $container = $this->containerFixture(); - $object = $container->proxyObject(self::FNAME); - - $this->assertEquals(self::FNAME, $object->name()); - $this->assertEquals(self::FTYPE, $object->contentType()); - - $etag = md5(self::FCONTENT); - $this->assertEquals($etag, $object->eTag()); - - $md = $object->metadata(); - $this->assertEquals(1, count($md)); - - // Note that headers are normalized remotely to have initial - // caps. Since we have no way of knowing what the original - // metadata casing is, we leave it with initial caps. - $this->assertEquals('1234', $md['Foo']); - - $content = $object->content(); - $this->assertEquals(self::FCONTENT, $content); - - // Make sure I can do this twice (regression). - // Note that this SHOULD perform another request. - $this->assertEquals(self::FCONTENT, $object->content()); - - // Overwrite the copy: - $object->setContent('HI'); - $this->assertEquals('HI', $object->content()); - - // Make sure I can do this twice (regression check). - $this->assertEquals('HI', $object->content()); - } - - - /** - * @depends testProxyObject - */ - public function testRefresh() - { - $container = $this->containerFixture(); - $object = $container->proxyObject(self::FNAME); - - $content = (string) $object->content(); - $object->setContent('FOO'); - $this->assertEquals('FOO', $object->content()); - - $object->refresh(true); - $this->assertEquals($content, (string) $object->content()); - - $object->refresh(false); - $this->assertEquals($content, (string) $object->content()); - - } - - /** - * @depends testProxyObject - */ - public function testObject() - { - $container = $this->containerFixture(); - $object = $container->object(self::FNAME); - - $this->assertEquals(self::FNAME, $object->name()); - $this->assertEquals(self::FTYPE, $object->contentType()); - - $etag = md5(self::FCONTENT); - $this->assertEquals($etag, $object->eTag()); - - $md = $object->metadata(); - $this->assertEquals(1, count($md)); - - // Note that headers are normalized remotely to have initial - // caps. Since we have no way of knowing what the original - // metadata casing is, we leave it with initial caps. - $this->assertEquals('1234', $md['Foo']); - - $content = $object->content(); - - $this->assertEquals(self::FCONTENT, $content); - - // Overwrite the copy: - $object->setContent('HI'); - $this->assertEquals('HI', $object->content()); - - // Make sure this throws a 404. - try { - $foo = $container->object('no/such'); - } catch (\OpenStack\Common\Exception $e) { - $this->assertInstanceOf('OpenStack\Common\Transport\Exception\ResourceNotFoundException', $e); - } - } - - /** - * @depends testSave - */ - public function testObjects() - { - $container = $this->containerFixture(); - $obj1 = new Object('a/' . self::FNAME, self::FCONTENT, self::FTYPE); - $obj2 = new Object('a/b/' . self::FNAME, self::FCONTENT, self::FTYPE); - - $container->save($obj1); - $container->save($obj2); - - // Now we have a container with three items. - $objects = $container->objects(); - - $this->assertEquals(3, count($objects)); - - $objects = $container->objects(1, 'a/' . self::FNAME); - - $this->assertEquals(1, count($objects)); - } - - /** - * @depends testObjects - */ - public function testGetIterator() - { - $container = $this->containerFixture(); - - $it = $container->getIterator(); - $this->assertInstanceOf('Traversable', $it); - - $i = 0; - foreach ($container as $item) { - ++$i; - } - $this->assertEquals(3, $i); - } - - /** - * @depends testObjects - */ - public function testObjectsWithPrefix() - { - $container = $this->containerFixture(); - - $objects = $container->objectsWithPrefix('a/'); - $this->assertEquals(2, count($objects)); - - foreach ($objects as $o) { - if ($o instanceof Object) { - $this->assertEquals('a/' . self::FNAME, $o->name()); - } else { - $this->assertEquals('a/b/', $o->path()); - } - - } - - // Since we set the delimiter to ':' we will get back - // all of the objects in a/. This is because none of - // the objects contain ':' in their names. - $objects = $container->objectsWithPrefix('a/', ':'); - $this->assertEquals(2, count($objects)); - - foreach ($objects as $o) { - $this->assertInstanceOf('\OpenStack\ObjectStore\v1\Resource\Object', $o); - } - - // This should give us one file and one subdir. - $objects = $container->objectsWithPrefix('', '/'); - $this->assertEquals(2, count($objects)); - - foreach ($objects as $o) { - if ($o instanceof Object) { - $this->assertEquals(self::FNAME, $o->name()); - } else { - $this->assertEquals('a/', $o->path()); - } - } - } - - /** - * @depends testObjects - */ - public function testObjectsWithPath() - { - $container = $this->containerFixture(); - $objects = $container->objectsByPath('a/b/'); - - $this->assertEquals(1, count($objects)); - - $o = array_shift($objects); - $this->assertEquals('a/b/' . self::FNAME, $o->name()); - } - - /** - * @depends testProxyObject - */ - public function testUpdateMetadata() - { - $container = $this->containerFixture(); - $object = $container->proxyObject(self::FNAME); - - $md = $object->metadata(); - - $this->assertEquals('1234', $md['Foo']); - - $md['Foo'] = 456; - $md['Bar'] = 'bert'; - $object->setMetadata($md); - - $container->updateMetadata($object); - - $copy = $container->proxyObject(self::FNAME); - - $this->assertEquals('456', $md['Foo']); - $this->assertEquals('bert', $md['Bar']); - - // Now we need to canary test: - $this->assertEquals($object->contentType(), $copy->contentType()); - $this->assertEquals($object->contentLength(), $copy->contentLength()); - - - } - - /** - * @depends testProxyObject - */ - public function testCopy() - { - $container = $this->containerFixture(); - $object = $container->proxyObject(self::FNAME); - - $container->copy($object, 'FOO-1.txt'); - - $copy = $container->proxyObject('FOO-1.txt'); - - $this->assertEquals($object->contentType(), $copy->contentType()); - $this->assertEquals($object->etag(), $copy->etag()); - - $container->delete('foo-1.txt'); - - } - - /** - * @depends testCopy - */ - public function testCopyAcrossContainers() - { - // Create a new container. - $store = $this->objectStore(); - $cname = self::$settings['openstack.swift.container'] . 'COPY'; - if ($store->hasContainer($cname)) { - $this->eradicateContainer($cname); - } - - $store->createContainer($cname); - $newContainer = $store->container($cname); - - // Get teh old container and its object. - $container = $this->containerFixture(); - $object = $container->proxyObject(self::FNAME); - - $ret = $container->copy($object, 'foo-1.txt', $cname); - - $this->assertTrue($ret); - - $copy = $newContainer->proxyObject('foo-1.txt'); - - $this->assertEquals($object->etag(), $copy->etag()); - - $this->eradicateContainer($cname); - - } - - /** - * @depends testSave - */ - public function testDelete() - { - $container = $this->containerFixture(); - - $ret = $container->delete(self::FNAME); - - $fail = $container->delete('no_such_file.txt'); - - $this->destroyContainerFixture(); - $this->assertTrue($ret); - $this->assertFalse($fail); - } - - /** - * @group public - */ - public function testAcl() - { - $store = $this->objectStore(); - $cname = self::$settings['openstack.swift.container'] . 'PUBLIC'; - - if ($store->hasContainer($cname)) { - $store->deleteContainer($cname); - } - - $store->createContainer($cname, ACL::makePublic()); - - $store->containers(); - $container = $store->container($cname); - - $acl = $container->acl(); - - $this->assertInstanceOf('\OpenStack\ObjectStore\v1\Resource\ACL', $acl); - $this->assertTrue($acl->isPublic()); - - $store->deleteContainer($cname); - } -} diff --git a/tests/Tests/ObjectStore/v1/Resource/ObjectTest.php b/tests/Tests/ObjectStore/v1/Resource/ObjectTest.php deleted file mode 100644 index f3940a6..0000000 --- a/tests/Tests/ObjectStore/v1/Resource/ObjectTest.php +++ /dev/null @@ -1,150 +0,0 @@ -setContent(self::FCONTENT, self::FTYPE); - - return $o; - } - - public function testConstructor() - { - $o = $this->basicObjectFixture(); - - $this->assertEquals(self::FNAME, $o->name()); - - $o = new Object('a', 'b', 'text/plain'); - - $this->assertEquals('a', $o->name()); - $this->assertEquals('b', $o->content()); - $this->assertEquals('text/plain', $o->contentType()); - } - - public function testContentType() - { - // Don't use the fixture, we want to test content - // type in its raw state. - $o = new Object('foo.txt'); - - $this->assertEquals('application/octet-stream', $o->contentType()); - - $o->setContentType('text/plain; charset=UTF-8'); - $this->assertEquals('text/plain; charset=UTF-8', $o->contentType()); - } - - public function testContent() - { - $o = $this->basicObjectFixture(); - - $this->assertEquals(self::FCONTENT, $o->content()); - - // Test binary data. - $bin = sha1(self::FCONTENT, true); - $o->setContent($bin, 'application/octet-stream'); - - $this->assertEquals($bin, $o->content()); - } - - public function testEtag() - { - $o = $this->basicObjectFixture(); - $md5 = md5(self::FCONTENT); - - $this->assertEquals($md5, $o->eTag()); - } - - public function testIsChunked() - { - $o = $this->basicObjectFixture(); - $this->assertFalse($o->isChunked()); - } - - public function testContentLength() - { - $o = $this->basicObjectFixture(); - $this->assertEquals(strlen(self::FCONTENT), $o->contentLength()); - - // Test on binary data. - $bin = sha1(self::FCONTENT, true); - - $o->setContent($bin); - $this->assertFalse($o->contentLength() == 0); - $this->assertEquals(strlen($bin), $o->contentLength()); - } - - public function testMetadata() - { - $md = [ - 'Immanuel' => 'Kant', - 'David' => 'Hume', - 'Gottfried' => 'Leibniz', - 'Jean-Jaques' => 'Rousseau', - ]; - - $o = $this->basicObjectFixture(); - $o->setMetadata($md); - - $got = $o->metadata(); - - $this->assertEquals(4, count($got)); - $this->assertArrayHasKey('Immanuel', $got); - $this->assertEquals('Leibniz', $got['Gottfried']); - - } - - public function testAdditionalHeaders() - { - $o = $this->basicObjectFixture(); - - $extra = [ - 'a' => 'b', - 'aaa' => 'bbb', - 'ccc' => 'bbb', - ]; - $o->setAdditionalHeaders($extra); - - $got = $o->additionalHeaders(); - $this->assertEquals(3, count($got)); - - $o->removeHeaders(['ccc']); - - $got = $o->additionalHeaders(); - $this->assertEquals(2, count($got)); - } -} diff --git a/tests/Tests/ObjectStore/v1/Resource/RemoteObjectTest.php b/tests/Tests/ObjectStore/v1/Resource/RemoteObjectTest.php deleted file mode 100644 index 4b50f5d..0000000 --- a/tests/Tests/ObjectStore/v1/Resource/RemoteObjectTest.php +++ /dev/null @@ -1,330 +0,0 @@ -containerFixture(); - - $object = new Object(self::FNAME, self::FCONTENT, self::FTYPE); - $object->setMetadata([self::FMETA_NAME => self::FMETA_VALUE]); - $object->setDisposition(self::FDISPOSITION); - $object->setEncoding(self::FENCODING); - $object->setAdditionalHeaders([ - 'Access-Control-Allow-Origin' => 'http://example.com', - 'Access-control-allow-origin' => 'http://example.com', - ]); - - // Need some headers that Swift actually stores and returns. This - // one does not seem to be returned ever. - //$object->setAdditionalHeaders(array(self::FCORS_NAME => self::FCORS_VALUE)); - - $container->save($object); - } - - public function testNewFromHeaders() - { - // This is tested via the container. - - $this->destroyContainerFixture(); - $container = $this->containerFixture(); - $this->createAnObject(); - - $obj = $container->proxyObject(self::FNAME); - - $this->assertInstanceOf('\OpenStack\ObjectStore\v1\Resource\RemoteObject', $obj); - - return $obj; - } - /** - * @depends testNewFromHeaders - */ - public function testContentLength($obj) - { - $len = strlen(self::FCONTENT); - - $this->assertEquals($len, $obj->contentLength()); - - return $obj; - } - - /** - * @depends testContentLength - */ - public function testContentType($obj) - { - $this->assertEquals(self::FTYPE, $obj->contentType()); - - return $obj; - } - - /** - * @depends testContentType - */ - public function testEtag($obj) - { - $hash = md5(self::FCONTENT); - - $this->assertEquals($hash, $obj->eTag()); - - return $obj; - } - - /** - * @depends testContentType - */ - public function testLastModified($obj) - { - $date = $obj->lastModified(); - - $this->assertTrue(is_int($date)); - $this->assertTrue($date > 0); - } - - /** - * @depends testNewFromHeaders - */ - public function testMetadata($obj) - { - $md = $obj->metadata(); - - $this->assertArrayHasKey(self::FMETA_NAME, $md); - $this->assertEquals(self::FMETA_VALUE, $md[self::FMETA_NAME]); - } - - /** - * @depends testNewFromHeaders - */ - public function testDisposition($obj) - { - $this->assertEquals(self::FDISPOSITION, $obj->disposition()); - } - - /** - * @depends testNewFromHeaders - */ - public function testEncoding($obj) - { - $this->assertEquals(self::FENCODING, $obj->encoding()); - } - - /** - * @depends testNewFromHeaders - */ - public function testHeaders($obj) - { - $headers = $obj->headers(); - $this->assertTrue(count($headers) > 1); - - //fwrite(STDOUT, print_r($headers, true)); - - $this->assertNotEmpty($headers['Date']); - - $obj->removeHeaders(['Date']); - - $headers = $obj->headers(); - $this->assertFalse(isset($headers['Date'])); - - // Swift doesn't return CORS headers even though it is supposed to. - //$this->assertEquals(self::FCORS_VALUE, $headers[self::FCORS_NAME]); - } - - /** - * @depends testNewFromHeaders - */ - public function testUrl($obj) - { - $url = $obj->url(); - - $this->assertTrue(strpos($obj->url(), $obj->name())> 0); - } - /** - * @depends testNewFromHeaders - */ - public function testStream($obj) - { - $res = $obj->stream(); - - $this->assertTrue(is_resource($res)); - - $res_md = stream_get_meta_data($res); - - $content = fread($res, $obj->contentLength()); - - fclose($res); - - $this->assertEquals(self::FCONTENT, $content); - - // Now repeat the tests, only with a local copy of the data. - // This allows us to test the local tempfile buffering. - - $obj->setContent($content); - - $res2 = $obj->stream(); - $res_md = stream_get_meta_data($res2); - - $this->assertEquals('PHP', $res_md['wrapper_type']); - - $content = fread($res2, $obj->contentLength()); - - fclose($res2); - - $this->assertEquals(self::FCONTENT, $content); - - // Finally, we redo the first part of the test to make sure that - // refreshing gets us a new copy: - - $res3 = $obj->stream(true); - $res_md = stream_get_meta_data($res3); - $this->assertEquals('PHP', $res_md['wrapper_type']); - fclose($res3); - - return $obj; - } - - // To avoid test tainting from testStream(), we start over. - public function testContent() - { - $container = $this->containerFixture(); - $obj = $container->object(self::FNAME); - - $content = $obj->content(); - $this->assertEquals(self::FCONTENT, $content); - - // Make sure proxyObject retrieves the same content. - $obj = $container->proxyObject(self::FNAME); - $content = $obj->content(); - $this->assertEquals(self::FCONTENT, $content); - - } - - /** - * @depends testStream - */ - public function testCaching() - { - $container = $this->containerFixture(); - $obj = $container->proxyObject(self::FNAME); - - $this->assertFalse($obj->isCaching()); - - $content = $obj->content(); - - $res1 = $obj->stream(); - $md = stream_get_meta_data($res1); - $this->assertEquals('PHP', $md['wrapper_type']); - - fclose($res1); - - // Enable caching and retest. - $obj->setCaching(true); - $this->assertTrue($obj->isCaching()); - - // This will cache the content. - $content = $obj->content(); - - $res2 = $obj->stream(); - $md = stream_get_meta_data($res2); - - // If this is using the PHP version, it built content from the - // cached version. - $this->assertEquals('PHP', $md['wrapper_type']); - - fclose($res2); - } - - /** - * @depends testNewFromHeaders - */ - public function testContentVerification($obj) - { - $this->assertTrue($obj->isVerifyingContent()); - $obj->setContentVerification(false); - $this->assertfalse($obj->isVerifyingContent()); - $obj->setContentVerification(true); - } - - /** - * @depends testCaching - */ - public function testIsDirty() - { - $container = $this->containerFixture(); - $obj = $container->proxyObject(self::FNAME); - - // THere is no content. Assert false. - $this->assertFalse($obj->isDirty()); - - $obj->setCaching(true); - $obj->content(); - - // THere is content, but it is unchanged. - $this->assertFalse($obj->isDirty()); - - // Change content and retest. - $obj->setContent('foo'); - - $this->assertTrue($obj->isDirty()); - } - - /** - * @depends testIsDirty - */ - public function testRefresh() - { - $container = $this->containerFixture(); - $obj = $container->proxyObject(self::FNAME); - - $obj->setContent('foo'); - $this->assertTrue($obj->isDirty()); - - $obj->refresh(false); - $this->assertFalse($obj->isDirty()); - $this->assertEquals(self::FCONTENT, $obj->content()); - - $obj->setContent('foo'); - $this->assertTrue($obj->isDirty()); - - $obj->refresh(true); - $this->assertFalse($obj->isDirty()); - $this->assertEquals(self::FCONTENT, $obj->content()); - - $this->destroyContainerFixture(); - - } - -} diff --git a/tests/Tests/ObjectStore/v1/Resource/StreamWrapperFSTest.php b/tests/Tests/ObjectStore/v1/Resource/StreamWrapperFSTest.php deleted file mode 100644 index 68189c1..0000000 --- a/tests/Tests/ObjectStore/v1/Resource/StreamWrapperFSTest.php +++ /dev/null @@ -1,353 +0,0 @@ -context)['swiftfs']; - $this->assertNotEmpty($context['token']); - $this->assertNotEmpty($context['swift_endpoint']); - $this->assertEquals(self::FTYPE, $context['content_type']); - } - - public function testRegister() - { - $this->assertNotEmpty(StreamWrapperFS::DEFAULT_SCHEME); - $this->assertContains(StreamWrapperFS::DEFAULT_SCHEME, stream_get_wrappers()); - } - - public function testOpenFailureWithoutContext() - { - $url = $this->createNewUrl('non_existent_container/foo.txt'); - $this->assertFalse(@fopen($url, 'r')); - } - - public function testResourceType() - { - $this->assertInternalType('resource', $this->resource); - } - - public function testCreatingResourceInWriteMode() - { - $resource = $this->createNewResource($this->createNewUrl(), 'w+'); - $this->assertInternalType('resource', $resource); - fclose($resource); - } - - public function testCreatingResourceInCreateMode() - { - $resource = $this->createNewResource($this->createNewUrl(), 'c+'); - $this->assertInternalType('resource', $resource); - fclose($resource); - } - - public function testTell() - { - // Sould be at the beginning of the buffer. - $this->assertEquals(0, ftell($this->resource)); - } - - public function testWrite() - { - $string = 'To be is to be the value of a bound variable. -- Quine'; - fwrite($this->resource, $string); - $this->assertGreaterThan(0, ftell($this->resource)); - } - - public function testStat() - { - $this->assertEquals(0, fstat($this->resource)['size']); - - fwrite($this->resource, 'foo'); - fflush($this->resource); - $this->assertGreaterThan(0, fstat($this->resource)['size']); - } - - public function testSeek() - { - $text = 'Foo bar'; - fwrite($this->resource, $text); - - fseek($this->resource, 0, SEEK_END); - $pointer = ftell($this->resource); - - $this->assertGreaterThan(0, $pointer); - } - - public function testEof() - { - $this->assertFalse(feof($this->resource)); - - fwrite($this->resource, 'foo'); - rewind($this->resource); - stream_get_contents($this->resource); - - $this->assertTrue(feof($this->resource)); - } - - public function testFlush() - { - $content = str_repeat('foo', 50); - - fwrite($this->resource, $content); - fflush($this->resource); - rewind($this->resource); - - $this->assertEquals($content, stream_get_contents($this->resource)); - } - - public function testStreamGetMetadata() - { - $object = stream_get_meta_data($this->resource)['wrapper_data']->object(); - $this->assertInstanceOf('OpenStack\ObjectStore\v1\Resource\Object', $object); - $this->assertEquals(self::FTYPE, $object->contentType()); - } - - public function testClose() - { - fclose($this->resource); - $this->assertFalse(is_resource($this->resource)); - } - - public function testCast() - { - $read = [$this->resource]; - $write = []; - $except = []; - $this->assertGreaterThan(0, stream_select($read, $write, $except, 0)); - } - - public function testUrlStat() - { - $stat = stat($this->url); - - // Check that the array looks right. - $this->assertCount(26, $stat); - $this->assertEquals(0, $stat[3]); - $this->assertEquals($stat[2], $stat['mode']); - } - - public function testFileExists() - { - $this->assertTrue(file_exists($this->url)); - } - - public function testFileIsReadable() - { - $this->assertTrue(is_readable($this->url)); - } - - public function testFileIsWritable() - { - $this->assertTrue(is_writeable($this->url)); - } - - public function testFileModifyTime() - { - $this->assertGreaterThan(0, filemtime($this->url)); - } - - public function testFileSize() - { - $url = $this->createNewUrl('file_size_test'); - - $resource = $this->createNewResource($url, 'w+'); - fwrite($resource, '!'); - fclose($resource); - - $this->assertEquals(1, filesize($url)); - unlink($url); - } - - public function testPermissions() - { - $perm = fileperms($this->url); - - // Assert that this is a file. Objects are *always* marked as files. - $this->assertEquals(0x8000, $perm & 0x8000); - - // Assert writeable by owner. - $this->assertEquals(0x0080, $perm & 0x0080); - - // Assert not world writable. - $this->assertEquals(0, $perm & 0x0002); - } - - public function testFileGetContents() - { - $url = $this->createNewUrl('get_contents'); - $resource = $this->createNewResource($url, 'w+'); - - fwrite($resource, '!'); - fclose($resource); - - $contents = file_get_contents($url, null, $this->context); - $this->assertEquals('!', $contents); - unlink($url); - } - - public function testCopy() - { - $newUrl = '/tmp/new_file_from_swift.txt'; - copy($this->url, $newUrl, $this->context); - - $this->assertTrue(file_exists($newUrl)); - unlink($newUrl); - } - - public function testUnlink() - { - unlink($this->url, $this->context); - $this->assertFalse(file_exists($this->url)); - } - - public function testSetOption() - { - $this->assertTrue(stream_set_blocking($this->resource, 1)); - - // Returns 0 on success. - $this->assertEquals(0, stream_set_write_buffer($this->resource, 8192)); - - // Cannot set a timeout on a tmp storage: - $this->assertFalse(stream_set_timeout($this->resource, 10)); - } - - public function testRename() - { - $oldUrl = $this->createNewUrl('old'); - $newUrl = $this->createNewUrl('new'); - - $original = $this->createNewResource($oldUrl, 'w+'); - fwrite($original, 'fooooo'); - fclose($original); - - rename($oldUrl, $newUrl, $this->context); - - $this->assertTrue(file_exists($newUrl)); - $this->assertFalse(file_exists($this->url)); - - unlink($newUrl, $this->context); - } - - public function testOpenDir() - { - $baseDirectory = opendir($this->createNewUrl(''), $this->context); - $this->assertInternalType('resource', $baseDirectory); - closedir($baseDirectory); - } - - public function testReadDir() - { - $paths = ['test1.txt', 'foo/test2.txt', 'foo/test3.txt', 'bar/test4.txt']; - - foreach ($paths as $path) { - $file = fopen($this->createNewUrl($path), 'c+', false, $this->context); - fwrite($file, 'Test.'); - fclose($file); - } - - $baseDirectory = opendir($this->createNewUrl(''), $this->context); - - $expectedPaths = ['bar/', 'foo/', 'test1.txt']; - while (false !== ($currentEntry = readdir($baseDirectory))) { - $nextPath = array_shift($expectedPaths); - $this->assertEquals($nextPath, $currentEntry); - } - - $this->assertFalse(readdir($baseDirectory)); - - closedir($baseDirectory); - } - - public function testRewindDir() - { - $baseDirectory = opendir($this->createNewUrl(''), $this->context); - rewinddir($baseDirectory); - - $this->assertEquals('bar/', readdir($baseDirectory)); - - closedir($baseDirectory); - } - - public function testCloseDir() - { - $baseDirectory = opendir($this->createNewUrl(''), $this->context); - closedir($baseDirectory); - $this->assertFalse(is_resource($baseDirectory)); - } - - public function testOpenSubdir() - { - // Opening foo we should find test2.txt and test3.txt. - $url = $this->createNewUrl('foo/'); - $dir = opendir($url, $this->context); - - $this->assertEquals('test2.txt', readdir($dir)); - $this->assertEquals('test3.txt', readdir($dir)); - - $array = scandir($url, -1, $this->context); - $this->assertEquals(2, count($array)); - $this->assertEquals('test3.txt', $array[0]); - } - - public function testIsDir() - { - // Object names are pathy. If objects exist starting with this path we can - // consider the directory to exist. - $url = $this->createNewUrl('baz/'); - $this->assertFalse(is_dir($url)); - - $url = $this->createNewUrl('foo/'); - $this->assertTrue(is_dir($url)); - } - - public function testMkdir() - { - // Object names are pathy. If no object names start with the a path we can - // consider mkdir passed. If object names exist we should fail mkdir. - $url = $this->createNewUrl('baz/'); - $this->assertTrue(mkdir($url, 0700, true, $this->context)); - - // Test the case for an existing directory. - $url = $this->createNewUrl('foo/'); - $this->assertFalse(mkdir($url, 0700, true, $this->context)); - } - - public function testRmdir() - { - // Object names are pathy. If no object names start with the a path we can - // consider rmdir passed. If object names exist we should fail rmdir. - $url = $this->createNewUrl('baz/'); - $this->assertTrue(rmdir($url, $this->context)); - - // Test the case for an existing directory. - $url = $this->createNewUrl('foo/'); - $this->assertFalse(rmdir($url, $this->context)); - } -} \ No newline at end of file diff --git a/tests/Tests/ObjectStore/v1/Resource/StreamWrapperTest.php b/tests/Tests/ObjectStore/v1/Resource/StreamWrapperTest.php deleted file mode 100644 index b361844..0000000 --- a/tests/Tests/ObjectStore/v1/Resource/StreamWrapperTest.php +++ /dev/null @@ -1,314 +0,0 @@ -context)['swift']; - $this->assertNotEmpty($context['token']); - $this->assertNotEmpty($context['swift_endpoint']); - $this->assertEquals(self::FTYPE, $context['content_type']); - } - - public function testOpenFailureWithoutContext() - { - $url = $this->createNewUrl('non_existent_container/foo.txt'); - $this->assertFalse(@fopen($url, 'r')); - } - - public function testResourceType() - { - $this->assertInternalType('resource', $this->resource); - } - - public function testCreatingResourceInWriteMode() - { - $resource = $this->createNewResource($this->createNewUrl(), 'w+'); - $this->assertInternalType('resource', $resource); - fclose($resource); - } - - public function testCreatingResourceInCreateMode() - { - $resource = $this->createNewResource($this->createNewUrl(), 'c+'); - $this->assertInternalType('resource', $resource); - fclose($resource); - } - - public function testTell() - { - // Sould be at the beginning of the buffer. - $this->assertEquals(0, ftell($this->resource)); - } - - public function testWrite() - { - $string = 'To be is to be the value of a bound variable. -- Quine'; - fwrite($this->resource, $string); - $this->assertGreaterThan(0, ftell($this->resource)); - } - - public function testStat() - { - $this->assertEquals(0, fstat($this->resource)['size']); - - fwrite($this->resource, 'foo'); - fflush($this->resource); - $this->assertGreaterThan(0, fstat($this->resource)['size']); - } - - public function testSeek() - { - $text = 'Foo bar'; - fwrite($this->resource, $text); - - fseek($this->resource, 0, SEEK_END); - $pointer = ftell($this->resource); - - $this->assertGreaterThan(0, $pointer); - } - - public function testEof() - { - $this->assertFalse(feof($this->resource)); - - fwrite($this->resource, 'foo'); - rewind($this->resource); - stream_get_contents($this->resource); - - $this->assertTrue(feof($this->resource)); - } - - public function testFlush() - { - $content = str_repeat('foo', 50); - - fwrite($this->resource, $content); - fflush($this->resource); - rewind($this->resource); - - $this->assertEquals($content, stream_get_contents($this->resource)); - } - - public function testStreamGetMetadata() - { - $object = stream_get_meta_data($this->resource)['wrapper_data']->object(); - $this->assertInstanceOf('OpenStack\ObjectStore\v1\Resource\Object', $object); - $this->assertEquals(self::FTYPE, $object->contentType()); - } - - public function testClose() - { - fclose($this->resource); - $this->assertFalse(is_resource($this->resource)); - } - - public function testCast() - { - $read = [$this->resource]; - $write = []; - $except = []; - $this->assertGreaterThan(0, stream_select($read, $write, $except, 0)); - } - - public function testUrlStat() - { - $stat = stat($this->url); - - // Check that the array looks right. - $this->assertCount(26, $stat); - $this->assertEquals(0, $stat[3]); - $this->assertEquals($stat[2], $stat['mode']); - } - - public function testFileExists() - { - $this->assertTrue(file_exists($this->url)); - } - - public function testFileIsReadable() - { - $this->assertTrue(is_readable($this->url)); - } - - public function testFileIsWritable() - { - $this->assertTrue(is_writeable($this->url)); - } - - public function testFileModifyTime() - { - $this->assertGreaterThan(0, filemtime($this->url)); - } - - public function testFileSize() - { - $url = $this->createNewUrl('file_size_test'); - - $resource = $this->createNewResource($url, 'w+'); - fwrite($resource, '!'); - fclose($resource); - - $this->assertEquals(1, filesize($url)); - unlink($url); - } - - public function testPermissions() - { - $perm = fileperms($this->url); - - // Assert that this is a file. Objects are *always* marked as files. - $this->assertEquals(0x8000, $perm & 0x8000); - - // Assert writeable by owner. - $this->assertEquals(0x0080, $perm & 0x0080); - - // Assert not world writable. - $this->assertEquals(0, $perm & 0x0002); - } - - public function testFileGetContents() - { - $url = $this->createNewUrl('get_contents'); - $resource = $this->createNewResource($url, 'w+'); - - fwrite($resource, '!'); - fclose($resource); - - $contents = file_get_contents($url, null, $this->context); - $this->assertEquals('!', $contents); - unlink($url); - } - - public function testCopy() - { - $newUrl = '/tmp/new_file_from_swift.txt'; - copy($this->url, $newUrl, $this->context); - - $this->assertTrue(file_exists($newUrl)); - unlink($newUrl); - } - - public function testUnlink() - { - unlink($this->url, $this->context); - $this->assertFalse(file_exists($this->url)); - } - - public function testSetOption() - { - $this->assertTrue(stream_set_blocking($this->resource, 1)); - - // Returns 0 on success. - $this->assertEquals(0, stream_set_write_buffer($this->resource, 8192)); - - // Cannot set a timeout on a tmp storage: - $this->assertFalse(stream_set_timeout($this->resource, 10)); - } - - public function testRename() - { - $oldUrl = $this->createNewUrl('old'); - $newUrl = $this->createNewUrl('new'); - - $original = $this->createNewResource($oldUrl, 'w+'); - fwrite($original, 'fooooo'); - fclose($original); - - rename($oldUrl, $newUrl, $this->context); - - $this->assertTrue(file_exists($newUrl)); - $this->assertFalse(file_exists($this->url)); - - unlink($newUrl, $this->context); - } - - public function testOpenDir() - { - $baseDirectory = opendir($this->createNewUrl(''), $this->context); - $this->assertInternalType('resource', $baseDirectory); - closedir($baseDirectory); - } - - public function testReadDir() - { - $paths = ['test1.txt', 'foo/test2.txt', 'foo/test3.txt', 'bar/test4.txt']; - - foreach ($paths as $path) { - $file = fopen($this->createNewUrl($path), 'c+', false, $this->context); - fwrite($file, 'Test.'); - fclose($file); - } - - $baseDirectory = opendir($this->createNewUrl(''), $this->context); - - $expectedPaths = ['bar/', 'foo/', 'test1.txt']; - while (false !== ($currentEntry = readdir($baseDirectory))) { - $nextPath = array_shift($expectedPaths); - $this->assertEquals($nextPath, $currentEntry); - } - - $this->assertFalse(readdir($baseDirectory)); - - closedir($baseDirectory); - } - - public function testRewindDir() - { - $baseDirectory = opendir($this->createNewUrl(''), $this->context); - rewinddir($baseDirectory); - - $this->assertEquals('bar/', readdir($baseDirectory)); - - closedir($baseDirectory); - } - - public function testCloseDir() - { - $baseDirectory = opendir($this->createNewUrl(''), $this->context); - closedir($baseDirectory); - $this->assertFalse(is_resource($baseDirectory)); - } - - public function testOpenSubdir() - { - // Opening foo we should find test2.txt and test3.txt. - $url = $this->createNewUrl('foo/'); - $dir = opendir($url, $this->context); - - $this->assertEquals('test2.txt', readdir($dir)); - $this->assertEquals('test3.txt', readdir($dir)); - - $array = scandir($url, -1, $this->context); - $this->assertEquals(2, count($array)); - $this->assertEquals('test3.txt', $array[0]); - } -} \ No newline at end of file diff --git a/tests/Tests/ObjectStore/v1/Resource/StreamWrapperTestCase.php b/tests/Tests/ObjectStore/v1/Resource/StreamWrapperTestCase.php deleted file mode 100644 index bd370bc..0000000 --- a/tests/Tests/ObjectStore/v1/Resource/StreamWrapperTestCase.php +++ /dev/null @@ -1,135 +0,0 @@ -createContainer($containerName); - - try { - self::$container = $service->container($containerName); - } catch (\Exception $e) { - $service->deleteContainer($containerName); - throw $e; - } - - self::$settings += [ - 'username' => self::$settings['openstack.identity.username'], - 'password' => self::$settings['openstack.identity.password'], - 'endpoint' => self::$settings['openstack.identity.url'], - 'tenantid' => self::$settings['openstack.identity.tenantId'], - 'token' => $service->token(), - 'swift_endpoint' => $service->url(), - ]; - Bootstrap::setConfiguration(self::$settings); - } - - public static function tearDownAfterClass() - { - if (!self::$container) { - return; - } - - foreach (self::$container as $object) { - try { - self::$container->delete($object->name()); - } catch (\Exception $e) {} - } - - $service = self::createObjectStoreService(); - $service->deleteContainer(self::$container->name()); - } - - public function setUp() - { - Bootstrap::useStreamWrappers(); - - $this->url = $this->createNewUrl(); - $this->context = $this->createStreamContext(); - $this->resource = $this->createNewResource($this->url); - } - - public function tearDown() - { - if (is_resource($this->resource)) { - fclose($this->resource); - } - - $this->resource = null; - stream_wrapper_unregister(static::SCHEME); - } - - protected function createNewResource($url, $mode = self::DEFAULT_MODE) - { - return fopen($url, $mode, false, $this->context); - } - - protected function createNewUrl($objectName = self::FILE_PATH) - { - return sprintf("%s://%s/%s", - static::SCHEME, - urlencode(self::$settings['openstack.swift.container']), - join('/', array_map('urlencode', explode('/', $objectName))) - ); - } - - private function createStreamContext(array $params = [], $scheme = null) - { - if (!$scheme) { - $scheme = static::SCHEME; - } - - if (!($objectStore = $this->objectStore())) { - throw new \Exception('Object storage service could not be created'); - } - - $params += [ - 'token' => $objectStore->token(), - 'swift_endpoint' => $objectStore->url(), - 'content_type' => self::FTYPE, - 'transport_client' => $this->getTransportClient(), - ]; - - return stream_context_create([ - $scheme => $params - ]); - } -} \ No newline at end of file diff --git a/tests/Tests/TestCase.php b/tests/Tests/TestCase.php deleted file mode 100644 index d48b02f..0000000 --- a/tests/Tests/TestCase.php +++ /dev/null @@ -1,240 +0,0 @@ -authenticateAsUser($username, $password, $tenantId); - - return $service; - } - - protected static function createObjectStoreService() - { - return ObjectStorage::newFromIdentity( - self::createIdentityService(), - self::$settings['openstack.swift.region'], - self::getTransportClient() - ); - } - - /** - * Get a handle to an IdentityService object. - * - * Authentication is performed, and the returned - * service has its tenant ID set already. - * - * identity()->token(); - * ?> - */ - protected function identity($reset = false) - { - if ($reset || empty(self::$ident)) { - self::$ident = self::createIdentityService(); - } - - return self::$ident; - } - - protected function objectStore($reset = false) - { - if ($reset || !$this->objectStoreService) { - $this->objectStoreService = self::createObjectStoreService(); - } - - return $this->objectStoreService; - } - - /** - * Get a container from the server. - */ - protected function containerFixture() - { - if (empty($this->containerFixture)) { - $store = $this->objectStore(); - $cname = self::$settings['openstack.swift.container']; - - try { - $store->createContainer($cname); - $this->containerFixture = $store->container($cname); - } catch (\Exception $e) { - // Delete the container. - $store->deleteContainer($cname); - throw $e; - } - } - - return $this->containerFixture; - } - - /** - * Clear and destroy a container. - * - * Destroy all of the files in a container, then destroy the - * container. - * - * If the container doesn't exist, this will silently return. - * - * @param string $cname The name of the container. - */ - protected function eradicateContainer($cname) - { - $store = $this->objectStore(); - try { - $container = $store->container($cname); - } catch (ResourceNotFoundException $e) { - // The container was never created. - return; - } - - foreach ($container as $object) { - try { - $container->delete($object->name()); - } catch (\Exception $e) {} - } - - $store->deleteContainer($cname); - - } - - /** - * Retrieve the HTTP Transport Client - * - * @return \OpenStack\Common\Transport\ClientInterface A transport client. - */ - public static function getTransportClient() - { - if (is_null(self::$httpClient)) { - $options = []; - - if (isset(self::$settings['transport.proxy'])) { - $options['proxy'] = self::$settings['transport.proxy']; - } - if (isset(self::$settings['transport.debug'])) { - $options['debug'] = self::$settings['transport.debug']; - } - if (isset(self::$settings['transport.ssl.verify'])) { - $options['ssl_verify'] = self::$settings['transport.ssl.verify']; - } - if (isset(self::$settings['transport.timeout'])) { - $options['timeout'] = self::$settings['transport.timeout']; - } - - self::$httpClient = GuzzleAdapter::create([ - 'defaults' => $options - ]); - } - - return self::$httpClient; - } - - /** - * Destroy a container fixture. - * - * This should be called in any method that uses containerFixture(). - */ - protected function destroyContainerFixture() - { - $store = $this->objectStore(); - $cname = self::$settings['openstack.swift.container']; - - try { - $container = $store->container($cname); - } - // The container was never created. - catch (ResourceNotFoundException $e) { - return; - } - - foreach ($container as $object) { - try { - $container->delete($object->name()); - } catch (\Exception $e) { - syslog(LOG_WARNING, $e); - } - } - - $store->deleteContainer($cname); - } -} \ No newline at end of file diff --git a/tests/bootstrap.php b/tests/bootstrap.php deleted file mode 100644 index 1e8ac20..0000000 --- a/tests/bootstrap.php +++ /dev/null @@ -1,19 +0,0 @@ -