diff options
Diffstat (limited to 'apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Service.php')
-rw-r--r-- | apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Service.php | 489 |
1 files changed, 489 insertions, 0 deletions
diff --git a/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Service.php b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Service.php new file mode 100644 index 00000000000..5b3aa729a97 --- /dev/null +++ b/apps/files_external/3rdparty/php-opencloud/lib/OpenCloud/Common/Service.php @@ -0,0 +1,489 @@ +<?php +/** + * PHP OpenCloud library. + * + * @copyright Copyright 2013 Rackspace US, Inc. See COPYING for licensing information. + * @license https://www.apache.org/licenses/LICENSE-2.0 Apache 2.0 + * @version 1.6.0 + * @author Jamie Hannaford <jamie.hannaford@rackspace.com> + */ + +namespace OpenCloud\Common; + +use OpenCloud\Common\Base; +use OpenCloud\Common\Lang; +use OpenCloud\OpenStack; +use OpenCloud\Common\Exceptions; + +/** + * This class defines a cloud service; a relationship between a specific OpenStack + * and a provided service, represented by a URL in the service catalog. + * + * Because Service is an abstract class, it cannot be called directly. Provider + * services such as Rackspace Cloud Servers or OpenStack Swift are each + * subclassed from Service. + * + * @author Glen Campbell <glen.campbell@rackspace.com> + */ + +abstract class Service extends Base +{ + + protected $conn; + private $service_type; + private $service_name; + private $service_region; + private $service_url; + + protected $_namespaces = array(); + + /** + * Creates a service on the specified connection + * + * Usage: `$x = new Service($conn, $type, $name, $region, $urltype);` + * The service's URL is defined in the OpenStack's serviceCatalog; it + * uses the $type, $name, $region, and $urltype to find the proper URL + * and set it. If it cannot find a URL in the service catalog that matches + * the criteria, then an exception is thrown. + * + * @param OpenStack $conn - a Connection object + * @param string $type - the service type (e.g., "compute") + * @param string $name - the service name (e.g., "cloudServersOpenStack") + * @param string $region - the region (e.g., "ORD") + * @param string $urltype - the specified URL from the catalog + * (e.g., "publicURL") + */ + public function __construct( + OpenStack $conn, + $type, + $name, + $region, + $urltype = RAXSDK_URL_PUBLIC, + $customServiceUrl = null + ) { + $this->setConnection($conn); + $this->service_type = $type; + $this->service_name = $name; + $this->service_region = $region; + $this->service_url = $customServiceUrl ?: $this->getEndpoint($type, $name, $region, $urltype); + } + + /** + * Set this service's connection. + * + * @param type $connection + */ + public function setConnection($connection) + { + $this->conn = $connection; + } + + /** + * Get this service's connection. + * + * @return type + */ + public function getConnection() + { + return $this->conn; + } + + /** + * Returns the URL for the Service + * + * @param string $resource optional sub-resource + * @param array $query optional k/v pairs for query strings + * @return string + */ + public function url($resource = '', array $param = array()) + { + $baseurl = $this->service_url; + + // use strlen instead of boolean test because '0' is a valid name + if (strlen($resource) > 0) { + $baseurl = Lang::noslash($baseurl).'/'.$resource; + } + + if (!empty($param)) { + $baseurl .= '?'.$this->MakeQueryString($param); + } + + return $baseurl; + } + + /** + * Returns the /extensions for the service + * + * @api + * @return array of objects + */ + public function extensions() + { + $ext = $this->getMetaUrl('extensions'); + return (is_object($ext) && isset($ext->extensions)) ? $ext->extensions : array(); + } + + /** + * Returns the /limits for the service + * + * @api + * @return array of limits + */ + public function limits() + { + $limits = $this->getMetaUrl('limits'); + return (is_object($limits)) ? $limits->limits : array(); + } + + /** + * Performs an authenticated request + * + * This method handles the addition of authentication headers to each + * request. It always adds the X-Auth-Token: header and will add the + * X-Auth-Project-Id: header if there is a tenant defined on the + * connection. + * + * @param string $url The URL of the request + * @param string $method The HTTP method (defaults to "GET") + * @param array $headers An associative array of headers + * @param string $body An optional body for POST/PUT requests + * @return \OpenCloud\HttpResult + */ + public function request( + $url, + $method = 'GET', + array $headers = array(), + $body = null + ) { + + $headers['X-Auth-Token'] = $this->conn->Token(); + + if ($tenant = $this->conn->Tenant()) { + $headers['X-Auth-Project-Id'] = $tenant; + } + + return $this->conn->request($url, $method, $headers, $body); + } + + /** + * returns a collection of objects + * + * @param string $class the class of objects to fetch + * @param string $url (optional) the URL to retrieve + * @param mixed $parent (optional) the parent service/object + * @return OpenCloud\Common\Collection + */ + public function collection($class, $url = null, $parent = null) + { + // Set the element names + $collectionName = $class::JsonCollectionName(); + $elementName = $class::JsonCollectionElement(); + + // Set the parent if empty + if (!$parent) { + $parent = $this; + } + + // Set the URL if empty + if (!$url) { + $url = $parent->url($class::ResourceName()); + } + + // Save debug info + $this->getLogger()->info( + '{class}:Collection({url}, {collectionClass}, {collectionName})', + array( + 'class' => get_class($this), + 'url' => $url, + 'collectionClass' => $class, + 'collectionName' => $collectionName + ) + ); + + // Fetch the list + $response = $this->request($url); + + $this->getLogger()->info('Response {status} [{body}]', array( + 'status' => $response->httpStatus(), + 'body' => $response->httpBody() + )); + + // Check return code + if ($response->httpStatus() > 204) { + throw new Exceptions\CollectionError(sprintf( + Lang::translate('Unable to retrieve [%s] list from [%s], status [%d] response [%s]'), + $class, + $url, + $response->httpStatus(), + $response->httpBody() + )); + } + + // Handle empty response + if (strlen($response->httpBody()) == 0) { + return new Collection($parent, $class, array()); + } + + // Parse the return + $object = json_decode($response->httpBody()); + $this->checkJsonError(); + + // See if there's a "next" link + // Note: not sure if the current API offers links as top-level structures; + // might have to refactor to allow $nextPageUrl as method argument + // @codeCoverageIgnoreStart + if (isset($object->links) && is_array($object->links)) { + foreach($object->links as $link) { + if (isset($link->rel) && $link->rel == 'next') { + if (isset($link->href)) { + $nextPageUrl = $link->href; + } else { + $this->getLogger()->warning( + 'Unexpected [links] found with no [href]' + ); + } + } + } + } + // @codeCoverageIgnoreEnd + + // How should we populate the collection? + $data = array(); + + if (!$collectionName) { + // No element name, just a plain object + // @codeCoverageIgnoreStart + $data = $object; + // @codeCoverageIgnoreEnd + } elseif (isset($object->$collectionName)) { + if (!$elementName) { + // The object has a top-level collection name only + $data = $object->$collectionName; + } else { + // The object has element levels which need to be iterated over + $data = array(); + foreach($object->$collectionName as $item) { + $subValues = $item->$elementName; + unset($item->$elementName); + $data[] = array_merge((array)$item, (array)$subValues); + } + } + } + + $collectionObject = new Collection($parent, $class, $data); + + // if there's a $nextPageUrl, then we need to establish a callback + // @codeCoverageIgnoreStart + if (!empty($nextPageUrl)) { + $collectionObject->setNextPageCallback(array($this, 'Collection'), $nextPageUrl); + } + // @codeCoverageIgnoreEnd + + return $collectionObject; + } + + /** + * returns the Region associated with the service + * + * @api + * @return string + */ + public function region() + { + return $this->service_region; + } + + /** + * returns the serviceName associated with the service + * + * This is used by DNS for PTR record lookups + * + * @api + * @return string + */ + public function name() + { + return $this->service_name; + } + + /** + * Returns a list of supported namespaces + * + * @return array + */ + public function namespaces() + { + return (isset($this->_namespaces) && is_array($this->_namespaces)) ? $this->_namespaces : array(); + } + + /** + * Given a service type, name, and region, return the url + * + * This function ensures that services are represented by an entry in the + * service catalog, and NOT by an arbitrarily-constructed URL. + * + * Note that it will always return the first match found in the + * service catalog (there *should* be only one, but you never know...) + * + * @param string $type The OpenStack service type ("compute" or + * "object-store", for example + * @param string $name The name of the service in the service catlog + * @param string $region The region of the service + * @param string $urltype The URL type; defaults to "publicURL" + * @return string The URL of the service + */ + private function getEndpoint($type, $name, $region, $urltype = 'publicURL') + { + $catalog = $this->getConnection()->serviceCatalog(); + + // Search each service to find The One + foreach ($catalog as $service) { + // Find the service by comparing the type ("compute") and name ("openstack") + if (!strcasecmp($service->type, $type) && !strcasecmp($service->name, $name)) { + foreach($service->endpoints as $endpoint) { + // Only set the URL if: + // a. It is a regionless service (i.e. no region key set) + // b. The region matches the one we want + if (isset($endpoint->$urltype) && + (!isset($endpoint->region) || !strcasecmp($endpoint->region, $region)) + ) { + $url = $endpoint->$urltype; + } + } + } + } + + // error if not found + if (empty($url)) { + throw new Exceptions\EndpointError(sprintf( + 'No endpoints for service type [%s], name [%s], region [%s] and urlType [%s]', + $type, + $name, + $region, + $urltype + )); + } + + return $url; + } + + /** + * Constructs a specified URL from the subresource + * + * Given a subresource (e.g., "extensions"), this constructs the proper + * URL and retrieves the resource. + * + * @param string $resource The resource requested; should NOT have slashes + * at the beginning or end + * @return \stdClass object + */ + private function getMetaUrl($resource) + { + $urlBase = $this->getEndpoint( + $this->service_type, + $this->service_name, + $this->service_region, + RAXSDK_URL_PUBLIC + ); + + $url = Lang::noslash($urlBase) . '/' . $resource; + + $response = $this->request($url); + + // check for NOT FOUND response + if ($response->httpStatus() == 404) { + return array(); + } + + // @codeCoverageIgnoreStart + if ($response->httpStatus() >= 300) { + throw new Exceptions\HttpError(sprintf( + Lang::translate('Error accessing [%s] - status [%d], response [%s]'), + $urlBase, + $response->httpStatus(), + $response->httpBody() + )); + } + // @codeCoverageIgnoreEnd + + // we're good; proceed + $object = json_decode($response->httpBody()); + + $this->checkJsonError(); + + return $object; + } + + /** + * Get all associated resources for this service. + * + * @access public + * @return void + */ + public function getResources() + { + return $this->resources; + } + + /** + * Internal method for accessing child namespace from parent scope. + * + * @return type + */ + protected function getCurrentNamespace() + { + $namespace = get_class($this); + return substr($namespace, 0, strrpos($namespace, '\\')); + } + + /** + * Resolves fully-qualified classname for associated local resource. + * + * @param string $resourceName + * @return string + */ + protected function resolveResourceClass($resourceName) + { + $className = substr_count($resourceName, '\\') + ? $resourceName + : $this->getCurrentNamespace() . '\\Resource\\' . ucfirst($resourceName); + + if (!class_exists($className)) { + throw new Exceptions\UnrecognizedServiceError(sprintf( + '%s resource does not exist, please try one of the following: %s', + $resourceName, + implode(', ', $this->getResources()) + )); + } + + return $className; + } + + /** + * Factory method for instantiating resource objects. + * + * @access public + * @param string $resourceName + * @param mixed $info (default: null) + * @return object + */ + public function resource($resourceName, $info = null) + { + $className = $this->resolveResourceClass($resourceName); + return new $className($this, $info); + } + + /** + * Factory method for instantiate a resource collection. + * + * @param string $resourceName + * @param string|null $url + * @return Collection + */ + public function resourceList($resourceName, $url = null, $service = null) + { + $className = $this->resolveResourceClass($resourceName); + return $this->collection($className, $url, $service); + } + +} |