Retire stackforge/openstack-sdk-php

This commit is contained in:
Monty Taylor 2015-10-17 16:04:03 -04:00
parent 4536bbc166
commit 9fb2743fc4
69 changed files with 5 additions and 13161 deletions

9
.gitignore vendored
View File

@ -1,9 +0,0 @@
.idea/
build/
vendor/
.DS_Store
composer.lock
composer.phar
phpunit.xml
tests/settings.ini*

View File

@ -1,4 +0,0 @@
[gerrit]
host=review.openstack.org
port=29418
project=stackforge/openstack-sdk-php.git

View File

@ -1,4 +0,0 @@
Release Notes
=============
This changelog contains the relevant feature additions and bug fixes.

View File

@ -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.

View File

@ -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 <http://semver.org/>`__. 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 <http://getcomposer.org>`__ 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 <https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4.md>`__,
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 <http://getcomposer.org>`__.
More information
----------------
`OpenStack <http://OpenStack.org>`__ 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

View File

@ -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"
}
}
}

View File

@ -1,227 +0,0 @@
<?php
/** About the OpenStack PHP-Client
*
* This is the documentation for the OpenStack PHP-Client library.
*
* Overview
*
* @see http://www.openstack.org is open source software for
* building public and private clouds.
*
* The PHP-Client library provides PHP developers with a fully tested,
* robust, and feature-rich library for working with the OpenStack services.
*
* Making use of this library will require that you have several pieces of
* account information for your OpenStack account:
* - account ID and secret key: For cases where you want account-wide
* authentication/authorization.
* - username/password: Typically, this is the same username/password you use
* to access the console.
* - tenant ID: This associates an account or user with a bundle of services.
* You can find this information in your console.
* - endpoint: You will need the URL to the OpenStack endpoint responsible for
* authenticating users. This can be found in your console.
*
* Where To Start
*
* Cruising a list of methods and classes is not exactly the best way to get
* started with a library. It's better to know where to start. Here's
* what we suggest:
*
*- There are a few tutorials inside this documentation that will help you
* get started.
* @see streams-tutorial Information about stream wrappers.
* @see oo-tutorial Getting started with the library itself
*- Connecting and logging in is almost inevitably going to be your first
* task. For that, you will want to look at IdentityServices.
*- ObjectStorage (a.k.a. swift) is the cloud storage system. There are
* two ways to use it:
* - You can explore the object oriented API, starting with ObjectStorage.
* - You can use the PHP stream wrappers to access your object storage. This
* is explained in StreamWrapper.
*
* Learn More
*
* This documentation is intended to provide a detailed reference to the
* PHP-Client library. To learn more about the APIs and OpenStack visit
* @see http://api.openstack.org/
* @see http://docs.openstack.org/
*
* Basic Example: Stream Wrappers
*
* The super-simple stream API:
*
* <?php
* require 'vendor/autoload.php';
*
* // Turn on stream wrappers.
* \OpenStack\Bootstrap::useStreamWrappers();
*
* // Create a stream context. You can get this
* // information (including tenant ID) from your
* // OpenStack console.
* $cxt = stream_context_create(array(
* 'username' => '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:
*
* <?php
* require 'vendor/autoload.php';
*
* use \OpenStack\Identity\v1\IdentityService;
*
* // Create a new identity service object, and tell it where to
* // go to authenticate. This URL can be found in your console.
* $identity = new IdentityService('http://get.url.from.hpcloud.com');
*
* // You can authenticate with a username/password (IdentityService::authenticateAsUser()).
* // In either case you can get the info you need from the console.
* $username = 'foobar';
* $password = 'dgasgasd';
* $tenantId = '56545654';
*
* // $token will be your authorization key when you connect to other
* // services. You can also get it from $identity->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:
*
* <?php
* // The explicit way:
* // Find out where our ObjectStorage instance lives:
* // $storageList = $identity->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.
*
*
*/
?>

View File

@ -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).

View File

@ -1,36 +0,0 @@
<?php
require_once __DIR__ . '/../vendor/autoload.php';
use \OpenStack\Identity\v2\IdentityService;
use \OpenStack\ObjectStore\v1\ObjectStorage;
use \OpenStack\ObjectStore\v1\ObjectStorage\Object;
// Load these from an ini file.
$ini = parse_ini_file(getenv('HOME') . '/.OpenStack.ini');
$username = $ini['username'];
$password = $ini['password'];
$tenantId = $ini['tenantId'];
$endpoint = $ini['url'];
$idService = new IdentityService($endpoint);
$token = $idService->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;

View File

@ -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 `<?php phpinfo(); ?>`.
### 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:
<?php
\OpenStack\ObjectStore\v1\Resource\RemoteObject
?>
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:
<?php
require 'vendor/autoload.php';
use \OpenStack\Bootstrap;
use \OpenStack\Identity\v2\IdentityService;
use \OpenStack\ObjectStore\v1\ObjectStorage;
use \OpenStack\ObjectStore\v1\Resource\Object;
The first line should be self-explanatory: we require the main autoloader file
which is generated by Composer.
After that, we declare a list of namespaces that we will use. This way we can
refer to classes by their short name, rather than by their fully qualified name.
There are some other fancy things that OpenStack::Bootstrap can do for
you. Most notably, you can pass configuration parameters into it. But
for the time being, we are good to go.
Our library is boostrapped. Next up: Let's connect to our account.
## Step 3: Connecting
Our programming goal, in this tutorial, is to interact with the Object
Storage service on OpenStack. (Object Storage is, for all intents and
purposes, basically a service for storing files in the cloud.)
But before we can interact directly with Object Storage, we need to
authenticate to the system. And to do this, we need the following four
pieces of information:
- Username: The username for an account.
- Password: The password associated with the username.
- Tenant ID: An identifier that maps an account to a set of services.
(In theory at least, one account can have multiple tenant IDs, and one
tenant ID can be linked to multiple accounts.)
- Endpoint URL: The URL to the Identity Services endpoint for OpenStack.
Before you issue a forlorn sigh, envisioning some laborious task, let us
point out that all of this information is available in one place, Log
into the console and go to the `API Keys`
page. It's all there.
### Identity Services
OpenStack is composed of numerous services. There's the Compute
service, the Object Storage service... and so on.
Authenticating separately to each of these would be a collosal waste of
network resources. And behind the scenes, account management would be
difficult on the server side.
That's where Identity Services comes in. It is a central service that
handles all things authorization and authentication related. Roughly,
it works as follows:
- The client sends an authentication request
- If it fails, the service returns an error
- If authentication succeeds, the service returns a time-sensitive token
(basically a shared secret) and a "service catalog"
The *token* is valid for some fixed period of time (say, 30 minutes),
during which time it can be used for every other service. Each request
to an OpenStack service should send (along with other info) the token. The
remote service then validates the token with identity services, saving
our app the trouble of making another round trip.
The *service catalog* lists all of the OpenStack services that the present
account can access.
### Authenticating
With that little bit of theory behind us, we can now go about
authenticating.
<?php
$username = 'ADD USERNAME HERE';
$password = 'ADD PASSWORD HERE';
$tenantId = 'ADD TENANT ID HERE';
$endpoint = 'ADD ENDPOINT URL HERE';
$idService = new \OpenStack\Identity\v2\IdentityService($endpoint);
$token = $idService->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:
<?php
$catalog = $idService->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:
<?php
$store->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.
<?php
$name = 'hello.txt';
$content = 'Hello World';
$mime = 'text/plain';
$localObject = new Object($name, $content, $mime);
$container->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:
<?php
$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;
?>
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:
<?php
$object = $container->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

