1
0
Fork 0
mirror of https://github.com/fog/fog.git synced 2022-11-09 13:51:43 -05:00

Merge pull request #1644 from basho/csm-riak-cs-1.3

Add Riak CS provider in Fog.
This commit is contained in:
Wesley Beary 2013-03-11 09:34:10 -07:00
commit 0726951864
19 changed files with 851 additions and 2 deletions

View file

@ -14,6 +14,7 @@ module Fog
# @option options If-Unmodified-Since [Time] Returns object only if it has not been modified since this time, otherwise returns 412 (Precodition Failed).
# @option options Range [String] Range of object to download
# @option options versionId [String] specify a particular version to retrieve
# @option options query[Hash] specify additional query string
#
# @return [Excon::Response] response:
# * body [String]- Contents of object
@ -34,8 +35,11 @@ module Fog
end
params = { :headers => {} }
params[:query] = options.delete('query') || {}
if version_id = options.delete('versionId')
params[:query] = {'versionId' => version_id}
params[:query] = params[:query].merge({'versionId' => version_id})
end
params[:headers].merge!(options)
if options['If-Modified-Since']
@ -54,7 +58,7 @@ module Fog
:host => "#{bucket_name}.#{@host}",
:idempotent => true,
:method => 'GET',
:path => CGI.escape(object_name),
:path => CGI.escape(object_name)
}))
end

View file

@ -81,6 +81,7 @@ require 'fog/bin/local'
require 'fog/bin/bare_metal_cloud'
require 'fog/bin/ninefold'
require 'fog/bin/rackspace'
require 'fog/bin/riakcs'
require 'fog/bin/openstack'
require 'fog/bin/ovirt'
require 'fog/bin/serverlove'

27
lib/fog/bin/riakcs.rb Normal file
View file

@ -0,0 +1,27 @@
class RiakCS < Fog::Bin
class << self
def class_for(key)
case key
when :provisioning
Fog::RiakCS::Provisioning
when :usage
Fog::RiakCS::Usage
else
raise ArgumentError, "Unrecognized service: #{key}"
end
end
def [](service)
@@connections ||= Hash.new do |hash, key|
hash[key] = class_for(key)
end
@@connections[service]
end
def services
Fog::RiakCS.services
end
end
end

View file

@ -71,6 +71,8 @@ An alternate file may be used by placing its path in the FOG_RC environment vari
:rackspace_username:
:rackspace_servicenet:
:rackspace_cdn_ssl:
:riakcs_access_key_id:
:riakcs_secret_access_key:
:stormondemand_username:
:stormondemand_password:
:terremark_username:

View file

@ -22,6 +22,7 @@ require 'fog/local'
require 'fog/bare_metal_cloud'
require 'fog/ninefold'
require 'fog/rackspace'
require 'fog/riakcs'
require 'fog/openstack'
require 'fog/ovirt'
require 'fog/serverlove'

122
lib/fog/riakcs.rb Normal file
View file

