Refactors ClientInterface and Guzzle adapter.

This patch achieves three interrelated changes

1. an update to the ClientInterface, making it more efficient and
consistent with other HTTP libraries. PSR-FIG messages have also been
added as separate classes, rather than lumped as one. Guzzle
functionality has also been moved to its own namespace;

2. the refactoring of `GuzzleClient` to `GuzzleAdapter`, including
relevant changes needed for the interface change (1). We now have
ADAPTERS that bridge our interfaces with Guzzle's - making that
difference much clearer, extensible, less tightly coupled and less
brittle;

3. moving "bad request" error handling into its own dedicated space,
based on how the new Adapter functionality (2). Previously the client
tried to do all the exception logic - but this is not strictly its
responsibility. This logic has been shipped out to a base
RequestException which instantiates one of its children exception based
on the HTTP status code.

Although I have attempted to keep the scope of this patch to a minimum,
as granular as possible, because the interface is a core internal API,
various other files need to be modified to reflect the change. In terms
of the other two changes, these are inextricably linked to the interface
change, so cannot be teased out into their own patches.

Change-Id: Ibc1b50cec125c11d32ee6e4f0dbb395fcaea864e
This commit is contained in:
Jamie Hannaford 2014-04-30 19:10:26 +02:00
parent bcdcba06f7
commit c91e2932aa
47 changed files with 2624 additions and 2175 deletions

View File

