diff --git a/lib/fog/rackspace/compute_v2.rb b/lib/fog/rackspace/compute_v2.rb index a86bba26f..79df7b6ef 100644 --- a/lib/fog/rackspace/compute_v2.rb +++ b/lib/fog/rackspace/compute_v2.rb @@ -54,6 +54,8 @@ module Fog collection :attachments model :network collection :networks + model :keypair + collection :keypairs request_path 'fog/rackspace/requests/compute_v2' request :list_servers @@ -97,6 +99,11 @@ module Fog request :create_network request :delete_network + request :list_keypairs + request :create_keypair + request :delete_keypair + request :get_keypair + class Mock < Fog::Rackspace::Service include Fog::Rackspace::MockData diff --git a/lib/fog/rackspace/mock_data.rb b/lib/fog/rackspace/mock_data.rb index 369fe6545..07aa4921c 100644 --- a/lib/fog/rackspace/mock_data.rb +++ b/lib/fog/rackspace/mock_data.rb @@ -8,10 +8,11 @@ module Fog @@data ||= Hash.new do |hash, key| hash[key] = begin #Compute V2 - flavor_id = Fog.credentials[:rackspace_flavor_id].to_s ||= Fog::Mock.random_numbers(1) - image_id = Fog.credentials[:rackspace_image_id] ||= Fog::Rackspace::MockData.uuid + flavor_id = Fog.credentials[:rackspace_flavor_id].to_s ||= Fog::Mock.random_numbers(1) + image_id = Fog.credentials[:rackspace_image_id] ||= Fog::Rackspace::MockData.uuid image_name = Fog::Mock.random_letters(6) network_id = Fog::Rackspace::MockData.uuid + user_id = Fog::Mock.random_numbers(6).to_s flavor = { "OS-FLV-EXT-DATA:ephemeral" => 4, @@ -88,6 +89,14 @@ module Fog 'cidr' => '192.168.0.0/24' } + key_pair = { + 'public_key' => "ssh-rsa ".concat(Fog::Mock.random_letters(372)).concat(" Generated by Nova\n"), + 'private_key' => "-----BEGIN RSA PRIVATE KEY-----\n".concat(Fog::Mock.random_letters(1635)).concat("\n-----END RSA PRIVATE KEY-----\n"), + 'user_id' => user_id, + 'name' => Fog::Mock.random_letters(32), + 'fingerprint' => "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00" + } + #Block Storage volume_type1_id = Fog::Mock.random_numbers(3).to_s volume_type2_id = Fog::Mock.random_numbers(3).to_s @@ -106,11 +115,13 @@ module Fog mock_data = { #Compute V2 - :flavors => Hash.new { |h,k| h[k] = flavor unless k == NOT_FOUND_ID}, - :images => Hash.new { |h,k| h[k] = image unless k == NOT_FOUND_ID }, + :flavors => Hash.new { |h,k| h[k] = flavor unless k == NOT_FOUND_ID }, + :images => Hash.new { |h,k| h[k] = image unless k == NOT_FOUND_ID }, :networks => Hash.new { |h,k| h[k] = network unless k == NOT_FOUND_ID }, - - :servers => {}, + :keys => [], + :keypair => key_pair, + :keypairs => [], + :servers => {}, #Block Storage :volumes => {}, @@ -123,7 +134,7 @@ module Fog mock_data[:flavors][flavor_id] = flavor mock_data[:images][image_id] = image mock_data[:networks][network_id] = network - + mock_data end end[@rackspace_api_key] diff --git a/lib/fog/rackspace/models/compute_v2/keypair.rb b/lib/fog/rackspace/models/compute_v2/keypair.rb new file mode 100644 index 000000000..ae4f7a606 --- /dev/null +++ b/lib/fog/rackspace/models/compute_v2/keypair.rb @@ -0,0 +1,56 @@ +require 'fog/core/model' + +module Fog + module Compute + class RackspaceV2 + class Keypair < Fog::Model + + # @!attribute [rw] name + # @return [String] the keypair name + identity :name + + # @!attribute [r] public_key + # @return [String] the public key + attribute :public_key + + # @!attribute [r] private_key + # @return [String] the private key + attribute :private_key + + # @!attribute [r] user_id + # @return [String] the user_id associated to + attribute :user_id + + # @!attribute [r] fingerprint + # @return [String] unique fingerprint + attribute :fingerprint + + # Creates a keypair + # @return [Boolean] true if the keypair is successfully created + # @raise [Fog::Compute::RackspaceV2::NotFound] + # @raise [Fog::Compute::RackspaceV2::BadRequest] + # @raise [Fog::Compute::RackspaceV2::InternalServerError] + # @raise [Fog::Compute::RackspaceV2::ServiceError] + def save + requires :name + data = service.create_keypair(name, public_key) + merge_attributes(data.body['keypair']) + data.body['keypair']['name'] == name + end + + # Destroys a keypair + # @return [Boolean] true if the keypair is successfully deleted + # @raise [Fog::Compute::RackspaceV2::NotFound] + # @raise [Fog::Compute::RackspaceV2::BadRequest] + # @raise [Fog::Compute::RackspaceV2::InternalServerError] + # @raise [Fog::Compute::RackspaceV2::ServiceError] + def destroy + requires :identity + service.delete_keypair(identity) + true + end + + end + end + end +end diff --git a/lib/fog/rackspace/models/compute_v2/keypairs.rb b/lib/fog/rackspace/models/compute_v2/keypairs.rb new file mode 100644 index 000000000..94bab546d --- /dev/null +++ b/lib/fog/rackspace/models/compute_v2/keypairs.rb @@ -0,0 +1,43 @@ +require 'fog/core/collection' +require 'fog/rackspace/models/compute_v2/keypair' + +module Fog + module Compute + class RackspaceV2 + + class Keypairs < Fog::Collection + + model Fog::Compute::RackspaceV2::Keypair + + # Fetch the list of known keypairs + # @return [Fog::Compute::RackspaceV2::Keypairs] the retreived keypairs + # @raise [Fog::Compute::RackspaceV2::NotFound] + # @raise [Fog::Compute::RackspaceV2::BadRequest] + # @raise [Fog::Compute::RackspaceV2::InternalServerError] + # @raise [Fog::Compute::RackspaceV2::ServiceError] + def all + data = [] + service.list_keypairs.body['keypairs'].each do |kp| + data << kp['keypair'] if kp['keypair'] + end + load(data) + end + + # Fetch keypair details + # @param [String] key_name: name of the key to request + # @return [Fog::Compute::RackspaceV2::Keypair] the requested keypair or 'nil' when not found + # @raise [Fog::Compute::RackspaceV2::BadRequest] + # @raise [Fog::Compute::RackspaceV2::InternalServerError] + # @raise [Fog::Compute::RackspaceV2::ServiceError] + def get(key_name) + begin + new(service.get_keypair(key_name).body['keypair']) + rescue Fog::Compute::RackspaceV2::NotFound + nil + end + end + + end + end + end +end diff --git a/lib/fog/rackspace/models/compute_v2/server.rb b/lib/fog/rackspace/models/compute_v2/server.rb index 193495e25..ce8c99dcf 100644 --- a/lib/fog/rackspace/models/compute_v2/server.rb +++ b/lib/fog/rackspace/models/compute_v2/server.rb @@ -51,6 +51,11 @@ module Fog # @return [String] server status. # @see http://docs.rackspace.com/servers/api/v2/cs-devguide/content/List_Servers-d1e2078.html#server_status attribute :state, :aliases => 'status' + + # @!attribute [r] state_ext + # @return [String] server (extended) status. + # @see http://docs.rackspace.com/servers/api/v2/cs-devguide/content/List_Servers-d1e2078.html#server_status + attribute :state_ext, :aliases => 'OS-EXT-STS:task_state' # @!attribute [r] progress # @return [Fixnum] The build completion progress, as a percentage. Value is from 0 to 100. @@ -198,11 +203,11 @@ module Fog options[:disk_config] = disk_config unless disk_config.nil? options[:metadata] = metadata.to_hash unless @metadata.nil? options[:personality] = personality unless personality.nil? + options[:keypair] ||= attributes[:keypair] if options[:networks] options[:networks].map! { |id| { :uuid => id } } end - data = service.create_server(name, image_id, flavor_id, 1, 1, options) merge_attributes(data.body['server']) true @@ -331,7 +336,7 @@ module Fog # Server's private IPv4 address # @return [String] private IPv4 address def private_ip_address - addresses['private'].select{|a| a["version"] == 4}[0]["addr"] + addresses['private'].select{|a| a["version"] == 4}[0]["addr"] rescue '' end # Server's public IPv4 address diff --git a/lib/fog/rackspace/requests/compute_v2/create_keypair.rb b/lib/fog/rackspace/requests/compute_v2/create_keypair.rb new file mode 100644 index 000000000..14473dbb8 --- /dev/null +++ b/lib/fog/rackspace/requests/compute_v2/create_keypair.rb @@ -0,0 +1,53 @@ +module Fog + module Compute + class RackspaceV2 + class Real + + # Request a new keypair to be created + # @param [String] key_name: unique name of the keypair to create + # @return [Excon::Response] response : + # * body [Hash]: - + # * 'keypair' [Hash]: - + # * 'fingerprint' [String]: unique fingerprint of the keypair + # * 'name' [String]: unique name of the keypair + # * 'private_key' [String]: the private key of the keypair (only available here, at creation time) + # * 'public_key' [String]: the public key of the keypair + # * 'user_id' [String]: the user id + # @raise [Fog::Compute::RackspaceV2::NotFound] + # @raise [Fog::Compute::RackspaceV2::BadRequest] + # @raise [Fog::Compute::RackspaceV2::InternalServerError] + # @raise [Fog::Compute::RackspaceV2::ServiceError] + # @see http://docs.rackspace.com/servers/api/v2/cs-devguide/content/CreateKeyPair.html + def create_keypair(key_name, public_key=nil) + data = { + 'keypair' => { + 'name' => key_name + } + } + + request( + :method => 'POST', + :expects => 200, + :path => '/os-keypairs', + :body => Fog::JSON.encode(data) + ) + end + end + + class Mock + def create_keypair(key_name, public_key=nil) + # 409 response when already existing + raise Fog::Compute::RackspaceV2::ServiceError if not self.data[:keypairs].select { |k| key_name.include? k['keypair']['name'] }.first.nil? + + k = self.data[:keypair] + k['name'] = key_name + self.data[:keypairs] << { 'keypair' => k } + + response( :status => 200, + :body => { 'keypair' => k } ) + end + end + + end + end +end diff --git a/lib/fog/rackspace/requests/compute_v2/create_server.rb b/lib/fog/rackspace/requests/compute_v2/create_server.rb index fb51f0780..d41357b03 100644 --- a/lib/fog/rackspace/requests/compute_v2/create_server.rb +++ b/lib/fog/rackspace/requests/compute_v2/create_server.rb @@ -12,12 +12,13 @@ module Fog # @option options [Hash] metadata key value pairs of server metadata # @option options [String] OS-DCF:diskConfig The disk configuration value. (AUTO or MANUAL) # @option options [Hash] personality Hash containing data to inject into the file system of the cloud server instance during server creation. + # @option options [String] keypair Name of the kay-pair to associate with this server. # @return [Excon::Response] response: # * body [Hash]: # * server [Hash]: # * name [String] - name of server # * imageRef [String] - id of image used to create server - # * flavorRef [String] - id of flavor used to create server + # * flavorRef [String] - id of flavor used to create server # * OS-DCF:diskConfig [String] - The disk configuration value. # * name [String] - name of server # * metadata [Hash] - Metadata key and value pairs. @@ -43,11 +44,11 @@ module Fog def create_server(name, image_id, flavor_id, min_count, max_count, options = {}) data = { 'server' => { - 'name' => name, - 'imageRef' => image_id, + 'name' => name, + 'imageRef' => image_id, 'flavorRef' => flavor_id, - 'minCount' => min_count, - 'maxCount' => max_count + 'minCount' => min_count, + 'maxCount' => max_count } } @@ -58,12 +59,13 @@ module Fog { :uuid => '00000000-0000-0000-0000-000000000000' }, { :uuid => '11111111-1111-1111-1111-111111111111' } ] + data['server']['key_name'] = options[:keypair] unless options[:keypair].nil? request( - :body => Fog::JSON.encode(data), + :body => Fog::JSON.encode(data), :expects => [202], - :method => 'POST', - :path => "servers" + :method => 'POST', + :path => "servers" ) end end diff --git a/lib/fog/rackspace/requests/compute_v2/delete_keypair.rb b/lib/fog/rackspace/requests/compute_v2/delete_keypair.rb new file mode 100644 index 000000000..0fd7b88df --- /dev/null +++ b/lib/fog/rackspace/requests/compute_v2/delete_keypair.rb @@ -0,0 +1,36 @@ +module Fog + module Compute + class RackspaceV2 + class Real + + # Delete the key specified with key_name + # @param [String] key_name: name of the key to delete + # @return [Excon::Response] response + # @raise [Fog::Compute::RackspaceV2::NotFound] + # @raise [Fog::Compute::RackspaceV2::BadRequest] + # @raise [Fog::Compute::RackspaceV2::InternalServerError] + # @raise [Fog::Compute::RackspaceV2::ServiceError] + # @see http://docs.rackspace.com/servers/api/v2/cs-devguide/content/DeleteKeyPair.html + def delete_keypair(key_name) + request( + :method => 'DELETE', + :expects => 202, + :path => "/os-keypairs/#{key_name}" + ) + end + end + + class Mock + def delete_keypair(key_name) + if self.data[:keypairs].select { |k| key_name.include? k['keypair']['name'] }.empty? + raise Fog::Compute::RackspaceV2::NotFound + else + self.data[:keypairs].reject! { |k| key_name.include? k['keypair']['name'] } + response(:status => 202) + end + end + end + + end + end +end diff --git a/lib/fog/rackspace/requests/compute_v2/get_keypair.rb b/lib/fog/rackspace/requests/compute_v2/get_keypair.rb new file mode 100644 index 000000000..a832ee60e --- /dev/null +++ b/lib/fog/rackspace/requests/compute_v2/get_keypair.rb @@ -0,0 +1,41 @@ +module Fog + module Compute + class RackspaceV2 + class Real + + # Retreive single keypair details + # @param [String] key_name: name of the key for which to request the details + # @return [Excon::Response] response : + # * body [Hash]: - + # * 'keypair' [Hash]: - + # * 'fingerprint' [String]: unique fingerprint of the keypair + # * 'name' [String]: unique name of the keypair + # * 'public_key' [String]: the public key assigne to the keypair + # @raise [Fog::Compute::RackspaceV2::NotFound] + # @raise [Fog::Compute::RackspaceV2::BadRequest] + # @raise [Fog::Compute::RackspaceV2::InternalServerError] + # @raise [Fog::Compute::RackspaceV2::ServiceError] + # @see http://docs.rackspace.com/servers/api/v2/cs-devguide/content/ListKeyPairs.html + def get_keypair(key_name) + request( + :method => 'GET', + :expects => 200, + :path => "/os-keypairs/#{key_name}" + ) + end + end + + class Mock + def get_keypair(key_name) + key = self.data[:keypairs].select { |k| key_name.include? k['keypair']['name'] }.first + if key.nil? + raise Fog::Compute::RackspaceV2::NotFound + end + + response(:body => key, :status => 200) + end + end + + end + end +end diff --git a/lib/fog/rackspace/requests/compute_v2/list_keypairs.rb b/lib/fog/rackspace/requests/compute_v2/list_keypairs.rb new file mode 100644 index 000000000..137f39032 --- /dev/null +++ b/lib/fog/rackspace/requests/compute_v2/list_keypairs.rb @@ -0,0 +1,37 @@ +module Fog + module Compute + class RackspaceV2 + class Real + + # Returns a list of all key pairs associated with an account. + # @return [Excon::Response] response : + # * body [Hash]: - + # * 'keypairs' [Array]: list of keypairs + # * 'keypair' [Hash]: - + # * 'fingerprint' [String]: unique fingerprint of the keypair + # * 'name' [String]: unique name of the keypair + # * 'public_key' [String]: the public key assigned to the keypair + # @raise [Fog::Compute::RackspaceV2::NotFound] + # @raise [Fog::Compute::RackspaceV2::BadRequest] + # @raise [Fog::Compute::RackspaceV2::InternalServerError] + # @raise [Fog::Compute::RackspaceV2::ServiceError] + # @see http://docs.rackspace.com/servers/api/v2/cs-devguide/content/ListKeyPairs.html + def list_keypairs + request( + :method => 'GET', + :expects => 200, + :path => '/os-keypairs' + ) + end + end + + class Mock + def list_keypairs + response( :status => 200, + :body => { 'keypairs' => self.data[:keypairs] }) + end + end + + end + end +end diff --git a/tests/rackspace/models/compute_v2/keypairs_tests.rb b/tests/rackspace/models/compute_v2/keypairs_tests.rb new file mode 100644 index 000000000..1c2c943a0 --- /dev/null +++ b/tests/rackspace/models/compute_v2/keypairs_tests.rb @@ -0,0 +1,47 @@ +Shindo.tests('Fog::Compute::RackspaceV2 | keypairs', ['rackspace']) do + service = Fog::Compute::RackspaceV2.new + + name = Fog::Mock.random_letters(32) + key = nil + + tests("API access") do + begin + tests("create").succeeds do + key = service.keypairs.create({:name => name}) + end + + tests("list all").succeeds do + service.keypairs.all + end + + tests("get").succeeds do + service.keypairs.get(name) + end + + tests("delete").succeeds do + key = nil if service.keypairs.destroy(name) + key == nil + end + + tests("get unknown").returns(nil) do + service.keypairs.get(Fog::Mock.random_letters(32)) + end + + tests("delete unknown").raises(Fog::Compute::RackspaceV2::NotFound) do + service.keypairs.destroy(Fog::Mock.random_letters(32)) + end + + tests("create again after delete").succeeds do + key = service.keypairs.create({:name => name}) + end + + tests("create already existing").raises(Fog::Compute::RackspaceV2::ServiceError) do + service.keypairs.create({:name => name}) + end + + ensure + key.destroy if key + end + + end +end diff --git a/tests/rackspace/requests/compute_v2/keypair_tests.rb b/tests/rackspace/requests/compute_v2/keypair_tests.rb new file mode 100644 index 000000000..1590df0c3 --- /dev/null +++ b/tests/rackspace/requests/compute_v2/keypair_tests.rb @@ -0,0 +1,55 @@ +Shindo.tests('Fog::Compute::RackspaceV2 | keypair_tests', ['rackspace']) do + + keypair_format = { + 'name' => String, + 'public_key' => String, + 'fingerprint' => String, + } + + create_keypair_format = { + 'keypair' => keypair_format.merge({ + 'user_id' => String, + 'private_key' => String + }) + } + + list_keypair_format = { + 'keypairs' => [ 'keypair' => keypair_format ] + } + + get_keypair_format = { + 'keypair' => keypair_format + } + + service = Fog::Compute.new(:provider => 'Rackspace', :version => 'V2') + keypair_name = Fog::Mock.random_letters(32) + + tests('success') do + tests('#create_keypair').formats(create_keypair_format) do + service.create_keypair(keypair_name).body + end + + tests('#list_keypairs').formats(list_keypair_format) do + service.list_keypairs.body + end + + tests('#get_keypair').formats(get_keypair_format) do + service.get_keypair(keypair_name).body + end + + tests('#delete_keypair') do + service.delete_keypair(keypair_name).body + end + end + + unknown_keypair_name = Fog::Mock.random_letters(32) + tests('failure') do + tests('#get_unknown_keypair').raises(Fog::Compute::RackspaceV2::NotFound) do + service.get_keypair(unknown_keypair_name).body + end + + tests('#delete_unknown_keypair').raises(Fog::Compute::RackspaceV2::NotFound) do + service.delete_keypair(unknown_keypair_name).body + end + end +end