@ -0,0 +1,122 @@
require(File.expand_path(File.join(File.dirname(__FILE__), 'core')))
module Fog
module RiakCS
module MultipartUtils
require 'net/http'
class Headers
include Net::HTTPHeader
def initialize
initialize_http_header({})
end
# Parse a single header line into its key and value
# @param [String] chunk a single header line
def self.parse(chunk)
line = chunk.strip
# thanks Net::HTTPResponse
return [nil,nil] if chunk =~ /\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)\s*(.*)\z/in
m = /\A([^:]+):\s*/.match(line)
[m[1], m.post_match] rescue [nil, nil]
end
# Parses a header line and adds it to the header collection
# @param [String] chunk a single header line
def parse(chunk)
key, value = self.class.parse(chunk)
add_field(key, value) if key && value
end
end
def parse(data, boundary)
contents = data.match(end_boundary_regex(boundary)).pre_match rescue ""
contents.split(inner_boundary_regex(boundary)).reject(&:empty?).map do |part|
parse_multipart_section(part)
end.compact
end
def extract_boundary(header_string)
$1 if header_string =~ /boundary=([A-Za-z0-9\'()+_,-.\/:=?]+)/
end
private
def end_boundary_regex(boundary)
/\r?\n--#{Regexp.escape(boundary)}--\r?\n?/
end
def inner_boundary_regex(boundary)
/\r?\n--#{Regexp.escape(boundary)}\r?\n/
end
def parse_multipart_section(part)
headers = Headers.new
if md = part.match(/\r?\n\r?\n/)
body = md.post_match
md.pre_match.split(/\r?\n/).each do |line|
headers.parse(line)
end
if headers["content-type"] =~ /multipart\/mixed/
boundary = extract_boundary(headers.to_hash["content-type"].first)
parse(body, boundary)
else
{:headers => headers.to_hash, :body => body}
end
end
end
end
module UserUtils
def update_riakcs_user(key_id, user)
response = @s3_connection.put_object('riak-cs', "user/#{key_id}", MultiJson.encode(user), { 'Content-Type' => 'application/json' })
if !response.body.empty?
response.body = MultiJson.decode(response.body)
end
response
end
def update_mock_user(key_id, user)
if data[key_id]
if status = user[:status]
data[key_id][:status] = status
end
if regrant = user[:new_key_secret]
data[key_id][:key_secret] = rand(100).to_s
end
Excon::Response.new.tap do |response|
response.status = 200
response.body = data[key_id]
end
else
Excon::Response.new.tap do |response|
response.status = 403
end
end
end
end
module Utils
def configure_uri_options(options = {})
@host = options[:host] || 'localhost'
@persistent = options[:persistent] || true
@port = options[:port] || 8080
@scheme = options[:scheme] || 'http'
end
def riakcs_uri
"#{@scheme}://#{@host}:#{@port}"
end
end
extend Fog::Provider
service(:provisioning, 'riakcs/provisioning', 'Provisioning')
service(:usage, 'riakcs/usage', 'Usage')
end
end

View file

@ -0,0 +1,100 @@
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'riakcs'))
module Fog
module RiakCS
class Provisioning < Fog::Service
class UserAlreadyExists < Fog::RiakCS::Provisioning::Error; end
class ServiceUnavailable < Fog::RiakCS::Provisioning::Error; end
requires :riakcs_access_key_id, :riakcs_secret_access_key
recognizes :host, :path, :port, :scheme, :persistent
request_path 'fog/riakcs/requests/provisioning'
request :create_user
request :update_user
request :disable_user
request :enable_user
request :list_users
request :get_user
request :regrant_secret
class Mock
include Utils
def self.data
@data ||= Hash.new({})
end
def self.reset
@data = nil
end
def initialize(options = {})
configure_uri_options(options)
end
def data
self.class.data[riakcs_uri]
end
def reset_data
self.class.data.delete(riakcs_uri)
end
end
class Real
include Utils
def initialize(options = {})
require 'mime/types'
require 'multi_json'
configure_uri_options(options)
@riakcs_access_key_id = options[:riakcs_access_key_id]
@riakcs_secret_access_key = options[:riakcs_secret_access_key]
@connection_options = options[:connection_options] || {}
@persistent = options[:persistent] || false
@raw_connection = Fog::Connection.new(riakcs_uri, @persistent, @connection_options)
@s3_connection = Fog::Storage.new(
:provider => 'AWS',
:aws_access_key_id => @riakcs_access_key_id,
:aws_secret_access_key => @riakcs_secret_access_key,
:host => @host,
:port => @port,
:scheme => @scheme
)
end
def request(params, parse_response = true, &block)
begin
response = @raw_connection.request(params.merge({
:host => @host,
:path => "#{@path}/#{params[:path]}",
}), &block)
rescue Excon::Errors::HTTPStatusError => error
if match = error.message.match(/<Code>(.*?)<\/Code>(?:.*<Message>(.*?)<\/Message>)?/m)
case match[1]
when 'UserAlreadyExists'
raise Fog::RiakCS::Provisioning.const_get(match[1]).new
when 'ServiceUnavailable'
raise Fog::RiakCS::Provisioning.const_get(match[1]).new
else
raise error
end
else
raise error
end
end
if !response.body.empty? && parse_response
response.body = MultiJson.decode(response.body)
end
response
end
end
end
end
end

View file