@ -23,6 +23,8 @@
namespace OpenStack;
use OpenStack\Identity\v2\IdentityService;
use OpenStack\ObjectStore\v1\Resource\StreamWrapper;
use OpenStack\ObjectStore\v1\Resource\StreamWrapperFS;
/**
* Bootstrapping services.
@ -50,7 +52,7 @@ use OpenStack\Identity\v2\IdentityService;
* <?php
* $config = array(
* // We use Guzzle, which defaults to CURL, for a transport layer.
* 'transport' => '\OpenStack\Common\Transport\GuzzleClient',
* 'transport' => 'OpenStack\Common\Transport\Guzzle\GuzzleAdapter',
* // Set the HTTP max wait time to 500 seconds.
* 'transport.timeout' => 500,
* );
@ -81,9 +83,11 @@ use OpenStack\Identity\v2\IdentityService;
*/
class Bootstrap
{
const VERSION = '0.0.1';
public static $config = array(
// The transport implementation. By default, we use the Guzzle Client
'transport' => '\OpenStack\Common\Transport\GuzzleClient',
'transport' => 'OpenStack\Common\Transport\Guzzle\GuzzleAdapter',
);
/**
@ -119,17 +123,29 @@ class Bootstrap
*/
public static function useStreamWrappers()
{
$swift = stream_wrapper_register(
\OpenStack\ObjectStore\v1\ObjectStorage\StreamWrapper::DEFAULT_SCHEME,
'\OpenStack\ObjectStore\v1\ObjectStorage\StreamWrapper'
self::enableStreamWrapper(
StreamWrapper::DEFAULT_SCHEME,
'OpenStack\ObjectStore\v1\Resource\StreamWrapper'
);
$swiftfs = stream_wrapper_register(
\OpenStack\ObjectStore\v1\ObjectStorage\StreamWrapperFS::DEFAULT_SCHEME,
'\OpenStack\ObjectStore\v1\ObjectStorage\StreamWrapperFS'
self::enableStreamWrapper(
StreamWrapperFS::DEFAULT_SCHEME,
'OpenStack\ObjectStore\v1\Resource\StreamWrapperFS'
);
}
return ($swift && $swiftfs);
/**
* 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);
}
/**
@ -144,7 +160,7 @@ class Bootstrap
* Common configuration directives:
*
* - 'transport': The namespaced classname for the transport that
* should be used. Example: \OpenStack\Common\Transport\GuzzleClient
* 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.
@ -276,10 +292,10 @@ class Bootstrap
$options['proxy'] = $proxy;
}
$klass = self::config('transport');
self::$transport = new $klass($options);
$class = self::config('transport');
self::$transport = $class::create($options);
}
return self::$transport;
}
}
}

View File

@ -0,0 +1,52 @@
<?php
/**
* Copyright 2014 OpenStack Foundation
*
* 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

@ -14,9 +14,6 @@
See the License for the specific language governing permissions and
limitations under the License.
============================================================================ */
/**
* This file contains the interface for transporters.
*/
namespace OpenStack\Common\Transport;
@ -24,7 +21,7 @@ namespace OpenStack\Common\Transport;
* Describes a transport client.
*
* Transport clients are responsible for moving data from the remote cloud to
* the local host. Transport clinets are responsible only for the transport
* the local host. Transport clients are responsible only for the transport
* protocol, not for the payloads.
*
* The current OpenStack services implementation is oriented toward
@ -34,91 +31,106 @@ namespace OpenStack\Common\Transport;
*/
interface ClientInterface
{
const HTTP_USER_AGENT = 'OpenStack-PHP/1.0';
/**
* 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 = []);
/**
* Setup for the HTTP Client.
* Sends a request.
*
* @param array $options Options for the HTTP Client including:
* - headers (array) A key/value mapping of default headers for each request.
* - proxy (string) A proxy specified as a URI.
* - debug (bool) True if debug output should be displayed.
* - timeout (int) The timeout, in seconds, a request should wait before
* timing out.
* - ssl_verify (bool|string) True, the default, verifies the SSL certificate,
* false disables verification, and a string is the path to a CA to verify
* against.
* @param \OpenStack\Common\Transport\RequestInterface $request Request to execute
*
* @return \OpenStack\Common\Transport\ResponseInterface
*/
public function __construct(array $options = []);
public function send(RequestInterface $request);
/**
* Perform a request.
* Execute a GET request.
*
* Invoking this method causes a single request to be relayed over the
* transporter. The transporter MUST be capable of handling multiple
* invocations of a doRequest() call.
* @param string|array|\OpenStack\Common\Transport\Url $uri URL the request will send to
* @param array $options Configuration options, such as headers
*
* @param string $uri The target URI.
* @param string $method The method to be sent.
* @param array $headers An array of name/value header pairs.
* @param string $body The string containing the request body.
*
* @return \OpenStack\Common\Transport\ResponseInterface The response. The response
* is implicit rather than explicit. The interface is based on a draft for
* messages from PHP FIG. Individual implementing libraries will have their
* own reference to interfaces. For example, see Guzzle.
*
* @throws \OpenStack\Common\Transport\Exception\ForbiddenException
* @throws \OpenStack\Common\Transport\Exception\UnauthorizedException
* @throws \OpenStack\Common\Transport\Exception\FileNotFoundException
* @throws \OpenStack\Common\Transport\Exception\MethodNotAllowedException
* @throws \OpenStack\Common\Transport\Exception\ConflictException
* @throws \OpenStack\Common\Transport\Exception\LengthRequiredException
* @throws \OpenStack\Common\Transport\Exception\UnprocessableEntityException
* @throws \OpenStack\Common\Transport\Exception\ServerException
* @throws \OpenStack\Common\Exception
* @return \OpenStack\Common\Transport\ResponseInterface
*/
public function doRequest($uri, $method = 'GET', array $headers = [], $body = '');
public function get($uri, array $options = []);
/**
* Perform a request, but use a resource to read the body.
* Execute a HEAD request.
*
* This is a special version of the doRequest() function.
* It handles a very spefic case where...
* @param string|array|\OpenStack\Common\Transport\Url $uri URL the request will send to
* @param array $options Configuration options, such as headers
*
* - The HTTP verb requires a body (viz. PUT, POST)
* - The body is in a resource, not a string
*
* Examples of appropriate cases for this variant:
*
* - Uploading large files.
* - Streaming data out of a stream and into an HTTP request.
* - Minimizing memory usage ($content strings are big).
*
* Note that all parameters are required.
*
* @param string $uri The target URI.
* @param string $method The method to be sent.
* @param array $headers An array of name/value header pairs.
* @param mixed $resource The string with a file path or a stream URL; or a
* file object resource. If it is a string, then it will be opened with the
* default context. So if you need a special context, you should open the
* file elsewhere and pass the resource in here.
*
* @return \OpenStack\Common\Transport\ResponseInterface The response. The response
* is implicit rather than explicit. The interface is based on a draft for
* messages from PHP FIG. Individual implementing libraries will have their
* own reference to interfaces. For example, see Guzzle.
*
* @throws \OpenStack\Common\Transport\Exception\ForbiddenException
* @throws \OpenStack\Common\Transport\Exception\UnauthorizedException
* @throws \OpenStack\Common\Transport\Exception\FileNotFoundException
* @throws \OpenStack\Common\Transport\Exception\MethodNotAllowedException
* @throws \OpenStack\Common\Transport\Exception\ConflictException
* @throws \OpenStack\Common\Transport\Exception\LengthRequiredException
* @throws \OpenStack\Common\Transport\Exception\UnprocessableEntityException
* @throws \OpenStack\Common\Transport\Exception\ServerException
* @throws \OpenStack\Common\Exception
* @return \OpenStack\Common\Transport\ResponseInterface
*/
public function doRequestWithResource($uri, $method, array $headers = [], $resource);
}
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

@ -16,11 +16,14 @@
============================================================================ */
namespace OpenStack\Common\Transport\Exception;
/**
* Represents an HTTP 409 error.
* Exception that represents a 409 Conflict HTTP error.
*
* During DELETE requests, this occurs when a remote resource cannot be
* deleted because the resource is not empty or deleteable. (viz.
* containers).
* 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 \OpenStack\Common\Exception {}
class ConflictException extends RequestException
{
}

View File

@ -1,22 +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\Transport\Exception;
/**
* Represents an HTTP File Not Found error.
*/
class FileNotFoundException extends \OpenStack\Common\Exception {}

View File

@ -14,15 +14,17 @@
See the License for the specific language governing permissions and
limitations under the License.
============================================================================ */
/**
* @file
*
* The permission denied exception.
*/
namespace OpenStack\Common\Transport\Exception;
/**
* Thrown when an access constraint is not met.
* Exception that represents a 403 Forbidden HTTP error.
*
* Represents an HTTP 403 exception.
* 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 AuthorizationException {}
class ForbiddenException extends RequestException
{
}

View File

@ -16,9 +16,14 @@
============================================================================ */
namespace OpenStack\Common\Transport\Exception;
/**
* Represents an HTTP 412 error.
* Exception that represents a 411 Length Required HTTP error.
*
* During some PUT requests, Content-Length is a required header.
* 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 \OpenStack\Common\Exception {}
class LengthRequiredException extends RequestException
{
}

View File

@ -16,7 +16,13 @@
============================================================================ */
namespace OpenStack\Common\Transport\Exception;
/**
* Represents an HTTP 405 error.
* 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 \OpenStack\Common\Exception {}
class MethodNotAllowedException extends RequestException
{
}

View File

@ -0,0 +1,110 @@
<?php
/**
* Copyright 2014 OpenStack Foundation
*
* 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

@ -14,13 +14,15 @@
See the License for the specific language governing permissions and
limitations under the License.
============================================================================ */
/**
* The authorization exception.
*/
namespace OpenStack\Common\Transport\Exception;
/**
* Thrown when an access constraint is not met.
* Exception that represents a 404 Not Found HTTP error.
*
* Represents an HTTP 401 or 403 exception.
* This class is thrown when a server has not found any resource matching the
* Request's URI.
*/
class AuthorizationException extends \OpenStack\Common\Exception {}
class ResourceNotFoundException extends RequestException
{
}

View File

@ -16,7 +16,14 @@
============================================================================ */
namespace OpenStack\Common\Transport\Exception;
/**
* Represents an HTTP 500 error.
* 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 \OpenStack\Common\Exception {}
class ServerException extends RequestException
{
}

View File

@ -18,9 +18,13 @@
* The authorization exception.
*/
namespace OpenStack\Common\Transport\Exception;
/**
* Thrown when authorization fails.
* Exception that represents a 401 Unauthorized HTTP error.
*
* Represents an HTTP 401 exception.
* This class is thrown when a server indicates that authorization has been
* refused for a set of credentials.
*/
class UnauthorizedException extends AuthorizationException {}
class UnauthorizedException extends RequestException
{
}

View File

@ -16,10 +16,13 @@
============================================================================ */
namespace OpenStack\Common\Transport\Exception;
/**
* Represents an HTTP 422 error.
* Exception that represents a 422 Unprocessable Entity HTTP error.
*
* This often represents a case where a checksum or hash did not match
* the generated checksum on the remote end.
* This class is thrown when a request was well-formed but was unable to be
* processed due to semantic errors.
*/
class UnprocessableEntityException extends \OpenStack\Common\Exception {}
class UnprocessableEntityException extends RequestException
{
}

View File

@ -0,0 +1,157 @@
<?php
/* ============================================================================
(c) Copyright 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\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

@ -0,0 +1,56 @@
<?php
/**
* Copyright 2014 OpenStack Foundation
*
* 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

@ -0,0 +1,117 @@
<?php
/**
* Copyright 2014 OpenStack Foundation
*
* 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

@ -0,0 +1,54 @@
<?php
/**
* Copyright 2014 OpenStack Foundation
*
* 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

@ -0,0 +1,49 @@
<?php
/**
* Copyright 2014 OpenStack Foundation
*
* 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,227 +0,0 @@
<?php
/* ============================================================================
(c) Copyright 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 contains the interface for transporter clients.
*/
namespace OpenStack\Common\Transport;
class GuzzleClient implements ClientInterface, \Serializable
{
const HTTP_USER_AGENT_SUFFIX = ' Guzzle/4.0';
/**
* The Guzzle client used for the implementation.
*/
protected $client;
protected $options;
/**
* Setup for the HTTP Client.
*
* @param array $options Options for the HTTP Client including:
* - headers (array) A key/value mapping of default headers for each request.
* - proxy (string) A proxy specified as a URI.
* - debug (bool) True if debug output should be displayed.
* - timeout (int) The timeout, in seconds, a request should wait before
* timing out.
* - ssl_verify (bool|string) True, the default, verifies the SSL certificate,
* false disables verification, and a string is the path to a CA to verify
* against.
* - client (mixed) A guzzle client object to use instead of the default.
* This can be either a string to the class or an existing object. If an
* existing object is passed in the other options will be ignored.
*/
public function __construct(array $options = [])
{
$this->options = $options;
$this->client = $this->setup($options);
}
/**
* Setup is a protected method to setup the client.
*
* The functionality would typically be in the constructor. It was broken out
* to be used by the constructor and serialization process.
*
* @param array $options The options as passed to the constructor.
* @return mixed The Guzzle based client.
*/
protected function setup(array $options = [])
{
// If no client has been passed in we create one. This is the default case.
if (!isset($options['client']) || is_string($options['client'])) {
$defaultOptions = ['defaults' => []];
if (isset($options['headers'])) {
$defaultOptions['defaults']['headers'] = $options['headers'];
}
if (isset($options['proxy'])) {
$defaultOptions['defaults']['proxy'] = $options['proxy'];
}
if (isset($options['debug'])) {
$defaultOptions['defaults']['debug'] = $options['debug'];
}
if (isset($options['ssl'])) {
$defaultOptions['defaults']['verify'] = $options['ssl_verify'];
}
if (isset($options['timeout'])) {
$defaultOptions['defaults']['timeout'] = $options['timeout'];
}
// Add a user agent if not already specificed.
if (!isset($defaultOptions['defaults']['headers']['User-Agent'])) {
$defaultOptions['defaults']['headers']['User-Agent'] = self::HTTP_USER_AGENT . self::HTTP_USER_AGENT_SUFFIX;
}
$clientClass = '\GuzzleHttp\Client';
if (isset($options['client']) && is_string($options['client'])) {
$clientClass = $options['client'];
}
$options['client'] = new $clientClass($defaultOptions);
}
return $options['client'];
}
/**
* {@inheritdoc}
*/
public function doRequest($uri, $method = 'GET', array $headers = [], $body = '')
{
$options = [
'headers' => $headers,
'body' => $body,
];
// We use our own exceptions for errors to provide a common exception
// interface to applications implementing the SDK.
try {
$response = $this->client->send($this->client->createRequest($method, $uri, $options));
} catch (\GuzzleHttp\Exception\ClientException $e) {
$this->handleException($e);
} catch (\GuzzleHttp\Exception\ServerException $e) {
$this->handleException($e);
} catch (\GuzzleHttp\Exception\RequestException $e) {
$this->handleException($e);
}
return $response;
}
/**
* {@inheritdoc}
*/
public function doRequestWithResource($uri, $method, array $headers = [], $resource)
{
// Guzzle messes with the resource in such a manner that it can no longer be
// used by something else after the fact. So, we clone the content into
// temporary stream.
$tmp = $out = fopen('php://temp', 'wb+');
stream_copy_to_stream($resource, $tmp);
$options = [
'headers' => $headers,
'body' => $tmp,
];
// We use our own exceptions for errors to provide a common exception
// interface to applications implementing the SDK.
try {
$response = $this->client->send($this->client->createRequest($method, $uri, $options));
} catch (\GuzzleHttp\Exception\ClientException $e) {
$this->handleException($e);
} catch (\GuzzleHttp\Exception\ServerException $e) {
$this->handleException($e);
} catch (\GuzzleHttp\Exception\RequestException $e) {
$this->handleException($e);
}
return $response;
}
/**
* Handle errors on a response.
*
* @param mixed The Guzzle exception.
*
* @return \OpenStack\Common\Transport\ResponseInterface The response.
*
* @throws \OpenStack\Common\Transport\Exception\ForbiddenException
* @throws \OpenStack\Common\Transport\Exception\UnauthorizedException
* @throws \OpenStack\Common\Transport\Exception\FileNotFoundException
* @throws \OpenStack\Common\Transport\Exception\MethodNotAllowedException
* @throws \OpenStack\Common\Transport\Exception\ConflictException
* @throws \OpenStack\Common\Transport\Exception\LengthRequiredException
* @throws \OpenStack\Common\Transport\Exception\UnprocessableEntityException
* @throws \OpenStack\Common\Transport\Exception\ServerException
* @throws \OpenStack\Common\Exception
*/
protected function handleException($exception)
{
$response = $exception->getResponse();
$request = $exception->getRequest();
if (!is_null($response)) {
$code = $response->getStatusCode();
switch ($code) {
case '403':
throw new \OpenStack\Common\Transport\Exception\ForbiddenException($response->getReasonPhrase());
case '401':
throw new \OpenStack\Common\Transport\Exception\UnauthorizedException($response->getReasonPhrase());
case '404':
throw new \OpenStack\Common\Transport\Exception\FileNotFoundException($response->getReasonPhrase() . " ({$response->getEffectiveUrl()})");
case '405':
throw new \OpenStack\Common\Transport\Exception\MethodNotAllowedException($response->getReasonPhrase() . " ({$request->getMethod()} {$response->getEffectiveUrl()})");
case '409':
throw new \OpenStack\Common\Transport\Exception\ConflictException($response->getReasonPhrase());
case '412':
throw new \OpenStack\Common\Transport\Exception\LengthRequiredException($response->getReasonPhrase());
case '422':
throw new \OpenStack\Common\Transport\Exception\UnprocessableEntityException($response->getReasonPhrase());
case '500':
throw new \OpenStack\Common\Transport\Exception\ServerException($response->getReasonPhrase());
default:
throw new \OpenStack\Common\Exception($response->getReasonPhrase());
}
}
// The exception was one other than a HTTP error. For example, a HTTP layer
// timeout occurred.
else {
throw new \OpenStack\Common\Exception($exception->getMessage());
}
return $response;
}
public function serialize()
{
$data = ['options' => $this->options];
return serialize($data);
}
public function unserialize($data)
{
$vals = unserialize($data);
$this->options = $vals['options'];
$this->client = $this->setup($vals['options']);
}
}

View File

@ -0,0 +1,159 @@
<?php
/**
* Copyright 2014 OpenStack Foundation
*
* 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

@ -0,0 +1,65 @@
<?php
/**
* Copyright 2014 OpenStack Foundation
*
* 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

@ -14,170 +14,35 @@
See the License for the specific language governing permissions and
limitations under the License.
============================================================================ */
/**
* This file contains the response interface for a HTTP request.
*/
namespace OpenStack\Common\Transport;
/**
* A Response is what comes back from a HTTP Request.
* A HTTP response message.
*
* This interface is equivalent to the proposed PHP FIG http message interface
* for a response that can be found at https://github.com/php-fig/fig-standards/blob/master/proposed/http-message.md#33-psrhttpresponseinterface
* @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
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.
*/
* 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.
*/
* 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();
/**
* 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

@ -19,8 +19,8 @@
*/
namespace OpenStack\Identity\v2;
use OpenStack\Common\Transport\GuzzleClient;
use OpenStack\Common\Transport\ClientInterface;
use OpenStack\Common\Transport\Guzzle\GuzzleAdapter;
/**
* IdentityService provides authentication and authorization.
@ -203,7 +203,7 @@ class IdentityService
*
* @param \OpenStack\Common\Transport\ClientInterface $client An optional HTTP client to use when making the requests.
*/
public function __construct($url, \OpenStack\Common\Transport\ClientInterface $client = null)
public function __construct($url, ClientInterface $client = null)
{
$parts = parse_url($url);
@ -215,7 +215,7 @@ class IdentityService
// Guzzle is the default client to use.
if (is_null($client)) {
$this->client = new GuzzleClient();
$this->client = GuzzleAdapter::create();
} else {
$this->client = $client;
}
@ -278,13 +278,13 @@ class IdentityService
$body = json_encode($envelope);
$headers = array(
'Content-Type' => 'application/json',
'Accept' => self::ACCEPT_TYPE,
$headers = [
'Content-Type' => 'application/json',
'Accept' => self::ACCEPT_TYPE,
'Content-Length' => strlen($body),
);
];
$response = $this->client->doRequest($url, 'POST', $headers, $body);
$response = $this->client->post($url, $body, ['headers' => $headers]);
$this->handleResponse($response);
@ -327,7 +327,7 @@ class IdentityService
'passwordCredentials' => array(
'username' => $username,
'password' => $password,
),
)
);
// If a tenant ID is provided, added it to the auth array.
@ -599,18 +599,14 @@ class IdentityService
$token = $this->token();
}
$headers = array(
$headers = [
'X-Auth-Token' => $token,
'Accept' => 'application/json',
//'Content-Type' => 'application/json',
);
'Accept' => 'application/json'
];
$response = $this->client->doRequest($url, 'GET', $headers);
$json = $response->json();
return $json['tenants'];
$response = $this->client->get($url, ['headers' => $headers]);
return $response->json()['tenants'];
}
/**
@ -644,25 +640,24 @@ class IdentityService
public function rescopeUsingTenantId($tenantId)
{
$url = $this->url() . '/tokens';
$token = $this->token();
$data = array(
'auth' => array(
$body = json_encode([
'auth' => [
'tenantId' => $tenantId,
'token' => array(
'id' => $token,
),
),
);
$body = json_encode($data);
'token' => [
'id' => $this->token(),
]
]
]);
$headers = array(
'Accept' => self::ACCEPT_TYPE,
'Content-Type' => 'application/json',
'Content-Length' => strlen($body),
//'X-Auth-Token' => $token,
);
$headers = [
'Accept' => self::ACCEPT_TYPE,
'Content-Type' => 'application/json',
'Content-Length' => strlen($body)
];
$response = $this->client->post($url, $body, ['headers' => $headers]);
$response = $this->client->doRequest($url, 'POST', $headers, $body);
$this->handleResponse($response);
return $this->token();
@ -699,25 +694,24 @@ class IdentityService
public function rescopeUsingTenantName($tenantName)
{
$url = $this->url() . '/tokens';
$token = $this->token();
$data = array(
'auth' => array(
$body = json_encode([
'auth' => [
'tenantName' => $tenantName,
'token' => array(
'id' => $token,
),
),
);
$body = json_encode($data);
'token' => [
'id' => $this->token()
]
]
]);
$headers = array(
'Accept' => self::ACCEPT_TYPE,
'Content-Type' => 'application/json',
'Content-Length' => strlen($body),
//'X-Auth-Token' => $token,
);
$headers = [
'Accept' => self::ACCEPT_TYPE,
'Content-Type' => 'application/json',
'Content-Length' => strlen($body)
];
$response = $this->client->post($url, $body, ['headers' => $headers]);
$response = $this->client->doRequest($url, 'POST', $headers, $body);
$this->handleResponse($response);
return $this->token();

View File

@ -25,9 +25,14 @@
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;
use OpenStack\Common\Transport\GuzzleClient;
/**
* Access to ObjectStorage (Swift).
@ -126,9 +131,7 @@ class ObjectStorage
if ($catalog[$i]['type'] == self::SERVICE_TYPE) {
foreach ($catalog[$i]['endpoints'] as $endpoint) {
if (isset($endpoint['publicURL']) && $endpoint['region'] == $region) {
$os = new ObjectStorage($authToken, $endpoint['publicURL'], $client);
return $os;
return new ObjectStorage($authToken, $endpoint['publicURL'], $client);
}
}
}
@ -150,14 +153,14 @@ class ObjectStorage
* after authentication.
* @param \OpenStack\Common\Transport\ClientInterface $client The HTTP client
*/
public function __construct($authToken, $url, \OpenStack\Common\Transport\ClientInterface $client = null)
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 = new GuzzleClient();
$this->client = GuzzleAdapter::create();
} else {
$this->client = $client;
}
@ -226,7 +229,9 @@ class ObjectStorage
$url .= sprintf('&marker=%d', $marker);
}
$containers = $this->get($url);
$headers = ['X-Auth-Token' => $this->token];
$response = $this->client->get($url, ['headers' => $headers]);
$containers = $response->json();
$containerList = array();
foreach ($containers as $container) {
@ -246,23 +251,24 @@ class ObjectStorage
*
* @return \OpenStack\ObjectStore\v1\Resource\Container A container.
*
* @throws \OpenStack\Common\Transport\Exception\FileNotFoundException if the named container is not
* @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);
$data = $this->req($url, 'HEAD', false);
$status = $data->getStatusCode();
$headers = ['X-Auth-Token' => $this->token()];
$response = $this->client->head($url, ['headers' => $headers]);
$status = $response->getStatusCode();
if ($status == 204) {
$container = Container::newFromResponse($name, $data, $this->token(), $this->url());
return $container;
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 \OpenStack\Common\Exception("Unknown status: $status");
throw new Exception(sprintf("Unknown status: %d", $status));
}
/**
@ -282,7 +288,7 @@ class ObjectStorage
{
try {
$container = $this->container($name);
} catch (\OpenStack\Common\Transport\Exception\FileNotFoundException $fnfe) {
} catch (ResourceNotFoundException $e) {
return false;
}
@ -351,9 +357,7 @@ class ObjectStorage
public function createContainer($name, ACL $acl = null, $metadata = array())
{
$url = $this->url() . '/' . rawurlencode($name);
$headers = array(
'X-Auth-Token' => $this->token(),
);
$headers = ['X-Auth-Token' => $this->token()];
if (!empty($metadata)) {
$prefix = Container::CONTAINER_METADATA_HEADER_PREFIX;
@ -365,8 +369,7 @@ class ObjectStorage
$headers += $acl->headers();
}
$data = $this->client->doRequest($url, 'PUT', $headers);
//syslog(LOG_WARNING, print_r($data, true));
$data = $this->client->put($url, null, ['headers' => $headers]);
$status = $data->getStatusCode();
@ -374,10 +377,9 @@ class ObjectStorage
return true;
} elseif ($status == 202) {
return false;
}
// According to the OpenStack docs, there are no other return codes.
else {
throw new \OpenStack\Common\Exception('Server returned unexpected code: ' . $status);
} else {
// According to the OpenStack docs, there are no other return codes.
throw new Exception('Server returned unexpected code: ' . $status);
}
}
@ -442,14 +444,18 @@ class ObjectStorage
$url = $this->url() . '/' . rawurlencode($name);
try {
$data = $this->req($url, 'DELETE', false);
} catch (\OpenStack\Common\Transport\Exception\FileNotFoundException $e) {
$headers = ['X-Auth-Token' => $this->token()];
$data = $this->client->delete($url, ['headers' => $headers]);
} catch (ResourceNotFoundException $e) {
return false;
}
// XXX: I'm not terribly sure about this. Why not just throw the
// ConflictException?
catch (\OpenStack\Common\Transport\Exception\ConflictException $e) {
throw new Exception\ContainerNotEmptyException("Non-empty container cannot be deleted.");
} 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();
@ -457,11 +463,9 @@ class ObjectStorage
// 204 indicates that the container has been deleted.
if ($status == 204) {
return true;
}
// OpenStacks documentation doesn't suggest any other return
// codes.
else {
throw new \OpenStack\Common\Exception('Server returned unexpected code: ' . $status);
} else {
// OpenStacks documentation doesn't suggest any other return codes.
throw new Exception('Server returned unexpected code: ' . $status);
}
}
@ -483,43 +487,13 @@ class ObjectStorage
*/
public function accountInfo()
{
$url = $this->url();
$data = $this->req($url, 'HEAD', false);
$headers = ['X-Auth-Token' => $this->token()];
$response = $this->client->head($this->url(), ['headers' => $headers]);
$results = array(
'bytes' => $data->getHeader('X-Account-Bytes-Used', 0),
'containers' => $data->getHeader('X-Account-Container-Count', 0),
'objects' => $data->getHeader('X-Account-Container-Count', 0),
);
return $results;
}
/**
* Do a GET on Swift.
*
* This is a convenience method that handles the
* most common case of Swift requests.
*/
protected function get($url, $jsonDecode = true)
{
return $this->req($url, 'GET', $jsonDecode);
}
/**
* Internal request issuing command.
*/
protected function req($url, $method = 'GET', $jsonDecode = true, $body = '')
{
$headers = array(
'X-Auth-Token' => $this->token(),
);
$res = $this->client->doRequest($url, $method, $headers, $body);
if (!$jsonDecode) {
return $res;
}
return $res->json();
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

@ -20,7 +20,10 @@
namespace OpenStack\ObjectStore\v1\Resource;
use OpenStack\Common\Transport\GuzzleClient;
use OpenStack\Common\Exception;
use OpenStack\Common\Transport\ClientInterface;
use OpenStack\Common\Transport\Exception\ResourceNotFoundException;
use OpenStack\Common\Transport\Guzzle\GuzzleAdapter;
/**
* A container in an ObjectStorage.
@ -77,8 +80,7 @@ class Container implements \Countable, \IteratorAggregate
//protected $properties = array();
protected $name = null;
// These were both changed from 0 to null to allow
// lazy loading.
// These were both changed from 0 to null to allow lazy loading.
protected $count = null;
protected $bytes = null;
@ -209,7 +211,7 @@ class Container implements \Countable, \IteratorAggregate
*
* @return \OpenStack\ObjectStore\v1\Resource\Container A new container object.
*/
public static function newFromJSON($jsonArray, $token, $url, \OpenStack\Common\Transport\ClientInterface $client = null)
public static function newFromJSON($jsonArray, $token, $url, ClientInterface $client = null)
{
$container = new Container($jsonArray['name'], null, null, $client);
@ -249,7 +251,7 @@ class Container implements \Countable, \IteratorAggregate
*
* @return \OpenStack\ObjectStore\v1\Resource\Container The Container object, initialized and ready for use.
*/
public static function newFromResponse($name, $response, $token, $url, \OpenStack\Common\Transport\ClientInterface $client = null)
public static function newFromResponse($name, $response, $token, $url, ClientInterface $client = null)
{
$container = new Container($name, null, null, $client);
$container->bytes = $response->getHeader('X-Container-Bytes-Used', 0);
@ -310,7 +312,7 @@ class Container implements \Countable, \IteratorAggregate
* @param string $token The auth token.
* @param \OpenStack\Common\Transport\ClientInterface $client A HTTP transport client.
*/
public function __construct($name , $url = null, $token = null, \OpenStack\Common\Transport\ClientInterface $client = null)
public function __construct($name , $url = null, $token = null, ClientInterface $client = null)
{
$this->name = $name;
$this->url = $url;
@ -318,7 +320,7 @@ class Container implements \Countable, \IteratorAggregate
// Guzzle is the default client to use.
if (is_null($client)) {
$this->client = new GuzzleClient();
$this->client = GuzzleAdapter::create();
} else {
$this->client = $client;
}
@ -453,10 +455,10 @@ class Container implements \Countable, \IteratorAggregate
public function save(Object $obj, $file = null)
{
if (empty($this->token)) {
throw new \OpenStack\Common\Exception('Container does not have an auth token.');
throw new Exception('Container does not have an auth token.');
}
if (empty($this->url)) {
throw new \OpenStack\Common\Exception('Container does not have a URL to send data.');
throw new Exception('Container does not have a URL to send data.');
}
//$url = $this->url . '/' . rawurlencode($obj->name());
@ -507,12 +509,11 @@ class Container implements \Countable, \IteratorAggregate
} else {
$headers['Content-Length'] = $obj->contentLength();
}
$response = $this->client->doRequest($url, 'PUT', $headers, $obj->content());
$response = $this->client->put($url, $obj->content(), ['headers' => $headers]);
} else {
// Rewind the file.
rewind($file);
// XXX: What do we do about Content-Length header?
//$headers['Transfer-Encoding'] = 'chunked';
$stat = fstat($file);
@ -527,12 +528,11 @@ class Container implements \Countable, \IteratorAggregate
// Not sure if this is necessary:
rewind($file);
$response = $this->client->doRequestWithResource($url, 'PUT', $headers, $file);
$response = $this->client->put($url, $file, ['headers' => $headers]);
}
if ($response->getStatusCode() != 201) {
throw new \OpenStack\Common\Exception('An unknown error occurred while saving: ' . $response->status());
throw new Exception('An unknown error occurred while saving: ' . $response->status());
}
return true;
@ -553,32 +553,33 @@ class Container implements \Countable, \IteratorAggregate
*
* @return boolean true if the metadata was updated.
*
* @throws \OpenStack\Common\Transport\Exception\FileNotFoundException if the object does not already
* @throws \OpenStack\Common\Transport\Exception\ResourceNotFoundException if the object does not already
* exist on the object storage.
*/
public function updateMetadata(Object $obj)
{
//$url = $this->url . '/' . rawurlencode($obj->name());
$url = self::objectUrl($this->url, $obj->name());
$headers = array();
$headers = ['X-Auth-Token' => $this->token];
// See if we have any metadata. We post this even if there
// is no metadata.
$md = $obj->metadata();
if (!empty($md)) {
$headers = self::generateMetadataHeaders($md, Container::METADATA_HEADER_PREFIX);
$metadata = $obj->metadata();
if (!empty($metadata)) {
$headers += self::generateMetadataHeaders($metadata, Container::METADATA_HEADER_PREFIX);
}
$headers['X-Auth-Token'] = $this->token;
// In spite of the documentation's claim to the contrary,
// content type IS reset during this operation.
$headers['Content-Type'] = $obj->contentType();
// The POST verb is for updating headers.
$response = $this->client->doRequest($url, 'POST', $headers, $obj->content());
$response = $this->client->post($url, $obj->content(), ['headers' => $headers]);
if ($response->getStatusCode() != 202) {
throw new \OpenStack\Common\Exception('An unknown error occurred while saving: ' . $response->status());
throw new Exception(sprintf(
"An unknown error occurred while saving: %d", $response->status()
));
}
return true;
@ -609,11 +610,10 @@ class Container implements \Countable, \IteratorAggregate
*/
public function copy(Object $obj, $newName, $container = null)
{
//$sourceUrl = $obj->url(); // This doesn't work with Object; only with RemoteObject.
$sourceUrl = self::objectUrl($this->url, $obj->name());
if (empty($newName)) {
throw new \OpenStack\Common\Exception("An object name is required to copy the object.");
throw new Exception("An object name is required to copy the object.");
}
// Figure out what container we store in.
@ -623,16 +623,18 @@ class Container implements \Countable, \IteratorAggregate
$container = rawurlencode($container);
$destUrl = self::objectUrl('/' . $container, $newName);
$headers = array(
$headers = [
'X-Auth-Token' => $this->token,
'Destination' => $destUrl,
'Destination' => $destUrl,
'Content-Type' => $obj->contentType(),
];
$response = $this->client->send(
$this->client->createRequest('COPY', $sourceUrl, null, ['headers' => $headers])
);
$response = $this->client->doRequest($sourceUrl, 'COPY', $headers);
if ($response->getStatusCode() != 201) {
throw new \OpenStack\Common\Exception("An unknown condition occurred during copy. " . $response->getStatusCode());
throw new Exception("An unknown condition occurred during copy. " . $response->getStatusCode());
}
return true;
@ -664,15 +666,12 @@ class Container implements \Countable, \IteratorAggregate
public function object($name)
{
$url = self::objectUrl($this->url, $name);
$headers = array();
$headers = ['X-Auth-Token' => $this->token];
// Auth token.
$headers['X-Auth-Token'] = $this->token;
$response = $this->client->doRequest($url, 'GET', $headers);
$response = $this->client->get($url, ['headers' => $headers]);
if ($response->getStatusCode() != 200) {
throw new \OpenStack\Common\Exception('An unknown error occurred while saving: ' . $response->status());
throw new Exception('An unknown error occurred while saving: ' . $response->status());
}
$remoteObject = RemoteObject::newFromHeaders($name, self::reformatHeaders($response->getHeaders()), $this->token, $url, $this->client);
@ -712,21 +711,17 @@ class Container implements \Countable, \IteratorAggregate
public function proxyObject($name)
{
$url = self::objectUrl($this->url, $name);
$headers = array(
'X-Auth-Token' => $this->token,
);
$headers = ['X-Auth-Token' => $this->token];
$response = $this->client->doRequest($url, 'HEAD', $headers);
$response = $this->client->head($url, ['headers' => $headers]);
if ($response->getStatusCode() != 200) {
throw new \OpenStack\Common\Exception('An unknown error occurred while saving: ' . $response->status());
throw new Exception('An unknown error occurred while saving: ' . $response->status());
}
$headers = self::reformatHeaders($response->getHeaders());
$obj = RemoteObject::newFromHeaders($name, $headers, $this->token, $url, $this->client);
return $obj;
return RemoteObject::newFromHeaders($name, $headers, $this->token, $url, $this->client);
}
/**
@ -758,9 +753,7 @@ class Container implements \Countable, \IteratorAggregate
*/
public function objects($limit = null, $marker = null)
{
$params = array();
return $this->objectQuery($params, $limit, $marker);
return $this->objectQuery([], $limit, $marker);
}
/**
@ -818,10 +811,10 @@ class Container implements \Countable, \IteratorAggregate
*/
public function objectsWithPrefix($prefix, $delimiter = '/', $limit = null, $marker = null)
{
$params = array(
'prefix' => $prefix,
'delimiter' => $delimiter,
);
$params = [
'prefix' => $prefix,
'delimiter' => $delimiter
];
return $this->objectQuery($params, $limit, $marker);
}
@ -862,10 +855,10 @@ class Container implements \Countable, \IteratorAggregate
*/
public function objectsByPath($path, $delimiter = '/', $limit = null, $marker = null)
{
$params = array(
'path' => $path,
$params = [
'path' => $path,
'delimiter' => $delimiter,
);
];
return $this->objectQuery($params, $limit, $marker);
}
@ -920,18 +913,17 @@ class Container implements \Countable, \IteratorAggregate
*/
protected function loadExtraData()
{
// If URL and token are empty, we are dealing with
// a local item that has not been saved, and was not
// created with Container::createContainer(). We treat
// this as an error condition.
// If URL and token are empty, we are dealing with a local item that
// has not been saved, and was not created with Container::createContainer().
// We treat this as an error condition.
if (empty($this->url) || empty($this->token)) {
throw new \OpenStack\Common\Exception('Remote data cannot be fetched. A Token and endpoint URL are required.');
throw new Exception('Remote data cannot be fetched. A Token and endpoint URL are required.');
}
// Do a GET on $url to fetch headers.
$headers = array(
'X-Auth-Token' => $this->token,
);
$response = $this->client->doRequest($this->url, 'GET', $headers);
$headers = ['X-Auth-Token' => $this->token];
$response = $this->client->get($this->url, ['headers' => $headers]);
$headers = self::reformatHeaders($response->getHeaders());
// Get ACL.
$this->acl = ACL::newFromHeaders($headers);
@ -967,16 +959,14 @@ class Container implements \Countable, \IteratorAggregate
$query = str_replace('%2F', '/', $query);
$url = $this->url . '?' . $query;
$headers = array(
'X-Auth-Token' => $this->token,
);
$headers = ['X-Auth-Token' => $this->token];
$response = $this->client->doRequest($url, 'GET', $headers);
$response = $this->client->get($url, ['headers' => $headers]);
// The only codes that should be returned are 200 and the ones
// already thrown by doRequest.
// already thrown by GET.
if ($response->getStatusCode() != 200) {
throw new \OpenStack\Common\Exception('An unknown exception occurred while processing the request.');
throw new Exception('An unknown exception occurred while processing the request.');
}
$json = $response->json();
@ -987,7 +977,7 @@ class Container implements \Countable, \IteratorAggregate
if (!empty($item['subdir'])) {
$list[] = new Subdir($item['subdir'], $params['delimiter']);
} elseif (empty($item['name'])) {
throw new \OpenStack\Common\Exception('Unexpected entity returned.');
throw new Exception('Unexpected entity returned.');
} else {
//$url = $this->url . '/' . rawurlencode($item['name']);
$url = self::objectUrl($this->url, $item['name']);
@ -1044,13 +1034,15 @@ class Container implements \Countable, \IteratorAggregate
);
try {
$response = $this->client->doRequest($url, 'DELETE', $headers);
} catch (\OpenStack\Common\Transport\Exception\FileNotFoundException $fnfe) {
$response = $this->client->delete($url, ['headers' => $headers]);
} catch (ResourceNotFoundException $e) {
return false;
}
if ($response->getStatusCode() != 204) {
throw new \OpenStack\Common\Exception("An unknown exception occured while deleting $name.");
throw new Exception(sprintf(
"An unknown exception occured while deleting %s", $name
));
}
return true;
@ -1090,5 +1082,4 @@ class Container implements \Countable, \IteratorAggregate
return $newHeaders;
}
}

View File

@ -20,7 +20,8 @@
namespace OpenStack\ObjectStore\v1\Resource;
use OpenStack\Common\Transport\GuzzleClient;
use OpenStack\Common\Transport\ClientInterface;
use OpenStack\Common\Transport\Guzzle\GuzzleAdapter;
use OpenStack\ObjectStore\v1\Exception;
/**
@ -76,7 +77,7 @@ class RemoteObject extends Object
* @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, \OpenStack\Common\Transport\ClientInterface $client = null)
public static function newFromJSON($data, $token, $url, ClientInterface $client = null)
{
$object = new RemoteObject($data['name']);
$object->setContentType($data['content_type']);
@ -92,7 +93,7 @@ class RemoteObject extends Object
// back in JSON?
if (is_null($client)) {
$client = new GuzzleClient();
$client = GuzzleAdapter::create();
}
$object->setClient($client);
@ -115,7 +116,7 @@ class RemoteObject extends Object
*
* @return \OpenStack\ObjectStore\v1\Resource\RemoteObject A new RemoteObject.
*/
public static function newFromHeaders($name, $headers, $token, $url, \OpenStack\Common\Transport\ClientInterface $client = null)
public static function newFromHeaders($name, $headers, $token, $url, ClientInterface $client = null)
{
$object = new RemoteObject($name);
@ -152,7 +153,7 @@ class RemoteObject extends Object
$object->url = $url;
if (is_null($client)) {
$client = new GuzzleClient();
$client = GuzzleAdapter::create();
}
$object->setClient($client);
@ -164,7 +165,7 @@ class RemoteObject extends Object
*
* @param OpenStackTransportClientInterface $client The HTTP Client
*/
public function setClient(\OpenStack\Common\Transport\ClientInterface $client)
public function setClient(ClientInterface $client)
{
$this->client = $client;
}
@ -348,7 +349,7 @@ class RemoteObject extends Object
*
* @return string The contents of the file as a string.
*
* @throws \OpenStack\Common\Transport\Exception\FileNotFoundException when the requested content cannot be
* @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.
@ -370,7 +371,7 @@ class RemoteObject extends Object
// Should fix that.
$check = md5($content);
if ($this->isVerifyingContent() && $check != $this->etag()) {
throw new ContentVerificationException("Checksum $check does not match Etag " . $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
@ -624,11 +625,11 @@ class RemoteObject extends Object
{
$method = $fetchContent ? 'GET' : 'HEAD';
$headers = array(
'X-Auth-Token' => $this->token,
);
$headers = ['X-Auth-Token' => $this->token];
$response = $this->client->doRequest($this->url, $method, $headers);
$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.');
@ -666,4 +667,4 @@ class RemoteObject extends Object
return $this;
}
}
}

View File

@ -21,7 +21,9 @@
namespace OpenStack\ObjectStore\v1\Resource;
use \OpenStack\Bootstrap;
use OpenStack\Common\Transport\Exception\ResourceNotFoundException;
use \OpenStack\ObjectStore\v1\ObjectStorage;
use OpenStack\Common\Exception;
/**
* Provides stream wrapping for Swift.
@ -604,6 +606,7 @@ class StreamWrapper
// Force-clear the memory hogs.
unset($this->obj);
fclose($this->objStream);
}
@ -793,10 +796,9 @@ class StreamWrapper
// server roundtrip?
try {
$this->container = $this->store->container($containerName);
} catch (\OpenStack\Common\Transport\Exception\FileNotFoundException $e) {
trigger_error('Container not found.', E_USER_WARNING);
return false;
} catch (ResourceNotFoundException $e) {
trigger_error('Container not found.', E_USER_WARNING);
return false;
}
try {
@ -838,16 +840,15 @@ class StreamWrapper
if ($this->isAppending) {
fseek($this->objStream, -1, SEEK_END);
}
}
// If a 404 is thrown, we need to determine whether
// or not a new file should be created.
catch (\OpenStack\Common\Transport\Exception\FileNotFoundException $nf) {
} catch (ResourceNotFoundException $nf) {
// If a 404 is thrown, we need to determine whether
// or not a new file should be created.
// For many modes, we just go ahead and create.
if ($this->createIfNotFound) {
$this->obj = new Object($objectName);
$this->objStream = fopen('php://temp', 'rb+');
$this->isDirty = true;
} else {
//if ($this->triggerErrors) {
@ -856,9 +857,8 @@ class StreamWrapper
return false;
}
}
// All other exceptions are fatal.
catch (\OpenStack\Common\Exception $e) {
} catch (Exception $e) {
// All other exceptions are fatal.
//if ($this->triggerErrors) {
trigger_error('Failed to fetch object: ' . $e->getMessage(), E_USER_WARNING);
//}

View File

@ -27,7 +27,7 @@ use \OpenStack\ObjectStore\v1\ObjectStorage;
use \OpenStack\Identity\v2\IdentityService;
$config = array(
'transport' => '\OpenStack\Common\Transport\GuzzleClient',
'transport' => 'OpenStack\Common\Transport\Guzzle\GuzzleAdapter',
'transport.timeout' => 240,
//'transport.debug' => 1,
'transport.ssl.verify' => 0,

View File

@ -0,0 +1,93 @@
<?php
/**
* Copyright 2014 OpenStack Foundation
*
* 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

@ -0,0 +1,90 @@
<?php
/* ============================================================================
(c) Copyright 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\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

@ -0,0 +1,127 @@
<?php
/**
* Copyright 2014 OpenStack Foundation
*
* 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

@ -0,0 +1,138 @@
<?php
/**
* Copyright 2014 OpenStack Foundation
*
* 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

@ -0,0 +1,68 @@
<?php
/**
* Copyright 2014 OpenStack Foundation
*
* 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

@ -0,0 +1,54 @@
<?php
/**
* Copyright 2014 OpenStack Foundation
*
* 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,73 +0,0 @@
<?php
/* ============================================================================
(c) Copyright 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\Common\Transport;
use OpenStack\Common\Transport\GuzzleClient;
class GuzzleClientTest extends \OpenStack\Tests\TestCase
{
/**
* Get the config from the test settings file and pass that into the client.
*/
public function buildClient()
{
$options = array();
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'];
}
return new GuzzleClient($options);
}
public function testDoRequest()
{
$url = 'http://www.openstack.org';
$method = 'GET';
$client = $this->buildClient();
$this->assertInstanceOf('\OpenStack\Common\Transport\GuzzleClient', $client);
$response = $client->doRequest($url, $method);
$this->assertInstanceOf('\GuzzleHttp\Message\Response', $response);
}
/**
* @depends testDoRequest
* @expectedException \OpenStack\Common\Transport\Exception\FileNotFoundException
*/
public function testDoRequestException()
{
$url = 'http://www.openstack.org/this-does-no-exist';
$method = 'GET';
$client = $this->buildClient();
$client->doRequest($url, $method);
}
}

View File

@ -251,15 +251,10 @@ class ObjectStorageTest extends \OpenStack\Tests\TestCase
// we get some data back.
$url = $container->url() . '?format=xml';
// Use CURL to get better debugging:
//$client = \OpenStack\Transport::instance();
//$response = $client->doRequest($url, 'GET');
$data = file_get_contents($url);
$this->assertNotEmpty($data, $url);
$containers = $store->containers();
//throw new \Exception(print_r($containers, true));
$store->deleteContainer($testCollection);
}
@ -276,13 +271,14 @@ class ObjectStorageTest extends \OpenStack\Tests\TestCase
}
$ret = $store->createContainer($testCollection);
$acl = \OpenStack\ObjectStore\v1\Resource\ACL::makePublic();
$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);

View File

@ -19,61 +19,43 @@
*/
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 \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';
// The factory functions (newFrom*) are tested in the
// ObjectStorage tests, as they are required there.
// Rather than build a Mock to achieve the same test here,
// we just don't test them again.
public function testConstructor()
public function testConstructorSetsName()
{
$container = new Container('foo');
$this->assertEquals('foo', $container->name());
// These will now cause the system to try to fetch a remote
// container.
//$this->assertEquals(0, $container->bytes());
//$this->assertEquals(0, $container->count());
}
/**
* @expectedException \OpenStack\Common\Exception
*/
public function testConstructorFailure()
public function testExceptionIsThrownWhenContainerNotFound()
{
$container = new Container('foo');
$this->assertEquals('foo', $container->name());
// These will now cause the system to try to fetch a remote
// container. This is a failure condition.
$this->assertEquals(0, $container->bytes());
$container->bytes();
}
public function testCountable()
{
// Verify that the interface Countable is properly
// implemented.
$mockJSON = array('count' => 5, 'bytes' => 128, 'name' => 'foo');
// Verify that the interface Countable is properly implemented.
$mockJSON = array('count' => 5, 'bytes' => 128, 'name' => 'foo');
$container = Container::newFromJSON($mockJSON, 'fake', 'fake');
$this->assertEquals(5, count($container));
$this->assertCount(5, $container);
}
const FNAME = 'testSave';
const FCONTENT = 'This is a test.';
const FTYPE = 'application/x-monkey-file';
public function testSave()
{
// Clean up anything left.
@ -81,13 +63,13 @@ class ContainerTest extends \OpenStack\Tests\TestCase
$container = $this->containerFixture();
$obj = new Object(self::FNAME, self::FCONTENT, self::FTYPE);
$obj->setMetadata(array('foo' => '1234'));
$object = new Object(self::FNAME, self::FCONTENT, self::FTYPE);
$object->setMetadata(array('foo' => '1234'));
$this->assertEquals(self::FCONTENT, $obj->content());
$this->assertEquals(self::FCONTENT, $object->content());
try {
$ret = $container->save($obj);
$ret = $container->save($object);
} catch (\Exception $e) {
$this->destroyContainerFixture();
throw $e;
@ -188,7 +170,7 @@ class ContainerTest extends \OpenStack\Tests\TestCase
try {
$foo = $container->object('no/such');
} catch (\OpenStack\Common\Exception $e) {
$this->assertInstanceOf('\OpenStack\Common\Transport\Exception\FileNotFoundException', $e);
$this->assertInstanceOf('OpenStack\Common\Transport\Exception\ResourceNotFoundException', $e);
}
}
@ -229,7 +211,6 @@ class ContainerTest extends \OpenStack\Tests\TestCase
++$i;
}
$this->assertEquals(3, $i);
}
/**
@ -286,35 +267,6 @@ class ContainerTest extends \OpenStack\Tests\TestCase
$o = array_shift($objects);
$this->assertEquals('a/b/' . self::FNAME, $o->name());
/*
* The Open Stack documentation is unclear about how best to
* use paths. Experimentation suggests that if you rely on paths
* instead of prefixes, your best bet is to create directory
* markers.
*/
// Test subdir listings:
// This does not work (by design?) with Path. You have to use prefix
// or else create directory markers.
// $obj1 = new Object('a/aa/aaa/' . self::FNAME, self::FCONTENT, self::FTYPE);
// $container->save($obj1);
// $objects = $container->objectsByPath('a/aaa', '/');
// $this->assertEquals(1, count($objects), 'One subdir');
// $objects = $container->objectsByPath('a/');
// throw new \Exception(print_r($objects, true));
// $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());
// }
// }
}
/**
@ -397,7 +349,6 @@ class ContainerTest extends \OpenStack\Tests\TestCase
}
/**
* @depends testSave
*/
@ -412,7 +363,6 @@ class ContainerTest extends \OpenStack\Tests\TestCase
$this->destroyContainerFixture();
$this->assertTrue($ret);
$this->assertFalse($fail);
}
/**
@ -429,7 +379,6 @@ class ContainerTest extends \OpenStack\Tests\TestCase
$store->createContainer($cname, ACL::makePublic());
$store->containers();
$container = $store->container($cname);
@ -439,7 +388,5 @@ class ContainerTest extends \OpenStack\Tests\TestCase
$this->assertTrue($acl->isPublic());
$store->deleteContainer($cname);
}
}

View File

@ -0,0 +1,353 @@
<?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.
============================================================================ */
/**
* Unit tests for the stream wrapper file systema.
*/
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

