Files
openstack-sdk-php/src/OpenStack/ObjectStore/v1/ObjectStorage.php
Jamie Hannaford 3de5e4749b Upgrading arrays to 5.4 short syntax
Change-Id: I1a238996a3a8ab56fa0282cddc51c06f5c9bd139
2014-06-10 07:07:48 +00:00

500 lines
19 KiB
PHP

<?php
/* ============================================================================
(c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
============================================================================ */
/**
* This file provides the ObjectStorage class, which is the primary
* representation of the ObjectStorage system.
*
* ObjectStorage (aka Swift) is the OpenStack service for providing
* storage of complete and discrete pieces of data (e.g. an image file,
* a text document, a binary).
*/
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)
];
}
}