View File

@ -1,40 +0,0 @@
<?php
require_once __DIR__ . '/../vendor/autoload.php';
use \OpenStack\Bootstrap;
Bootstrap::useStreamWrappers();
$ini = parse_ini_file(getenv('HOME') . '/.OpenStack.ini');
$settings = [
'account' => $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);

View File

@ -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:
<?php
// 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;
?>
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:
<?php
require 'vendor/autoload.php';
use \OpenStack\Bootstrap;
Bootstrap::useStreamWrappers();
The first thing the example above does is require Composer's autoloader
file, which contains code necessary to autoload anything else we will need.
Next, we call Bootstrap::useStreamWrappers(), which tells OpenStack to register
its stream wrapper classes.
In a nutshell, PHP allows libraries to map a particular URL pattern to a
stream wrapper. PHP-Client registers the `swift://` URL prefix. So any
request to a URL beginning with `swift://` will be proxied through the
OpenStack PHP-Client library.
## Setting Up Authentication
When working with remote OpenStack Object Storage, you must authenticate
to the remote system. Authentication requires the following four pieces
of information:
- account: Your account ID
- key: Your account's secret key
- tenantid: The tenant ID for the services you wish to use
- endpoint: The endpoint URL for OpenStack's Identity Service. It usually
looks something like this: `https://region-a.geo-1.identity.hpcloudsvc.com:35357`
All four of these pieces of information can be found in the **API Keys**
section of your console account.
(Note: You can use your username and password instead of account and
key, but you still must supply the tenant ID. Instead of supplying
`account` and `key`, use `username` and `password`.)
We are going to look at two ways to set authentication information. The
first is global. That means we supply it once, and all stream and file
functions automatically use that information. The second is to pass
authentication information into the stream context.
### Global Configuration
Supplying global account information has two distinct advantages:
-# It reduces the complexity of your code
-# It allows context-less functions like `file_exists` and `stat` to
work.
But it has a disadvantage: *Only one account can be used at a time.* Since
that account's information is shared across all stream wrappers, they
all share the same account, tenant Id, and service catalog.
If you are working on an application that needs to connect to more than
one account in the same request, you may find this setup imperfect for
your needs.
That said, here's how we set up a global configuration:
$settings = array(
'username' => 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:
<?php
require __DIR__ . '/../vendor/autoload.php';
use \OpenStack\Bootstrap;
Bootstrap::useStreamWrappers();
$cxt = stream_context_create(array(
'swift' => 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.

View File

@ -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 <a href="https://github.com/mattfarina/Coding-Standards">here</a> 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}
<?php
class Pillow {
function __construct($name, $a2 = NULL, $a3 = NULL) {
// ....
if (is_string($a2)) {
// Do one thing...
}
elseif (is_object($a2)) {
// Do another thing.
}
}
}
?>
~~~
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.

View File

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true" bootstrap="./tests/bootstrap.php">
<testsuites>
<testsuite name="PHPUnit">
<directory>tests/Tests/</directory>
</testsuite>
</testsuites>
<logging>
<log
type="coverage-html"
target="build/coverage"
charset="UTF-8"
yui="true"
highlight="true"
lowUpperBound="35"
highLowerBound="70"
showUncoveredFiles="true"
/>
<log type="coverage-clover" target="build/logs/clover.xml"/>
</logging>
</phpunit>

View File

@ -1,298 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack;
use OpenStack\Identity\v2\IdentityService;
use OpenStack\ObjectStore\v1\Resource\StreamWrapper;
use OpenStack\ObjectStore\v1\Resource\StreamWrapperFS;
/**
* Bootstrapping services.
*
* There is no requirement that this class be used. OpenStack is
* built to be flexible, and any individual component can be
* used directly, with one caveat: No explicit `require` or
* `include` calls are made.
*
* This class provides the following services:
*
* - Configuration: "global" settings are set here.
* See the setConfiguration() method to see how they
* can be set, and the config() and hasConfig() methods to see
* how configuration might be checked.
* - Stream Wrappers: This class can initialize a set of stream
* wrappers which will make certain OpenStack services available
* through the core PHP stream support.
*
* Configuration
*
* Configuration directives can be merged into the existing confiuration
* using the setConfiguration method.
*
* <?php
* $config = array(
* // We use Guzzle, which defaults to CURL, for a transport layer.
* 'transport' => '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;
}
}

View File

@ -1,27 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Common;
/**
* The top-level OpenStack exception.
*
* In most cases, the library will throw a more finely
* grained exception, but all exceptions thrown directly
* by OpenStack will be an instance of this exception.
*/
class Exception extends \Exception {}

View File

@ -1,53 +0,0 @@
<?php
/*
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Common\Transport;
/**
* Class that implements {@see ClientInterface} and contains common
* functionality for clients or client adapters. Most of the methods defined
* are purely for convenience, and don't necessarily need a client to implement
* them with their own custom logic.
*/
abstract class AbstractClient implements ClientInterface
{
public function get($uri, array $options = [])
{
return $this->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));
}
}

View File