@ -0,0 +1,77 @@
module Fog
module RiakCS
class Provisioning
class Real
def create_user(email, name, options = {})
payload = MultiJson.encode({ :email => email, :name => name })
headers = { 'Content-Type' => 'application/json' }
if(options[:anonymous])
request(
:expects => [201],
:method => 'POST',
:path => 'user',
:body => payload,
:headers => headers
)
else
begin
response = @s3_connection.put_object('riak-cs', 'user', payload, headers)
if !response.body.empty?
case response.headers['Content-Type']
when 'application/json'
response.body = MultiJson.decode(response.body)
end
end
response
rescue Excon::Errors::Conflict => e
raise Fog::RiakCS::Provisioning::UserAlreadyExists.new
rescue Excon::Errors::BadRequest => e
raise Fog::RiakCS::Provisioning::ServiceUnavailable.new
end
end
end
end
class Mock
def invalid_email?(email)
!email.include?('@')
end
def user_exists?(email)
data.detect do |key, value|
value[:email] == email
end
end
def create_user(email, name, options = {})
if invalid_email?(email)
raise Fog::RiakCS::Provisioning::ServiceUnavailable, "The email address you provided is not a valid."
end
if user_exists?(email)
raise Fog::RiakCS::Provisioning::UserAlreadyExists, "User with email #{email} already exists."
end
key_id = rand(1000).to_s
key_secret = rand(1000).to_s
data[key_id] = { :email => email, :name => name, :status => 'enabled', :key_secret => key_secret }
Excon::Response.new.tap do |response|
response.status = 200
response.headers['Content-Type'] = 'application/json'
response.body = {
"email" => data[:email],
"display_name" => data[:name],
"name" => "user123",
"key_id" => key_id,
"key_secret" => key_secret,
"id" => "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"status" => "enabled"
}
end
end
end
end
end
end

View file

@ -0,0 +1,23 @@
module Fog
module RiakCS
class Provisioning
class Real
include Utils
include UserUtils
include MultipartUtils
def disable_user(key_id)
update_riakcs_user(key_id, { :status => 'disabled' })
end
end
class Mock
include UserUtils
def disable_user(key_id)
update_mock_user(key_id, { :status => 'disabled' })
end
end
end
end
end

View file

@ -0,0 +1,23 @@
module Fog
module RiakCS
class Provisioning
class Real
include Utils
include UserUtils
include MultipartUtils
def enable_user(key_id)
update_riakcs_user(key_id, { :status => 'enabled' })
end
end
class Mock
include UserUtils
def enable_user(key_id)
update_mock_user(key_id, { :status => 'enabled' })
end
end
end
end
end

View file

@ -0,0 +1,41 @@
module Fog
module RiakCS
class Provisioning
class Real
include Utils
include MultipartUtils
def get_user(key_id)
response = @s3_connection.get_object('riak-cs', "user/#{key_id}", { 'Accept' => 'application/json' })
response.body = MultiJson.decode(response.body)
response
end
end
class Mock
def get_user(key_id)
if user = data[key_id]
Excon::Response.new.tap do |response|
response.status = 200
response.headers['Content-Type'] = 'application/json'
response.body = {
"email" => user[:email],
"display_name" => user[:name],
"name" => "user123",
"key_id" => "XXXXXXXXXXXXXXXXXXXX",
"key_secret" => user[:key_secret],
"id" => "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"status" => user[:status]
}
end
else
Excon::Response.new.tap do |response|
response.status = 404
response.headers['Content-Type'] = 'application/json'
end
end
end
end
end
end
end

View file

@ -0,0 +1,43 @@
module Fog
module RiakCS
class Provisioning
class Real
include Utils
include MultipartUtils
def list_users(options = {})
response = @s3_connection.get_object('riak-cs', 'users', { 'Accept' => 'application/json', 'query' => options })
boundary = extract_boundary(response.headers['Content-Type'])
parts = parse(response.body, boundary)
decoded = parts.map { |part| MultiJson.decode(part[:body]) }
response.body = decoded.flatten
response
end
end
class Mock
def list_users(options = {})
filtered_data = options[:status] ? data.select { |key, value| value[:status] == options[:status] } : data
Excon::Response.new.tap do |response|
response.status = 200
response.body = filtered_data.map do |key, value|
{
"email" => value[:email],
"display_name" => value[:name],
"name" => "user123",
"key_id" => key,
"key_secret" => value[:key_secret],
"id" => "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"status" => value[:status]
}
end.compact
end
end
end
end
end
end

View file

@ -0,0 +1,23 @@
module Fog
module RiakCS
class Provisioning
class Real
include Utils
include UserUtils
include MultipartUtils
def regrant_secret(key_id)
update_riakcs_user(key_id, { :new_key_secret => true })
end
end
class Mock
include UserUtils
def regrant_secret(key_id)
update_mock_user(key_id, { :new_key_secret => true })
end
end
end
end
end

View file

@ -0,0 +1,23 @@
module Fog
module RiakCS
class Provisioning
class Real
include Utils
include UserUtils
include MultipartUtils
def update_user(key_id, user)
update_riakcs_user(key_id, user)
end
end
class Mock
include UserUtils
def update_user(key_id, user)
update_mock_user(key_id, user)
end
end
end
end
end

