fog-radosgw 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +23 -0
- data/CONTRIBUTORS.md +3 -0
- data/Gemfile +2 -0
- data/LICENSE.md +20 -0
- data/README.md +65 -0
- data/Rakefile +82 -0
- data/fog-radosgw.gemspec +29 -0
- data/lib/fog/radosgw.rb +2 -0
- data/lib/fog/radosgw/core.rb +144 -0
- data/lib/fog/radosgw/provisioning.rb +101 -0
- data/lib/fog/radosgw/requests/provisioning/create_user.rb +83 -0
- data/lib/fog/radosgw/requests/provisioning/delete_user.rb +57 -0
- data/lib/fog/radosgw/requests/provisioning/disable_user.rb +23 -0
- data/lib/fog/radosgw/requests/provisioning/enable_user.rb +23 -0
- data/lib/fog/radosgw/requests/provisioning/get_user.rb +66 -0
- data/lib/fog/radosgw/requests/provisioning/list_users.rb +70 -0
- data/lib/fog/radosgw/requests/provisioning/update_user.rb +23 -0
- data/lib/fog/radosgw/requests/provisioning/user.json +17 -0
- data/lib/fog/radosgw/requests/usage/get_usage.rb +60 -0
- data/lib/fog/radosgw/usage.rb +62 -0
- data/lib/fog/radosgw/version.rb +5 -0
- data/tests/helper.rb +9 -0
- data/tests/helpers/mock_helper.rb +15 -0
- data/tests/radosgw/helper.rb +1 -0
- data/tests/radosgw/requests/provisioning/provisioning_tests.rb +170 -0
- data/tests/radosgw/requests/usage/usage_tests.rb +23 -0
- metadata +156 -0
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
*~
|
2
|
+
*.gem
|
3
|
+
*.rbc
|
4
|
+
.rbenv
|
5
|
+
.rvmrc
|
6
|
+
.ruby-gemset
|
7
|
+
.ruby-version
|
8
|
+
.bundle
|
9
|
+
.DS_Store
|
10
|
+
.idea
|
11
|
+
.yardoc
|
12
|
+
/tests/.fog
|
13
|
+
bin/*
|
14
|
+
!bin/fog
|
15
|
+
.fog
|
16
|
+
coverage
|
17
|
+
doc/*
|
18
|
+
docs/_site/*
|
19
|
+
Gemfile.lock
|
20
|
+
gemfiles/*.lock
|
21
|
+
yardoc
|
22
|
+
pkg
|
23
|
+
tags
|
data/CONTRIBUTORS.md
ADDED
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2009-2014 [CONTRIBUTORS.md](https://github.com/fog/fog-radosgw/blob/master/CONTRIBUTORS.md)
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# Fog::Radosgw
|
2
|
+
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/fog-radosgw.svg)](http://badge.fury.io/rb/fog-radosgw) [![Build Status](https://travis-ci.org/fog/fog-radosgw.svg?branch=master)](https://travis-ci.org/fog/fog-radosgw) [![Dependency Status](https://gemnasium.com/fog/fog-radosgw.svg)](https://gemnasium.com/fog/fog-radosgw) [![Coverage Status](https://img.shields.io/coveralls/fog/fog-radosgw.svg)](https://coveralls.io/r/fog/fog-radosgw) [![Code Climate](https://codeclimate.com/github/fog/fog-radosgw.png)](https://codeclimate.com/github/fog/fog-radosgw) [![Stories in Ready](https://badge.waffle.io/fog/fog-radosgw.png?label=ready&title=Ready)](https://waffle.io/fog/fog-radosgw)
|
4
|
+
|
5
|
+
Fog backend for provisioning Ceph Radosgw - the Swift and S3 compatible REST API for Ceph.
|
6
|
+
Currently, the gem only supports the S3 API, not Swift.
|
7
|
+
|
8
|
+
Based on the Riak CS backend.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
gem 'fog-radosgw'
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install fog-radosgw
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
Example:
|
27
|
+
|
28
|
+
require 'fog/radosgw'
|
29
|
+
|
30
|
+
provision_client = Fog::Radosgw::Provisioning.new(
|
31
|
+
host: 'objects.example.com',
|
32
|
+
radosgw_access_key_id: 'CHIA1GEE0EIJ9OHSAIK6',
|
33
|
+
radosgw_secret_access_key: 'IFooJ7airaebaele6baihaiw2fequuto9Foh7Sei'
|
34
|
+
)
|
35
|
+
|
36
|
+
response = provision_client.create_user('fog', 'Fog User', 'fog@example.com')
|
37
|
+
userkey = response.body['keys'][0]['access_key']
|
38
|
+
usersecret = response.body['keys'][0]['secret_key']
|
39
|
+
|
40
|
+
## Tests
|
41
|
+
|
42
|
+
Mock tests:
|
43
|
+
|
44
|
+
bundle exec rake mock[radosgw]
|
45
|
+
|
46
|
+
Live tests:
|
47
|
+
|
48
|
+
bundle exec rake live[radosgw]
|
49
|
+
|
50
|
+
To run live tests, you have to place credentials in `tests/.fog`, e.g.:
|
51
|
+
|
52
|
+
default:
|
53
|
+
host: objects.example.com
|
54
|
+
radosgw_access_key_id: CHIA1GEE0EIJ9OHSAIK6
|
55
|
+
radosgw_secret_access_key: IFooJ7airaebaele6baihaiw2fequuto9Foh7Sei
|
56
|
+
|
57
|
+
## Contributing
|
58
|
+
|
59
|
+
Please refer to the
|
60
|
+
[CONTRIBUTING.md](https://github.com/fog/fog/blob/master/CONTRIBUTING.md)
|
61
|
+
page for the main Fog project.
|
62
|
+
|
63
|
+
## License
|
64
|
+
|
65
|
+
Please refer to [LICENSE.md](https://github.com/fog/fog-radosgw/blob/master/LICENSE.md).
|
data/Rakefile
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'date'
|
5
|
+
require 'rubygems'
|
6
|
+
require 'rubygems/package_task'
|
7
|
+
require 'yard'
|
8
|
+
require 'fog/core'
|
9
|
+
require 'fog/json'
|
10
|
+
|
11
|
+
#############################################################################
|
12
|
+
#
|
13
|
+
# Helper functions
|
14
|
+
#
|
15
|
+
#############################################################################
|
16
|
+
|
17
|
+
def name
|
18
|
+
@name ||= Dir['*.gemspec'].first.split('.').first
|
19
|
+
end
|
20
|
+
|
21
|
+
def version
|
22
|
+
Fog::Radosgw::VERSION
|
23
|
+
end
|
24
|
+
|
25
|
+
def date
|
26
|
+
Date.today.to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
def rubyforge_project
|
30
|
+
name
|
31
|
+
end
|
32
|
+
|
33
|
+
def gemspec_file
|
34
|
+
"#{name}.gemspec"
|
35
|
+
end
|
36
|
+
|
37
|
+
def gem_file
|
38
|
+
"#{name}-#{version}.gem"
|
39
|
+
end
|
40
|
+
|
41
|
+
def replace_header(head, header_name)
|
42
|
+
head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
|
43
|
+
end
|
44
|
+
|
45
|
+
#############################################################################
|
46
|
+
#
|
47
|
+
# Standard tasks
|
48
|
+
#
|
49
|
+
#############################################################################
|
50
|
+
|
51
|
+
GEM_NAME = "#{name}"
|
52
|
+
task :default => :test
|
53
|
+
task :travis => ['test', 'test:travis']
|
54
|
+
|
55
|
+
Rake::TestTask.new do |t|
|
56
|
+
t.pattern = File.join("spec", "**", "*_spec.rb")
|
57
|
+
end
|
58
|
+
|
59
|
+
namespace :test do
|
60
|
+
mock = ENV['FOG_MOCK'] || 'true'
|
61
|
+
task :travis do
|
62
|
+
sh("export FOG_MOCK=#{mock} && bundle exec shindont")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
desc 'Run mocked tests for a specific provider'
|
67
|
+
task :mock, :provider do |t, args|
|
68
|
+
if args.to_a.size != 1
|
69
|
+
fail 'USAGE: rake mock[<provider>]'
|
70
|
+
end
|
71
|
+
provider = args[:provider]
|
72
|
+
sh("export FOG_MOCK=true && bundle exec shindont tests/#{provider}")
|
73
|
+
end
|
74
|
+
|
75
|
+
desc 'Run live tests against a specific provider'
|
76
|
+
task :live, :provider do |t, args|
|
77
|
+
if args.to_a.size != 1
|
78
|
+
fail 'USAGE: rake live[<provider>]'
|
79
|
+
end
|
80
|
+
provider = args[:provider]
|
81
|
+
sh("export FOG_MOCK=false PROVIDER=#{provider} && bundle exec shindont tests/#{provider}")
|
82
|
+
end
|
data/fog-radosgw.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'fog/radosgw/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = 'fog-radosgw'
|
8
|
+
s.version = Fog::Radosgw::VERSION
|
9
|
+
s.authors = %q(Jon Kåre Hellan)
|
10
|
+
s.email = %q(hellan@acm.org)
|
11
|
+
s.summary = %q{Fog backend for provisioning Ceph Radosgw.}
|
12
|
+
s.description = %q{Fog backend for provisioning users on Ceph Radosgw - the Swift and S3 compatible REST API for Ceph.}
|
13
|
+
s.homepage = 'https://github.com/fog/fog-radosgw'
|
14
|
+
s.license = 'MIT'
|
15
|
+
|
16
|
+
s.files = `git ls-files -z`.split("\x0")
|
17
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
19
|
+
s.require_paths = %w(lib)
|
20
|
+
|
21
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
22
|
+
s.extra_rdoc_files = %w[README.md LICENSE.md]
|
23
|
+
|
24
|
+
s.add_dependency 'fog-json'
|
25
|
+
s.add_dependency 'fog', '>=1.21.0'
|
26
|
+
s.add_development_dependency 'rake'
|
27
|
+
s.add_development_dependency 'yard'
|
28
|
+
s.add_development_dependency 'shindo'
|
29
|
+
end
|
data/lib/fog/radosgw.rb
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'fog'
|
2
|
+
|
3
|
+
module Fog
|
4
|
+
module Radosgw
|
5
|
+
module MultipartUtils
|
6
|
+
require 'net/http'
|
7
|
+
|
8
|
+
class Headers
|
9
|
+
include Net::HTTPHeader
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
initialize_http_header({})
|
13
|
+
end
|
14
|
+
|
15
|
+
# Parse a single header line into its key and value
|
16
|
+
# @param [String] chunk a single header line
|
17
|
+
def self.parse(chunk)
|
18
|
+
line = chunk.strip
|
19
|
+
# thanks Net::HTTPResponse
|
20
|
+
return [nil,nil] if chunk =~ /\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)\s*(.*)\z/in
|
21
|
+
m = /\A([^:]+):\s*/.match(line)
|
22
|
+
[m[1], m.post_match] rescue [nil, nil]
|
23
|
+
end
|
24
|
+
|
25
|
+
# Parses a header line and adds it to the header collection
|
26
|
+
# @param [String] chunk a single header line
|
27
|
+
def parse(chunk)
|
28
|
+
key, value = self.class.parse(chunk)
|
29
|
+
add_field(key, value) if key && value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def parse(data, boundary)
|
34
|
+
contents = data.match(end_boundary_regex(boundary)).pre_match rescue ""
|
35
|
+
contents.split(inner_boundary_regex(boundary)).reject(&:empty?).map do |part|
|
36
|
+
parse_multipart_section(part)
|
37
|
+
end.compact
|
38
|
+
end
|
39
|
+
|
40
|
+
def extract_boundary(header_string)
|
41
|
+
$1 if header_string =~ /boundary=([A-Za-z0-9\'()+_,-.\/:=?]+)/
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def end_boundary_regex(boundary)
|
46
|
+
/\r?\n--#{Regexp.escape(boundary)}--\r?\n?/
|
47
|
+
end
|
48
|
+
|
49
|
+
def inner_boundary_regex(boundary)
|
50
|
+
/\r?\n--#{Regexp.escape(boundary)}\r?\n/
|
51
|
+
end
|
52
|
+
|
53
|
+
def parse_multipart_section(part)
|
54
|
+
headers = Headers.new
|
55
|
+
if md = part.match(/\r?\n\r?\n/)
|
56
|
+
body = md.post_match
|
57
|
+
md.pre_match.split(/\r?\n/).each do |line|
|
58
|
+
headers.parse(line)
|
59
|
+
end
|
60
|
+
|
61
|
+
if headers["content-type"] =~ /multipart\/mixed/
|
62
|
+
boundary = extract_boundary(headers.to_hash["content-type"].first)
|
63
|
+
parse(body, boundary)
|
64
|
+
else
|
65
|
+
{:headers => headers.to_hash, :body => body}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
module UserUtils
|
72
|
+
|
73
|
+
def update_radosgw_user(user_id, user)
|
74
|
+
path = "admin/user"
|
75
|
+
user_id = Fog::AWS.escape(user_id)
|
76
|
+
params = {
|
77
|
+
:method => 'POST',
|
78
|
+
:path => path,
|
79
|
+
}
|
80
|
+
query = "?uid=#{user_id}&format=json&suspended=#{user[:suspended]}"
|
81
|
+
begin
|
82
|
+
response = Excon.post("#{@scheme}://#{@host}/#{path}#{query}",
|
83
|
+
:headers => signed_headers(params))
|
84
|
+
if !response.body.empty?
|
85
|
+
case response.headers['Content-Type']
|
86
|
+
when 'application/json'
|
87
|
+
response.body = Fog::JSON.decode(response.body)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
response
|
91
|
+
rescue Excon::Errors::NotFound => e
|
92
|
+
raise Fog::Radosgw::Provisioning::NoSuchUser.new
|
93
|
+
rescue Excon::Errors::BadRequest => e
|
94
|
+
raise Fog::Radosgw::Provisioning::ServiceUnavailable.new
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def update_mock_user(user_id, user)
|
99
|
+
if data[user_id]
|
100
|
+
if suspended = user[:suspended]
|
101
|
+
data[user_id][:suspended] = suspended
|
102
|
+
end
|
103
|
+
|
104
|
+
Excon::Response.new.tap do |response|
|
105
|
+
response.status = 200
|
106
|
+
response.body = data[user_id]
|
107
|
+
end
|
108
|
+
else
|
109
|
+
Excon::Response.new.tap do |response|
|
110
|
+
response.status = 403
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
module Utils
|
117
|
+
def configure_uri_options(options = {})
|
118
|
+
@host = options[:host] || 'localhost'
|
119
|
+
@persistent = options[:persistent] || true
|
120
|
+
@port = options[:port] || 8080
|
121
|
+
@scheme = options[:scheme] || 'http'
|
122
|
+
end
|
123
|
+
|
124
|
+
def radosgw_uri
|
125
|
+
"#{@scheme}://#{@host}:#{@port}"
|
126
|
+
end
|
127
|
+
|
128
|
+
def signed_headers(params)
|
129
|
+
expires = Fog::Time.now.to_date_header
|
130
|
+
auth = @s3_connection.signature(params,expires)
|
131
|
+
awskey = @radosgw_access_key_id
|
132
|
+
headers = {
|
133
|
+
'Date' => expires,
|
134
|
+
'Authorization' => "AWS #{awskey}:#{auth}"
|
135
|
+
}
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
extend Fog::Provider
|
140
|
+
|
141
|
+
service(:provisioning, 'Provisioning')
|
142
|
+
service(:usage, 'Usage')
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'fog/radosgw/core'
|
2
|
+
|
3
|
+
module Fog
|
4
|
+
module Radosgw
|
5
|
+
class Provisioning < Fog::Service
|
6
|
+
|
7
|
+
class UserAlreadyExists < Fog::Radosgw::Provisioning::Error; end
|
8
|
+
class NoSuchUser < Fog::Radosgw::Provisioning::Error; end
|
9
|
+
class ServiceUnavailable < Fog::Radosgw::Provisioning::Error; end
|
10
|
+
|
11
|
+
requires :radosgw_access_key_id, :radosgw_secret_access_key
|
12
|
+
recognizes :host, :path, :port, :scheme, :persistent, :path_style
|
13
|
+
|
14
|
+
request_path 'fog/radosgw/requests/provisioning'
|
15
|
+
request :create_user
|
16
|
+
request :delete_user
|
17
|
+
request :update_user
|
18
|
+
request :disable_user
|
19
|
+
request :enable_user
|
20
|
+
request :list_users
|
21
|
+
request :get_user
|
22
|
+
|
23
|
+
class Mock
|
24
|
+
include Utils
|
25
|
+
|
26
|
+
def self.data
|
27
|
+
@data ||= Hash.new({})
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.reset
|
31
|
+
@data = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(options = {})
|
35
|
+
configure_uri_options(options)
|
36
|
+
end
|
37
|
+
|
38
|
+
def data
|
39
|
+
self.class.data[radosgw_uri]
|
40
|
+
end
|
41
|
+
|
42
|
+
def reset_data
|
43
|
+
self.class.data.delete(radosgw_uri)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class Real
|
48
|
+
include Utils
|
49
|
+
|
50
|
+
def initialize(options = {})
|
51
|
+
configure_uri_options(options)
|
52
|
+
@radosgw_access_key_id = options[:radosgw_access_key_id]
|
53
|
+
@radosgw_secret_access_key = options[:radosgw_secret_access_key]
|
54
|
+
@connection_options = options[:connection_options] || {}
|
55
|
+
@persistent = options[:persistent] || false
|
56
|
+
@path_style = options[:path_style] || false
|
57
|
+
|
58
|
+
@raw_connection = Fog::XML::Connection.new(radosgw_uri, @persistent, @connection_options)
|
59
|
+
|
60
|
+
@s3_connection = Fog::Storage.new(
|
61
|
+
:provider => 'AWS',
|
62
|
+
:aws_access_key_id => @radosgw_access_key_id,
|
63
|
+
:aws_secret_access_key => @radosgw_secret_access_key,
|
64
|
+
:host => @host,
|
65
|
+
:port => @port,
|
66
|
+
:scheme => @scheme,
|
67
|
+
:path_style => @path_style,
|
68
|
+
:connection_options => @connection_options
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
def request(params, parse_response = true, &block)
|
73
|
+
begin
|
74
|
+
response = @raw_connection.request(params.merge({
|
75
|
+
:host => @host,
|
76
|
+
:path => "#{@path}/#{params[:path]}",
|
77
|
+
}), &block)
|
78
|
+
rescue Excon::Errors::HTTPStatusError => error
|
79
|
+
if match = error.message.match(/<Code>(.*?)<\/Code>(?:.*<Message>(.*?)<\/Message>)?/m)
|
80
|
+
case match[1]
|
81
|
+
when 'UserAlreadyExists'
|
82
|
+
raise Fog::Radosgw::Provisioning.const_get(match[1]).new
|
83
|
+
when 'ServiceUnavailable'
|
84
|
+
raise Fog::Radosgw::Provisioning.const_get(match[1]).new
|
85
|
+
else
|
86
|
+
raise error
|
87
|
+
end
|
88
|
+
else
|
89
|
+
raise error
|
90
|
+
end
|
91
|
+
end
|
92
|
+
if !response.body.empty? && parse_response
|
93
|
+
response.body = Fog::JSON.decode(response.body)
|
94
|
+
end
|
95
|
+
response
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|