@ -1,138 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Common\Transport;
/**
* Describes a transport client.
*
* Transport clients are responsible for moving data from the remote cloud to
* the local host. Transport clients are responsible only for the transport
* protocol, not for the payloads.
*
* The current OpenStack services implementation is oriented toward
* REST-based services, and consequently the transport layers are
* HTTP/HTTPS, and perhaps SPDY some day. The interface reflects this.
* it is not designed as a protocol-neutral transport layer
*/
interface ClientInterface
{
/**
* Create a new Request object. To send, use the {see send()} method.
*
* @param string $method HTTP method
* @param string|array|\OpenStack\Common\Transport\Url $uri URL the request will send to
* @param string|resource $body Entity body being sent
* @param array $options Configuration options, such as headers
*
* @return \OpenStack\Common\Transport\RequestInterface
*/
public function createRequest($method,
$uri = null,
$body = null,
array $options = []);
/**
* Sends a request.
*
* @param \OpenStack\Common\Transport\RequestInterface $request Request to execute
*
* @return \OpenStack\Common\Transport\ResponseInterface
*/
public function send(RequestInterface $request);
/**
* Execute a GET request.
*
* @param string|array|\OpenStack\Common\Transport\Url $uri URL the request will send to
* @param array $options Configuration options, such as headers
*
* @return \OpenStack\Common\Transport\ResponseInterface
*/
public function get($uri, array $options = []);
/**
* Execute a HEAD request.
*
* @param string|array|\OpenStack\Common\Transport\Url $uri URL the request will send to
* @param array $options Configuration options, such as headers
*
* @return \OpenStack\Common\Transport\ResponseInterface
*/
public function head($uri, array $options = []);
/**
* Execute a POST request.
*
* @param string|array|\OpenStack\Common\Transport\Url $uri URL the request will send to
* @param mixed $body Entity body being sent
* @param array $options Configuration options, such as headers
*
* @return \OpenStack\Common\Transport\ResponseInterface
*/
public function post($uri, $body, array $options = []);
/**
* Execute a PUT request.
*
* @param string|array|\OpenStack\Common\Transport\Url $uri URL the request will send to
* @param mixed $body Entity body being sent
* @param array $options Configuration options, such as headers
*
* @return \OpenStack\Common\Transport\ResponseInterface
*/
public function put($uri, $body, array $options = []);
/**
* Execute a DELETE request.
*
* @param string|array|\OpenStack\Common\Transport\Url $uri URL the request will send to
* @param array $options Configuration options, such as headers
*
* @return \OpenStack\Common\Transport\ResponseInterface
*/
public function delete($uri, array $options = []);
/**
* Sets a particular configuration option, depending on how the client
* implements it. It could, for example, alter cURL configuration or a
* default header.
*
* @param string $key The key being updated
* @param mixed $value The value being set
*/
public function setOption($key, $value);
/**
* Returns the value of a particular configuration option. If the options
* is not set, NULL is returned.
*
* @param string $key The option name
*
* @return mixed|null
*/
public function getOption($key);
/**
* Returns the base URL that the client points towards.
*
* @return string
*/
public function getBaseUrl();
}

View File

@ -1,31 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Common\Transport\Exception;
/**
* Exception that represents a 409 Conflict HTTP error.
*
* This class is thrown when a request could not be completed due to a conflict
* with the current state of the API resource. For example, when a remote
* container cannot be deleted because it is not empty.
*/
class ConflictException extends RequestException
{
}

View File

@ -1,32 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Common\Transport\Exception;
/**
* Exception that represents a 403 Forbidden HTTP error.
*
* This class is thrown when a server has understood the request, but is
* refusing to fulfill it. For example, a user has successfully authenticated
* but is not authorized to perform a particular action - perhaps due to ACL
* criteria.
*/
class ForbiddenException extends RequestException
{
}

View File

@ -1,31 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Common\Transport\Exception;
/**
* Exception that represents a 411 Length Required HTTP error.
*
* This class is thrown when a server refused to accept the request without a
* defined Content-Length or Content-Type header. For example, this might occur
* when uploading an object without the necessary Content- headers.
*/
class LengthRequiredException extends RequestException
{
}

View File

@ -1,30 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Common\Transport\Exception;
/**
* Exception that represents a 405 Method Not Allowed HTTP error.
*
* This class is thrown when a request's specified method is not allowed by the
* server.
*/
class MethodNotAllowedException extends RequestException
{
}

View File

@ -1,111 +0,0 @@
<?php
/*
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Common\Transport\Exception;
use OpenStack\Common\Exception;
use OpenStack\Common\Transport\RequestInterface;
use OpenStack\Common\Transport\ResponseInterface;
/**
* Base exception that is thrown for requests that result in a HTTP error.
*/
class RequestException extends Exception
{
/** @var \OpenStack\Common\Transport\RequestInterface */
protected $request;
/** @var \OpenStack\Common\Transport\ResponseInterface */
protected $response;
/**
* Construct this exception like any other, but also inject Request and
* Response objects in case the user needs them for debugging.
*
* @param string $errorMessage Human-readable explanation of error
* @param \OpenStack\Common\Transport\RequestInterface $request The failed request
* @param \OpenStack\Common\Transport\ResponseInterface $response The server's response
*/
public function __construct($errorMessage, RequestInterface $request, ResponseInterface $response)
{
parent::__construct($errorMessage, $response->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;
}
}

View File

@ -1,30 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Common\Transport\Exception;
/**
* Exception that represents a 404 Not Found HTTP error.
*
* This class is thrown when a server has not found any resource matching the
* Request's URI.
*/
class ResourceNotFoundException extends RequestException
{
}

View File

@ -1,31 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Common\Transport\Exception;
/**
* Exception that represents a 500 Internal Server Error.
*
* This class is thrown when a server encounters an unexpected condition which
* prevents it from fulfilling the request. Sometimes this error is used as a
* generic catch-all by an OpenStack API.
*/
class ServerException extends RequestException
{
}

View File

@ -1,30 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Common\Transport\Exception;
/**
* Exception that represents a 401 Unauthorized HTTP error.
*
* This class is thrown when a server indicates that authorization has been
* refused for a set of credentials.
*/
class UnauthorizedException extends RequestException
{
}

View File

@ -1,30 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Common\Transport\Exception;
/**
* Exception that represents a 422 Unprocessable Entity HTTP error.
*
* This class is thrown when a request was well-formed but was unable to be
* processed due to semantic errors.
*/
class UnprocessableEntityException extends RequestException
{
}

View File

@ -1,159 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Common\Transport\Guzzle;
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface as GuzzleClientInterface;
use GuzzleHttp\Exception\RequestException as GuzzleRequestException;
use OpenStack\Bootstrap;
use OpenStack\Common\Transport\AbstractClient;
use OpenStack\Common\Transport\Exception;
use OpenStack\Common\Transport\RequestInterface;
/**
* An adapter class which wraps the Guzzle HTTP client. This adapter satisfies
* {@see OpenStack\Common\Transport\ClientInterface}, acting as an intermediary
* between Guzzle and our interface, reconciling their differences.
*/
class GuzzleAdapter extends AbstractClient
{
/**
* @var \GuzzleHttp\Client The client being wrapped.
*/
protected $client;
/**
* A factory method that allows for the easy creation of this adapter. It
* accepts an array of options which will be fed into the Guzzle client.
* This method also handles the configuration of the client being wrapped,
* such as overriding error handling and the default User-Agent header.
*
* @param array $options The options passed in to the Guzzle client. For a
* full run-through of available configuration values,
* view the {@link http://docs.guzzlephp.org/en/latest/clients.html#creating-a-client official docs}.
* @return self
*/
public static function create(array $options = [])
{
if (empty($options['defaults'])) {
$options['defaults'] = [];
}
// Disable Guzzle error handling and define our own error subscriber.
// Also override default User-Agent header with our own version.
$options['defaults'] += ['exceptions' => 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());
}
}

View File

@ -1,57 +0,0 @@
<?php
/*
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Common\Transport\Guzzle;
use GuzzleHttp\Event\CompleteEvent;
use GuzzleHttp\Event\RequestEvents;
use GuzzleHttp\Event\SubscriberInterface;
use GuzzleHttp\Subscriber\HttpError as GuzzleHttpError;
use OpenStack\Common\Transport\Exception\RequestException;
/**
* A subscriber for capturing Guzzle's HTTP error events and processing them in
* a standardised manner.
*/
class HttpError implements SubscriberInterface
{
public function getEvents()
{
return ['complete' => ['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);
}
}
}

View File

@ -1,118 +0,0 @@
<?php
/*
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Common\Transport\Guzzle;
use OpenStack\Common\Transport\MessageInterface;
use GuzzleHttp\Message\MessageInterface as GuzzleMessageInterface;
/**
* An adapter class which wraps {@see GuzzleHttp\Message\MessageInterface}
* objects. Until PSR releases a standardised interface that all projects can
* share, we need to adapt the different interfaces.
*
* As you will notice, most of this adapter is a like-for-like method
* translation. Although it seems verbose, it is actually a lot more explicit,
* clearer and easier to debug than using magic methods.
*/
class MessageAdapter implements MessageInterface
{
/** @var \GuzzleHttp\Message\MessageInterface The Guzzle message being wrapped */
protected $message;
/**
* @param \GuzzleHttp\Message\MessageInterface $guzzleMessage
*/
public function __construct(GuzzleMessageInterface $guzzleMessage)
{
$this->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);
}
}

View File

@ -1,55 +0,0 @@
<?php
/*
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Common\Transport\Guzzle;
use GuzzleHttp\Message\RequestInterface as GuzzleRequestInterface;
use OpenStack\Common\Transport\RequestInterface;
/**
* This class wraps {@see \GuzzleHttp\Message\RequestInterface}.
*
* @inheritDoc
*/
class RequestAdapter extends MessageAdapter implements RequestInterface
{
public function __construct(GuzzleRequestInterface $guzzleRequest)
{
$this->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);
}
}