@ -0,0 +1,312 @@
<?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\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

@ -0,0 +1,133 @@
<?php
/**
* Copyright 2014 OpenStack Foundation
*
* 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,642 +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.
============================================================================ */
/**
* Unit tests for the stream wrapper file systema.
*/
namespace OpenStack\Tests\ObjectStore\v1\Resource;
use OpenStack\Common\Transport\Exception\FileNotFoundException;
use OpenStack\ObjectStore\v1\Resource\StreamWrapperFS;
use OpenStack\Tests\TestCase;
/**
* @group streamWrapper
*/
class StreamWrapperFSTest extends TestCase
{
const FNAME = 'streamTest.txt';
const FTYPE = 'application/x-tuna-fish; charset=iso-8859-13';
/**
* Cleaning up the test container so we can reuse it for other tests.
*/
public static function tearDownAfterClass()
{
/** @var \OpenStack\ObjectStore\v1\ObjectStore $store */
$store = self::createObjectStoreService();
// Delete the container and all the contents.
$cname = self::$settings['openstack.swift.container'];
try {
$container = $store->container($cname);
}
// The container was never created.
catch (FileNotFoundException $e) {
return;
}
foreach ($container as $object) {
try {
$container->delete($object->name());
} catch (\Exception $e) {}
}
$store->deleteContainer($cname);
}
protected function newUrl($objectName)
{
$scheme = StreamWrapperFS::DEFAULT_SCHEME;
$cname = self::$settings['openstack.swift.container'];
$cname = urlencode($cname);
$objectParts = explode('/', $objectName);
for ($i = 0; $i < count($objectParts); ++$i) {
$objectParts[$i] = urlencode($objectParts[$i]);
}
$objectName = implode('/', $objectParts);
$url = $scheme . '://' . $cname . '/' . $objectName;
return $url;
}
/**
* This assumes auth has already been done.
*/
protected function basicSwiftContext($add = array(), $scheme = null)
{
$cname = self::$settings['openstack.swift.container'];
if (empty($scheme)) {
$scheme = StreamWrapperFS::DEFAULT_SCHEME;
}
if (empty(self::$ostore)) {
throw new \Exception('OStore is gone.');
}
$params = $add + array(
'token' => $this->objectStore()->token(),
'swift_endpoint' => $this->objectStore()->url(),
'content_type' => self::FTYPE,
'transport_client' => $this->getTransportClient(),
);
$cxt = array($scheme => $params);
return stream_context_create($cxt);
}
/**
* This performs authentication via context.
*/
protected function authSwiftContext(array $params = [], $scheme = null)
{
if (empty($scheme)) {
$scheme = StreamWrapperFS::DEFAULT_SCHEME;
}
$params += [
'username' => self::$settings['openstack.identity.username'],
'password' => self::$settings['openstack.identity.password'],
'endpoint' => self::$settings['openstack.identity.url'],
'tenantid' => self::$settings['openstack.identity.tenantId'],
'content_type' => self::FTYPE,
'transport_client' => $this->getTransportClient(),
];
return stream_context_create([
$scheme => $params
]);
}
/**
* Add additional params to the config.
*
* This allows us to insert credentials into the
* bootstrap config, which in turn allows us to run
* high-level context-less functions like
* file_get_contents(), stat(), and is_file().
*/
protected function addBootstrapConfig()
{
$opts = array(
'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' => $this->objectStore()->token(),
'swift_endpoint' => $this->objectStore()->url(),
);
\OpenStack\Bootstrap::setConfiguration($opts);
}
// Canary. There are UTF-8 encoding issues in stream wrappers.
public function testStreamContext()
{
// Clear old values.
\OpenStack\Bootstrap::setConfiguration(array(
'token' => null,
));
$cxt = $this->authSwiftContext();
$array = stream_context_get_options($cxt);
$opts = $array['swiftfs'];
$endpoint = self::conf('openstack.identity.url');
$this->assertEquals($endpoint, $opts['endpoint'], 'A UTF-8 encoding issue.');
}
/**
* @depends testStreamContext
*/
public function testRegister()
{
// Canary
$this->assertNotEmpty(StreamWrapperFS::DEFAULT_SCHEME);
$klass = '\OpenStack\ObjectStore\v1\Resource\StreamWrapperFS';
stream_wrapper_register(StreamWrapperFS::DEFAULT_SCHEME, $klass);
$wrappers = stream_get_wrappers();
$this->assertContains(StreamWrapperFS::DEFAULT_SCHEME, $wrappers);
}
/**
* @depends testRegister
*/
public function testOpenFailureWithoutContext()
{
$url = $this->newUrl('foo→/bar.txt');
$ret = @fopen($url, 'r');
$this->assertFalse($ret);
}
/**
* @depends testRegister
*/
public function testOpen()
{
$cname = self::$settings['openstack.swift.container'];
// Create a fresh container.
$this->eradicateContainer($cname);
$this->containerFixture();
// Simple write test.
$oUrl = $this->newUrl('foo→/test.csv');
$res = fopen($oUrl, 'nope', false, $this->authSwiftContext());
$this->assertTrue(is_resource($res));
$md = stream_get_meta_data($res);
$wrapper = $md['wrapper_data'];
fclose($res);
// Now we test the same, but re-using the auth token:
$cxt = $this->basicSwiftContext(array('token' => $wrapper->token()));
$res = fopen($oUrl, 'nope', false, $cxt);
$this->assertTrue(is_resource($res));
fclose($res);
}
/**
* @depends testOpen
*/
public function testOpenFailureWithRead()
{
$url = $this->newUrl(__FUNCTION__);
$res = @fopen($url, 'r', false, $this->basicSwiftContext());
$this->assertFalse($res);
}
// DO we need to test other modes?
/**
* @depends testOpen
*/
public function testOpenCreateMode()
{
$url = $this->newUrl(self::FNAME);
$res = fopen($url, 'c+', false, $this->basicSwiftContext());
$this->assertTrue(is_resource($res));
//fclose($res);
return $res;
}
/**
* @depends testOpenCreateMode
*/
public function testTell($res)
{
// Sould be at the beginning of the buffer.
$this->assertEquals(0, ftell($res));
return $res;
}
/**
* @depends testTell
*/
public function testWrite($res)
{
$str = 'To be is to be the value of a bound variable. -- Quine';
fwrite($res, $str);
$this->assertGreaterThan(0, ftell($res));
return $res;
}
/**
* @depends testWrite
*/
public function testStat($res)
{
$stat = fstat($res);
$this->assertGreaterThan(0, $stat['size']);
return $res;
}
/**
* @depends testStat
*/
public function testSeek($res)
{
$then = ftell($res);
rewind($res);
$now = ftell($res);
// $now should be 0
$this->assertLessThan($then, $now);
$this->assertEquals(0, $now);
fseek($res, 0, SEEK_END);
$final = ftell($res);
$this->assertEquals($then, $final);
return $res;
}
/**
* @depends testSeek
*/
public function testEof($res)
{
rewind($res);
$this->assertEquals(0, ftell($res));
$this->assertFalse(feof($res));
fseek($res, 0, SEEK_END);
$this->assertGreaterThan(0, ftell($res));
$read = fread($res, 8192);
$this->assertEmpty($read);
$this->assertTrue(feof($res));
return $res;
}
/**
* @depends testEof
*/
public function testFlush($res)
{
$stat1 = fstat($res);
fflush($res);
// Grab a copy of the object.
$url = $this->newUrl(self::FNAME);
$newObj = fopen($url, 'r', false, $this->basicSwiftContext());
$stat2 = fstat($newObj);
$this->assertEquals($stat1['size'], $stat2['size']);
return $res;
}
/**
* @depends testFlush
*/
public function testStreamGetMetadata($res)
{
// Grab a copy of the object.
$url = $this->newUrl(self::FNAME);
$newObj = fopen($url, 'r', false, $this->basicSwiftContext());
$md = stream_get_meta_data($newObj);
//throw new \Exception(print_r($md, true));
$obj = $md['wrapper_data']->object();
$this->assertInstanceOf('\OpenStack\ObjectStore\v1\Resource\RemoteObject', $obj);
$this->assertEquals(self::FTYPE, $obj->contentType());
}
/**
* @depends testFlush
*/
public function testClose($res)
{
$this->assertTrue(is_resource($res));
fwrite($res, '~~~~');
//throw new \Exception(stream_get_contents($res));
fflush($res);
// This is occasionally generating seemingly
// spurious PHP errors about Bootstrap::$config.
fclose($res);
$url = $this->newUrl(self::FNAME);
$res2 = fopen($url, 'r', false, $this->basicSwiftContext());
$this->assertTrue(is_resource($res2));
$contents = stream_get_contents($res2);
fclose($res2);
$this->assertRegExp('/~{4}$/', $contents);
}
/**
* @depends testClose
*/
public function testCast()
{
$url = $this->newUrl(self::FNAME);
$res = fopen($url, 'r', false, $this->basicSwiftContext());
$read = array($res);
$write = array();
$except = array();
$num_changed = stream_select($read, $write, $except, 0);
$this->assertGreaterThan(0, $num_changed);
}
/**
* @depends testClose
*/
public function testUrlStat()
{
// Add context to the bootstrap config.
$this->addBootstrapConfig();
$url = $this->newUrl(self::FNAME);
$ret = stat($url);
// Check that the array looks right.
$this->assertEquals(26, count($ret));
$this->assertEquals(0, $ret[3]);
$this->assertEquals($ret[2], $ret['mode']);
$this->assertTrue(file_exists($url));
$this->assertTrue(is_readable($url));
$this->assertTrue(is_writeable($url));
$this->assertFalse(is_link($url));
$this->assertGreaterThan(0, filemtime($url));
$this->assertGreaterThan(5, filesize($url));
$perm = fileperms($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);
$contents = file_get_contents($url);
$this->assertGreaterThan(5, strlen($contents));
$fsCopy = '/tmp/hpcloud-copy-test.txt';
copy($url, $fsCopy, $this->basicSwiftContext());
$this->assertTrue(file_exists($fsCopy));
unlink($fsCopy);
}
/**
* @depends testFlush
*/
public function testUnlink()
{
$url = $this->newUrl(self::FNAME);
$cxt = $this->basicSwiftContext();
$ret = unlink($url, $cxt);
$this->assertTrue($ret);
$ret2 = unlink($url, $cxt);
$this->assertFalse($ret2);
}
public function testSetOption()
{
$url = $this->newUrl('fake.foo');
$fake = fopen($url, 'nope', false, $this->basicSwiftContext());
$this->assertTrue(stream_set_blocking($fake, 1));
// Returns 0 on success.
$this->assertEquals(0, stream_set_write_buffer($fake, 8192));
// Cant set a timeout on a tmp storage:
$this->assertFalse(stream_set_timeout($fake, 10));
fclose($fake);
}
/**
* @depends testUnlink
*/
public function testRename()
{
$url = $this->newUrl('rename.foo');
$fake = fopen($url, 'w+', false, $this->basicSwiftContext());
fwrite($fake, 'test');
fclose($fake);
$this->assertTrue(file_exists($url));
$url2 = $this->newUrl('rename.txt');
rename($url, $url2, $this->basicSwiftContext());
$this->assertTrue(file_exists($url2));
$this->assertFalse(file_exists($url));
unlink($url2, $this->basicSwiftContext());
}
/**
* @depends testUnlink
*/
public function testOpenDir()
{
$urls = array('test1.txt', 'foo/test2.txt', 'foo/test3.txt', 'bar/test4.txt');
foreach ($urls as $base) {
$url = $this->newUrl($base);
$f = fopen($url, 'c+', false, $this->basicSwiftContext());
fwrite($f, 'Test.');
fclose($f);
}
$dirUrl = $this->newUrl('');
$dir = opendir($dirUrl, $this->basicSwiftContext());
$this->assertTrue(is_resource($dir));
return $dir;
}
/**
* @depends testOpenDir
*/
public function testReaddir($dir)
{
// Order should be newest to oldest.
$expects = array('bar/', 'foo/', 'test1.txt');
$buffer = array();
while (($entry = readdir($dir)) !== false) {
$should_be = array_shift($expects);
$this->assertEquals($should_be, $entry);
}
$this->assertFalse(readdir($dir));
return $dir;
}
/**
* @depends testReaddir
*/
public function testRewindDir($dir)
{
$this->assertFalse(readdir($dir));
rewinddir($dir);
$this->assertEquals('bar/', readdir($dir));
return $dir;
}
/**
* @depends testRewindDir
*/
public function testCloseDir($dir)
{
$this->assertTrue(is_resource($dir));
closedir($dir);
// There is a bug in PHP where a
// resource buffer is not getting cleared.
// So this might return a value even though
// the underlying stream is cleared.
//$this->assertFalse(readdir($dir));
}
/**
* @depends testCloseDir
*/
public function testOpenSubdir()
{
// Opening foo we should find test2.txt and test3.txt.
$url = $this->newUrl('foo/');
$dir = opendir($url, $this->basicSwiftContext());
// I don't know why, but these are always returned in
// lexical order.
$this->assertEquals('test2.txt', readdir($dir));
$this->assertEquals('test3.txt', readdir($dir));
$array = scandir($url, -1, $this->basicSwiftContext());
$this->assertEquals(2, count($array));
$this->assertEquals('test3.txt', $array[0]);
}
/**
* @depends testReaddir
*/
public function testIsdir($dir)
{
// Object names are pathy. If objects exist starting with this path we can
// consider the directory to exist.
$url = $this->newUrl('baz/');
$this->assertFALSE(is_dir($url));
$url = $this->newUrl('foo/');
$this->assertTRUE(is_dir($url));
}
/**
* @depends testReaddir
*/
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->newUrl('baz/');
$this->assertTrue(mkdir($url, 0700, true, $this->basicSwiftContext()));
// Test the case for an existing directory.
$url = $this->newUrl('foo/');
$this->assertFalse(mkdir($url, 0700, true, $this->basicSwiftContext()));
}
/**
* @depends testReaddir
*/
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->newUrl('baz/');
$this->assertTrue(rmdir($url, $this->basicSwiftContext()));
// Test the case for an existing directory.
$url = $this->newUrl('foo/');
$this->assertFalse(rmdir($url, $this->basicSwiftContext()));
}
}

