diff --git a/lib/fog/bin/rackspace.rb b/lib/fog/bin/rackspace.rb index 30352a1c8..af0797184 100644 --- a/lib/fog/bin/rackspace.rb +++ b/lib/fog/bin/rackspace.rb @@ -8,6 +8,8 @@ class Rackspace < Fog::Bin Fog::Rackspace::BlockStorage when :cdn Fog::CDN::Rackspace + when :cdn_v2 + Fog::Rackspace::CDNV2 when :compute Fog::Compute::Rackspace when :compute_v2 @@ -43,6 +45,8 @@ class Rackspace < Fog::Bin when :cdn Fog::Logger.warning("Rackspace[:cdn] is not recommended, use CDN[:rackspace] for portability") Fog::CDN.new(:provider => 'Rackspace') + when :cdn_v2 + Fog::Rackspace::CDNV2.new when :compute Fog::Logger.warning("Rackspace[:compute] is not recommended, use Compute[:rackspace] for portability") Fog::Compute.new(:provider => 'Rackspace') diff --git a/lib/fog/rackspace.rb b/lib/fog/rackspace.rb index bf189e331..c7154e53e 100644 --- a/lib/fog/rackspace.rb +++ b/lib/fog/rackspace.rb @@ -1,6 +1,7 @@ require 'fog/rackspace/auto_scale' require 'fog/rackspace/block_storage' require 'fog/rackspace/cdn' +require 'fog/rackspace/cdn_v2' require 'fog/rackspace/compute' require 'fog/rackspace/compute_v2' require 'fog/rackspace/databases' diff --git a/lib/fog/rackspace/cdn_v2.rb b/lib/fog/rackspace/cdn_v2.rb new file mode 100644 index 000000000..49f3c0bce --- /dev/null +++ b/lib/fog/rackspace/cdn_v2.rb @@ -0,0 +1,141 @@ +require 'fog/rackspace/core' + +module Fog + module Rackspace + class CDNV2 < Fog::Service + requires :rackspace_api_key, :rackspace_username + recognizes :rackspace_auth_url, :persistent, :rackspace_cdn_ssl, :rackspace_region, :rackspace_cdn_url + + PREVIEW_API_URL = "preview.cdn.api.rackspacecloud.com" + + model_path 'fog/rackspace/models/cdn_v2' + model :service + collection :services + + model :flavor + collection :flavors + + request_path 'fog/rackspace/requests/cdn_v2' + request :create_service + request :delete_assets + request :delete_service + request :get_flavor + request :get_home_document + request :get_ping + request :get_service + request :list_flavors + request :list_services + request :update_service + + class ServiceError < Fog::Rackspace::Errors::ServiceError; end + class InternalServerError < Fog::Rackspace::Errors::InternalServerError; end + class BadRequest < Fog::Rackspace::Errors::BadRequest; end + + class Mock < Fog::Rackspace::Service + include Fog::Rackspace::MockData + + def initialize(options) + @rackspace_api_key = options[:rackspace_api_key] + end + + def request(params) + Fog::Mock.not_implemented + end + + def response(params={}) + body = params[:body] || {} + status = params[:status] || 200 + headers = params[:headers] || {} + + response = Excon::Response.new(:body => body, :headers => headers, :status => status) + if params.key?(:expects) && ![*params[:expects]].include?(response.status) + raise(Excon::Errors.status_error(params, response)) + else response + end + end + end + + class Real < Fog::Rackspace::Service + def initialize(options = {}) + @rackspace_api_key = options[:rackspace_api_key] + @rackspace_username = options[:rackspace_username] + @rackspace_auth_url = options[:rackspace_auth_url] + @rackspace_must_reauthenticate = false + @connection_options = options[:connection_options] || {} + + authenticate + + @persistent = options[:persistent] || false + @connection = Fog::Core::Connection.new(endpoint_uri.to_s, @persistent, @connection_options) + end + + def request(params, parse_json = true) + # TODO: This MUST be removed when CDNV2 is live + rewrite_cdn_v2_host! + # TODO: This MUST be removed when CDNV2 is live + + super + rescue Excon::Errors::NotFound => error + raise NotFound.slurp(error, self) + rescue Excon::Errors::BadRequest => error + raise BadRequest.slurp(error, self) + rescue Excon::Errors::InternalServerError => error + raise InternalServerError.slurp(error, self) + rescue Excon::Errors::HTTPStatusError => error + raise ServiceError.slurp(error, self) + end + + # TODO: This MUST be removed when CDNV2 is live + def rewrite_cdn_v2_host! + conn = @connection.instance_variable_get("@excon") + conn.data[:host] = PREVIEW_API_URL + conn.instance_variable_set("@socket_key", PREVIEW_API_URL) + + if conn.data.has_key?(:__construction_args) + conn.data[:__construction_args][:host] = PREVIEW_API_URL + end + end + # TODO: This MUST be removed when CDNV2 is live + + def request_uri(path, options={}) + return path if options == {} + require "addressable/uri" + Addressable::URI.new({:path=>path, :query_values=>options}).request_uri + end + + def ping + self.get_ping.status == 204 + end + + def home_document + self.get_home_document.body + end + + def authenticate(options={}) + super({ + :rackspace_api_key => @rackspace_api_key, + :rackspace_username => @rackspace_username, + :rackspace_auth_url => @rackspace_auth_url, + :connection_options => @connection_options + }) + end + + def service_name + :rackCDN + end + + def request_id_header + "x-raxcdn-id" + end + + def region + @rackspace_region + end + + def endpoint_uri(service_endpoint_url=nil) + @uri = super(@rackspace_endpoint || service_endpoint_url) + end + end + end + end +end diff --git a/lib/fog/rackspace/core.rb b/lib/fog/rackspace/core.rb index 7a91314e1..3e44781c7 100644 --- a/lib/fog/rackspace/core.rb +++ b/lib/fog/rackspace/core.rb @@ -88,6 +88,7 @@ module Fog service(:auto_scale, 'AutoScale') service(:block_storage, 'BlockStorage') service(:cdn, 'CDN') + service(:cdn_v2, 'CDN v2') service(:compute, 'Compute') service(:compute_v2, 'Compute v2') service(:dns, 'DNS') diff --git a/lib/fog/rackspace/docs/cdn_v2.md b/lib/fog/rackspace/docs/cdn_v2.md new file mode 100644 index 000000000..0c8fd7adf --- /dev/null +++ b/lib/fog/rackspace/docs/cdn_v2.md @@ -0,0 +1,351 @@ +#Rackspace CDNV2 + +This document explains how to get started using CDNV2 with Fog. It assumes you have read the [Getting Started with Fog and the Rackspace Open Cloud](getting_started.md) document. + +## Starting irb console + +Start by executing the following command: + + irb + +Once `irb` has launched you need to require the Fog library. + +If using Ruby 1.8.x execute: + + require 'rubygems' + require 'fog' + +If using Ruby 1.9.x execute: + + require 'fog' + +## Create Service + +Next, create a connection to Rackspace's CDNV2 API: + +Using a US-based account: + + service = Fog::Rackspace::CDNV2.new({ + :rackspace_username => RACKSPACE_USER_NAME, # Your Rackspace Username + :rackspace_api_key => RACKSPACE_API, # Your Rackspace API key + :rackspace_region => :ord, # Defaults to :dfw + }) + +Using a UK-based account: + + service = Fog::Compute.new({ + :rackspace_username => RACKSPACE_USER_NAME, # Your Rackspace Username + :rackspace_api_key => RACKSPACE_API, # Your Rackspace API key + :rackspace_auth_url => Fog::Rackspace::UK_AUTH_ENDPOINT, + :rackspace_region => :lon, + }) + +To learn more about obtaining cloud credentials refer to the [Getting Started with Fog and the Rackspace Open Cloud](getting_started.md) document. + +By default `Fog::Rackspace::CDNV2` will authenticate against the US authentication endpoint and connect to the DFW region. You can specify alternative authentication endpoints using the key `:rackspace_auth_url`. Please refer to [Alternate Authentication Endpoints](http://docs.rackspace.com/auth/api/v2.0/auth-client-devguide/content/Endpoints-d1e180.html) for a list of alternative Rackspace authentication endpoints. + +Alternative regions are specified using the key `:rackspace_region `. A list of regions available for Cloud Servers can be found by executing the following: + + identity_service = Fog::Identity({ + :provider => 'Rackspace', # Rackspace Fog provider + :rackspace_username => RACKSPACE_USER_NAME, # Your Rackspace Username + :rackspace_api_key => RACKSPACE_API, # Your Rackspace API key + :rackspace_auth_url => Fog::Rackspace::UK_AUTH_ENDPOINT # Not specified for US Cloud + }) + + identity_service.service_catalog.display_service_regions :cloudServersOpenStack + +### Optional Connection Parameters + +Fog supports passing additional connection parameters to its underlying HTTP library (Excon) using the `:connection_options` parameter. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyDescription
:connect_timeoutConnection timeout (default: 60 seconds)
:read_timeoutRead timeout for connection (default: 60 seconds)
:write_timeoutWrite timeout for connection (default: 60 seconds)
:proxyProxy for HTTP and HTTPS connections
:ssl_ca_pathPath to SSL certificate authorities
:ssl_ca_fileSSL certificate authority file
:ssl_verify_peerSSL verify peer (default: true)
+ + +## Fog Abstractions + +Fog provides both a **model** and **request** abstraction. The request abstraction provides the most efficient interface and the model abstraction wraps the request abstraction to provide a convenient `ActiveModel` like interface. + +### Request Layer + +The request abstraction maps directly to the [CDNV2 API](http://docs.rackspace.com/networks/api/v2/cn-devguide/content/ch_overview.html). It provides the most efficient interface to the Rackspace CDNV2 + +To see a list of requests supported by the service: + + service.requests + +This returns: + + [:create_service, :delete_assets, :delete_service, :get_flavor, :get_home_document, :get_ping, :get_service, :list_flavors, :list_services, :update_service] + +#### Example Request + +To request a list of services: + + response = service.list_services + +This returns in the following `Excon::Response`: + + #{"services"=>[{"name"=>"SomethingDifferent.net", "domains"=>[{"domain"=>"google.com", "protocol"=>"http"}], "origins"=>[{"origin"=>"google.com", "port"=>80, "ssl"=>false, "rules"=>[]}], "restrictions"=>[], "caching"=>[], "status"=>"create_in_progress", "flavor_id"=>"cdn", "errors"=>[] + +To view the status of the response: + + response.status + +**Note**: Fog is aware of valid HTTP response statuses for each request type. If an unexpected HTTP response status occurs, Fog will raise an exception. + +To view response body: + + response.body + +This will return: + + {"services"=>[{"name"=>"SomethingDifferent.net", "domains"=>[{"domain"=>"google.com",... + + +To learn more about CDNV2 request methods refer to [rdoc](http://www.rubydoc.info/gems/fog/Fog/Rackspace/CDNV2/Real). To learn more about Excon refer to [Excon GitHub repo](https://github.com/geemus/excon). + +### Model Layer + +Fog models behave in a manner similar to `ActiveModel`. Models will generally respond to `create`, `save`, `persisted?`, `destroy`, `reload` and `attributes` methods. Additionally, fog will automatically create attribute accessors. + +Here is a summary of common model methods: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodDescription
create + Accepts hash of attributes and creates object.
+ Note: creation is a non-blocking call and you will be required to wait for a valid state before using resulting object. +
saveSaves object.
+ Note: not all objects support updating object.
persisted?Returns true if the object has been persisted.
destroy + Destroys object.
+ Note: this is a non-blocking call and object deletion might not be instantaneous. +
reloadUpdates object with latest state from service.
ready?Returns true if object is in a ready state and able to perform actions. This method will raise an exception if object is in an error state.
attributesReturns a hash containing the list of model attributes and values.
identity + Returns the identity of the object.
+ Note: This might not always be equal to object.id. +
wait_forThis method periodically reloads model and then yields to specified block until block returns true or a timeout occurs.
+ +The remainder of this document details the model abstraction. + +## List Services + +To retrieve a list of available services: + + service.services + +This returns a collection of `Fog::Rackspace::CDNV2::Service` models: + + "google.com", "protocol"=>"http"}], + origins=[{"origin"=>"google.com", "port"=>80, "ssl"=>false, "rules"=>[]}], + caching=[], + restrictions=[], + flavor_id="cdn", + status="create_in_progress", + links=[{"href"=>"", "rel"=>"self"}, {"href"=>"...", "rel"=>"flavor"}] + ... + + +## Create Service + +Create a service: + + s = service.services.new + s.name = "work.com" + s.flavor_id = "cdn" + s.add_domain "google.com" + s.add_origin "google.com" + s.save + +## Update Service +You may add, remove, or update. -- **DOCS NEEDED** -- + + s = service.services.first + s.add_operation({ + op: "add", + path: "/domains/0", + value: { + origin: "cdn.somewhere.org", + port: 80, + ssl: false, + rules: [ + { + name: "Something", + request_url: "google.com" + } + ] + } + }) + + s.save + +## Get Service + +To retrieve individual service: + + service.services.get "087ffeb0-462d-4f44-b24a-2914fbfb1d42" + +This returns an `Fog::Rackspace::CDNV2::Service` instance: + + "google.com", "protocol"=>"http"}], + origins=[{"origin"=>"google.com", "port"=>80, "ssl"=>false, "rules"=>[]}], + caching=[], + restrictions=[], + flavor_id="cdn", + status="create_in_progress", + links=[{"href"=>"", "rel"=>"self"}, {"href"=>"...", "rel"=>"flavor"}] + +## Delete Service + +To delete a service: + + service.destroy + +**Note**: The service is not immediately destroyed, but it does occur shortly there after. + +## Delete Service Assets + +To delete a service's assets (or any owned asset via url): + + service.destroy_assets(url: "/") + +**Note**: The service's asset is not immediately destroyed, but it does occur shortly there after. + + +## List Flavors + +To retrieve a list of available flavors: + + service.flavors + +This returns a collection of `Fog::Rackspace::CDNV2::Flavor` models: + + "akamai", "links"=>[{"href"=>"http://www.akamai.com", "rel"=>"provider_url"}]}], + links=[{"href"=>"...", "rel"=>"self"}] + > + ] + > + +## Get Flavor + +To retrieve individual flavor: + + service.flavors.get "cdn" + +This returns an `Fog::Rackspace::CDNV2::Flavor` instance: + + "akamai", "links"=>[{"href"=>"http://www.akamai.com", "rel"=>"provider_url"}]}], + links=[{"href"=>"...", "rel"=>"self"}] + > + +## Ping + +To ping the CDN: + + service.ping + +This returns an boolean based on successful ping. + +## Get Home Document + +To retrieve the home document: + + service.home_document + +This returns a JSON blob that describes the home document. + +## Additional Resources + +* [fog.io](http://fog.io/) +* [Fog rdoc](http://rubydoc.info/gems/fog/) +* [Fog Github repo](https://github.com/fog/fog) +* [Fog Github Issues](https://github.com/fog/fog/issues) +* [Excon Github repo](https://github.com/geemus/excon) +* [Rackspace Networking API](http://docs.rackspace.com/networking/api/v2/cs-devguide/content/ch_preface.html) + +## Support and Feedback + +Your feedback is appreciated! If you have specific issues with the **fog** SDK, you should file an [issue via Github](https://github.com/fog/fog/issues). + +For general feedback and support requests, send an email to: . diff --git a/lib/fog/rackspace/models/cdn_v2/flavor.rb b/lib/fog/rackspace/models/cdn_v2/flavor.rb new file mode 100644 index 000000000..1ee54c3f2 --- /dev/null +++ b/lib/fog/rackspace/models/cdn_v2/flavor.rb @@ -0,0 +1,14 @@ +require 'fog/core/model' + +module Fog + module Rackspace + class CDNV2 < Fog::Service + class Flavor < Fog::Model + identity :id + + attribute :providers + attribute :links + end + end + end +end diff --git a/lib/fog/rackspace/models/cdn_v2/flavors.rb b/lib/fog/rackspace/models/cdn_v2/flavors.rb new file mode 100644 index 000000000..f6711097d --- /dev/null +++ b/lib/fog/rackspace/models/cdn_v2/flavors.rb @@ -0,0 +1,24 @@ +require 'fog/core/collection' +require 'fog/rackspace/models/cdn_v2/flavor' + +module Fog + module Rackspace + class CDNV2 < Fog::Service + class Flavors < Fog::Collection + model Fog::Rackspace::CDNV2::Flavor + + def all + data = service.list_flavors.body['flavors'] + load(data) + end + + def get(name) + data = service.get_flavor(name).body + new(data) + rescue Fog::Rackspace::CDNV2::NotFound + nil + end + end + end + end +end diff --git a/lib/fog/rackspace/models/cdn_v2/service.rb b/lib/fog/rackspace/models/cdn_v2/service.rb new file mode 100644 index 000000000..f5945bb7d --- /dev/null +++ b/lib/fog/rackspace/models/cdn_v2/service.rb @@ -0,0 +1,66 @@ +require 'fog/core/model' + +module Fog + module Rackspace + class CDNV2 < Fog::Service + class Service < Fog::Model + + attr_accessor :operations + + UUID_REGEX = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/ + + identity :id + + attribute :name + attribute :domains + attribute :origins + attribute :caching + attribute :restrictions + attribute :flavor_id + attribute :status + attribute :links + + def initialize(options={}) + self.operations = [] + super + end + + def add_domain(domain, options={}) + self.domains ||= [] + self.domains << {domain: domain}.merge(options) + self.domains + end + + def add_origin(origin, options={}) + self.origins ||= [] + self.origins << {origin: origin}.merge(options) + self.origins + end + + def add_operation(options={}) + self.operations << options + end + + def save + if id.nil? + data = service.create_service(self) + loc = data.headers["Location"] + id = UUID_REGEX.match(loc)[0] + merge_attributes(id: id) + else + service.update_service(self) + end + end + + def destroy + service.delete_service(self) + end + + def destroy_assets(options={}) + service.delete_assets(self, options) + end + + end + end + end +end diff --git a/lib/fog/rackspace/models/cdn_v2/services.rb b/lib/fog/rackspace/models/cdn_v2/services.rb new file mode 100644 index 000000000..b95962914 --- /dev/null +++ b/lib/fog/rackspace/models/cdn_v2/services.rb @@ -0,0 +1,24 @@ +require 'fog/core/collection' +require 'fog/rackspace/models/cdn_v2/service' + +module Fog + module Rackspace + class CDNV2 < Fog::Service + class Services < Fog::Collection + model Fog::Rackspace::CDNV2::Service + + def all(options={}) + data = service.list_services(options).body['services'] + load(data) + end + + def get(id) + data = service.get_service(id).body + new(data) + rescue Fog::Rackspace::CDNV2::NotFound + nil + end + end + end + end +end diff --git a/lib/fog/rackspace/requests/cdn_v2/create_service.rb b/lib/fog/rackspace/requests/cdn_v2/create_service.rb new file mode 100644 index 000000000..3bf539632 --- /dev/null +++ b/lib/fog/rackspace/requests/cdn_v2/create_service.rb @@ -0,0 +1,10 @@ +class Fog::Rackspace::CDNV2::Real + def create_service(service) + request( + :expects => [201, 202], + :method => 'POST', + :body => Fog::JSON.encode(service), + :path => "services" + ) + end +end diff --git a/lib/fog/rackspace/requests/cdn_v2/delete_assets.rb b/lib/fog/rackspace/requests/cdn_v2/delete_assets.rb new file mode 100644 index 000000000..0a318a390 --- /dev/null +++ b/lib/fog/rackspace/requests/cdn_v2/delete_assets.rb @@ -0,0 +1,12 @@ +class Fog::Rackspace::CDNV2::Real + def delete_assets(service, options={}) + uri = request_uri("services/#{service.id}/assets", options) + + request( + :expects => 202, + :method => 'DELETE', + :path => uri, + :headers => { :accept => "*/*" } + ) + end +end diff --git a/lib/fog/rackspace/requests/cdn_v2/delete_service.rb b/lib/fog/rackspace/requests/cdn_v2/delete_service.rb new file mode 100644 index 000000000..5803b6285 --- /dev/null +++ b/lib/fog/rackspace/requests/cdn_v2/delete_service.rb @@ -0,0 +1,9 @@ +class Fog::Rackspace::CDNV2::Real + def delete_service(service) + request( + :expects => 202, + :method => 'DELETE', + :path => "services/#{service.id}" + ) + end +end diff --git a/lib/fog/rackspace/requests/cdn_v2/get_flavor.rb b/lib/fog/rackspace/requests/cdn_v2/get_flavor.rb new file mode 100644 index 000000000..26b8a794d --- /dev/null +++ b/lib/fog/rackspace/requests/cdn_v2/get_flavor.rb @@ -0,0 +1,9 @@ +class Fog::Rackspace::CDNV2::Real + def get_flavor(id) + request( + :expects => [200], + :method => 'GET', + :path => "flavors/#{id}" + ) + end +end diff --git a/lib/fog/rackspace/requests/cdn_v2/get_home_document.rb b/lib/fog/rackspace/requests/cdn_v2/get_home_document.rb new file mode 100644 index 000000000..266cb03d3 --- /dev/null +++ b/lib/fog/rackspace/requests/cdn_v2/get_home_document.rb @@ -0,0 +1,9 @@ +class Fog::Rackspace::CDNV2::Real + def get_home_document + request( + :expects => [200], + :method => 'GET', + :path => "" + ) + end +end diff --git a/lib/fog/rackspace/requests/cdn_v2/get_ping.rb b/lib/fog/rackspace/requests/cdn_v2/get_ping.rb new file mode 100644 index 000000000..8ac4efa1b --- /dev/null +++ b/lib/fog/rackspace/requests/cdn_v2/get_ping.rb @@ -0,0 +1,10 @@ +class Fog::Rackspace::CDNV2::Real + def get_ping + request( + :expects => [204], + :method => 'GET', + :path => "ping", + :headers => { :accept => "*/*" } + ) + end +end diff --git a/lib/fog/rackspace/requests/cdn_v2/get_service.rb b/lib/fog/rackspace/requests/cdn_v2/get_service.rb new file mode 100644 index 000000000..bbfcb468f --- /dev/null +++ b/lib/fog/rackspace/requests/cdn_v2/get_service.rb @@ -0,0 +1,9 @@ +class Fog::Rackspace::CDNV2::Real + def get_service(id) + request( + :expects => 200, + :method => 'GET', + :path => "services/#{id}" + ) + end +end diff --git a/lib/fog/rackspace/requests/cdn_v2/list_flavors.rb b/lib/fog/rackspace/requests/cdn_v2/list_flavors.rb new file mode 100644 index 000000000..cda76ce07 --- /dev/null +++ b/lib/fog/rackspace/requests/cdn_v2/list_flavors.rb @@ -0,0 +1,9 @@ +class Fog::Rackspace::CDNV2::Real + def list_flavors + request( + :expects => [200], + :method => 'GET', + :path => "flavors" + ) + end +end diff --git a/lib/fog/rackspace/requests/cdn_v2/list_services.rb b/lib/fog/rackspace/requests/cdn_v2/list_services.rb new file mode 100644 index 000000000..8bb7a4c40 --- /dev/null +++ b/lib/fog/rackspace/requests/cdn_v2/list_services.rb @@ -0,0 +1,9 @@ +class Fog::Rackspace::CDNV2::Real + def list_services(options={}) + request( + :expects => [200], + :method => 'GET', + :path => request_uri("services", options) + ) + end +end diff --git a/lib/fog/rackspace/requests/cdn_v2/update_service.rb b/lib/fog/rackspace/requests/cdn_v2/update_service.rb new file mode 100644 index 000000000..22480127a --- /dev/null +++ b/lib/fog/rackspace/requests/cdn_v2/update_service.rb @@ -0,0 +1,10 @@ +class Fog::Rackspace::CDNV2::Real + def update_service(service) + request( + :expects => [201, 202], + :method => 'PATCH', + :body => Fog::JSON.encode(service.operations), + :path => "services/#{service.id}" + ) + end +end diff --git a/lib/fog/rackspace/requests/identity/create_token.rb b/lib/fog/rackspace/requests/identity/create_token.rb index c1eec7342..b0672b992 100644 --- a/lib/fog/rackspace/requests/identity/create_token.rb +++ b/lib/fog/rackspace/requests/identity/create_token.rb @@ -96,6 +96,8 @@ module Fog "https://storage101.#{r}#{Fog::Mock.random_numbers(1)}.clouddrive.com/v1/#{object_tenant}" end), + service_catalog_entry("rackCDN", "rax:cdn", compute_tenant, :rackspace_api_version => '1.0'), + service_catalog_entry("cloudMonitoring", "rax:monitor", compute_tenant, :single_endpoint => true, :rackspace_api_name => 'monitoring'),