View File

@ -1,50 +0,0 @@
<?php
/*
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Common\Transport\Guzzle;
use GuzzleHttp\Message\ResponseInterface as GuzzleResponseInterface;
use OpenStack\Common\Transport\ResponseInterface;
/**
* This class wraps {@see \GuzzleHttp\Message\ResponseInterface}.
*
* @inheritDoc
*/
class ResponseAdapter extends MessageAdapter implements ResponseInterface
{
public function __construct(GuzzleResponseInterface $guzzleResponse)
{
$this->setMessage($guzzleResponse);
}
public function getStatusCode()
{
return $this->message->getStatusCode();
}
public function getReasonPhrase()
{
return $this->message->getReasonPhrase();
}
public function json()
{
return $this->message->json();
}
}

View File

@ -1,160 +0,0 @@
<?php
/*
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Common\Transport;
/**
* HTTP messages consist of requests from a client to a server and responses
* from a server to a client.
*
* @link https://github.com/php-fig/fig-standards/blob/master/proposed/http-message.md#31-psrhttpmessageinterface
*/
interface MessageInterface
{
/**
* Gets the HTTP protocol version.
*
* @return string HTTP protocol version.
*/
public function getProtocolVersion();
/**
* Gets the body of the message.
*
* @return StreamInterface|null Returns the body, or null if not set.
*/
public function getBody();
/**
* Sets the body of the message.
*
* The body MUST be a StreamInterface object. Setting the body to null MUST
* remove the existing body.
*
* @param StreamInterface|null $body Body.
*
* @return self Returns the message.
*
* @throws \InvalidArgumentException When the body is not valid.
*/
public function setBody(/* StreamInterface */ $body = null);
/**
* Gets all message headers.
*
* The keys represent the header name as it will be sent over the wire, and
* each value is an array of strings associated with the header.
*
* // Represent the headers as a string
* foreach ($message->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);
}

View File

@ -1,66 +0,0 @@
<?php
/*
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Common\Transport;
/**
* A HTTP request message.
*
* @link https://github.com/php-fig/fig-standards/blob/master/proposed/http-message.md#32-psrhttprequestinterface
* @link http://tools.ietf.org/html/rfc2616#section-5
*/
interface RequestInterface extends MessageInterface
{
/**
* Gets the HTTP method of the request.
*
* @return string Returns the request method.
*/
public function getMethod();
/**
* Sets the method to be performed on the resource identified by the
* Request-URI. While method names are case case-sensitive, implementations
* SHOULD convert the method to all uppercase characters.
*
* @param string $method Case-insensitive method.
*
* @return self Returns the request.
*/
public function setMethod($method);
/**
* Gets the request URL.
*
* @return string Returns the URL as a string.
*/
public function getUrl();
/**
* Sets the request URL.
*
* The URL MUST be a string, or an object that implements the
* `__toString()` method.
*
* @param string $url Request URL.
*
* @return self Reference to the request.
* @throws \InvalidArgumentException If the URL is invalid.
*/
public function setUrl($url);
}

View File

@ -1,50 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Common\Transport;
/**
* A HTTP response message.
*
* @link https://github.com/php-fig/fig-standards/blob/master/proposed/http-message.md#33-psrhttpresponseinterface
* @link http://tools.ietf.org/html/rfc2616#section-6
*/
interface ResponseInterface extends MessageInterface
{
/**
* Gets the response Status-Code, a 3-digit integer result code of the
* server's attempt to understand and satisfy the request.
*
* @return integer Status code.
*/
public function getStatusCode();
/**
* Gets the response Reason-Phrase, a short textual description of the
* Status-Code.
*
* Because a Reason-Phrase is not a required element in response
* Status-Line, the Reason-Phrase value MAY be null. Implementations MAY
* choose to return the default RFC 2616 recommended reason phrase for the
* response's Status-Code.
*
* @return string|null Reason phrase, or null if unknown.
*/
public function getReasonPhrase();
}

View File

@ -1,321 +0,0 @@
<?php
/*
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace OpenStack\Common\Transport;
/**
* Represents a URL, containing its various syntax components. Please note that
* this class does not validate input or enforce RFC3986 standards; instead it
* is meant to serve as a usable model of a URL within our SDK.
*
* @link http://tools.ietf.org/html/rfc3986
*/
class Url
{
private $scheme;
private $host;
private $port;
private $user;
private $password;
private $path;
private $query = [];
private $fragment;
/**
* @param array|string $value Either a string or array input value that
* will be parsed and populated
*
* @throws \InvalidArgumentException If argument is not a string or array
*/
public function __construct($value)
{
if (is_string($value)) {
$value = parse_url($value);
} elseif (!is_array($value)) {
throw new \InvalidArgumentException(
"Url can only be populated with a string or array of values"
);
}
$this->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;
}
}

View File

@ -1,740 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Identity\v2;
use OpenStack\Common\Transport\ClientInterface;
use OpenStack\Common\Transport\Guzzle\GuzzleAdapter;
/**
* IdentityService provides authentication and authorization.
*
* IdentityService (a.k.a. Keystone) provides a central service for managing
* other services. Through it, you can do the following:
*
* - Authenticate
* - Obtain tokens valid accross services
* - Obtain a list of the services currently available with a token
* - Associate with tenants using tenant IDs.
*
* AUTHENTICATION
*
* The authentication process consists of a single transaction during which the
* client (us) submits credentials and the server verifies those credentials,
* returning a token (for subsequent requests), user information, and the
* service catalog.
*
* Authentication credentials:
*
* - Username and password
* - Account ID and Secret Key
*
* Other mechanisms may be supported in the future.
*
* TENANTS
*
* Services are associated with tenants. A token is returned when
* authentication succeeds. It *may* be associated with a tenant. If it is not,
* it is called "unscoped", and it will not have access to any services.
*
* A token that is associated with a tenant is considered "scoped". This token
* can be used to access any of the services attached to that tenant.
*
* There are two different ways to attach a tenant to a token:
*
* - During authentication, provide a tenant ID. This will attach a tenant at
* the outset.
* - After authentication, "rescope" the token to attach it to a tenant. This
* is done with either the rescopeUsingTenantId() or rescopeUsingTenantName()
* method.
*
* Where do I get a tenant ID?
*
* There are two notable places to get this information:
*
* A list of tenants associated with this user can be obtain programatically
* using the tenants() method on this object.
*
* OpenStack users can find their tenant ID in the console along with their
* username and password.
*
* EXAMPLE
*
* The following example illustrates typical use of this class.
*
* <?php
* // You may need to use \OpenStack\Bootstrap to set things up first.
*
* use \OpenStack\Identity\v2\IdentityService;
*
* // Create a new object with the endpoint URL (no version number)
* $ident = new IdentityService('https://example.com:35357');
*
* // Authenticate and set the tenant ID simultaneously.
* $ident->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:
*
* <?php
* array(
* 'id' => '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.
*
* <?php
* $cs = new \OpenStack\Identity\v2\IdentityService('http://example.com');
* $token = $cs->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:
*
* <?php
* $cs = new \OpenStack\Identity\v2\IdentityService($url);
* $ops = array(
* 'passwordCredentials' => 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.
*
* <?php
* array(
* 'id' => '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.
*
* <?php
* array(
* array(
* 'name' : 'Object Storage',
* 'type' => '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:
*
* <?php
* array(
* 'name' => '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:
*
* <?php
* array(
* array(
* "id" => "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;
}
}

View File

@ -1,29 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\ObjectStore\v1\Exception;
/**
* Indicatest that a container is not empty.
*
* Certain operations, notably container deletion, require that a
* container be empty before the operation can be performed. This
* exception is thrown when such an operation encounters an unempty
* container when it requires an empty one.
*/
class ContainerNotEmptyException extends \OpenStack\Common\Transport\Exception\ServerException {}

View File

@ -1,28 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\ObjectStore\v1\Exception;
/**
* Content Verification error condition.
*
* This occurs when the server sends content whose value does
* not match the supplied checksum. See
* RemoteObject::setContentVerification().
*/
class ContentVerificationException extends \OpenStack\Common\Exception {}

View File

@ -1,23 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\ObjectStore\v1\Exception;
/**
* Thrown if an object that is read only is modified.
*/
class ReadOnlyObjectException extends \OpenStack\Common\Exception {}

View File

@ -1,493 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\ObjectStore\v1;
use OpenStack\Common\Exception;
use OpenStack\Common\Transport\ClientInterface;
use OpenStack\Common\Transport\Exception\ConflictException;
use OpenStack\Common\Transport\Exception\ResourceNotFoundException;
use OpenStack\Common\Transport\Guzzle\GuzzleAdapter;
use OpenStack\ObjectStore\v1\Exception\ContainerNotEmptyException;
use OpenStack\ObjectStore\v1\Resource\Container;
use OpenStack\ObjectStore\v1\Resource\ACL;
/**
* Access to ObjectStorage (Swift).
*
* This is the primary piece of the Object Oriented representation of
* the Object Storage service. Developers wishing to work at a low level
* should use this API.
*
* There is also a stream wrapper interface that exposes ObjectStorage
* to PHP's streams system. For common use of an object store, you may
* prefer to use that system. (@see \OpenStack\Bootstrap).
*
* To authenticate, use the IdentityService authentication mechanism (@see
* \OpenStack\Identity\v2\IdentityService).
*
* Common Tasks
*
* - Create a new container with createContainer().
* - List containers with containers().
* - Remove a container with deleteContainer().
*
* @todo ObjectStorage is not yet constrained to a particular version
* of the API. It attempts to use whatever version is passed in to the
* URL. This is different from IdentityService, which uses a fixed version.
*/
class ObjectStorage
{
/**
* The name of this service type in OpenStack.
*
* This is used with IdentityService::serviceCatalog().
*/
const SERVICE_TYPE = 'object-store';
const API_VERSION = '1';
/**
* The authorization token.
*/
protected $token = null;
/**
* The URL to the Swift endpoint.
*/
protected $url = null;
/**
* The HTTP Client
*/
protected $client;
/**
* Given an IdentityService instance, create an ObjectStorage instance.
*
* This constructs a new ObjectStorage from an authenticated instance
* of an \OpenStack\Identity\v2\IdentityService object.
*
* @param \OpenStack\Identity\v2\IdentityService $identity An identity services object that already
* has a valid token and a service catalog.
* @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 newFromIdentity($identity, $region, \OpenStack\Common\Transport\ClientInterface $client = null)
{
$cat = $identity->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:
*
* <?php
* // Get the container.
* $container = $objectStorage->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)
];
}
}

View File

@ -1,567 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\ObjectStore\v1\Resource;
/**
* Access control list for object storage.
*
* EXPERIMENTAL: This is bassed on a feature of Swift that is likely to
* change. Most of this is based on undocmented features of the API
* discovered both in the Python docs and in discussions by various
* members of the OpenStack community.
*
* Swift access control rules are broken into two permissions: READ and
* WRITE. Read permissions grant the user the ability to access the file
* (using verbs like GET and HEAD), while WRITE permissions allow any
* modification operation. WRITE does not imply READ.
*
* In the current implementation of Swift, access can be assigned based
* on two different factors:
*
* - Accounts: Access can be granted to specific accounts, and within
* those accounts, can be further specified to specific users. See the
* addAccount() method for details on this.
* - Referrers: Access can be granted based on host names or host name
* patterns. For example, only subdomains of *.example.com may be
* granted READ access to a particular object.
*
* ACLs are transmitted within the HTTP headers for an object or
* container. Two headers are used: `X-Container-Read` for READ rules, and
* `X-Container-Write` for WRITE rules. Each header may have a chain of
* rules.
*
* Examples
*
* For most casual cases, only the static constructor functions are
* used. For example, an ACL that does not grant any public access can
* be created with a single call:
*
* <?php
* $acl = ACL::makeNonPublic();
* ?>
*
* Public read access is granted like this:
*
* <?php
* $acl = ACL::makePublic();
* ?>
*
* (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:`
*
* <?php
* $acl = new ACL();
*
* // Grant READ to example.com users.
* $acl->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);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,524 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\ObjectStore\v1\Resource;
/**
* An object for ObjectStorage.
*
* The OpenStack ObjectStorage system provides a method for storing
* complete chunks of data (objects) in the cloud. This class describes
* such a chunk of data.
*
* An object has the following basic components:
*
* - Name: A filename (which may be pathlike, subject to OpenStack's
* pathing rules).
* - Content: The content of the object.
* - Content type: The MIME type of the object. Examples:
* - text/plain; charset=UTF-8
* - image/png
* - application/x-my-custom-mime
* - Metadata: File attributes that are stored along with the file on
* object store.
*
* Objects are stored and retrieved by name. So it is assumed
* that, per container, no more than one file with a given name exists.
*
* You may create Object instance and then store them in Containers.
* Likewise, a Container instance can retrieve Object instances from the
* remote object store.
*/
class Object
{
const DEFAULT_CONTENT_TYPE = 'application/octet-stream';
/**
* The name of the object.
*
* This can be path-like, subject to OpenStack's definition
* of "path-like".
*/
protected $name;
/**
* The content.
*
* Subclasses needn't use this to store an object's content,
* as they may prefer filesystem backing.
*/
protected $content;
/**
* The content type.
*
* The default type is 'application/octet-stream', which marks this as
* a generic byte stream.
*/
protected $contentType = self::DEFAULT_CONTENT_TYPE;
/**
* Associative array of stored metadata.
*/
protected $metadata = [];
protected $contentEncoding;
protected $contentDisposition;
/**
* Extension mechanism for new headers.
*/
protected $additionalHeaders = [];
/**
* Construct a new object for storage.
*
* @param string $name A name (may be pathlike) for the object.
* @param string $content Optional content to store in this object. This is
* the same as calling setContent().
* @param string $type Optional content type for this content. This is the
* same as calling setContentType().
*/
public function __construct($name, $content = null, $type = null)
{
$this->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:
*
* <?php
* $o = new Object('my.html');
* $o->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:
*
* <?php
* $object->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;
}
}

View File

@ -1,669 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\ObjectStore\v1\Resource;
use OpenStack\Common\Transport\ClientInterface;
use OpenStack\Common\Transport\Guzzle\GuzzleAdapter;
use OpenStack\ObjectStore\v1\Exception;
/**
* A representation of an object stored in remote Object Storage.
*
* A remote object is one whose canonical copy is stored in a remote
* object storage. It represents a local (and possibly partial) copy of
* an object. (Contrast this with \OpenStack\ObjectStore\v1\Resource\Object)
*
* Depending on how the object was constructed, it may or may not have a
* local copy of the entire contents of the file. It may only have the
* object's "metadata" (information such as name, type, modification
* date, and length of the object). Or it may have all of that in
* addition to the entire content of the file.
*
* Remote objects can be modified locally. Simply modifying an object
* will not result in those modifications being stored on the remote
* server. The object must be saved (see
* \OpenStack\ObjectStore\v1\Resource\Container::save()). When an
* object is modified so that its local contents differ from the remote
* stored copy, it is marked dirty (see isDirty()).
*/
class RemoteObject extends Object
{
protected $contentLength = 0;
protected $etag = '';
protected $lastModified = 0;
protected $contentVerification = true;
protected $caching = false;
/**
* All headers received from a remote are stored in this array.
* Implementing subclasses can access this array for complete access
* to the HTTP headers.
*
* This will be empty if the object was constructed from JSON, and may
* serve as a good indicator that the object does not have all
* attributes set.
*/
protected $allHeaders = [];
/**
* The HTTP Client
*/
protected $client;
/**
* Create a new RemoteObject from JSON data.
*
* @param array $data The JSON data as an array.
* @param string $token The authentication token.
* @param $url The URL to the object on the remote server
* @param \OpenStack\Common\Transport\ClientInterface $client A HTTP transport client.
*/
public static function newFromJSON($data, $token, $url, ClientInterface $client = null)
{
$object = new RemoteObject($data['name']);
$object->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;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,218 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/**
* Contains the stream wrapper for `swiftfs://` URLs.
*
* Note, this stream wrapper is in early testing.
*
* The stream wrapper implemented in \OpenStack\ObjectStore\v1\Resource\StreamWrapper
* only supports the elements of a stream that are implemented by object
* storage. This is how the PHP documentation states a stream wrapper should be
* created. Because some features do not exist, attempting to treat a stream
* wrapper as if it were a file system will not entirely work. For example,
* while there are not directories objects have pathy names (with / separators).
* Directory calls to object storage with the default stream wrappers will not
* operate how they would for a file system.
*
* StreamWrapperFS is an attempt to make a filesystem like stream wrapper.
* Hence the protocol is swiftfs standing for swift file system.
*
* To understand how this stream wrapper works start by first reading the
* documentation on the \OpenStack\ObjectStore\v1\Resource\StreamWrapper.
*
* DIRECTORIES
*
* Because OpenStack Swift does not support directories the swift:// stream
* wrapper does not support them. This stream wrapper attempts to fake them by
* faking directory stats, mkdir, and rmdir. By default (see the options below
* for how to change these) directories have permissions of 777, timestamps
* close to that of the request, and the user and group called by php. We mock
* these on the fly though information is stored in the PHP stat cache.
*
* In addition to the parameters supported by StreamWrapper, the following
* parameters may be set either in the stream context or through
* OpenStack\Bootstrap::setConfiguration():
* - swiftfs_fake_stat_mode: Directories don't exist in swift. When stat() is
* is called on a directory we mock the stat information so functions like
* is_dir will work. The default file permissions is 0777. Though this
* parameter you can pass is a different set of file permissions to use
* for these mock stats.
* - swiftfs_fake_isdir_true: Directory functions like mkdir and is_dir (stat)
* check to see if there are objects with the the passed in directory as a
* prefix to see if it already exists. If you want is_dir to always return
* true even if it is not an existing prefix set this to true. Defaults to
* false.
*/
namespace OpenStack\ObjectStore\v1\Resource;
use \OpenStack\Bootstrap;
use \OpenStack\ObjectStore\v1\ObjectStorage;
/**
* Provides stream wrapping for Swift like a file system.
*
* This provides a full stream wrapper to expose `swiftfs://` URLs to the
* PHP stream system.
*
* @see http://us3.php.net/manual/en/class.streamwrapper.php
*/
class StreamWrapperFS extends StreamWrapper
{
const DEFAULT_SCHEME = 'swiftfs';
protected $schemeName = self::DEFAULT_SCHEME;
/**
* Fake a make a dir.
*
* ObjectStorage has pathy objects not directories. If no objects with a path
* prefix exist we can pass creating a directory. If objects with a path
* prefix exist adding the directory will fail.
*/
public function mkdir($uri, $mode, $options)
{
return ($this->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;
}
}

View File

@ -1,75 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\ObjectStore\v1\Resource;
/**
* Represent a subdirectory (subdir) entry.
*
* Depending on the method with which Swift container requests are
* executed, Swift may return subdir entries instead of Objects.
*
* Subdirs are used for things that are directory-like.
*/
class Subdir
{
/**
* @var string The path string that this subdir describes
*/
protected $path;
/**
* @var string The delimiter used in this path
*/
protected $delimiter;
/**
* Create a new subdirectory.
*
* This represents a remote response's tag for a subdirectory.
*
* @param string $path The path string that this subdir describes.
* @param string $delimiter The delimiter used in this path.
*/
public function __construct($path, $delimiter = '/')
{
$this->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;
}
}

View File

@ -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.

View File

@ -1,30 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Tests;
class BootstrapTest extends TestCase
{
/**
* Canary test.
*/
public function testSettings()
{
$this->assertTrue(!empty(self::$settings));
}
}

View File

@ -1,94 +0,0 @@
<?php
/*
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Tests\Common\Transport;
use OpenStack\Tests\TestCase;
class AbstractClientTest extends TestCase
{
const URI = 'http://openstack.org';
private $client;
private $request;
private $options = ['foo' => '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);
}
}

View File

@ -1,92 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Tests\Common\Transport\Guzzle;
use OpenStack\Common\Transport\Guzzle\GuzzleAdapter;
use OpenStack\Tests\TestCase;
class GuzzleClientTest extends TestCase
{
const TEST_URL = 'http://openstack.org';
private $mockClient;
private $adapter;
public function setUp()
{
$this->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');
}
}

View File

@ -1,128 +0,0 @@
<?php
/*
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Tests\Common\Transport\Guzzle;
use GuzzleHttp\Adapter\Transaction;
use GuzzleHttp\Client;
use GuzzleHttp\Event\CompleteEvent;
use GuzzleHttp\Message\Request;
use GuzzleHttp\Message\Response;
use OpenStack\Common\Transport\Guzzle\HttpError;
use OpenStack\Tests\TestCase;
class HttpErrorTest extends TestCase
{
public function testInheritance()
{
$sub = new HttpError();
$this->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);
}
}

View File

@ -1,139 +0,0 @@
<?php
/*
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Tests\Common\Transport\Guzzle;
use OpenStack\Common\Transport\Guzzle\MessageAdapter;
use OpenStack\Tests\TestCase;
class MessageAdapterTest extends TestCase
{
const REQUEST_CLASS = 'GuzzleHttp\Message\Request';
const RESPONSE_CLASS = 'GuzzleHttp\Message\Response';
private $mock;
private $adapter;
private function getStub($class)
{
return $this->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');
}
}

View File

@ -1,69 +0,0 @@
<?php
/*
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Tests\Common\Transport\Guzzle;
use OpenStack\Common\Transport\Guzzle\RequestAdapter;
use OpenStack\Tests\TestCase;
class RequestAdapterTest extends TestCase
{
private $mock;
private $adapter;
private function getStub($class)
{
return $this->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');
}
}

View File

@ -1,55 +0,0 @@
<?php
/*
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Tests\Common\Transport\Guzzle;
use OpenStack\Common\Transport\Guzzle\ResponseAdapter;
use OpenStack\Tests\TestCase;
class ResponseAdapterTest extends TestCase
{
private $mock;
private $adapter;
private function getStub($class)
{
return $this->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();
}
}

View File

@ -1,113 +0,0 @@
<?php
/*
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace OpenStack\Tests\Common\Transport;
use OpenStack\Common\Transport\Url;
use OpenStack\Tests\TestCase;
class UrlTest extends TestCase
{
const URL_STRING = 'https://username:password@openstack.org:80/community/members#anchor';
private $url;
public function setUp()
{
$this->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);
}
}

View File

@ -1,439 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Tests\Identity\v2;
use \OpenStack\Identity\v2\IdentityService;
use \OpenStack\Bootstrap;
class IdentityServicesTest extends \OpenStack\Tests\TestCase
{
public function testConstructor()
{
$endpoint = self::conf('openstack.identity.url');
$this->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);
}
}

View File

@ -1,287 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Tests\ObjectStore\v1\Resource;
use \OpenStack\ObjectStore\v1\Resource\Object;
use \OpenStack\ObjectStore\v1\Resource\ACL;
class ObjectStorageTest extends \OpenStack\Tests\TestCase
{
public function testSettings()
{
$this->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);
}
}

View File

@ -1,215 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Tests\ObjectStore\v1\Resource;
use \OpenStack\ObjectStore\v1\Resource\ACL;
/**
* @ingroup Tests
*/
class ACLTest extends \OpenStack\Tests\TestCase
{
public function testConstructor()
{
$acl = new ACL();
$this->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());
}
}

View File

@ -1,393 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Tests\ObjectStore\v1\Resource;
use OpenStack\Bootstrap;
use \OpenStack\ObjectStore\v1\Resource\Container;
use \OpenStack\ObjectStore\v1\Resource\Object;
use \OpenStack\ObjectStore\v1\Resource\ACL;
use OpenStack\Tests\TestCase;
class ContainerTest extends TestCase
{
const FILENAME = 'unit-test-dummy.txt';
const FILESTR = 'This is a test.';
const FNAME = 'testSave';
const FCONTENT = 'This is a test.';
const FTYPE = 'application/x-monkey-file';
public function testConstructorSetsName()
{
$container = new Container('foo');
$this->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);
}
}

View File

@ -1,150 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Tests\ObjectStore\v1\Resource;
use \OpenStack\ObjectStore\v1\Resource\Object;
class ObjectTest extends \OpenStack\Tests\TestCase
{
const FNAME = 'descartes.txt';
const FCONTENT = 'Cogito ergo sum.';
const FTYPE = 'text/plain; charset=ISO-8859-1';
/**
* Set up a basic object fixture.
*
* This provides an Object initialized with the main constants defined
* for this class. Use this as a fixture to avoid repetition.
*
* @return Object An initialized object.
*/
public function basicObjectFixture()
{
$o = new Object(self::FNAME);
$o->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));
}
}

View File

@ -1,330 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Tests\ObjectStore\v1\Resource;
use \OpenStack\ObjectStore\v1\Resource\RemoteObject;
use \OpenStack\ObjectStore\v1\Resource\Object;
use \OpenStack\ObjectStore\v1\Resource\Container;
class RemoteObjectTest extends \OpenStack\Tests\TestCase
{
const FNAME = 'RemoteObjectTest';
//const FTYPE = 'text/plain; charset=UTF-8';
const FTYPE = 'application/octet-stream; charset=UTF-8';
const FCONTENT = 'Rah rah ah ah ah. Roma roma ma. Gaga oh la la.';
const FMETA_NAME = 'Foo';
const FMETA_VALUE = 'Bar';
const FDISPOSITION = 'attachment; roma.gaga';
const FENCODING = 'gzip';
const FCORS_NAME = 'Access-Control-Max-Age';
const FCORS_VALUE = '2000';
protected function createAnObject()
{
$container = $this->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();
}
}

View File

@ -1,353 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Tests\ObjectStore\v1\Resource;
use OpenStack\ObjectStore\v1\Resource\StreamWrapperFS;
/**
* @group streamWrapper
*/
class StreamWrapperFSTest extends StreamWrapperTestCase
{
const SCHEME = StreamWrapperFS::DEFAULT_SCHEME;
public function testStreamContext()
{
$context = stream_context_get_options($this->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));
}
}