View File

@ -1,598 +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.
============================================================================ */
/**
* Unit tests for the stream wrapper.
*/
namespace OpenStack\Tests\ObjectStore\v1\Resource;
use OpenStack\Common\Transport\Exception\FileNotFoundException;
use OpenStack\ObjectStore\v1\Resource\StreamWrapper;
use OpenStack\Tests\TestCase;
/**
* @group streamWrapper
*/
class StreamWrapperTest extends TestCase
{
const FNAME = 'streamTest.txt';
const FTYPE = 'application/x-tuna-fish; charset=iso-8859-13';
public static function tearDownAfterClass()
{
/** @var \OpenStack\ObjectStore\v1\ObjectStorage $store */
$store = self::createObjectStoreService();
// Delete the container and all the contents.
$cname = self::$settings['openstack.swift.container'];
try {
$container = $store->container($cname);
} catch (FileNotFoundException $e) {
// The container was never created.
return;
}
foreach ($container as $object) {
try {
$container->delete($object->name());
} catch (\Exception $e) {}
}
$store->deleteContainer($cname);
}
protected function newUrl($objectName)
{
$scheme = StreamWrapper::DEFAULT_SCHEME;
$cname = self::$settings['openstack.swift.container'];
$cname = urlencode($cname);
$objectParts = explode('/', $objectName);
for ($i = 0; $i < count($objectParts); ++$i) {
$objectParts[$i] = urlencode($objectParts[$i]);
}
$objectName = implode('/', $objectParts);
$url = $scheme . '://' . $cname . '/' . $objectName;
return $url;
}
/**
* This assumes auth has already been done.
*/
protected function basicSwiftContext($add = array(), $scheme = null)
{
$cname = self::$settings['openstack.swift.container'];
if (empty($scheme)) {
$scheme = StreamWrapper::DEFAULT_SCHEME;
}
if (empty(self::$ostore)) {
throw new \Exception('OStore is gone.');
}
$params = $add + array(
'token' => $this->objectStore()->token(),
'swift_endpoint' => $this->objectStore()->url(),
'content_type' => self::FTYPE,
'transport_client' => $this->getTransportClient(),
);
$cxt = array($scheme => $params);
return stream_context_create($cxt);
}
/**
* This performs authentication via context.
*/
protected function authSwiftContext(array $params = [], $scheme = null)
{
if (empty($scheme)) {
$scheme = StreamWrapper::DEFAULT_SCHEME;
}
$params += [
'username' => self::$settings['openstack.identity.username'],
'password' => self::$settings['openstack.identity.password'],
'endpoint' => self::$settings['openstack.identity.url'],
'tenantid' => self::$settings['openstack.identity.tenantId'],
'content_type' => self::FTYPE,
'transport_client' => $this->getTransportClient()
];
return stream_context_create([
$scheme => $params
]);
}
/**
* Add additional params to the config.
*
* This allows us to insert credentials into the
* bootstrap config, which in turn allows us to run
* high-level context-less functions like
* file_get_contents(), stat(), and is_file().
*/
protected function addBootstrapConfig()
{
$opts = array(
'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' => $this->objectStore()->token(),
'swift_endpoint' => $this->objectStore()->url(),
);
\OpenStack\Bootstrap::setConfiguration($opts);
}
// Canary. There are UTF-8 encoding issues in stream wrappers.
public function testStreamContext()
{
// Reset this in case something else left its
// auth token lying around.
\OpenStack\Bootstrap::setConfiguration(array(
'token' => null,
));
$cxt = $this->authSwiftContext();
$array = stream_context_get_options($cxt);
$opts = $array['swift'];
$endpoint = self::conf('openstack.identity.url');
$this->assertEquals($endpoint, $opts['endpoint'], 'A UTF-8 encoding issue.');
}
/**
* @depends testStreamContext
*/
public function testRegister()
{
// Canary
$this->assertNotEmpty(StreamWrapper::DEFAULT_SCHEME);
$klass = '\OpenStack\ObjectStore\v1\Resource\StreamWrapper';
stream_wrapper_register(StreamWrapper::DEFAULT_SCHEME, $klass);
$wrappers = stream_get_wrappers();
$this->assertContains(StreamWrapper::DEFAULT_SCHEME, $wrappers);
}
/**
* @depends testRegister
*/
public function testOpenFailureWithoutContext()
{
$cname = self::$settings['openstack.swift.container'];
// Create a fresh container.
$this->eradicateContainer($cname);
$this->containerFixture();
$url = $this->newUrl('foo→/bar.txt');
$ret = @fopen($url, 'r');
$this->assertFalse($ret);
}
/**
* @depends testRegister
*/
public function testOpen()
{
$cname = self::$settings['openstack.swift.container'];
// Create a fresh container.
$this->eradicateContainer($cname);
$this->containerFixture();
// Simple write test.
$oUrl = $this->newUrl('foo→/test.csv');
$res = fopen($oUrl, 'nope', false, $this->authSwiftContext());
$this->assertTrue(is_resource($res));
$md = stream_get_meta_data($res);
$wrapper = $md['wrapper_data'];
fclose($res);
// Now we test the same, but re-using the auth token:
$cxt = $this->basicSwiftContext(array('token' => $wrapper->token()));
$res = fopen($oUrl, 'nope', false, $cxt);
$this->assertTrue(is_resource($res));
fclose($res);
}
/**
* @depends testOpen
*/
public function testOpenFailureWithRead()
{
$url = $this->newUrl(__FUNCTION__);
$res = @fopen($url, 'r', false, $this->basicSwiftContext());
$this->assertFalse($res);
}
// DO we need to test other modes?
/**
* @depends testOpen
*/
public function testOpenCreateMode()
{
$url = $this->newUrl(self::FNAME);
$res = fopen($url, 'c+', false, $this->basicSwiftContext());
$this->assertTrue(is_resource($res));
//fclose($res);
return $res;
}
/**
* @depends testOpenCreateMode
*/
public function testTell($res)
{
// Sould be at the beginning of the buffer.
$this->assertEquals(0, ftell($res));
return $res;
}
/**
* @depends testTell
*/
public function testWrite($res)
{
$str = 'To be is to be the value of a bound variable. -- Quine';
fwrite($res, $str);
$this->assertGreaterThan(0, ftell($res));
return $res;
}
/**
* @depends testWrite
*/
public function testStat($res)
{
$stat = fstat($res);
$this->assertGreaterThan(0, $stat['size']);
return $res;
}
/**
* @depends testStat
*/
public function testSeek($res)
{
$then = ftell($res);
rewind($res);
$now = ftell($res);
// $now should be 0
$this->assertLessThan($then, $now);
$this->assertEquals(0, $now);
fseek($res, 0, SEEK_END);
$final = ftell($res);
$this->assertEquals($then, $final);
return $res;
}
/**
* @depends testSeek
*/
public function testEof($res)
{
rewind($res);
$this->assertEquals(0, ftell($res));
$this->assertFalse(feof($res));
fseek($res, 0, SEEK_END);
$this->assertGreaterThan(0, ftell($res));
$read = fread($res, 8192);
$this->assertEmpty($read);
$this->assertTrue(feof($res));
return $res;
}
/**
* @depends testEof
*/
public function testFlush($res)
{
$stat1 = fstat($res);
fflush($res);
// Grab a copy of the object.
$url = $this->newUrl(self::FNAME);
$newObj = fopen($url, 'r', false, $this->basicSwiftContext());
$stat2 = fstat($newObj);
$this->assertEquals($stat1['size'], $stat2['size']);
return $res;
}
/**
* @depends testFlush
*/
public function testStreamGetMetadata($res)
{
// Grab a copy of the object.
$url = $this->newUrl(self::FNAME);
$newObj = fopen($url, 'r', false, $this->basicSwiftContext());
$md = stream_get_meta_data($newObj);
//throw new \Exception(print_r($md, true));
$obj = $md['wrapper_data']->object();
$this->assertInstanceOf('\OpenStack\ObjectStore\v1\Resource\RemoteObject', $obj);
$this->assertEquals(self::FTYPE, $obj->contentType());
}
/**
* @depends testFlush
*/
public function testClose($res)
{
$this->assertTrue(is_resource($res));
fwrite($res, '~~~~');
//throw new \Exception(stream_get_contents($res));
fflush($res);
// This is occasionally generating seemingly
// spurious PHP errors about Bootstrap::$config.
fclose($res);
$url = $this->newUrl(self::FNAME);
$res2 = fopen($url, 'r', false, $this->basicSwiftContext());
$this->assertTrue(is_resource($res2));
$contents = stream_get_contents($res2);
fclose($res2);
$this->assertRegExp('/~{4}$/', $contents);
}
/**
* @depends testClose
*/
public function testCast()
{
$url = $this->newUrl(self::FNAME);
$res = fopen($url, 'r', false, $this->basicSwiftContext());
$read = array($res);
$write = array();
$except = array();
$num_changed = stream_select($read, $write, $except, 0);
$this->assertGreaterThan(0, $num_changed);
}
/**
* @depends testClose
*/
public function testUrlStat()
{
// Add context to the bootstrap config.
$this->addBootstrapConfig();
$url = $this->newUrl(self::FNAME);
$ret = stat($url);
// Check that the array looks right.
$this->assertEquals(26, count($ret));
$this->assertEquals(0, $ret[3]);
$this->assertEquals($ret[2], $ret['mode']);
$this->assertTrue(file_exists($url));
$this->assertTrue(is_readable($url));
$this->assertTrue(is_writeable($url));
$this->assertFalse(is_link($url));
$this->assertGreaterThan(0, filemtime($url));
$this->assertGreaterThan(5, filesize($url));
$perm = fileperms($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);
$contents = file_get_contents($url);
$this->assertGreaterThan(5, strlen($contents));
$fsCopy = '/tmp/hpcloud-copy-test.txt';
copy($url, $fsCopy, $this->basicSwiftContext());
$this->assertTrue(file_exists($fsCopy));
unlink($fsCopy);
}
/**
* @depends testFlush
*/
public function testUnlink()
{
$url = $this->newUrl(self::FNAME);
$cxt = $this->basicSwiftContext();
$ret = unlink($url, $cxt);
$this->assertTrue($ret);
$ret2 = unlink($url, $cxt);
$this->assertFalse($ret2);
}
public function testSetOption()
{
$url = $this->newUrl('fake.foo');
$fake = fopen($url, 'nope', false, $this->basicSwiftContext());
$this->assertTrue(stream_set_blocking($fake, 1));
// Returns 0 on success.
$this->assertEquals(0, stream_set_write_buffer($fake, 8192));
// Cant set a timeout on a tmp storage:
$this->assertFalse(stream_set_timeout($fake, 10));
fclose($fake);
}
/**
* @depends testUnlink
*/
public function testRename()
{
$url = $this->newUrl('rename.foo');
$fake = fopen($url, 'w+', false, $this->basicSwiftContext());
fwrite($fake, 'test');
fclose($fake);
$this->assertTrue(file_exists($url));
$url2 = $this->newUrl('rename.txt');
rename($url, $url2, $this->basicSwiftContext());
$this->assertTrue(file_exists($url2));
$this->assertFalse(file_exists($url));
unlink($url2, $this->basicSwiftContext());
}
/**
* @depends testUnlink
*/
public function testOpenDir()
{
$urls = array('test1.txt', 'foo/test2.txt', 'foo/test3.txt', 'bar/test4.txt');
foreach ($urls as $base) {
$url = $this->newUrl($base);
$f = fopen($url, 'c+', false, $this->basicSwiftContext());
fwrite($f, 'Test.');
fclose($f);
}
$dirUrl = $this->newUrl('');
$dir = opendir($dirUrl, $this->basicSwiftContext());
$this->assertTrue(is_resource($dir));
return $dir;
}
/**
* @depends testOpenDir
*/
public function testReaddir($dir)
{
// Order should be newest to oldest.
$expects = array('bar/', 'foo/', 'test1.txt');
$buffer = array();
while (($entry = readdir($dir)) !== false) {
$should_be = array_shift($expects);
$this->assertEquals($should_be, $entry);
}
$this->assertFalse(readdir($dir));
return $dir;
}
/**
* @depends testReaddir
*/
public function testRewindDir($dir)
{
$this->assertFalse(readdir($dir));
rewinddir($dir);
$this->assertEquals('bar/', readdir($dir));
return $dir;
}
/**
* @depends testRewindDir
*/
public function testCloseDir($dir)
{
$this->assertTrue(is_resource($dir));
closedir($dir);
// There is a bug in PHP where a
// resource buffer is not getting cleared.
// So this might return a value even though
// the underlying stream is cleared.
//$this->assertFalse(readdir($dir));
}
/**
* @depends testCloseDir
*/
public function testOpenSubdir()
{
// Opening foo we should find test2.txt and test3.txt.
$url = $this->newUrl('foo/');
$dir = opendir($url, $this->basicSwiftContext());
// I don't know why, but these are always returned in
// lexical order.
$this->assertEquals('test2.txt', readdir($dir));
$this->assertEquals('test3.txt', readdir($dir));
$array = scandir($url, -1, $this->basicSwiftContext());
$this->assertEquals(2, count($array));
$this->assertEquals('test3.txt', $array[0]);
}
}

