diff --git a/lib/fog/bin/rackspace.rb b/lib/fog/bin/rackspace.rb index 68f85ddce..1ba1771fd 100644 --- a/lib/fog/bin/rackspace.rb +++ b/lib/fog/bin/rackspace.rb @@ -3,6 +3,8 @@ class Rackspace < Fog::Bin def class_for(key) case key + when :auto_scale + Fog::Rackspace::AutoScale when :block_storage Fog::Rackspace::BlockStorage when :cdn @@ -29,6 +31,8 @@ class Rackspace < Fog::Bin def [](service) @@connections ||= Hash.new do |hash, key| hash[key] = case key + when :auto_scale + Fog::Rackspace::AutoScale.new when :cdn Fog::Logger.warning("Rackspace[:cdn] is not recommended, use CDN[:rackspace] for portability") Fog::CDN.new(:provider => 'Rackspace') diff --git a/lib/fog/rackspace.rb b/lib/fog/rackspace.rb index 5ecd5dafe..63d5f2d5e 100644 --- a/lib/fog/rackspace.rb +++ b/lib/fog/rackspace.rb @@ -72,6 +72,7 @@ module Fog end end + service(:auto_scale, 'rackspace/auto_scale', 'AutoScale') service(:block_storage, 'rackspace/block_storage', 'BlockStorage') service(:cdn, 'rackspace/cdn', 'CDN') service(:compute, 'rackspace/compute', 'Compute') diff --git a/lib/fog/rackspace/auto_scale.rb b/lib/fog/rackspace/auto_scale.rb new file mode 100644 index 000000000..0cc913856 --- /dev/null +++ b/lib/fog/rackspace/auto_scale.rb @@ -0,0 +1,103 @@ +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'rackspace')) + +module Fog + module Rackspace + class AutoScale < Fog::Service + include Fog::Rackspace::Errors + + class ServiceError < Fog::Rackspace::Errors::ServiceError; end + class InternalServerError < Fog::Rackspace::Errors::InternalServerError; end + + class BadRequest < Fog::Rackspace::Errors::BadRequest + attr_reader :validation_errors + + def self.slurp(error) + if error && error.response + status_code = error.response.status + if error.response.body + body = Fog::JSON.decode(error.response.body) + message = "#{body['type']} - #{body['message']}" + details = error.response.body['details'] + end + end + + new_error = new(message) + new_error.set_backtrace(error.backtrace) + new_error.instance_variable_set(:@validation_errors, details) + new_error.instance_variable_set(:@status_code, status_code) + + new_error + end + end + + requires :rackspace_username, :rackspace_api_key + recognizes :rackspace_auth_url + recognizes :rackspace_region + recognizes :rackspace_auto_scale_url + + model_path 'fog/rackspace/models/auto_scale' + + request_path 'fog/rackspace/requests/auto_scale' + request :list_groups + request :get_group + request :create_group + request :delete_group + request :get_group_state + + request :update_config + request :get_config + + class Mock < Fog::Rackspace::Service + def initialize(options) + @rackspace_api_key = options[:rackspace_api_key] + end + + def request(params) + Fog::Mock.not_implemented + end + end + + + class Real < Fog::Rackspace::Service + + def initialize(options = {}) + @options = options + @options[:connection_options] ||= {} + @options[:persistent] ||= false + + authenticate + + @connection = Fog::Connection.new(endpoint_uri.to_s, @options[:persistent], @options[:connection_options]) + end + + def request(params, parse_json = true, &block) + super(params, parse_json, &block) + rescue Excon::Errors::NotFound => error + raise NotFound.slurp(error, region) + rescue Excon::Errors::BadRequest => error + raise BadRequest.slurp error + rescue Excon::Errors::InternalServerError => error + raise InternalServerError.slurp error + rescue Excon::Errors::HTTPStatusError => error + raise ServiceError.slurp error + end + + def endpoint_uri(service_endpoint_url=nil) + @uri = super(@options[:rackspace_auto_scale_url], :rackspace_auto_scale_url) + end + + def authenticate(options={}) + super(select_options([:rackspace_username, :rackspace_api_key, :rackspace_auth_url, :connection_options])) + end + + def service_name + :autoscale + end + + def region + @options[:rackspace_region] + end + end + end + end +end \ No newline at end of file diff --git a/lib/fog/rackspace/requests/auto_scale/create_group.rb b/lib/fog/rackspace/requests/auto_scale/create_group.rb new file mode 100644 index 000000000..aad0c1017 --- /dev/null +++ b/lib/fog/rackspace/requests/auto_scale/create_group.rb @@ -0,0 +1,88 @@ +module Fog + module Rackspace + class AutoScale + class Real + + def create_group + request( + :expects => [201], + :method => 'POST', + :path => 'groups', + :body => <<-JSON + { + \"groupConfiguration\": { + \"name\": \"workers2\", + \"cooldown\": 60, + \"minEntities\": 5, + \"maxEntities\": 25, + \"metadata\": { + \"firstkey\": \"this is a string\", + \"secondkey\": \"1\" + } + }, + \"launchConfiguration\": { + \"type\": \"launch_server\", + \"args\": { + \"server\": { + \"flavorRef\": 3, + \"name\": \"webhead\", + \"imageRef\": \"0d589460-f177-4b0f-81c1-8ab8903ac7d8\", + \"OS-DCF:diskConfig\": \"AUTO\", + \"metadata\": { + \"mykey\": \"myvalue\" + }, + \"personality\": [ + { + \"path\": \"/root/.ssh/authorized_keys\", + \"contents\": \"ssh-rsa AAAAB3Nza...LiPk== user@example.net\" + } + ], + \"networks\": [ + { + \"uuid\": \"11111111-1111-1111-1111-111111111111\" + } + ] + }, + \"loadBalancers\": [ + { + \"loadBalancerId\": 2200, + \"port\": 8081 + } + ] + } + }, + \"scalingPolicies\": [ + { + \"name\": \"scale up by 10\", + \"change\": 10, + \"cooldown\": 5, + \"type\": \"webhook\" + }, + { + \"name\": \"scale down by 5.5 percent\", + \"changePercent\": -5.5, + \"cooldown\": 6, + \"type\": \"webhook\" + }, + { + \"name\": \"set number of servers to 10\", + \"desiredCapacity\": 10, + \"cooldown\": 3, + \"type\": \"webhook\" + } + ] + } + JSON + ) + end + end + + class Mock + def create_group + Fog::Mock.not_implemented + + end + end + end + end +end diff --git a/lib/fog/rackspace/requests/auto_scale/delete_group.rb b/lib/fog/rackspace/requests/auto_scale/delete_group.rb new file mode 100644 index 000000000..698f815f7 --- /dev/null +++ b/lib/fog/rackspace/requests/auto_scale/delete_group.rb @@ -0,0 +1,22 @@ +module Fog + module Rackspace + class AutoScale + class Real + + def delete_group(group_id) + request( + :expects => [204], + :method => 'DELETE', + :path => "groups/#{group_id}" + ) + end + end + + class Mock + def delete_group(group_id) + Fog::Mock.not_implemented + end + end + end + end +end diff --git a/lib/fog/rackspace/requests/auto_scale/get_config.rb b/lib/fog/rackspace/requests/auto_scale/get_config.rb new file mode 100644 index 000000000..ca9be79bc --- /dev/null +++ b/lib/fog/rackspace/requests/auto_scale/get_config.rb @@ -0,0 +1,24 @@ +module Fog + module Rackspace + class AutoScale + + class Real + + def get_config(group_id) + request( + :expects => [200], + :method => 'GET', + :path => "groups/#{group_id}/config", + ) + end + end + + class Mock + def get_config(group_id) + Fog::Mock.not_implemented + end + end + + end + end +end \ No newline at end of file diff --git a/lib/fog/rackspace/requests/auto_scale/get_group.rb b/lib/fog/rackspace/requests/auto_scale/get_group.rb new file mode 100644 index 000000000..7faae3f90 --- /dev/null +++ b/lib/fog/rackspace/requests/auto_scale/get_group.rb @@ -0,0 +1,24 @@ +module Fog + module Rackspace + class AutoScale + + class Real + + def get_group(group_id) + request( + :expects => [200], + :method => 'GET', + :path => "groups/#{group_id}", + ) + end + end + + class Mock + def get_group(group_id) + Fog::Mock.not_implemented + end + end + + end + end +end \ No newline at end of file diff --git a/lib/fog/rackspace/requests/auto_scale/get_group_state.rb b/lib/fog/rackspace/requests/auto_scale/get_group_state.rb new file mode 100644 index 000000000..66ca58b8b --- /dev/null +++ b/lib/fog/rackspace/requests/auto_scale/get_group_state.rb @@ -0,0 +1,24 @@ +module Fog + module Rackspace + class AutoScale + + class Real + + def get_group_state(group_id) + request( + :expects => [200], + :method => 'GET', + :path => "groups/#{group_id}/state", + ) + end + end + + class Mock + def get_group_state(group_id) + Fog::Mock.not_implemented + end + end + + end + end +end \ No newline at end of file diff --git a/lib/fog/rackspace/requests/auto_scale/list_groups.rb b/lib/fog/rackspace/requests/auto_scale/list_groups.rb new file mode 100644 index 000000000..806a4ef72 --- /dev/null +++ b/lib/fog/rackspace/requests/auto_scale/list_groups.rb @@ -0,0 +1,35 @@ +module Fog + module Rackspace + class AutoScale + class Real + + # Retrieves a list of images + # @return [Excon::Response] response: + # * body [Hash]: + # * images [Array]: + # * [Hash]: + # * id [String] - flavor id + # * links [Array] - image links + # * name [String] - image name + # @raise [Fog::Compute::RackspaceV2::NotFound] - HTTP 404 + # @raise [Fog::Compute::RackspaceV2::BadRequest] - HTTP 400 + # @raise [Fog::Compute::RackspaceV2::InternalServerError] - HTTP 500 + # @raise [Fog::Compute::RackspaceV2::ServiceError] + # @see http://docs.rackspace.com/servers/api/v2/cs-devguide/content/List_Flavors-d1e4188.html + def list_groups + request( + :expects => [200], + :method => 'GET', + :path => 'groups' + ) + end + end + + class Mock + def list_groups + Fog::Mock.not_implemented + end + end + end + end +end diff --git a/lib/fog/rackspace/requests/auto_scale/update_config.rb b/lib/fog/rackspace/requests/auto_scale/update_config.rb new file mode 100644 index 000000000..6cdf577bd --- /dev/null +++ b/lib/fog/rackspace/requests/auto_scale/update_config.rb @@ -0,0 +1,37 @@ +module Fog + module Rackspace + class AutoScale + + class Real + + def update_config(group_id) + + h = { + "name" => "workers", + "cooldown" => 60, + "minEntities" => 0, + "maxEntities" => 0, + "metadata" => { + "firstkey" => "this is a string", + "secondkey" => "1" + } + } + + request( + :expects => [204], + :method => 'PUT', + :path => "groups/#{group_id}/config", + :body => Fog::JSON.encode(h) + ) + end + end + + class Mock + def update_config(group_id) + Fog::Mock.not_implemented + end + end + + end + end +end \ No newline at end of file diff --git a/lib/fog/rackspace/service.rb b/lib/fog/rackspace/service.rb index 8649cf273..a60d7dfaa 100644 --- a/lib/fog/rackspace/service.rb +++ b/lib/fog/rackspace/service.rb @@ -116,6 +116,11 @@ module Fog @auth_token || @identity_service.auth_token end + def select_options(keys) + return nil unless @options && keys + @options.select {|k,v| keys.include?(k)} + end + end end end diff --git a/tests/rackspace/auto_scale_tests.rb b/tests/rackspace/auto_scale_tests.rb new file mode 100644 index 000000000..6b41614a7 --- /dev/null +++ b/tests/rackspace/auto_scale_tests.rb @@ -0,0 +1,84 @@ +Shindo.tests('Fog::Rackspace::AutoScale', ['rackspace']) do + + def assert_method(url, method) + @service.instance_variable_set "@rackspace_auth_url", url + returns(method) { @service.send :authentication_method } + end + + tests('#authentication_method') do + @service = Fog::Rackspace::AutoScale.new :rackspace_region => :dfw + + assert_method nil, :authenticate_v2 + + assert_method 'auth.api.rackspacecloud.com', :authenticate_v1 # chef's default auth endpoint + + assert_method 'https://identity.api.rackspacecloud.com', :authenticate_v1 + assert_method 'https://identity.api.rackspacecloud.com/v1', :authenticate_v1 + assert_method 'https://identity.api.rackspacecloud.com/v1.1', :authenticate_v1 + assert_method 'https://identity.api.rackspacecloud.com/v2.0', :authenticate_v2 + + assert_method 'https://lon.identity.api.rackspacecloud.com', :authenticate_v1 + assert_method 'https://lon.identity.api.rackspacecloud.com/v1', :authenticate_v1 + assert_method 'https://lon.identity.api.rackspacecloud.com/v1.1', :authenticate_v1 + assert_method 'https://lon.identity.api.rackspacecloud.com/v2.0', :authenticate_v2 + end + + + tests('current authentation') do + pending if Fog.mocking? + + tests('variables populated').succeeds do + @service = Fog::Rackspace::AutoScale.new :rackspace_auth_url => 'https://identity.api.rackspacecloud.com/v2.0', :connection_options => {:ssl_verify_peer => true}, :rackspace_region => :dfw + returns(true, "auth token populated") { !@service.send(:auth_token).nil? } + returns(false, "path populated") { @service.instance_variable_get("@uri").host.nil? } + identity_service = @service.instance_variable_get("@identity_service") + returns(false, "identity service was used") { identity_service.nil? } + returns(true, "connection_options are passed") { identity_service.instance_variable_get("@connection_options").has_key?(:ssl_verify_peer) } + @service.list_groups + end + tests('dfw region').succeeds do + @service = Fog::Rackspace::AutoScale.new :rackspace_auth_url => 'https://identity.api.rackspacecloud.com/v2.0', :rackspace_region => :dfw + returns(true, "auth token populated") { !@service.send(:auth_token).nil? } + returns(true) { (@service.instance_variable_get("@uri").host =~ /dfw/) != nil } + @service.list_groups + end + tests('ord region').succeeds do + @service = Fog::Rackspace::AutoScale.new :rackspace_auth_url => 'https://identity.api.rackspacecloud.com/v2.0', :rackspace_region => :ord + returns(true, "auth token populated") { !@service.send(:auth_token).nil? } + returns(true) { (@service.instance_variable_get("@uri").host =~ /ord/) != nil } + @service.list_groups + end + tests('custom endpoint') do + @service = Fog::Rackspace::AutoScale.new :rackspace_auth_url => 'https://identity.api.rackspacecloud.com/v2.0', + :rackspace_auto_scale_url => 'https://my-custom-endpoint.com' + returns(true, "auth token populated") { !@service.send(:auth_token).nil? } + returns(true, "uses custom endpoint") { (@service.instance_variable_get("@uri").host =~ /my-custom-endpoint\.com/) != nil } + end + end + + tests('default auth') do + pending if Fog.mocking? + + tests('specify region').succeeds do + @service = Fog::Rackspace::AutoScale.new :rackspace_region => :ord + returns(true, "auth token populated") { !@service.send(:auth_token).nil? } + returns(true) { (@service.instance_variable_get("@uri").host =~ /ord/ ) != nil } + @service.list_groups + end + tests('custom endpoint') do + @service = Fog::Rackspace::AutoScale.new :rackspace_auto_scale_url => 'https://my-custom-endpoint.com' + returns(true, "auth token populated") { !@service.send(:auth_token).nil? } + returns(true, "uses custom endpoint") { (@service.instance_variable_get("@uri").host =~ /my-custom-endpoint\.com/) != nil } + end + end + + tests('reauthentication') do + pending if Fog.mocking? + + @service = Fog::Rackspace::AutoScale.new :rackspace_region => :ord + returns(true, "auth token populated") { !@service.send(:auth_token).nil? } + @service.instance_variable_set("@auth_token", "bad_token") + returns(true) { [200, 203].include? @service.list_groups.status } + end + +end \ No newline at end of file