View File

@ -1,314 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Tests\ObjectStore\v1\Resource;
use OpenStack\Bootstrap;
use OpenStack\ObjectStore\v1\Resource\StreamWrapper;
use OpenStack\Tests\TestCase;
/**
* @group streamWrapper
*/
class StreamWrapperTest extends StreamWrapperTestCase
{
const SCHEME = StreamWrapper::DEFAULT_SCHEME;
public function testStreamContext()
{
$context = stream_context_get_options($this->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]);
}
}

View File

@ -1,135 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Tests\ObjectStore\v1\Resource;
use OpenStack\Bootstrap;
use OpenStack\ObjectStore\v1\Resource\StreamWrapper;
use OpenStack\Tests\TestCase;
abstract class StreamWrapperTestCase extends TestCase
{
const FTYPE = 'application/foo-bar; charset=iso-8859-13';
const DEFAULT_MODE = 'nope';
const FILE_PATH = 'foo→/test.csv';
const SCHEME = StreamWrapper::DEFAULT_SCHEME;
protected static $container;
protected $resource;
protected $context;
protected $url;
public static function setUpBeforeClass()
{
self::setConfiguration();
$service = self::createObjectStoreService();
$containerName = self::$settings['openstack.swift.container'];
$service->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
]);
}
}

View File