View File

@ -27,17 +27,21 @@
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
*/
class TestCase extends \PHPUnit_Framework_TestCase
abstract class TestCase extends \PHPUnit_Framework_TestCase
{
public static $settings = array();
public static $settings = [];
public static $ostore = null;
protected $objectStoreService;
/**
* The IdentityService instance.
@ -48,26 +52,20 @@ class TestCase extends \PHPUnit_Framework_TestCase
protected $containerFixture = null;
public static function setUpBeforeClass()
protected static function setConfiguration()
{
global $bootstrap_settings;
if (!isset($bootstrap_settings)) {
$bootstrap_settings = array();
}
self::$settings = $bootstrap_settings;
//$this->setTestNamespace('Tests\Units');
if (file_exists('tests/settings.ini')) {
self::$settings += parse_ini_file('tests/settings.ini');
self::$settings = parse_ini_file('tests/settings.ini');
} else {
throw new \Exception('Could not access test/settings.ini');
}
\OpenStack\Autoloader::useAutoloader();
\OpenStack\Bootstrap::setConfiguration(self::$settings);
Bootstrap::setConfiguration(self::$settings);
}
//parent::__construct($score, $locale, $adapter);
public static function setUpBeforeClass()
{
self::setConfiguration();
}
/**
@ -129,11 +127,11 @@ class TestCase extends \PHPUnit_Framework_TestCase
protected function objectStore($reset = false)
{
if ($reset || empty(self::$ostore)) {
self::$ostore = self::createObjectStoreService();
if ($reset || !$this->objectStoreService) {
$this->objectStoreService = self::createObjectStoreService();
}
return self::$ostore;
return $this->objectStoreService;
}
/**
@ -148,15 +146,11 @@ class TestCase extends \PHPUnit_Framework_TestCase
try {
$store->createContainer($cname);
$this->containerFixture = $store->container($cname);
}
// This is why PHP needs 'finally'.
catch (\Exception $e) {
} catch (\Exception $e) {
// Delete the container.
$store->deleteContainer($cname);
throw $e;
}
}
return $this->containerFixture;
@ -177,9 +171,8 @@ class TestCase extends \PHPUnit_Framework_TestCase
$store = $this->objectStore();
try {
$container = $store->container($cname);
}
// The container was never created.
catch (\OpenStack\Common\Transport\Exception\FileNotFoundException $e) {
} catch (ResourceNotFoundException $e) {
// The container was never created.
return;
}
@ -202,6 +195,7 @@ class TestCase extends \PHPUnit_Framework_TestCase
{
if (is_null(self::$httpClient)) {
$options = [];
if (isset(self::$settings['transport.proxy'])) {
$options['proxy'] = self::$settings['transport.proxy'];
}
@ -215,7 +209,9 @@ class TestCase extends \PHPUnit_Framework_TestCase
$options['timeout'] = self::$settings['transport.timeout'];
}
self::$httpClient = new self::$settings['transport']($options);
self::$httpClient = GuzzleAdapter::create([
'defaults' => $options
]);
}
return self::$httpClient;
@ -235,7 +231,7 @@ class TestCase extends \PHPUnit_Framework_TestCase
$container = $store->container($cname);
}
// The container was never created.
catch (\OpenStack\Common\Transport\Exception\FileNotFoundException $e) {
catch (ResourceNotFoundException $e) {
return;
}
@ -249,4 +245,4 @@ class TestCase extends \PHPUnit_Framework_TestCase
$store->deleteContainer($cname);
}
}
}

View File

@ -27,17 +27,17 @@ openstack.swift.key = abcdef123456
openstack.swift.url = https://region-a.geo-1.identity.hpcloudsvc.com:35357/auth/v1.0/
; Container used for testing.
openstack.swift.container = "I♡HPCloud"
openstack.swift.container = "test"
; Specified region name to test against.
openstack.swift.region = "region-a.geo-1"
openstack.swift.region = "Region1"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Configuration Parameters ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; The HTTP Transport Client to use.
transport = "\OpenStack\Common\Transport\GuzzleClient"
transport = "OpenStack\Common\Transport\Guzzle\GuzzleAdapter"
; If behind a proxy set to the https proxy server communications need
; to pass through.