View file

@ -0,0 +1,68 @@
module Fog
module RiakCS
class Usage
module Utils
TYPES_TO_STRING = { :access => 'a', :storage => 'b' }
DEFAULT_TYPES = TYPES_TO_STRING.keys
DEFAULT_FORMAT = :json
def sanitize_and_convert_time(time)
time.utc.iso8601.gsub(/[:-]/, '')
end
def format_and_types_to_path(format, types)
format_character = format.to_s.split('').first
type_characters = types.map { |t| TYPES_TO_STRING[t] }.compact
[type_characters, format_character].flatten.compact.join
end
def request_uri(access_key_id, options)
format = DEFAULT_FORMAT
types = options[:types] || DEFAULT_TYPES
start_time = options[:start_time] || Time.now.utc - 86400
end_time = options[:end_time] || Time.now.utc
[access_key_id,
format_and_types_to_path(format, types),
sanitize_and_convert_time(start_time),
sanitize_and_convert_time(end_time)].join('.')
end
end
class Real
include Utils
def get_usage(access_key_id, options = {})
response = @connection.get_object('riak-cs', ["usage", request_uri(access_key_id, options)].join("/"))
if !response.body.empty?
response.body = MultiJson.decode(response.body)
end
response
end
end
class Mock
include Utils
def get_usage(access_key, options = {})
Excon::Response.new.tap do |response|
response.status = 200
response.headers['Content-Type'] = 'application/json'
response.body = {
'Access' => {
'Nodes' => [],
'Errors' => []
},
'Storage' => {
'Samples' => [],
'Errors' => []
}
}
end
end
end
end
end
end

66
lib/fog/riakcs/usage.rb Normal file
View file

@ -0,0 +1,66 @@
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'riakcs'))
require 'time'
module Fog
module RiakCS
class Usage < Fog::Service
requires :riakcs_access_key_id, :riakcs_secret_access_key
recognizes :host, :path, :port, :scheme, :persistent
request_path 'fog/riakcs/requests/usage'
request :get_usage
class Mock
include Utils
def self.data
@data ||= Hash.new do |hash, key|
hash[key] = {}
end
end
def self.reset
@data = nil
end
def initialize(options = {})
configure_uri_options(options)
end
def data
self.class.data[riakcs_uri]
end
def reset_data
self.class.data.delete(riakcs_uri)
end
end
class Real
include Utils
def initialize(options = {})
require 'mime/types'
require 'multi_json'
configure_uri_options(options)
@riakcs_access_key_id = options[:riakcs_access_key_id]
@riakcs_secret_access_key = options[:riakcs_secret_access_key]
@connection_options = options[:connection_options] || {}
@persistent = options[:persistent] || false
@connection = Fog::Storage.new(
:provider => 'AWS',
:aws_access_key_id => @riakcs_access_key_id,
:aws_secret_access_key => @riakcs_secret_access_key,
:host => @host,
:port => @port,
:scheme => @scheme
)
end
end
end
end
end

View file

@ -69,6 +69,8 @@ if Fog.mock?
:libvirt_uri => 'qemu://libvirt/system',
:rackspace_api_key => 'rackspace_api_key',
:rackspace_username => 'rackspace_username',
:riakcs_access_key_id => 'riakcs_access_key_id',
:riakcs_secret_access_key => 'riakcs_secret_access_key',
:storm_on_demand_username => 'storm_on_demand_username',
:storm_on_demand_password => 'storm_on_demand_password',
:vcloud_host => 'vcloud_host',

View file