@ -1,240 +0,0 @@
<?php
/*
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace OpenStack\Tests;
use GuzzleHttp\Exception\ClientException;
use OpenStack\Bootstrap;
use OpenStack\Common\Transport\Exception\ResourceNotFoundException;
use OpenStack\Identity\v2\IdentityService;
use OpenStack\ObjectStore\v1\ObjectStorage;
use OpenStack\Common\Transport\Guzzle\GuzzleAdapter;
/**
* @ingroup Tests
*/
abstract class TestCase extends \PHPUnit_Framework_TestCase
{
public static $settings = [];
protected $objectStoreService;
/**
* The IdentityService instance.
*/
public static $ident;
public static $httpClient = null;
protected $containerFixture = null;
protected static function setConfiguration()
{
if (file_exists('tests/settings.ini')) {
self::$settings = parse_ini_file('tests/settings.ini');
} else {
throw new \Exception('Could not access test/settings.ini');
}
Bootstrap::setConfiguration(self::$settings);
}
public static function setUpBeforeClass()
{
self::setConfiguration();
}
/**
* Get a configuration value.
*
* Optionally, specify a default value to be used
* if none was found.
*/
public static function conf($name, $default = null)
{
if (isset(self::$settings[$name])) {
return self::$settings[$name];
}
return $default;
}
protected static function createIdentityService()
{
$username = self::conf('openstack.identity.username');
$password = self::conf('openstack.identity.password');
$url = self::conf('openstack.identity.url');
$tenantId = self::conf('openstack.identity.tenantId');
$service = new IdentityService($url);
$service->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.
*
* <?php
* // Get the current token.
* $this->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);
}
}

