diff --git a/lib/fog.rb b/lib/fog.rb index 18bae78a1..3a2cf89d1 100644 --- a/lib/fog.rb +++ b/lib/fog.rb @@ -15,6 +15,7 @@ require 'fog/terremark' require 'fog/compute' require 'fog/identity' require 'fog/image' +require 'fog/volume' require 'fog/cdn' require 'fog/dns' require 'fog/storage' diff --git a/lib/fog/openstack/requests/compute/list_volumes.rb b/lib/fog/openstack/requests/compute/list_volumes.rb index 9a45fba43..33e25ecf8 100644 --- a/lib/fog/openstack/requests/compute/list_volumes.rb +++ b/lib/fog/openstack/requests/compute/list_volumes.rb @@ -4,6 +4,8 @@ module Fog class Real def list_volumes(detailed=true) + require 'pry' + binding.pry path = detailed ? 'os-volumes/detail' : 'os-volumes' request( diff --git a/lib/fog/openstack/requests/volume/create_volume.rb b/lib/fog/openstack/requests/volume/create_volume.rb new file mode 100644 index 000000000..3931d8c76 --- /dev/null +++ b/lib/fog/openstack/requests/volume/create_volume.rb @@ -0,0 +1,54 @@ +module Fog + module Volume + class OpenStack + class Real + + def create_volume(name, description, size, options={}) + data = { + 'volume' => { + 'display_name' => name, + 'display_description' => description, + 'size' => size + } + } + + vanilla_options = ['snapshot_id'] + vanilla_options.select{|o| options[o]}.each do |key| + data['volume'][key] = options[key] + end + request( + :body => MultiJson.encode(data), + :expects => [200, 202], + :method => 'POST', + :path => "volumes" + ) + end + + end + + class Mock + + def create_volume(name, description, size, options={}) + response = Excon::Response.new + response.status = 202 + response.body = { + 'volume' => { + 'id' => Fog::Mock.random_numbers(2), + 'displayName' => name, + 'displayDescription' => description, + 'size' => size, + 'status' => 'creating', + 'snapshotId' => '4', + 'volumeType' => nil, + 'availabilityZone' => 'nova', + 'createdAt' => Time.now, + 'attchments' => [] + } + } + response + end + end + + end + end +end diff --git a/lib/fog/openstack/requests/volume/create_volume_snapshot.rb b/lib/fog/openstack/requests/volume/create_volume_snapshot.rb new file mode 100644 index 000000000..b3e38391b --- /dev/null +++ b/lib/fog/openstack/requests/volume/create_volume_snapshot.rb @@ -0,0 +1,32 @@ +module Fog + module Compute + class OpenStack + class Real + + def create_volume_snapshot(volume_id, name, description, force=false) + data = { + 'snapshot' => { + 'volume_id' => volume_id, + 'display_name' => name, + 'display_description' => description, + 'force' => force + } + } + + request( + :body => MultiJson.encode(data), + :expects => [200, 202], + :method => 'POST', + :path => "snapshots" + ) + end + + end + + class Mock + + end + + end + end +end diff --git a/lib/fog/openstack/requests/volume/delete_snapshot.rb b/lib/fog/openstack/requests/volume/delete_snapshot.rb new file mode 100644 index 000000000..886779cbe --- /dev/null +++ b/lib/fog/openstack/requests/volume/delete_snapshot.rb @@ -0,0 +1,26 @@ +module Fog + module Compute + class OpenStack + class Real + + def delete_snapshot(snapshot_id) + request( + :expects => 202, + :method => 'DELETE', + :path => "snapshots/#{snapshot_id}" + ) + end + + end + + class Mock + def delete_snapshot(snapshot_id) + response = Excon::Response.new + response.status = 204 + response + end + end + + end + end +end diff --git a/lib/fog/openstack/requests/volume/delete_volume.rb b/lib/fog/openstack/requests/volume/delete_volume.rb new file mode 100644 index 000000000..794f300e5 --- /dev/null +++ b/lib/fog/openstack/requests/volume/delete_volume.rb @@ -0,0 +1,26 @@ +module Fog + module Compute + class OpenStack + class Real + + def delete_volume(volume_id) + request( + :expects => 202, + :method => 'DELETE', + :path => "volumes/#{volume_id}" + ) + end + + end + + class Mock + def delete_volume(volume_id) + response = Excon::Response.new + response.status = 204 + response + end + end + + end + end +end diff --git a/lib/fog/openstack/requests/volume/get_snapshot_details.rb b/lib/fog/openstack/requests/volume/get_snapshot_details.rb new file mode 100644 index 000000000..bbb1aaf5f --- /dev/null +++ b/lib/fog/openstack/requests/volume/get_snapshot_details.rb @@ -0,0 +1,39 @@ +module Fog + module Compute + class OpenStack + class Real + + def get_snapshot_details(snapshot_id) + + request( + :expects => 200, + :method => 'GET', + :path => "snapshots/#{snapshot_id}" + ) + end + + end + + class Mock + + def get_snapshot_details(detailed=true) + response = Excon::Response.new + response.status = 200 + response.body = { + 'snapshot' => { + 'id' => '1', + 'displayName' => Fog::Mock.random_letters(rand(8) + 5), + 'displayDescription' => Fog::Mock.random_letters(rand(12) + 10), + 'size' => 3, + 'volumeId' => '4', + 'status' => 'online', + 'createdAt' => Time.now + } + } + response + end + end + + end + end +end diff --git a/lib/fog/openstack/requests/volume/get_volume_details.rb b/lib/fog/openstack/requests/volume/get_volume_details.rb new file mode 100644 index 000000000..69ecd85d2 --- /dev/null +++ b/lib/fog/openstack/requests/volume/get_volume_details.rb @@ -0,0 +1,42 @@ +module Fog + module Compute + class OpenStack + class Real + + def get_volume_details(volume_id) + + request( + :expects => 200, + :method => 'GET', + :path => "volumes/#{volume_id}" + ) + end + + end + + class Mock + + def get_volume_details(detailed=true) + response = Excon::Response.new + response.status = 200 + response.body = { + 'volume' => { + 'id' => '1', + 'displayName' => Fog::Mock.random_letters(rand(8) + 5), + 'displayDescription' => Fog::Mock.random_letters(rand(12) + 10), + 'size' => 3, + 'volumeType' => nil, + 'snapshotId' => '4', + 'status' => 'online', + 'availabilityZone' => 'nova', + 'createdAt' => Time.now, + 'attchments' => [] + } + } + response + end + end + + end + end +end diff --git a/lib/fog/openstack/requests/volume/list_snapshots.rb b/lib/fog/openstack/requests/volume/list_snapshots.rb new file mode 100644 index 000000000..344e3ed42 --- /dev/null +++ b/lib/fog/openstack/requests/volume/list_snapshots.rb @@ -0,0 +1,32 @@ +module Fog + module Compute + class OpenStack + class Real + + def list_snapshots(detailed=true) + + path = detailed ? 'snapshots/detail' : 'snapshots' + request( + :expects => 200, + :method => 'GET', + :path => path + ) + end + + end + + class Mock + + def list_snapshots(detailed=true) + response = Excon::Response.new + response.status = 200 + response.body = { + 'snapshots' => [get_snapshot_details.body["snapshot"]] + } + response + end + end + + end + end +end diff --git a/lib/fog/openstack/requests/volume/list_volumes.rb b/lib/fog/openstack/requests/volume/list_volumes.rb new file mode 100644 index 000000000..39900ce20 --- /dev/null +++ b/lib/fog/openstack/requests/volume/list_volumes.rb @@ -0,0 +1,55 @@ +module Fog + module Volume + class OpenStack + class Real + + def list_volumes(detailed=true) + + path = detailed ? 'volumes/detail' : 'volumes' + request( + :expects => 200, + :method => 'GET', + :path => path + ) + end + + end + + class Mock + + def list_volumes(detailed=true) + response = Excon::Response.new + response.status = 200 + self.data[:volumes] ||= [ + { "status" => "available", + "displayDescription" => "", + "availabilityZone" => "nova", + "displayName" => "test 1", + "attachments" => [{}], + "volumeType" => nil, + "snapshotId" => nil, + "size" => 1, + "id" => 6, + "createdAt" => "2012-03-30 05:31:00.655058", + "metadata" => {} }, + { "status" => "available", + "displayDescription" => "", + "availabilityZone" => "nova", + "displayName" => "test 2", + "attachments" => [{}], + "volumeType" => nil, + "snapshotId" => nil, + "size" => 1, + "id" => 8, + "createdAt" => "2012-03-30 16:14:55.582717", + "metadata" => {} } + ] + response.body = { 'volumes' => self.data[:volumes] } + response + end + end + + end + end +end + diff --git a/lib/fog/openstack/requests/volume/set_tenant.rb b/lib/fog/openstack/requests/volume/set_tenant.rb new file mode 100644 index 000000000..0e78819c0 --- /dev/null +++ b/lib/fog/openstack/requests/volume/set_tenant.rb @@ -0,0 +1,21 @@ +module Fog + module Volume + class OpenStack + + class Real + def set_tenant(tenant) + @openstack_must_reauthenticate = true + @openstack_tenant = tenant.to_s + authenticate + end + end + + class Mock + def set_tenant(tenant) + true + end + end + + end # class OpenStack + end # module Volume +end # module Fog diff --git a/lib/fog/openstack/volume.rb b/lib/fog/openstack/volume.rb new file mode 100644 index 000000000..a6925dba6 --- /dev/null +++ b/lib/fog/openstack/volume.rb @@ -0,0 +1,194 @@ +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'openstack')) +require 'fog/openstack' + +module Fog + module Volume + class OpenStack < Fog::Service + + requires :openstack_auth_url + recognizes :openstack_auth_token, :openstack_management_url, :persistent, + :openstack_service_name, :openstack_tenant, + :openstack_api_key, :openstack_username + + #model_path 'fog/openstack/models/volume' + + #model :volume + #collection :volumes + + + request_path 'fog/openstack/requests/volume' + + # Volume + request :list_volumes + request :create_volume + request :get_volume_details + request :delete_volume + + request :create_volume_snapshot + request :list_snapshots + request :get_snapshot_details + request :delete_snapshot + + request :set_tenant + + class Mock + def self.data + @data ||= Hash.new do |hash, key| + hash[key] = { + :users => {}, + :tenants => {} + } + end + end + + def self.reset + @data = nil + end + + def initialize(options={}) + require 'multi_json' + @openstack_username = options[:openstack_username] + + @data ||= { :users => {} } + unless @data[:users].find {|u| u['name'] == options[:openstack_username]} + id = Fog::Mock.random_numbers(6).to_s + @data[:users][id] = { + 'id' => id, + 'name' => options[:openstack_username], + 'email' => "#{options[:openstack_username]}@mock.com", + 'tenantId' => Fog::Mock.random_numbers(6).to_s, + 'enabled' => true + } + end + end + + def data + self.class.data[@openstack_username] + end + + def reset_data + self.class.data.delete(@openstack_username) + end + + def credentials + { :provider => 'openstack', + :openstack_auth_url => @openstack_auth_uri.to_s, + :openstack_auth_token => @auth_token, + :openstack_management_url => @openstack_management_url } + end + end + + class Real + + def initialize(options={}) + require 'multi_json' + + @openstack_auth_token = options[:openstack_auth_token] + + unless @openstack_auth_token + missing_credentials = Array.new + @openstack_api_key = options[:openstack_api_key] + @openstack_username = options[:openstack_username] + + missing_credentials << :openstack_api_key unless @openstack_api_key + missing_credentials << :openstack_username unless @openstack_username + raise ArgumentError, "Missing required arguments: #{missing_credentials.join(', ')}" unless missing_credentials.empty? + end + + @openstack_tenant = options[:openstack_tenant] + @openstack_auth_uri = URI.parse(options[:openstack_auth_url]) + @openstack_management_url = options[:openstack_management_url] + @openstack_must_reauthenticate = false + @openstack_service_name = options[:openstack_service_name] || ['volume'] + + @connection_options = options[:connection_options] || {} + + authenticate + + @persistent = options[:persistent] || false + @connection = Fog::Connection.new("#{@scheme}://#{@host}:#{@port}", @persistent, @connection_options) + end + + def credentials + { :provider => 'openstack', + :openstack_auth_url => @openstack_auth_uri.to_s, + :openstack_auth_token => @auth_token, + :openstack_management_url => @openstack_management_url } + end + + def reload + @connection.reset + end + + def request(params) + begin + response = @connection.request(params.merge({ + :headers => { + 'Content-Type' => 'application/json', + 'X-Auth-Token' => @auth_token + }.merge!(params[:headers] || {}), + :host => @host, + :path => "#{@path}/#{params[:path]}"#, + # Causes errors for some requests like tenants?limit=1 + # :query => ('ignore_awful_caching' << Time.now.to_i.to_s) + })) + rescue Excon::Errors::Unauthorized => error + if error.response.body != 'Bad username or password' # token expiration + @openstack_must_reauthenticate = true + authenticate + retry + else # bad credentials + raise error + end + rescue Excon::Errors::HTTPStatusError => error + raise case error + when Excon::Errors::NotFound + Fog::Compute::OpenStack::NotFound.slurp(error) + else + error + end + end + unless response.body.empty? + response.body = MultiJson.decode(response.body) + end + response + end + + private + + def authenticate + if @openstack_must_reauthenticate || @openstack_auth_token.nil? + options = { + :openstack_tenant => @openstack_tenant, + :openstack_api_key => @openstack_api_key, + :openstack_username => @openstack_username, + :openstack_auth_uri => @openstack_auth_uri, + :openstack_auth_token => @openstack_auth_token, + :openstack_service_name => @openstack_service_name, + :openstack_endpoint_type => 'adminURL' + } + + credentials = Fog::OpenStack.authenticate_v2(options, @connection_options) + + @openstack_must_reauthenticate = false + @auth_token = credentials[:token] + @openstack_management_url = credentials[:server_management_url] + uri = URI.parse(@openstack_management_url) + else + @auth_token = @openstack_auth_token + uri = URI.parse(@openstack_management_url) + end + + @host = uri.host + @path = uri.path + @path.sub!(/\/$/, '') + @port = uri.port + @scheme = uri.scheme + true + end + + end + end + end +end + diff --git a/lib/fog/volume.rb b/lib/fog/volume.rb new file mode 100644 index 000000000..4a2903241 --- /dev/null +++ b/lib/fog/volume.rb @@ -0,0 +1,25 @@ +module Fog + module Volume + + def self.[](provider) + self.new(:provider => provider) + end + + def self.new(attributes) + attributes = attributes.dup # Prevent delete from having side effects + case provider = attributes.delete(:provider).to_s.downcase.to_sym + when :openstack + require 'fog/openstack/volume' + Fog::Volume::OpenStack.new(attributes) + else + raise ArgumentError.new("#{provider} has no identity service") + end + end + + def self.providers + Fog.services[:volume] + end + + end +end +