@ -0,0 +1,174 @@
Shindo.tests('RiakCS::Provisioning | provisioning requests', ['riakcs']) do
current_timestamp = Time.now.to_i
user_format = {
'email' => String,
'display_name' => String,
'name' => String,
'key_id' => String,
'key_secret' => String,
'id' => String,
'status' => String,
}
tests('User creation') do
tests('is successful').returns(String) do
# Create a user.
#
email, name = "successful_user_creation_test_#{current_timestamp}@example.com", "Fog User"
key_id = Fog::RiakCS[:provisioning].create_user(email, name).body['key_id']
key_id.class
end
tests('is successful anonymously').returns(String) do
# Create a user.
#
email, name = "successful_anonymous_user_creation_test_#{current_timestamp}@example.com", "Fog User"
key_id = Fog::RiakCS[:provisioning].create_user(email, name, :anonymous => true).body['key_id']
key_id.class
end
tests('fails if duplicate').raises(Fog::RiakCS::Provisioning::UserAlreadyExists) do
2.times do
email, name = "failed_duplicate_user_creation_test_#{current_timestamp}@example.com", "Fog User"
key_id = Fog::RiakCS[:provisioning].create_user(email, name).body['key_id']
end
end
tests('fails if invalid email').raises(Fog::RiakCS::Provisioning::ServiceUnavailable) do
email, name = "failed_duplicate_user_creation_test_#{current_timestamp}", "Fog User"
key_id = Fog::RiakCS[:provisioning].create_user(email, name).body['key_id']
end
end
tests('User disable') do
tests('is successful').returns(200) do
# Create a user.
#
email, name = "successful_user_disable_test_#{current_timestamp}@example.com", "Fog User"
key_id = Fog::RiakCS[:provisioning].create_user(email, name).body['key_id']
Fog::RiakCS[:provisioning].disable_user(key_id).status
end
end
tests('User enable') do
tests('is successful').returns(200) do
# Create a user.
#
email, name = "successful_user_disable_enable_test_#{current_timestamp}@example.com", "Fog User"
key_id = Fog::RiakCS[:provisioning].create_user(email, name).body['key_id']
Fog::RiakCS[:provisioning].disable_user(key_id).status
Fog::RiakCS[:provisioning].enable_user(key_id).status
end
end
tests('User granted new key secret') do
tests('is successful').returns(true) do
# Create a user.
#
email, name = "successful_user_regrant_test_#{current_timestamp}@example.com", "Fog User"
user = Fog::RiakCS[:provisioning].create_user(email, name).body
key_id, key_secret = user['key_id'], user['key_secret']
Fog::RiakCS[:provisioning].regrant_secret(key_id).status
# Verify new secret.
#
new_key_secret = Fog::RiakCS[:provisioning].get_user(key_id).body['key_secret']
new_key_secret != key_secret
end
end
tests('User retrieval') do
tests('is successful').formats(user_format) do
# Create a user.
#
email, name = "user_retrieval_test_#{current_timestamp}@example.com", "Fog User"
key_id = Fog::RiakCS[:provisioning].create_user(email, name).body['key_id']
# Get user details.
#
Fog::RiakCS[:provisioning].get_user(key_id).body
end
end
tests('User listing') do
tests('sucessfully lists users').formats(user_format) do
# Create a user.
#
email, name = "user_listing_test_#{current_timestamp}@example.com", "Fog User"
key_id = Fog::RiakCS[:provisioning].create_user(email, name).body['key_id']
# Ensure the list users response contains the user that we just
# created.
#
Fog::RiakCS[:provisioning].list_users.body.select { |x| x['email'] == email }.first
end
tests('successfully lists users containing no disabled users').returns(nil) do
# Create a user.
#
email, name = "user_listing_without_disabled_users_test_#{current_timestamp}@example.com", "Fog User"
key_id = Fog::RiakCS[:provisioning].create_user(email, name).body['key_id']
# Disable that user.
#
Fog::RiakCS[:provisioning].disable_user(key_id)
# Ensure the list users response does not contain the user that we
# just created and disabled.
#
Fog::RiakCS[:provisioning].list_users(:status => :enabled).body.select { |x| x['Email'] == email }.first
end
tests('successfully lists users containing disabled users').formats(user_format) do
# Create a user.
#
email, name = "user_listing_with_disabled_users_test_#{current_timestamp}@example.com", "Fog User"
key_id = Fog::RiakCS[:provisioning].create_user(email, name).body['key_id']
# Disable that user.
#
Fog::RiakCS[:provisioning].disable_user(key_id)
# Ensure the list users response contains the user that we just
# created and disabled.
#
Fog::RiakCS[:provisioning].list_users.body.select { |x| x['email'] == email }.first
end
end
end

View file

@ -0,0 +1,29 @@
Shindo.tests('RiakCS::Usage | usage requests', ['riakcs']) do
@blank_usage_format = {
'Access' => {
'Nodes' => [],
'Errors' => []
},
'Storage' => {
'Samples' => [],
'Errors' => []
}
}
tests('Statistics retrieval with no data returned') do
start_time = Time.now.utc + 86400
end_time = start_time + 86400
tests('via JSON').returns(@blank_usage_format) do
Fog::RiakCS[:usage].get_usage(Fog.credentials[:riakcs_access_key_id],
:types => [:access, :storage],
:start_time => start_time,
:end_time => end_time).body
end
end
end