View File

@ -1,19 +0,0 @@
<?php
/*
* (c) Copyright 2014 Rackspace US, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
require dirname(__DIR__) . '/vendor/autoload.php';

View File

@ -1,53 +0,0 @@
;;;;;;;;;;;;;;;;;;;;;
; Identity Services ;
;;;;;;;;;;;;;;;;;;;;;
; This is the default Identity Service URL.
openstack.identity.url = https://region-a.geo-1.identity.hpcloudsvc.com:35357/v2.0
; Set the tenant ID
openstack.identity.tenantId =
openstack.identity.tenantName =
; For authentication by username.
openstack.identity.username =
openstack.identity.password =
;;;;;;;;;;;;;;;;;;
; Object Storage ;
;;;;;;;;;;;;;;;;;;
; Settings to work with swift:
; Account is the tenandId:console username.
openstack.swift.account = 12345678:87654321
; Key is the console account password.
openstack.swift.key = abcdef123456
; URL is the same as used for identity services calls (including port) except
; with /auth/v1.0/ appended to the end.
openstack.swift.url = https://region-a.geo-1.identity.hpcloudsvc.com:35357/auth/v1.0/
; Container used for testing.
openstack.swift.container = "test"
; Specified region name to test against.
openstack.swift.region = "Region1"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Configuration Parameters ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; The HTTP Transport Client to use.
transport = "OpenStack\Common\Transport\Guzzle\GuzzleAdapter"
; If behind a proxy set to the https proxy server communications need
; to pass through.
; transport.proxy = "https://username:password@foobar.com:1234"
; Turn on verbose debugging of the transport.
; transport.debug = 1
; Site the max time (in seconds) a connection will wait for the transaction to complete.
; transport.timeout = .5
; Tell SSL not to worry about certs that can be verified.
; transport.ssl.verify = 0