diff --git a/composer.json b/composer.json index eed385e82..71d8b7903 100644 --- a/composer.json +++ b/composer.json @@ -20,10 +20,17 @@ "OpenCloud": ["lib/", "tests/"] } }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/ycombinator/json-patch-php" + } + ], "require": { "php" : ">=5.3.3", "guzzle/http" : "~3.8", - "psr/log": "~1.0" + "psr/log": "~1.0", + "mikemccabe/json-patch-php": "dev-master" }, "require-dev" : { "phpunit/phpunit": "4.3.*", diff --git a/docs/userguide/CDN/README.md b/docs/userguide/CDN/README.md new file mode 100644 index 000000000..fbe813627 --- /dev/null +++ b/docs/userguide/CDN/README.md @@ -0,0 +1,96 @@ +# CDN + +**CDN** is a service that you can use to manage your CDN-enabled domains and the origins and assets associated with those domains. + +## Concepts + +To use the CDN service effectively, you should understand the following key concepts: + +* **Content delivery network**: A content delivery network (CDN) is a system of multiple computers that contains copies of data stored at various network nodes. A CDN is designed to improve performance of publicly distributed assets. Assets can be anything from website content, to web application components, to media such as videos, ads, and interactive experiences.  CDNs decrease the load time of these assets by caching them on edge servers, also called points of presence (PoPs).  Edge servers are distributed around the globe, meaning requests only travel to a local location to grab assets, rather than to and from a data center based far from the end user. + +* **Edge node**: CDN providers have many points of presence (PoP) servers around the world. These servers are known as edge nodes. These edge nodes cache the content and serve it directly to customers, thus reducing transit time to a customers location. + +* **Edge server**: An edge server is the same as an edge node. + +* **Origin**: An origin is an address (IP or domain) from which the CDN provider pulls content. A service can have multiple origins. + +* **Flavor**: A flavor is a configuration option. A flavor enables you to choose from a generic setting that is powered by one or more CDN providers. + +* **Service**: A service represents your web application that has its content cached to the edge nodes. + +* **Status**: The status indicates the current state of the service. The time it takes for a service configuration to be distributed amongst a CDN provider cache can vary. + +* **Purge**: Purging removes content from the edge servers - thus invalidating the content - so that it can be refreshed from your origin servers. + +* **Caching rule**: A caching rule provides you with fine-grained control over the time-to-live (TTL) of an object. When the TTL expires for an object, the edge node pulls the object from the origin again. + +* **Restriction**: A restriction enables you to define rules about who can or cannot access content from the cache. Examples of a restriction are allowing requests only from certain domains, geographies, or IP addresses. + +## Getting started + +### 1. Instantiate an OpenStack or Rackspace client. + +To use the CDN service, you must first instantiate a `OpenStack` or `Rackspace` client object. + +* If you are working with an OpenStack cloud, instantiate an `OpenCloud\OpenStack` client as follows: + + ```php + use OpenCloud\OpenStack; + + $client = new OpenStack('', array( + 'username' => '', + 'password' => '' + )); + ``` + +* If you are working with the Rackspace cloud, instantiate a `OpenCloud\Rackspace` client as follows: + + ```php + use OpenCloud\Rackspace; + + $client = new Rackspace(Rackspace::US_IDENTITY_ENDPOINT, array( + 'username' => '', + 'apiKey' => '' + )); + ``` + +### 2. Obtain a CDN service object from the client. +All CDN operations are done via an _CDN service object_. To +instantiate this object, call the `cdnService` method on the `$client` +object. This method takes one argument: + +| Position | Description | Data type | Required? | Default value | Example value | +| -------- | ----------- | ----------| --------- | ------------- | ------------- | +| 1 | Name of the service, as it appears in the service catalog | String | No | `null`; automatically determined when possible | `rackCDN` | + + +```php +$cdnService = $client->cdnService(); +``` + +### 3. Create a service (to represent your web application). +```php +$service = $cdnService->createService(array( + 'name' => 'acme_site', + 'domains' => array( + array( + 'domain' => 'www.acme.com' + ), + array( + 'domain' => 'acme.com' + ) + ), + 'origins' => array( + array( + 'origin' => 'origin.acme.com' + ) + ), + 'flavorId' => 'cdn' +)); +``` + +[ [Get the executable PHP script for this example](/samples/CDN/create-service.php) ] + +## Next steps + +Once you have created a service, there is more you can do with it. See [complete user guide for CDN](USERGUIDE.md). \ No newline at end of file diff --git a/docs/userguide/CDN/USERGUIDE.md b/docs/userguide/CDN/USERGUIDE.md new file mode 100644 index 000000000..971175219 --- /dev/null +++ b/docs/userguide/CDN/USERGUIDE.md @@ -0,0 +1,294 @@ +# Complete User Guide for the CDN Service + +CDN is a service that you can use to manage your CDN-enabled domains and the origins and assets associated with those domains. + +## Table of contents + * [Concepts](#concepts) + * [Prerequisites](#prerequisites) + * [Client](#client) + * [CDN service](#cdn-service) + * [Services](#services) + * [Create a service](#create-a-service-to-represent-your-web-application) + * [List Services](#list-services) + * [Get a service](#get-a-service) + * [Update a service](#update-a-service) + * [Delete a service](#delete-a-service) + * [Service Assets](#service-assets) + * [Purge all cached service assets](#purge-all-cached-service-assets) + * [Purge a specific cached service asset](#purge-a-specific-cached-service-asset) + * [Flavors](#flavors) + * [List flavors](#list-flavors) + * [Get a flavor](#get-a-flavor) + +## Concepts + +To use the CDN service effectively, you should understand the following +key concepts: + +* **Content delivery network**: A content delivery network (CDN) is a system of multiple computers that contains copies of data stored at various network nodes. A CDN is designed to improve performance of publicly distributed assets. Assets can be anything from website content, to web application components, to media such as videos, ads, and interactive experiences.  CDNs decrease the load time of these assets by caching them on edge servers, also called points of presence (PoPs).  Edge servers are distributed around the globe, meaning requests only travel to a local location to grab assets, rather than to and from a data center based far from the end user. + +* **Edge node**: CDN providers have many points of presence (PoP) servers around the world. These servers are known as edge nodes. These edge nodes cache the content and serve it directly to customers, thus reducing transit time to a customers location. + +* **Edge server**: An edge server is the same as an edge node. + +* **Origin**: An origin is an address (IP or domain) from which the CDN provider pulls content. A service can have multiple origins. + +* **Flavor**: A flavor is a configuration option. A flavor enables you to choose from a generic setting that is powered by one or more CDN providers. + +* **Service**: A service represents your web application that has its content cached to the edge nodes. + +* **Status**: The status indicates the current state of the service. The time it takes for a service configuration to be distributed amongst a CDN provider cache can vary. + +* **Purge**: Purging removes content from the edge servers - thus invalidating the content - so that it can be refreshed from your origin servers. + +* **Caching rule**: A caching rule provides you with fine-grained control over the time-to-live (TTL) of an object. When the TTL expires for an object, the edge node pulls the object from the origin again. + +* **Restriction**: A restriction enables you to define rules about who can or cannot access content from the cache. Examples of a restriction are allowing requests only from certain domains, geographies, or IP addresses. + + +## Prerequisites + +### Client +To use the CDN service, you must first instantiate a `OpenStack` or `Rackspace` client object. + +* If you are working with an OpenStack cloud, instantiate an `OpenCloud\OpenStack` client as follows: + + ```php + use OpenCloud\OpenStack; + + $client = new OpenStack('', array( + 'username' => '', + 'password' => '' + )); + ``` + +* If you are working with the Rackspace cloud, instantiate a `OpenCloud\Rackspace` client as follows: + + ```php + use OpenCloud\Rackspace; + + $client = new Rackspace(Rackspace::US_IDENTITY_ENDPOINT, array( + 'username' => '', + 'apiKey' => '' + )); + ``` + +### CDN service + +All CDN operations are done via an _CDN service object_. To +instantiate this object, call the `cdnService` method on the `$client` +object. This method takes one argument: + +| Position | Description | Data type | Required? | Default value | Example value | +| -------- | ----------- | ----------| --------- | ------------- | ------------- | +| 1 | Name of the service, as it appears in the service catalog | String | No | `null`; automatically determined when possible | `rackCDN` | + + +```php +$cdnService = $client->cdnService(); +``` + +## Services + +A service represents your web application that has its content cached to the edge nodes. + +### Create a service (to represent your web application) + + +This operation takes one parameter, an associative array, with the following keys: + +| Name | Description | Data type | Required? | Default value | Example value | +| ---- | ----------- | --------- | --------- | ------------- | ------------- | +| `name` | A human-readable name for the service. This name must be unique. | String | Yes | - | `acme_site` | +| `flavorId` | The ID of the flavor to use for this service. | String | Yes | - | `cdn` | +| `domains` | List of domain for your service. | Array of associative arrays | Yes | - | `array( ... )` | +| `domains[n]` | Information about a domain for your service. | Associative array | Yes | - | `array( ... )` | +| `domains[n]['domain']` | A domain name used by your service. | String | Yes | - | 'www.acme.com' | +| `domains[n]['protocol']` | The protocol used by your service web site, `http` or `https`. | String | No | `http` | `http` | +| `origins` | List of origin servers for your service. | Array of associative arrays | Yes | - | `array( ... )` | +| `origins[n]` | Information about an origin server for your service. | Associative array | Yes | - | `array( ... )` | +| `origins[n]['origin']` | The origin server address, from where the CDN will pull your web site's assets. | String | Yes | - | `origin.acme.com` | +| `origins[n]['port']` | The origin server's port. | Integer | No | 80 | `8080` | +| `origins[n]['ssl']` | Whether origin server uses SSL. | Boolean | No | `false` | `true` | +| `origins[n]['rules']` | List of rules defining the conditions when this origin should be accessed. | Array of associative arrays | No | `null` | `array( ... )` | +| `origins[n]['rules'][n]` | Information about an access rule. | Associative array | No | `null` | `array( ... )` | +| `origins[n]['rules'][n]['name']` | A human-readable name of the rule. | String | No | `null` | `images` | +| `origins[n]['rules'][n]['requestUrl']` | The request URL this rule should match (regex supported). | String | No | `null` | `^/images/.+$` | +| `caching` | List of TTL rules for assets of this service. | Array of associative arrays | No | `null` | `array( ... )` | +| `caching[n]` | Information about a TTL rule. | Associative array | No | `null` | `array( ... )` | +| `caching[n]['name']` | A human-readable name of the TTL rule. | String | No | `null` | `long_ttl` | +| `caching[n]['ttl']` | The TTL value, in seconds. | Integer | No | `null` | `604800` | +| `caching[n]['rules']` | List of rules that determine if this TTL should be applied to an asset. | Array of associative arrays | No | `null` | `array( ... )` | +| `caching[n]['rules'][n]` | Information about a TTL rule. | Associative array | No | `null` | `array( ... )` | +| `caching[n]['rules'][n]['name']` | A human-readable name of the TTL rule. | No | `null` | `images` | +| `caching[n]['rules'][n]['requestUrl']` | The request URL this rule should match (regex supported). | String | No | `null` | `^/images/.+$` | +| `restrictions` | List of restrictions on where the service can be accessed from. | Array of associative arrays | No | `null` | `array( ... )` | +| `restrictions[n]` | Information about an access restriction. | Associative array | No | `null` | `array( ... )` | +| `restrictions[n]['name']` | A human-readable name of the restriction. | String | No | `null` | `affiliate_sites_only` | +| `restrictions[n]['rules']` | List of restriction rules. | Array of associative arrays | No | `null` | `array( ... )` | +| `restrictions[n]['rules'][n]` | Information about a restriction rule. | Associative array | No | `null` | `array( ... )` | +| `restrictions[n]['rules'][n]['name']` | A human-readable name of the restriction rule. | String | No | `null` | `Wile E. Coyote's site` | +| `restrictions[n]['rules'][n]['referrer']` | The domain from which the service can be accessed. | String | No | `null` | `www.wilecoyote.com` | + +You can create a service as shown in the following example: + +```php +$service = $cdnService->createService(array( + 'name' => 'acme_site', + 'domains' => array( + array( + 'domain' => 'www.acme.com' + ), + array( + 'domain' => 'acme.com' + ) + ), + 'origins' => array( + array( + 'origin' => 'origin.acme.com' + ) + ), + 'flavorId' => 'cdn' +)); +``` + +[ [Get the executable PHP script for this example](/samples/CDN/create-service.php) ] + +### List Services + +You can list all the services you have created as shown in the following example: + +```php +$services = $cdnService->listServices(); +foreach ($services as $service) { + /** @var $service OpenCloud\CDN\Resource\Service **/ +} +``` + +[ [Get the executable PHP script for this example](/samples/CDN/list-services.php) ] + +### Get a service + +You can retrieve a specific service by using that service's ID, as shown in the following example: + +```php +$service = $cdnService->getService('0e09ad12-2bfe-4607-80fd-116fa68d9c79'); +/** @var $service OpenCloud\CDN\Resource\Service **/ +``` + +[ [Get the executable PHP script for this example](/samples/CDN/get-service.php) ] + +### Update a service + +This operation takes one parameter, an associative array, with the following keys: + +| Name | Description | Data type | Required? | Default value | Example value | +| ---- | ----------- | --------- | --------- | ------------- | ------------- | +| `name` | A human-readable name for the service. This name must be unique. | String | Yes | - | `acme_site` | +| `flavorId` | The ID of the flavor to use for this service. | String | Yes | - | `cdn` | +| `domains` | List of domain for your service. | Array of associative arrays | Yes | - | `array( ... )` | +| `domains[n]` | Information about a domain for your service. | Associative array | Yes | - | `array( ... )` | +| `domains[n]['domain']` | The domain name for your service. | String | Yes | - | 'www.acme.com' | +| `domains[n]['protocol']` | The protocol used by your service web site, `http` or `https`. | String | No | `http` | `http` | +| `origins` | List of origin servers for your service. | Array of associative arrays | Yes | - | `array( ... )` | +| `origins[n]` | Information about an origin server for your service. | Associative array | Yes | - | `array( ... )` | +| `origins[n]['origin']` | The origin server address, from where the CDN will pull your web site's assets. | String | Yes | - | `origin.acme.com` | +| `origins[n]['origin']['port']` | The origin server's port. | Integer | No | 80 | `8080` | +| `origins[n]['origin']['ssl']` | Whether origin server uses SSL. | Boolean | No | `false` | `true` | +| `origins[n]['origin']['rules']` | List of rules defining the conditions when this origin should be accessed. | Array of associative arrays | No | `null` | `array( ... )` | +| `origins[n]['origin']['rules'][n]` | Information about an access rule. | Associative array | No | `null` | `array( ... )` | +| `origins[n]['origin']['rules'][n]['name']` | A human-readable name of the rule. | String | No | `null` | `images` | +| `origins[n]['origin']['rules'][n]['requestUrl']` | The request URL this rule should match (regex supported). | String | No | `null` | `^/images/.+$` | +| `caching` | List of TTL rules for assets of this service. | Array of associative arrays | No | `null` | `array( ... )` | +| `caching[n]` | Information about a TTL rule. | Associative array | No | `null` | `array( ... )` | +| `caching[n]['name']` | A human-readable name of the TTL rule. | String | No | `null` | `long_ttl` | +| `caching[n]['ttl']` | The TTL value, in seconds. | Integer | No | `null` | `604800` | +| `caching[n]['rules']` | List of rules that determine if this TTL should be applied to an asset. | Array of associative arrays | No | `null` | `array( ... )` | +| `caching[n]['rules'][n]` | Information about a TTL rule. | Associative array | No | `null` | `array( ... )` | +| `caching[n]['rules'][n]['name']` | A human-readable name of the TTL rule. | No | `null` | `images` | +| `caching[n]['rules'][n]['requestUrl']` | The request URL this rule should match (regex supported). | String | No | `null` | `^/images/.+$` | +| `restrictions` | List of restrictions on where the service can be accessed from. | Array of associative arrays | No | `null` | `array( ... )` | +| `restrictions[n]` | Information about an access restriction. | Associative array | No | `null` | `array( ... )` | +| `restrictions[n]['name']` | A human-readable name of the restriction. | String | No | `null` | `affiliate_sites_only` | +| `restrictions[n]['rules']` | List of restriction rules. | Array of associative arrays | No | `null` | `array( ... )` | +| `restrictions[n]['rules'][n]` | Information about a restriction rule. | Associative array | No | `null` | `array( ... )` | +| `restrictions[n]['rules'][n]['name']` | A human-readable name of the restriction rule. | String | No | `null` | `Wile E. Coyote's site` | +| `restrictions[n]['rules'][n]['referrer']` | The domain from which the service can be accessed. | String | No | `null` | `www.wilecoyote.com` | + +You can update a service as shown in the following example: + +```php +$service->update(array( + 'origins' => array( + array( + 'origin' => '44.33.22.11', + 'port' => 80, + 'ssl' => false + ) + ) +)); +``` + +[ [Get the executable PHP script for this example](/samples/CDN/update-service.php) ] + +### Delete a service + +You can delete a service as shown in the following example: + +```php +$service->delete(); +``` + +[ [Get the executable PHP script for this example](/samples/CDN/delete-service.php) ] + +## Service Assets +A service will have its assets distributed and cached across a CDN's edge nodes. + +### Purge all cached service assets + +You can purge all cached assets of a service as shown in the following example: + +```php +$service->purgeAssets(); +``` + +[ [Get the executable PHP script for this example](/samples/CDN/purge-cached-service-assets.php) ] + +### Purge a specific cached service asset + +You can purge a specific asset of a service by providing its relative URL, as shown in the following example: + +```php +$service->purgeAssets('/images/logo.png'); +``` + +[ [Get the executable PHP script for this example](/samples/CDN/purge-cached-service-asset.php) ] + +## Flavors + +A flavor is a configuration option. A flavor enables you to choose from a generic setting that is powered by one or more CDN providers. + +### List flavors + +You can list all available flavors as shown in the following example: + +```php +$flavors = $cdnService->listFlavors(); +foreach ($flavors as $flavor) { + /** @var $flavor OpenCloud\CDN\Resource\Flavor **/ +} +``` + +[ [Get the executable PHP script for this example](/samples/CDN/list-flavors.php) ] + +### Get a flavor + +You can retrieve a specific flavor by using that flavor's ID, as shown in the +following example: + +```php +$flavor = $cdnService->getFlavor('cdn'); +/** @var $flavor OpenCloud\CDN\Resource\Flavor **/ +``` + +[ [Get the executable PHP script for this example](/samples/CDN/get-flavor.php) ] diff --git a/lib/OpenCloud/CDN/Resource/Flavor.php b/lib/OpenCloud/CDN/Resource/Flavor.php new file mode 100644 index 000000000..de13eb9f2 --- /dev/null +++ b/lib/OpenCloud/CDN/Resource/Flavor.php @@ -0,0 +1,51 @@ +noUpdate(); + } + + protected function createJson() + { + $createJson = parent::createJson(); + return $createJson->{self::$json_name}; + } +} diff --git a/lib/OpenCloud/CDN/Resource/Service.php b/lib/OpenCloud/CDN/Resource/Service.php new file mode 100644 index 000000000..5e3a2ff48 --- /dev/null +++ b/lib/OpenCloud/CDN/Resource/Service.php @@ -0,0 +1,115 @@ + 'flavorId', + 'http_host' => 'httpHost', + 'request_url' => 'requestUrl' + ); + + protected $createKeys = array( + 'name', + 'domains', + 'origins', + 'caching', + 'restrictions', + 'flavorId' + ); + + protected $updateKeys = array( + 'name', + 'domains', + 'origins', + 'caching', + 'restrictions', + 'flavorId' + ); + + public function purgeAssets($assetUrl = null) + { + $assetsUrl = $this->assetsUrl(); + if (null === $assetUrl) { + $assetsUrl->setQuery(array('all' => 'true')); + } else { + $assetsUrl->setQuery(array('url' => $assetUrl)); + } + + $request = $this->getClient()->delete($assetsUrl); + + // This is necessary because the response does not include a body + // and fails with a 406 Not Acceptable if the default + // 'Accept: application/json' header is used in the request. + $request->removeHeader('Accept'); + + return $request->send(); + } + + protected function assetsUrl() + { + $url = clone $this->getUrl(); + $url->addPath('assets'); + + return $url; + } + + protected function createJson() + { + $createJson = parent::createJson(); + return $createJson->{self::$json_name}; + } + + /** + * Update this resource + * + * @param array $params + * @return \Guzzle\Http\Message\Response + */ + public function update($params = array()) + { + $json = $this->generateJsonPatch($params); + + return $this->getClient() + ->patch($this->getUrl(), $this->getPatchHeaders(), $json) + ->send(); + } +} diff --git a/lib/OpenCloud/CDN/Service.php b/lib/OpenCloud/CDN/Service.php new file mode 100644 index 000000000..57c880052 --- /dev/null +++ b/lib/OpenCloud/CDN/Service.php @@ -0,0 +1,188 @@ +resource('Service', $id); + } + + /** + * Creates a new Service and returns it. + * + * @see https://github.com/rackspace/php-opencloud/blob/master/docs/userguide/CDN/USERGUIDE.md#create-a-service + * @param array $params Service creation parameters. + * @return \OpenCloud\CDN\Resource\Service Object representing created service + */ + public function createService(array $params = array()) + { + $service = $this->service(); + $service->create($params); + return $service; + } + + /** + * Returns a Service object associated with this CDN service + * + * @param string $id ID of service to retrieve + * @return \OpenCloud\CDN\Resource\Service object + */ + public function getService($id) + { + return $this->service($id); + } + + /** + * Returns a list of services you created + * + * @param array $params + * @return \OpenCloud\Common\Collection\PaginatedIterator + */ + public function listServices(array $params = array()) + { + $params['limit'] = isset($params['limit']) && $params['limit'] <= self::MAX_LIMIT ?: self::MAX_LIMIT; + + $url = clone $this->getUrl(); + $url->addPath(ServiceResource::resourceName())->setQuery($params); + + return $this->resourceList('Service', $url); + } + + /** + * Returns a Flavor object associated with this CDN service + * + * @param string $id ID of flavor to retrieve + * @return \OpenCloud\CDN\Resource\Flavor object + */ + public function flavor($id = null) + { + return $this->resource('Flavor', $id); + } + + /** + * Creates a new Flavor and returns it. + * + * @see https://github.com/rackspace/php-opencloud/blob/master/docs/userguide/CDN/USERGUIDE.md#create-a-flavor + * @param array $params Flavor creation parameters. + * @return \OpenCloud\CDN\Resource\Flavor Object representing created flavor + */ + public function createFlavor(array $params = array()) + { + $flavor = $this->flavor(); + $flavor->create($params); + return $flavor; + } + + /** + * Returns a Flavor object associated with this CDN service + * + * @param string $id ID of flavor to retrieve + * @return \OpenCloud\CDN\Resource\Flavor object + */ + public function getFlavor($id) + { + return $this->flavor($id); + } + + /** + * Returns a list of flavors you created + * + * @param array $params + * @return \OpenCloud\Common\Collection\PaginatedIterator + */ + public function listFlavors(array $params = array()) + { + $url = clone $this->getUrl(); + $url->addPath(Flavor::resourceName())->setQuery($params); + + return $this->resourceList('Flavor', $url); + } + + /** + * Returns the home document for the CDN service + * + * @return \stdClass home document response + */ + public function getHomeDocument() + { + $url = clone $this->getUrl(); + + // This hack is necessary otherwise Guzzle will remove the trailing + // slash from the URL and the request will fail because the service + // expects the trailing slash :( + $url->setPath($url->getPath() . '/'); + + $response = $this->getClient()->get($url)->send(); + return Formatter::decode($response); + } + + /** + * Returns the ping (status) response for the CDN service + * + * @return Guzzle\Http\Message\Response + */ + public function getPing() + { + $url = clone $this->getUrl(); + $url->addPath('ping'); + + $request = $this->getClient()->get($url); + + // This is necessary because the response does not include a body + // and fails with a 406 Not Acceptable if the default + // 'Accept: application/json' header is used in the request. + $request->removeHeader('Accept'); + + return $request->send(); + } + + /** + * Return namespaces. + * + * @return array + */ + public function namespaces() + { + return array(); + } +} diff --git a/lib/OpenCloud/Common/Base.php b/lib/OpenCloud/Common/Base.php index 3e0bc3dfb..fd638cc81 100644 --- a/lib/OpenCloud/Common/Base.php +++ b/lib/OpenCloud/Common/Base.php @@ -32,6 +32,8 @@ */ abstract class Base { + const PATCH_CONTENT_TYPE = MimeConst::JSON_PATCH; + /** * Holds all the properties added by overloading. * @@ -419,4 +421,9 @@ protected static function getJsonHeader() { return array(HeaderConst::CONTENT_TYPE => MimeConst::JSON); } + + protected static function getPatchHeaders() + { + return array(HeaderConst::CONTENT_TYPE => static::PATCH_CONTENT_TYPE); + } } diff --git a/lib/OpenCloud/Common/Constants/Mime.php b/lib/OpenCloud/Common/Constants/Mime.php index 23df6cfe9..4e2942ebb 100644 --- a/lib/OpenCloud/Common/Constants/Mime.php +++ b/lib/OpenCloud/Common/Constants/Mime.php @@ -21,4 +21,5 @@ class Mime { const JSON = 'application/json'; const TEXT = 'text/plain'; + const JSON_PATCH = 'application/json-patch+json'; } diff --git a/lib/OpenCloud/Common/Resource/PersistentResource.php b/lib/OpenCloud/Common/Resource/PersistentResource.php index c012b8e76..bb11b79be 100644 --- a/lib/OpenCloud/Common/Resource/PersistentResource.php +++ b/lib/OpenCloud/Common/Resource/PersistentResource.php @@ -25,6 +25,7 @@ use OpenCloud\Common\Exceptions\NameError; use OpenCloud\Common\Exceptions\UnsupportedExtensionError; use OpenCloud\Common\Exceptions\UpdateError; +use mikemccabe\JsonPatch\JsonPatch; abstract class PersistentResource extends BaseResource { @@ -265,7 +266,7 @@ protected function recursivelyAliasPropertyValue($propertyValue) } } } elseif (is_object($propertyValue) && ($propertyValue instanceof \stdClass)) { - foreach ($propertyValue as $key => $subValue) { + foreach (get_object_vars($propertyValue) as $key => $subValue) { unset($propertyValue->$key); $propertyValue->{$this->getAlias($key)} = $this->recursivelyAliasPropertyValue($subValue); } @@ -337,6 +338,54 @@ public function checkExtension($alias) return true; } + /** + * Returns the object's properties as an array + */ + protected function getUpdateablePropertiesAsArray() + { + $properties = get_object_vars($this); + + $propertiesToKeep = array(); + foreach ($this->updateKeys as $key) { + if (isset($properties[$key])) { + $propertiesToKeep[$key] = $properties[$key]; + } + } + + return $propertiesToKeep; + } + + /** + * Generates a JSON Patch representation and return its + * + * @param mixed $updatedProperties Properties of the resource to update + * @return String JSON Patch representation for updates + */ + protected function generateJsonPatch($updatedProperties) + { + // Normalize current and updated properties into nested arrays + $currentProperties = json_decode(json_encode($this->getUpdateablePropertiesAsArray()), true); + $updatedProperties = json_decode(json_encode($updatedProperties), true); + + // Add any properties that haven't changed to generate the correct patch + // (otherwise unchanging properties are marked as removed in the patch) + foreach ($currentProperties as $key => $value) { + if (!array_key_exists($key, $updatedProperties)) { + $updatedProperties[$key] = $value; + } + } + + // Recursively alias current and updated properties + $currentProperties = $this->recursivelyAliasPropertyValue($currentProperties); + $updatedProperties = $this->recursivelyAliasPropertyValue($updatedProperties); + + // Generate JSON Patch representation + $json = json_encode(JsonPatch::diff($currentProperties, $updatedProperties)); + $this->checkJsonError(); + + return $json; + } + /******** DEPRECATED METHODS ********/ /** diff --git a/lib/OpenCloud/Common/Service/ServiceBuilder.php b/lib/OpenCloud/Common/Service/ServiceBuilder.php index cf59f4a2b..99747e900 100644 --- a/lib/OpenCloud/Common/Service/ServiceBuilder.php +++ b/lib/OpenCloud/Common/Service/ServiceBuilder.php @@ -37,6 +37,7 @@ class ServiceBuilder public static function factory(ClientInterface $client, $class, array $options = array()) { $name = isset($options['name']) ? $options['name'] : null; + $type = isset($options['type']) ? $options['type'] : null; $urlType = isset($options['urlType']) ? $options['urlType'] : null; if (isset($options['region'])) { @@ -47,6 +48,6 @@ public static function factory(ClientInterface $client, $class, array $options = $region = null; } - return new $class($client, null, $name, $region, $urlType); + return new $class($client, $type, $name, $region, $urlType); } } diff --git a/lib/OpenCloud/Image/Resource/Image.php b/lib/OpenCloud/Image/Resource/Image.php index b8ab7407c..f24eb510b 100644 --- a/lib/OpenCloud/Image/Resource/Image.php +++ b/lib/OpenCloud/Image/Resource/Image.php @@ -35,6 +35,8 @@ class Image extends AbstractSchemaResource implements ImageInterface protected static $json_name = ''; protected static $json_collection_name = 'images'; + const PATCH_CONTENT_TYPE = 'application/openstack-images-v2.1-json-patch'; + /** * Update this resource * @@ -85,7 +87,7 @@ public function update(array $params, Schema $schema = null) $body = $document->toString(); return $this->getClient() - ->patch($this->getUrl(), $this->getService()->getPatchHeaders(), $body) + ->patch($this->getUrl(), $this->getPatchHeaders(), $body) ->send(); } diff --git a/lib/OpenCloud/Image/Service.php b/lib/OpenCloud/Image/Service.php index 86462c8d6..f0ed4d72a 100644 --- a/lib/OpenCloud/Image/Service.php +++ b/lib/OpenCloud/Image/Service.php @@ -17,7 +17,6 @@ namespace OpenCloud\Image; -use OpenCloud\Common\Constants\Header; use OpenCloud\Common\Service\CatalogService; use OpenCloud\Image\Resource\Image; use OpenCloud\Image\Resource\Schema\Schema; @@ -32,18 +31,6 @@ class Service extends CatalogService const DEFAULT_TYPE = 'image'; const DEFAULT_NAME = 'cloudImages'; - const PATCH_CONTENT_TYPE = 'application/openstack-images-v2.1-json-patch'; - - /** - * Get the default headers to send for PATCH requests - * - * @return array - */ - public function getPatchHeaders() - { - return array(Header::CONTENT_TYPE => self::PATCH_CONTENT_TYPE); - } - /** * This operation returns images you created, shared images that you accepted, and standard images. * diff --git a/lib/OpenCloud/OpenStack.php b/lib/OpenCloud/OpenStack.php index 25e576959..6bc6eeba1 100644 --- a/lib/OpenCloud/OpenStack.php +++ b/lib/OpenCloud/OpenStack.php @@ -566,4 +566,22 @@ public function networkingService($name = null, $region = null, $urltype = null) 'urlType' => $urltype )); } + + /** + * Creates a new CDN (Poppy) service object + * + * @param string $name The name of the service as it appears in the Catalog + * @param string $region The region (DFW, IAD, ORD, LON, SYD) + * @param string $urltype The URL type ("publicURL" or "internalURL") + * @return \OpenCloud\Cdn\Service + * @codeCoverageIgnore + */ + public function cdnService($name = null, $region = null, $urltype = null) + { + return ServiceBuilder::factory($this, 'OpenCloud\CDN\Service', array( + 'name' => $name, + 'region' => $region, + 'urlType' => $urltype + )); + } } diff --git a/lib/OpenCloud/Rackspace.php b/lib/OpenCloud/Rackspace.php index 8480a0d71..40473a4b0 100644 --- a/lib/OpenCloud/Rackspace.php +++ b/lib/OpenCloud/Rackspace.php @@ -174,4 +174,23 @@ public function queuesService($name = null, $region = null, $urltype = null) 'urlType' => $urltype )); } + + /** + * Creates a new CDN (Rackspace CDN) service object + * + * @param string $name The name of the service as it appears in the Catalog + * @param string $region The region (DFW, IAD, ORD, LON, SYD) + * @param string $urltype The URL type ("publicURL" or "internalURL") + * @return \OpenCloud\Cdn\Service + * @codeCoverageIgnore + */ + public function cdnService($name = null, $region = null, $urltype = null) + { + return ServiceBuilder::factory($this, 'OpenCloud\CDN\Service', array( + 'name' => $name, + 'type' => 'rax:cdn', + 'region' => $region, + 'urlType' => $urltype + )); + } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index cd44e77df..3d4b02cd2 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -25,4 +25,9 @@ + + + + + diff --git a/samples/CDN/create-flavor.php b/samples/CDN/create-flavor.php new file mode 100644 index 000000000..5e2433ad7 --- /dev/null +++ b/samples/CDN/create-flavor.php @@ -0,0 +1,47 @@ + '{username}', + 'apiKey' => '{apiKey}', +)); + +// 2. Obtain an CDN service object from the client. +$cdnService = $client->cdnService(); + +// 3. Create flavor. +$flavor = $cdnFlavor->createFlavor(array( + 'id' => '{flavorId}', + 'providers' => array( + array( + 'provider' => 'akamai', + 'links' => array( + array( + 'rel' => 'provider_url', + 'href' => 'http://www.akamai.com' + ) + ) + ) + ) +)); +/** @var $flavor OpenCloud\CDN\Resource\Flavor **/ diff --git a/samples/CDN/create-service.php b/samples/CDN/create-service.php new file mode 100644 index 000000000..102fd1f18 --- /dev/null +++ b/samples/CDN/create-service.php @@ -0,0 +1,43 @@ + '{username}', + 'apiKey' => '{apiKey}', +)); + +// 2. Obtain an CDN service object from the client. +$cdnService = $client->cdnService(); + +// 3. Create service. +$service = $cdnService->createService(array( + 'name' => '{name}', + 'domains' => array( + array('domain' => '{domainName}') + ), + 'origins' => array( + array('origin' => '{originAddress}') + ), + 'flavorId' => '{flavorId}' +)); +/** @var $service OpenCloud\CDN\Resource\Service **/ diff --git a/samples/CDN/delete-flavor.php b/samples/CDN/delete-flavor.php new file mode 100644 index 000000000..28f901c50 --- /dev/null +++ b/samples/CDN/delete-flavor.php @@ -0,0 +1,36 @@ + '{username}', + 'apiKey' => '{apiKey}', +)); + +// 2. Obtain an CDN service object from the client. +$cdnService = $client->cdnService(); + +// 3. Get flavor. +$flavor = $cdnFlavor->getFlavor('{flavorId}'); + +// 4. Delete it. +$flavor->delete(); diff --git a/samples/CDN/delete-service.php b/samples/CDN/delete-service.php new file mode 100644 index 000000000..2b223cdf7 --- /dev/null +++ b/samples/CDN/delete-service.php @@ -0,0 +1,36 @@ + '{username}', + 'apiKey' => '{apiKey}', +)); + +// 2. Obtain an CDN service object from the client. +$cdnService = $client->cdnService(); + +// 3. Get service. +$service = $cdnService->getService('{serviceId}'); + +// 4. Delete it. +$service->delete(); diff --git a/samples/CDN/get-flavor.php b/samples/CDN/get-flavor.php new file mode 100644 index 000000000..90e605c21 --- /dev/null +++ b/samples/CDN/get-flavor.php @@ -0,0 +1,34 @@ + '{username}', + 'apiKey' => '{apiKey}', +)); + +// 2. Obtain an CDN service object from the client. +$cdnService = $client->cdnService(); + +// 3. Get flavor. +$flavor = $cdnService->getFlavor('{flavorId}'); +/** @var $flavor OpenCloud\CDN\Resource\Flavor **/ diff --git a/samples/CDN/get-service.php b/samples/CDN/get-service.php new file mode 100644 index 000000000..113d4ae24 --- /dev/null +++ b/samples/CDN/get-service.php @@ -0,0 +1,34 @@ + '{username}', + 'apiKey' => '{apiKey}', +)); + +// 2. Obtain an CDN service object from the client. +$cdnService = $client->cdnService(); + +// 3. Get service. +$service = $cdnService->getService('{serviceId}'); +/** @var $service OpenCloud\CDN\Resource\Service **/ diff --git a/samples/CDN/list-flavors.php b/samples/CDN/list-flavors.php new file mode 100644 index 000000000..8ddf9c4ce --- /dev/null +++ b/samples/CDN/list-flavors.php @@ -0,0 +1,36 @@ + '{username}', + 'apiKey' => '{apiKey}', +)); + +// 2. Obtain an CDN service object from the client. +$cdnService = $client->cdnService(); + +// 3. Get flavor list. +$flavors = $cdnService->listFlavors(); +foreach ($flavors as $flavor) { + /** @var $flavor OpenCloud\CDN\Resource\Flavor **/ +} diff --git a/samples/CDN/list-services.php b/samples/CDN/list-services.php new file mode 100644 index 000000000..67f5d63a4 --- /dev/null +++ b/samples/CDN/list-services.php @@ -0,0 +1,36 @@ + '{username}', + 'apiKey' => '{apiKey}', +)); + +// 2. Obtain an CDN service object from the client. +$cdnService = $client->cdnService(); + +// 3. Get service list. +$services = $cdnService->listServices(); +foreach ($services as $service) { + /** @var $service OpenCloud\CDN\Resource\Service **/ +} diff --git a/samples/CDN/purge-cached-service-asset.php b/samples/CDN/purge-cached-service-asset.php new file mode 100644 index 000000000..fdb2f97c4 --- /dev/null +++ b/samples/CDN/purge-cached-service-asset.php @@ -0,0 +1,36 @@ + '{username}', + 'apiKey' => '{apiKey}', +)); + +// 2. Obtain an CDN service object from the client. +$cdnService = $client->cdnService(); + +// 3. Get service. +$service = $cdnService->getService('{serviceId}'); + +// 4. Purge a specific asset belonging to service. +$service->purgeAssets('{assetUrl}'); diff --git a/samples/CDN/purge-cached-service-assets.php b/samples/CDN/purge-cached-service-assets.php new file mode 100644 index 000000000..afc9cb1c6 --- /dev/null +++ b/samples/CDN/purge-cached-service-assets.php @@ -0,0 +1,36 @@ + '{username}', + 'apiKey' => '{apiKey}', +)); + +// 2. Obtain an CDN service object from the client. +$cdnService = $client->cdnService(); + +// 3. Get service. +$service = $cdnService->getService('{serviceId}'); + +// 4. Purge all assets belonging to service. +$service->purgeAssets(); diff --git a/samples/CDN/update-service.php b/samples/CDN/update-service.php new file mode 100644 index 000000000..8f5c9db6f --- /dev/null +++ b/samples/CDN/update-service.php @@ -0,0 +1,44 @@ + '{username}', + 'apiKey' => '{apiKey}', +)); + +// 2. Obtain an CDN service object from the client. +$cdnService = $client->cdnService(); + +// 3. Get service. +$service = $cdnService->getService('{serviceId}'); + +// 4. Update it. +$service->update(array( + 'origins' => array( + array( + 'origin' => '44.33.22.11', + 'port' => 80, + 'ssl' => false + ) + ) +)); diff --git a/tests/OpenCloud/Smoke/Runner.php b/tests/OpenCloud/Smoke/Runner.php index d1925ba83..57dc8852a 100644 --- a/tests/OpenCloud/Smoke/Runner.php +++ b/tests/OpenCloud/Smoke/Runner.php @@ -37,6 +37,7 @@ class Runner 'Autoscale', 'Compute', 'CloudMonitoring', + 'CDN', 'DNS', 'Database', 'Identity', diff --git a/tests/OpenCloud/Smoke/Unit/CDN.php b/tests/OpenCloud/Smoke/Unit/CDN.php new file mode 100644 index 000000000..da828676b --- /dev/null +++ b/tests/OpenCloud/Smoke/Unit/CDN.php @@ -0,0 +1,100 @@ +getConnection()->cdnService(); + return $service; + } + + public function main() + { + $this->step('Get home document'); + $homeDocument = $this->getService()->getHomeDocument(); + $this->stepInfo('Home document: %s', json_encode($homeDocument)); + + $this->step('Get ping'); + $ping = $this->getService()->getPing(); + $this->stepInfo('Ping successful'); + + $this->step('List flavors'); + $flavors = $this->getService()->listFlavors(); + $this->stepInfo('%-40s | %s', 'Flavor ID', 'Number of providers'); + $this->stepInfo('%-40s | %s', str_repeat('-', 40), str_repeat('-', 40)); + foreach ($flavors as $flavor) { + $this->stepInfo('%-40s | %d', $flavor->getId(), count($flavor->getProviders())); + } + + $this->step('Create service'); + $createdService = $this->getService()->createService(array( + 'name' => 'php-opencloud.com', + 'domains' => array( + array( 'domain' => 'php-opencloud.com' ), + array( 'domain' => 'www.php-opencloud.com' ) + ), + 'origins' => array( + array( 'origin' => 'origin.php-opencloud.com' ) + ), + 'flavorId' => 'cdn' + )); + $this->stepInfo('Service name: ' . $createdService->getName()); + + $this->step('List services'); + $services = $this->getService()->listServices(); + $this->stepInfo('%-40s | %s', 'Service Name', 'Number of domains'); + $this->stepInfo('%-40s | %s', str_repeat('-', 40), str_repeat('-', 40)); + foreach ($services as $service) { + $this->stepInfo('%-40s | %d', $service->getName(), count($service->getDomains())); + } + + $this->step('Get service'); + $service = $this->getService()->getService($createdService->getId()); + $this->stepInfo('Service name: ' . $service->getName()); + $this->stepInfo('Status: ' . $service->getStatus()); + $origins = $service->getOrigins(); + $this->stepInfo('Origin: ' . $origins[0]->origin); + + $this->step('Update service'); + $service->waitFor('deployed', null, function ($s) { + $this->stepInfo('Service is still being created. Waiting...'); + }); + $service->update(array( + 'origins' => array( + array( 'origin' => 'updated-origin.php-opencloud.com' ) + ) + )); + + $this->step('Purge ALL cached service assets'); + $service->waitFor('deployed', null, function ($s) { + $this->stepInfo('Service is still being updated. Waiting...'); + }); + $service->purgeAssets(); + + $this->step('Delete service'); + $createdService->delete(); + } + + public function teardown() + { + } +} diff --git a/tests/OpenCloud/Tests/CDN/CDNTestCase.php b/tests/OpenCloud/Tests/CDN/CDNTestCase.php new file mode 100644 index 000000000..4f07d58f4 --- /dev/null +++ b/tests/OpenCloud/Tests/CDN/CDNTestCase.php @@ -0,0 +1,49 @@ +service = $this->getClient()->cdnService(); + + $this->addMockSubscriber($this->makeResponse('{"name":"mywebsite.com","domains":[{"domain":"blog.mywebsite.com"}],"origins":[{"origin":"mywebsite.com","port":80,"ssl":false},{"origin":"77.66.55.44","port":80,"ssl":false,"rules":[{"name":"videos","request_url":"^/videos/*.m3u"}]}],"caching":[{"name":"default","ttl":3600},{"name":"home","ttl":17200,"rules":[{"name":"index","request_url":"/index.htm"}]},{"name":"images","ttl":12800,"rules":[{"name":"images","request_url":"*.png"}]}],"restrictions":[{"name":"website only","rules":[{"name":"mywebsite.com","http_host":"www.mywebsite.com"}]}],"flavor_id":"cdn","status":"deployed","links":[{"href":"https://global.cdn.api.rackspacecloud.com/v1.0/services/mywebsite.com","rel":"self"},{"href":"mywebsite.com","rel":"access_url"}]}')); + $this->serviceResource = $this->service->getService('mywebsite.com'); + } + + protected function assertIsService($object) + { + $this->assertInstanceOf('OpenCloud\CDN\Service', $object); + } + + protected function assertIsServiceResource($object) + { + $this->assertInstanceOf('OpenCloud\CDN\Resource\Service', $object); + } + + protected function assertIsFlavorResource($object) + { + $this->assertInstanceOf('OpenCloud\CDN\Resource\Flavor', $object); + } +} diff --git a/tests/OpenCloud/Tests/CDN/Resource/ServiceTest.php b/tests/OpenCloud/Tests/CDN/Resource/ServiceTest.php new file mode 100644 index 000000000..37bd61a50 --- /dev/null +++ b/tests/OpenCloud/Tests/CDN/Resource/ServiceTest.php @@ -0,0 +1,40 @@ +addMockSubscriber($this->makeResponse(null, 202)); + + $actualResponse = $this->serviceResource->purgeAssets('/images/foo.png'); + $this->assertEquals(202, $actualResponse->getStatusCode()); + } + + public function testPurgeAllAssets() + { + $this->addMockSubscriber($this->makeResponse(null, 202)); + + $actualResponse = $this->serviceResource->purgeAssets(); + $this->assertEquals(202, $actualResponse->getStatusCode()); + } +} diff --git a/tests/OpenCloud/Tests/CDN/ServiceTest.php b/tests/OpenCloud/Tests/CDN/ServiceTest.php new file mode 100644 index 000000000..b3a2bb7f7 --- /dev/null +++ b/tests/OpenCloud/Tests/CDN/ServiceTest.php @@ -0,0 +1,101 @@ +getClient()->cdnService(); + $this->assertIsService($service); + } + + public function testCreateService() + { + $this->assertIsServiceResource($this->service->createService(array( + 'name' => 'mywebsite' + ))); + } + + public function testListServices() + { + $this->addMockSubscriber($this->makeResponse('{"links":[{"rel":"next","href":"https://global.cdn.api.rackspacecloud.com/v1.0/services?marker=www.myothersite.com&limit=20"}],"services":[{"name":"mywebsite.com","domains":[{"domain":"www.mywebsite.com"}],"origins":[{"origin":"mywebsite.com","port":80,"ssl":false}],"caching":[{"name":"default","ttl":3600},{"name":"home","ttl":17200,"rules":[{"name":"index","request_url":"/index.htm"}]},{"name":"images","ttl":12800,"rules":[{"name":"images","request_url":"*.png"}]}],"restrictions":[{"name":"website only","rules":[{"name":"mywebsite.com","http_host":"www.mywebsite.com"}]}],"flavor_id":"cdn","status":"deployed","links":[{"href":"https://global.cdn.api.rackspacecloud.com/v1.0/services/mywebsite.com","rel":"self"},{"href":"mywebsite.com","rel":"access_url"}]},{"name":"myothersite.com","domains":[{"domain":"www.myothersite.com"}],"origins":[{"origin":"44.33.22.11","port":80,"ssl":false},{"origin":"77.66.55.44","port":80,"ssl":false,"rules":[{"name":"videos","request_url":"^/videos/*.m3u"}]}],"caching":[{"name":"default","ttl":3600}],"restrictions":[{}],"flavor_id":"cdn","status":"deployed","links":[{"href":"https://global.cdn.api.rackspacecloud.com/v1.0/services/myothersite.com","rel":"self"},{"href":"myothersite.com","rel":"access_url"}]}]}')); + + $services = $this->service->listServices(); + $this->isCollection($services); + $this->assertIsServiceResource($services->getElement(0)); + } + + public function testGetService() + { + $this->addMockSubscriber($this->makeResponse('{"name":"mywebsite.com","domains":[{"domain":"blog.mywebsite.com"}],"origins":[{"origin":"mywebsite.com","port":80,"ssl":false},{"origin":"77.66.55.44","port":80,"ssl":false,"rules":[{"name":"videos","request_url":"^/videos/*.m3u"}]}],"caching":[{"name":"default","ttl":3600},{"name":"home","ttl":17200,"rules":[{"name":"index","request_url":"/index.htm"}]},{"name":"images","ttl":12800,"rules":[{"name":"images","request_url":"*.png"}]}],"restrictions":[{"name":"website only","rules":[{"name":"mywebsite.com","http_host":"www.mywebsite.com"}]}],"flavor_id":"cdn","status":"deployed","links":[{"href":"https://global.cdn.api.rackspacecloud.com/v1.0/services/mywebsite.com","rel":"self"},{"href":"mywebsite.com","rel":"access_url"}]}')); + + $service = $this->service->getService('mywebsite.com'); + $this->assertIsServiceResource($service); + $this->assertEquals('mywebsite.com', $service->getName()); + $this->assertEquals('cdn', $service->getFlavorId()); + } + + public function testCreateFlavor() + { + $this->assertIsFlavorResource($this->service->createFlavor(array( + 'id' => 'asia' + ))); + } + + public function testListFlavors() + { + $this->addMockSubscriber($this->makeResponse('{"flavors":[{"id":"cdn","providers":[{"provider":"akamai","links":[{"href":"http://www.akamai.com","rel":"provider_url"}]}],"links":[{"href":"https://global.cdn.api.rackspacecloud.com/v1.0/flavors/cdn","rel":"self"}]} ]}')); + + $flavors = $this->service->listFlavors(); + $this->isCollection($flavors); + $this->assertIsFlavorResource($flavors->getElement(0)); + } + + public function testGetFlavor() + { + $this->addMockSubscriber($this->makeResponse('{"id":"cdn","providers":[{"provider":"akamai","links":[{"href":"http://www.akamai.com","rel":"provider_url"}]}],"links":[{"href":"http://preview.cdn.api.rackspacecloud.com/v1.0/flavors/cdn","rel":"self"}]}')); + + $flavor = $this->service->getFlavor('cdn'); + $this->assertIsFlavorResource($flavor); + $this->assertEquals('cdn', $flavor->getId()); + + $providers = $flavor->getProviders(); + $this->assertEquals('akamai', $providers[0]->provider); + } + + public function testGetHomeDocument() + { + $this->addMockSubscriber($this->makeResponse('{"resources":{"rel/cdn":{"href-template":"services{?marker,limit}","href-vars":{"marker":"param/marker","limit":"param/limit"},"hints":{"allow":["GET"],"formats":{"application/json":{}}}}}}')); + + $homeDocument = $this->service->getHomeDocument(); + $this->assertNotEmpty($homeDocument); + $this->assertEquals("services{?marker,limit}", $homeDocument->resources->{"rel/cdn"}->{"href-template"}); + } + + public function testGetPing() + { + $this->addMockSubscriber($this->makeResponse(null, 204)); + + $ping = $this->service->getPing(); + $this->assertEquals(204, $ping->getStatusCode()); + } +} diff --git a/tests/OpenCloud/Tests/Common/BaseTest.php b/tests/OpenCloud/Tests/Common/BaseTest.php index c0a1c44ea..625863fb7 100644 --- a/tests/OpenCloud/Tests/Common/BaseTest.php +++ b/tests/OpenCloud/Tests/Common/BaseTest.php @@ -36,6 +36,11 @@ public function getBar() { return $this->bar; } + + public static function getPatchHeaders() + { + return parent::getPatchHeaders(); + } } class BaseTest extends \OpenCloud\Tests\OpenCloudTestCase @@ -100,4 +105,14 @@ public function testSetProperty() $this->my->setBaz('goodbye'); $this->assertEquals('goodbye', $this->my->getBaz()); } + + public function testGetPatchHeaders() + { + $expectedHeaders = array( + 'Content-Type' => 'application/json-patch+json' + ); + + $my = $this->my; + $this->assertEquals($expectedHeaders, $my::getPatchHeaders()); + } } diff --git a/tests/OpenCloud/Tests/Common/Resource/PersistentResourceTest.php b/tests/OpenCloud/Tests/Common/Resource/PersistentResourceTest.php index cd81b93ab..79d350e3b 100644 --- a/tests/OpenCloud/Tests/Common/Resource/PersistentResourceTest.php +++ b/tests/OpenCloud/Tests/Common/Resource/PersistentResourceTest.php @@ -27,10 +27,28 @@ class PublicPersistentResource extends PersistentResource 'foo_bar' => 'fooBar' ); + protected $updateKeys = array( + 'baz', + 'tags', + 'domains', + 'origins', + 'status' + ); + public function recursivelyAliasPropertyValue($propertyValue) { return parent::recursivelyAliasPropertyValue($propertyValue); } + + public function getUpdateablePropertiesAsArray() + { + return parent::getUpdateablePropertiesAsArray(); + } + + public function generateJsonPatch($updateParams) + { + return parent::generateJsonPatch($updateParams); + } } class PersistentResourceTest extends OpenCloudTestCase @@ -106,4 +124,70 @@ public function testRecursivelyAliasPropertyValueWithObjects() $this->assertEquals($obj3Expected, $this->persistentResource->recursivelyAliasPropertyValue($obj3)); } + + public function testGetUpdateablePropertiesAsArray() + { + $this->persistentResource->id = 17; + $this->persistentResource->tags = array('foo', 'bar'); + $this->persistentResource->domains = array( + (object) array('domain' => 'foo.phpopencloud.com'), + array('domain' => 'bar.phpopencloud.com') + ); + $this->persistentResource->origins = array( + array('origin' => 'origin1.phpopencloud.com') + ); + $this->persistentResource->status = (object) array('message' => 'Creation in progress'); + + $expectedArray = array( + 'tags' => array('foo', 'bar'), + 'domains' => array( + (object) array('domain' => 'foo.phpopencloud.com'), + array('domain' => 'bar.phpopencloud.com') + ), + 'origins' => array( + array('origin' => 'origin1.phpopencloud.com'), + ), + 'status' => (object) array('message' => 'Creation in progress') + ); + + $this->assertEquals($expectedArray, $this->persistentResource->getUpdateablePropertiesAsArray()); + } + + public function testGenerateJsonPatch() + { + $this->persistentResource->id = 17; + $this->persistentResource->tags = array('foo', 'bar'); + $this->persistentResource->domains = array( + array('domain' => 'foo.phpopencloud.com'), + array('domain' => 'bar.phpopencloud.com') + ); + $this->persistentResource->origins = array( + array('origin' => 'origin1.phpopencloud.com') + ); + $this->persistentResource->status = array('message' => 'Creation in progress'); + $this->persistentResource->baz = (object) array( 'fooBar' => 'barbar'); + + $updateParams = array( + 'tags' => array('foo', 'qux', 'baz'), + 'domains' => array( + array('domain' => 'foo.phpopencloud.com') + ), + 'origins' => array( + array('origin' => 'origin1.phpopencloud.com'), + array('origin' => 'origin2.phpopencloud.com') + ), + 'baz' => array('fooBar' => 'barbarbar') + ); + + $expectedJsonPatch = json_encode(array( + array('op' => 'replace', 'path' => '/baz/foo_bar', 'value' => 'barbarbar'), + array('op' => 'add', 'path' => '/tags/2', 'value' => 'baz'), + array('op' => 'replace', 'path' => '/tags/1', 'value' => 'qux'), + array('op' => 'remove', 'path' => '/domains/1'), + array('op' => 'add', 'path' => '/origins/1', 'value' => array("origin" => "origin2.phpopencloud.com")) + )); + + $actualJsonPatch = $this->persistentResource->generateJsonPatch($updateParams); + $this->assertEquals($expectedJsonPatch, $actualJsonPatch); + } } diff --git a/tests/OpenCloud/Tests/Image/Resource/ImageTest.php b/tests/OpenCloud/Tests/Image/Resource/ImageTest.php index 5b9fc03b4..4a7554edc 100644 --- a/tests/OpenCloud/Tests/Image/Resource/ImageTest.php +++ b/tests/OpenCloud/Tests/Image/Resource/ImageTest.php @@ -22,11 +22,19 @@ use OpenCloud\Image\Resource\Schema\Schema; use OpenCloud\Tests\OpenCloudTestCase; +class PublicImage extends Image +{ + public static function getPatchHeaders() + { + return parent::getPatchHeaders(); + } +} + class ImageTest extends OpenCloudTestCase { public function setupObjects() { - $this->image = new Image($this->getClient()->imageService('cloudImages', 'IAD')); + $this->image = new PublicImage($this->getClient()->imageService('cloudImages', 'IAD')); } protected function getSchemaData() @@ -139,4 +147,14 @@ public function test_Delete_Tag() $this->assertInstanceOf('Guzzle\Http\Message\Response', $this->image->deleteTag(12345)); } + + public function testGetPatchHeaders() + { + $expectedHeaders = array( + 'Content-Type' => 'application/openstack-images-v2.1-json-patch' + ); + + $image = $this->image; + $this->assertEquals($expectedHeaders, $image::getPatchHeaders()); + } } diff --git a/tests/OpenCloud/Tests/_response/Auth.resp b/tests/OpenCloud/Tests/_response/Auth.resp index 518a634e7..add5174dc 100644 --- a/tests/OpenCloud/Tests/_response/Auth.resp +++ b/tests/OpenCloud/Tests/_response/Auth.resp @@ -364,6 +364,17 @@ Front-End-Https: on } ], "type": "network" + }, + { + "name": "rackCDN", + "endpoints": [ + { + "tenantId": "123456", + "publicURL": "https://global.cdn.api.rackspacecloud.com/v1.0/123456", + "internalURL": "https://global.cdn.api.rackspacecloud.com/v1.0/123456" + } + ], + "type": "rax:cdn" } ], "token": {