<?php
/**
* An abstraction that defines a cloud service
*
* @copyright 2012-2013 Rackspace Hosting, Inc.
* See COPYING for licensing information
*
* @package phpOpenCloud
* @version 1.0
* @author Glen Campbell <hide@address.com>
*/
namespace OpenCloud;
require_once(__DIR__.'/base.php');
/**
* This class defines a "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 <hide@address.com>
*/
abstract class Service extends Base {
protected
$_namespaces=array();
private
$conn,
$service_type,
$service_name,
$service_region,
$service_url;
/**
* 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) {
$this->conn = $conn;
$this->service_type = $type;
$this->service_name = $name;
$this->service_region = $region;
$this->service_url = $this->get_endpoint(
$type, $name, $region, $urltype);
} // public function __construct()
/**
* Returns the URL for the Service
*
* @param array $query optional k/v pairs for query strings
* @return string
*/
public function Url($param=array()) {
return $this->service_url .
(count($param) ? ('?'.$this->MakeQueryString($param)) : '');
} // public function Url()
/**
* Returns the /extensions for the service
*
* @api
* @return array of objects
*/
public function Extensions() {
$ext = $this->GetMetaUrl('extensions');
if (is_object($ext) && isset($ext->extensions))
return $ext->extensions;
else
return array();
} // public function Extensions()
/**
* Returns the /limits for the service
*
* @api
* @return array of limits
*/
public function Limits() {
$lim = $this->GetMetaUrl('limits');
if (is_object($lim))
return $lim->limits;
else
return array();
} // public function Limits()
/**
* 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',$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);
} // function Request()
/**
* 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
* @param array $parm (optional) array of key/value pairs to use as
* query strings
* @return \OpenCloud\Collection
*/
public function Collection($class, $url=NULL, $parent=NULL, $parm=array()) {
// set the element name
$collection = $class::JsonCollectionName();
$element = $class::JsonCollectionElement();
//$jsonname = $class::JsonName();
// save the parent
if (!isset($parent))
$parent = $this;
// determine the URL
if (!$url)
$url = $parent->Url($class::ResourceName());
// add query string parameters
if (count($parm))
$url .= '?' . $this->MakeQueryString($parm);
// save debug info
$this->debug('%s:Collection(%s, %s, %s)',
get_class($this), $url, $class, $collection);
// fetch the list
$response = $this->Request($url);
$this->debug('response %d [%s]',
$response->HttpStatus(), $response->HttpBody());
// check return code
if ($response->HttpStatus() > 204)
throw new \OpenCloud\CollectionError(sprintf(
_('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
$obj = json_decode($response->HttpBody());
if ($this->CheckJsonError())
return FALSE;
// see if there is a "next" link
if (isset($obj->links)) {
if (is_array($obj->links)) {
foreach($obj->links as $link) {
if (isset($link->rel) && ($link->rel=='next')) {
if (isset($link->href))
$next_page_url = $link->href;
else
throw new \OpenCloud\DomainError(
_('unexpected [links] found with no [href]'));
}
}
}
}
// and say goodbye
if (!$collection)
$coll_obj = new Collection($parent, $class, $obj);
elseif (isset($obj->$collection)) {
if (!$element)
$coll_obj = new Collection($parent, $class, $obj->$collection);
else { // handle element levels
$arr = array();
foreach($obj->$collection as $index => $item)
$arr[] = $item->$element;
$coll_obj = new Collection($parent, $class, $arr);
}
}
else
$coll_obj = new Collection($parent, $class, array());
// if there's a $next_page_url, then we need to establish a
// callback method
if (isset($next_page_url)) {
$coll_obj->SetNextPageCallback(
array($this, 'Collection'),
$next_page_url);
}
return $coll_obj;
}
/**
* 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() {
if (!isset($this->_namespaces))
return array();
if (is_array($this->_namespaces))
return $this->_namespaces;
return array();
}
/********** PRIVATE METHODS **********/
/**
* 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 get_endpoint($type, $name, $region, $urltype='publicURL') {
$found = 0;
$catalog = $this->conn->serviceCatalog();
// search each service to find The One
foreach($catalog as $service) {
// first, compare the type ("compute") and name ("openstack")
if ((!strcasecmp($service->type, $type)) &&
(!strcasecmp($service->name, $name))) {
// found the service, now we need to find the region
foreach($service->endpoints as $endpoint) {
// regionless service
if (!isset($endpoint->region) &&
isset($endpoint->$urltype)) {
++$found;
return $endpoint->$urltype;
}
// compare the regions
elseif ((!strcasecmp($endpoint->region, $region)) &&
isset($endpoint->$urltype)) {
// now we have a match! Yay!
++$found;
return $endpoint->$urltype;
}
}
}
}
// error if not found
if (!$found)
throw new EndpointError(
sprintf(_('No endpoints for service type [%s], name [%s], '.
'region [%s] and urlType [%s]'),
$type,
$name,
$region,
$urltype)
);
} // function get_endpoint()
/**
* 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->get_endpoint(
$this->service_type,
$this->service_name,
$this->service_region,
RAXSDK_URL_PUBLIC
);
if ($urlbase == '')
return array();
$ext_url = noslash($urlbase) .
'/' .
$resource;
$response = $this->Request($ext_url);
// check for NOT FOUND response
if ($response->HttpStatus() == 404)
return array();
// check for error status
if ($response->HttpStatus() >= 300)
throw new HttpError(sprintf(
_('Error accessing [%s] - status [%d], response [%s]'),
$urlbase, $response->HttpStatus(), $response->HttpBody()));
// we're good; proceed
$obj = json_decode($response->HttpBody());
if ($this->CheckJsonError())
return FALSE;
return $obj;
}
} // class Service