openstack-swifter 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/README.rdoc +47 -0
- data/Rakefile +2 -0
- data/config/swift.yml +4 -0
- data/lib/openstack-swift.rb +41 -0
- data/lib/openstack-swift/api.rb +223 -0
- data/lib/openstack-swift/client.rb +76 -0
- data/lib/openstack-swift/errors.rb +5 -0
- data/lib/openstack-swift/swift_config.rb +19 -0
- data/lib/openstack-swift/version.rb +6 -0
- data/openstack-swift.gemspec +23 -0
- data/spec/openstack-swift/api_spec.rb +120 -0
- data/spec/openstack-swift/client_spec.rb +62 -0
- data/spec/spec_helper.rb +13 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 84c79ccd617434d66fd9992d52a430c26b657b10
|
4
|
+
data.tar.gz: 867f2093ddc4efb803ee4d36b72469c428d95f8d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 35538c1b3fcb5229837641d74d321a4d11e0bf06475dc25050ebd734a83b4ab5743add0ada7072915d578ef54c879b59105d80c6926695e7a697748344248837
|
7
|
+
data.tar.gz: 894f21bb72cdd033e93978ecb1bf4b3b9ad2667725bdbf656ba7f00bf4e6f8674bf08c91d135de00379a876eb95c773a5f7d7c4f20cf3ed16c64af34ccc17dd3
|
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
= Openstack::Swift
|
2
|
+
|
3
|
+
Communicate with Swift
|
4
|
+
|
5
|
+
== Installation
|
6
|
+
|
7
|
+
gem install openstack-swift
|
8
|
+
|
9
|
+
== Usage
|
10
|
+
|
11
|
+
=== Simple upload
|
12
|
+
|
13
|
+
swift = Openstack::Swift::Client.new(
|
14
|
+
"https://myserver.com/swift",
|
15
|
+
"system:root",
|
16
|
+
"testpass"
|
17
|
+
)
|
18
|
+
|
19
|
+
swift.upload("choosed_container", "/path/of/your/file")
|
20
|
+
|
21
|
+
== Maintainers
|
22
|
+
|
23
|
+
* PotHix (Willian Molinari) (http://pothix.com)
|
24
|
+
* Morellon (Thiago Morello) (http://morellon.com.br)
|
25
|
+
|
26
|
+
== License
|
27
|
+
|
28
|
+
(The MIT License)
|
29
|
+
|
30
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
31
|
+
a copy of this software and associated documentation files (the
|
32
|
+
'Software'), to deal in the Software without restriction, including
|
33
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
34
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
35
|
+
permit persons to whom the Software is furnished to do so, subject to
|
36
|
+
the following conditions:
|
37
|
+
|
38
|
+
The above copyright notice and this permission notice shall be
|
39
|
+
included in all copies or substantial portions of the Software.
|
40
|
+
|
41
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
42
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
43
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
44
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
45
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
46
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
47
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
data/config/swift.yml
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
2
|
+
require "httparty"
|
3
|
+
require "net/http"
|
4
|
+
require "net/https"
|
5
|
+
require "fileutils"
|
6
|
+
require "openstack-swift/api"
|
7
|
+
require "openstack-swift/errors"
|
8
|
+
require "openstack-swift/client"
|
9
|
+
require "openstack-swift/swift_config"
|
10
|
+
|
11
|
+
module Openstack
|
12
|
+
module Swift
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module Net
|
17
|
+
class HTTPGenericRequest
|
18
|
+
private
|
19
|
+
def send_request_with_body_stream(sock, ver, path, f)
|
20
|
+
unless content_length() or chunked?
|
21
|
+
raise ArgumentError,
|
22
|
+
"Content-Length not given and Transfer-Encoding is not `chunked'"
|
23
|
+
end
|
24
|
+
supply_default_content_type
|
25
|
+
write_header sock, ver, path
|
26
|
+
if chunked?
|
27
|
+
while s = f.read(1024)
|
28
|
+
sock.write(sprintf("%x\r\n", s.length) << s << "\r\n")
|
29
|
+
end
|
30
|
+
sock.write "0\r\n\r\n"
|
31
|
+
else
|
32
|
+
bytes_written = 0
|
33
|
+
buffer=1024
|
34
|
+
while (s = f.read(buffer)) && bytes_written < content_length()
|
35
|
+
sock.write s
|
36
|
+
bytes_written += buffer
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
2
|
+
module Openstack
|
3
|
+
module Swift
|
4
|
+
module Api
|
5
|
+
extend self
|
6
|
+
MAX_SIZE = 4 * 1024 ** 3
|
7
|
+
|
8
|
+
# Authentication method to get the url and token to conect to swift
|
9
|
+
# Returns:
|
10
|
+
# x-storage-url
|
11
|
+
# x-storage-token
|
12
|
+
# x-auth-token
|
13
|
+
def auth(proxy, user, password)
|
14
|
+
res = HTTParty.get(proxy, :headers => {"X-Auth-User" => user, "X-Auth-Key" => password})
|
15
|
+
raise AuthenticationError unless res.code == 200
|
16
|
+
|
17
|
+
[res.headers["x-storage-url"],res.headers["x-storage-token"],res.headers["x-auth-token"]]
|
18
|
+
end
|
19
|
+
|
20
|
+
# Get informations about the currect account used to connect to swift
|
21
|
+
# Returns:
|
22
|
+
# x-account-bytes-used
|
23
|
+
# x-account-object-count
|
24
|
+
# x-account-container-count
|
25
|
+
def account(url, token)
|
26
|
+
query = {:format => "json"}
|
27
|
+
HTTParty.head(url, :headers => {"X-Auth-Token"=> token}, :query => query).headers
|
28
|
+
end
|
29
|
+
|
30
|
+
# List containers
|
31
|
+
# Note that swift only returns 1000 items, so to list more than this
|
32
|
+
# you should use the marker option as the name of the last returned item (1000th item)
|
33
|
+
# to return the next sequency (1001 - 2000)
|
34
|
+
# query options: marker, prefix, limit
|
35
|
+
def containers(url, token, query = {})
|
36
|
+
query = query.merge(:format => "json")
|
37
|
+
res = HTTParty.get(url, :headers => {"X-Auth-Token"=> token}, :query => query)
|
38
|
+
res.to_a
|
39
|
+
end
|
40
|
+
|
41
|
+
# Get all objects for a given container
|
42
|
+
# Query options:
|
43
|
+
# marker
|
44
|
+
# prefix
|
45
|
+
# limit
|
46
|
+
# delimiter
|
47
|
+
def objects(url, token, container, query = {})
|
48
|
+
query = query.merge(:format => "json")
|
49
|
+
res = HTTParty.get("#{url}/#{container}", :headers => {"X-Auth-Token"=> token}, :query => query)
|
50
|
+
res.to_a
|
51
|
+
end
|
52
|
+
|
53
|
+
# Delete a container for a given name from swift
|
54
|
+
def delete_container(url, token, container)
|
55
|
+
res = HTTParty.delete("#{url}/#{container}", :headers => {"X-Auth-Token"=> token})
|
56
|
+
raise "Could not delete container '#{container}'" if res.code < 200 or res.code >= 300
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
# Create a container on swift from a given name
|
61
|
+
def create_container(url, token, container)
|
62
|
+
res = HTTParty.put("#{url}/#{container}", :headers => {"X-Auth-Token"=> token})
|
63
|
+
raise "Could not create container '#{container}'" if res.code < 200 or res.code >= 300
|
64
|
+
true
|
65
|
+
end
|
66
|
+
|
67
|
+
# Get the object stat given the object name and the container this object is in
|
68
|
+
def object_stat(url, token, container, object)
|
69
|
+
url = "#{url}/#{container}/#{object}"
|
70
|
+
query = {:format => "json"}
|
71
|
+
HTTParty.head(url, :headers => {"X-Auth-Token"=> token}, :query => query).headers
|
72
|
+
end
|
73
|
+
|
74
|
+
# Creates the manifest file for a splitted upload
|
75
|
+
# Given the container and file path a manifest is created to guide the downloads of this
|
76
|
+
# splitted file
|
77
|
+
def create_manifest(url, token, container, file_path)
|
78
|
+
file_name = file_path.match(/.+\/(.+?)$/)[1]
|
79
|
+
file_size = File.size(file_path)
|
80
|
+
file_mtime = File.mtime(file_path).to_f.round(2)
|
81
|
+
manifest_path = "#{container}_segments/#{file_name}/#{file_mtime}/#{file_size}/"
|
82
|
+
|
83
|
+
res = HTTParty.put("#{url}/#{container}/#{file_name}", :headers => {
|
84
|
+
"X-Auth-Token" => token,
|
85
|
+
"x-object-manifest" => manifest_path,
|
86
|
+
"Content-Type" => "application/octet-stream",
|
87
|
+
"Content-Length" => "0"
|
88
|
+
})
|
89
|
+
|
90
|
+
raise "Could not create manifest for '#{file_path}'" if res.code < 200 or res.code >= 300
|
91
|
+
true
|
92
|
+
end
|
93
|
+
|
94
|
+
# Downloads an object (file) to disk and returns the saved file path
|
95
|
+
def download_object(url, token, container, object, file_name=nil)
|
96
|
+
file_name ||= "/tmp/swift/#{container}/#{object}"
|
97
|
+
|
98
|
+
# creating directory if it doesn't exist
|
99
|
+
FileUtils.mkdir_p(file_name.match(/(.+)\/.+?$/)[1])
|
100
|
+
file = File.open(file_name, "wb")
|
101
|
+
uri = URI.parse("#{url}/#{container}/#{object}")
|
102
|
+
|
103
|
+
req = Net::HTTP::Get.new(uri.path)
|
104
|
+
req.add_field("X-Auth-Token", token)
|
105
|
+
|
106
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
107
|
+
http.use_ssl = false
|
108
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
109
|
+
md5 = Digest::MD5.new
|
110
|
+
|
111
|
+
http.request(req) do |res|
|
112
|
+
res.read_body do |chunk|
|
113
|
+
file.write chunk
|
114
|
+
md5.update(chunk)
|
115
|
+
end
|
116
|
+
|
117
|
+
raise "MD5 checksum failed for #{container}/#{object}" if res["x-object-manifest"].nil? && res["etag"] != md5.hexdigest
|
118
|
+
end
|
119
|
+
|
120
|
+
file_name
|
121
|
+
ensure
|
122
|
+
file.close rescue nil
|
123
|
+
end
|
124
|
+
|
125
|
+
# Delete a object for a given container and object name
|
126
|
+
def delete_object(url, token, container, object)
|
127
|
+
res = HTTParty.delete("#{url}/#{container}/#{object}", :headers => {"X-Auth-Token"=> token})
|
128
|
+
raise "Could not delete object '#{object}'" if res.code < 200 or res.code >= 300
|
129
|
+
true
|
130
|
+
end
|
131
|
+
|
132
|
+
# Delete all files listed on a manifest following the swift standard order
|
133
|
+
# What it does is try to delete all files in sequence from a given manifest until there are no files to delete
|
134
|
+
# returning a 404 error code. We don't know how many files are stored for a manifest file.
|
135
|
+
def delete_objects_from_manifest(url, token, container, manifest)
|
136
|
+
manifest_info = object_stat(url, token, container, manifest)
|
137
|
+
files_path = "#{manifest_info["x-object-manifest"]}%08d"
|
138
|
+
|
139
|
+
manifest_info["content-length"].to_i.times do |index|
|
140
|
+
res = HTTParty.delete("#{url}/#{files_path % index}", :headers => {"X-Auth-Token"=> token})
|
141
|
+
raise ObjectNotFoundError if res.code == 404
|
142
|
+
end
|
143
|
+
rescue ObjectNotFoundError
|
144
|
+
true
|
145
|
+
end
|
146
|
+
|
147
|
+
def upload_object(url, token, container, file_path, options={})
|
148
|
+
options[:segments_size] ||= MAX_SIZE
|
149
|
+
|
150
|
+
create_container(url, token, container) rescue nil
|
151
|
+
|
152
|
+
file_name, file_mtime, file_size = file_info(file_path)
|
153
|
+
|
154
|
+
if file_size > options[:segments_size]
|
155
|
+
create_container(url, token, "#{container}_segments") rescue nil
|
156
|
+
|
157
|
+
segments_minus_one = file_size / options[:segments_size]
|
158
|
+
last_piece = file_size - segments_minus_one * options[:segments_size]
|
159
|
+
segments_minus_one.times do |segment|
|
160
|
+
upload_segment(
|
161
|
+
url, token, "#{container}_segments", file_path,
|
162
|
+
:size => options[:segments_size],
|
163
|
+
:position => options[:segments_size] * segment,
|
164
|
+
:object_name => upload_path_for(file_path, segment)
|
165
|
+
)
|
166
|
+
end
|
167
|
+
|
168
|
+
upload_segment(
|
169
|
+
url, token, "#{container}_segments", file_path,
|
170
|
+
:size => last_piece,
|
171
|
+
:position => options[:segments_size] * segments_minus_one,
|
172
|
+
:object_name => upload_path_for(file_path, segments_minus_one)
|
173
|
+
)
|
174
|
+
|
175
|
+
create_manifest(url, token, container, file_path)
|
176
|
+
else
|
177
|
+
upload_segment(url, token, container, file_path)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Uploads a given object to a given container
|
182
|
+
def upload_segment(url, token, container, file_path, options={})
|
183
|
+
options[:object_name] ||= file_path.match(/.+\/(.+?)$/)[1]
|
184
|
+
file = File.open(file_path, "rb")
|
185
|
+
|
186
|
+
file.seek(options[:position]) if options[:position]
|
187
|
+
uri = URI.parse("#{url}/#{container}/#{options[:object_name]}")
|
188
|
+
|
189
|
+
req = Net::HTTP::Put.new(uri.path)
|
190
|
+
req.add_field("X-Auth-Token", token)
|
191
|
+
req.body_stream = file
|
192
|
+
req.content_length = options[:size] || File.size(file_path)
|
193
|
+
req.content_type = "application/octet-stream"
|
194
|
+
|
195
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
196
|
+
http.use_ssl = true
|
197
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
198
|
+
http.request(req)
|
199
|
+
ensure
|
200
|
+
file.close rescue nil
|
201
|
+
end
|
202
|
+
|
203
|
+
private
|
204
|
+
# Returns the standard swift path for a given file path and segment
|
205
|
+
def upload_path_for(file_path, segment)
|
206
|
+
"%s/%s/%s/%08d" % (file_info(file_path) << segment)
|
207
|
+
end
|
208
|
+
|
209
|
+
# Get relevant informations about a file
|
210
|
+
# Returns an array with:
|
211
|
+
# file_name
|
212
|
+
# file_mtime
|
213
|
+
# file_size
|
214
|
+
def file_info(file_path)
|
215
|
+
[
|
216
|
+
file_path.match(/.+\/(.+?)$/)[1],
|
217
|
+
File.mtime(file_path).to_f.round(2),
|
218
|
+
File.size(file_path)
|
219
|
+
]
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
2
|
+
module Openstack
|
3
|
+
module Swift
|
4
|
+
class Client
|
5
|
+
# Initialize method
|
6
|
+
# It uses the authenticate method to store the tokens for future requests
|
7
|
+
def initialize(proxy, user, password)
|
8
|
+
@proxy, @user, @password = proxy, user, password
|
9
|
+
authenticate!
|
10
|
+
end
|
11
|
+
|
12
|
+
# Authentication method
|
13
|
+
# It stores the authentication url and token for future commands
|
14
|
+
# avoiding to request a new token for each request
|
15
|
+
# It should be used to force a new token
|
16
|
+
def authenticate!
|
17
|
+
@url, _, @token = Api.auth(@proxy, @user, @password)
|
18
|
+
|
19
|
+
if @url.empty? or @token.empty?
|
20
|
+
raise AuthenticationError
|
21
|
+
else
|
22
|
+
true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the following informations about the account:
|
27
|
+
# bytes_used: Number of bytes used by this account
|
28
|
+
# object_count: Number of objects that this account have allocated
|
29
|
+
# container_count: Number of containers
|
30
|
+
def account_info
|
31
|
+
headers = Api.account(@url, @token)
|
32
|
+
{
|
33
|
+
"bytes_used" => headers["x-account-bytes-used"],
|
34
|
+
"object_count" => headers["x-account-object-count"],
|
35
|
+
"container_count" => headers["x-account-container-count"]
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns the following informations about the object:
|
40
|
+
# last_modified
|
41
|
+
# md5
|
42
|
+
# content_type
|
43
|
+
# manifest
|
44
|
+
# content_length
|
45
|
+
def object_info(container, object)
|
46
|
+
headers = Api.object_stat(@url, @token, container, object)
|
47
|
+
{
|
48
|
+
"last_modified" => headers["last-modified"],
|
49
|
+
"md5" => headers["etag"],
|
50
|
+
"content_type" => headers["content-type"],
|
51
|
+
"manifest" => headers["x-object-manifest"],
|
52
|
+
"content_length" => headers["content-length"]
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
# This method uploads a file from a given to a given container
|
57
|
+
def upload(container, file_path, options={})
|
58
|
+
Api.upload_object(@url, @token, container, file_path, options)
|
59
|
+
end
|
60
|
+
|
61
|
+
# This method downloads a object from a given container
|
62
|
+
def download(container, object, options={})
|
63
|
+
Api.download_object(@url, @token, container, object, options[:file_path])
|
64
|
+
end
|
65
|
+
|
66
|
+
# Delete a given object from a given container
|
67
|
+
def delete(container, object)
|
68
|
+
if object_info(container, object)["manifest"]
|
69
|
+
Api.delete_objects_from_manifest(@url, @token, container, object)
|
70
|
+
else
|
71
|
+
Api.delete_object(@url, @token, container, object)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
2
|
+
module Openstack
|
3
|
+
module SwiftConfig
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def [](name)
|
7
|
+
configs[name]
|
8
|
+
end
|
9
|
+
|
10
|
+
module_function
|
11
|
+
def configs
|
12
|
+
@config_file ||= load_file
|
13
|
+
end
|
14
|
+
|
15
|
+
def load_file
|
16
|
+
YAML.load_file(File.expand_path(File.dirname(__FILE__)) + "/../../config/swift.yml")[:swift]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "openstack-swift/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "openstack-swifter"
|
7
|
+
s.version = Openstack::Swift::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["morellon", "pothix", "rranshous"]
|
10
|
+
s.email = ["morellon@gmail.com", "pothix@pothix.com"]
|
11
|
+
s.homepage = "http://github.com/rranshous/openstack-swift"
|
12
|
+
s.description = %q{Openstack's swift client}
|
13
|
+
s.summary = s.description
|
14
|
+
|
15
|
+
s.rubyforge_project = "openstack-swift"
|
16
|
+
|
17
|
+
s.files = Dir["./**/*"].reject {|file| file =~ /\.git|pkg/}
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_dependency "httparty"
|
21
|
+
s.add_development_dependency "rspec", "~> 2.6"
|
22
|
+
s.add_development_dependency "ruby-debug19"
|
23
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe Openstack::Swift::Api do
|
5
|
+
context "when authenticating" do
|
6
|
+
it "should authenticate on swift" do
|
7
|
+
expect {
|
8
|
+
subject.auth(Openstack::SwiftConfig[:url], Openstack::SwiftConfig[:user], Openstack::SwiftConfig[:pass])
|
9
|
+
}.to_not raise_error Openstack::Swift::AuthenticationError
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should raise error for a invalid url" do
|
13
|
+
expect {
|
14
|
+
subject.auth("http://pothix.com/swift", Openstack::SwiftConfig[:user], Openstack::SwiftConfig[:pass])
|
15
|
+
}.to raise_error Openstack::Swift::AuthenticationError
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should raise error for a invalid pass" do
|
19
|
+
expect {
|
20
|
+
subject.auth(Openstack::SwiftConfig[:url], Openstack::SwiftConfig[:user], "invalidpassword")
|
21
|
+
}.to raise_error Openstack::Swift::AuthenticationError
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should raise error for a invalid user" do
|
25
|
+
expect {
|
26
|
+
subject.auth(Openstack::SwiftConfig[:url], "system:weirduser", Openstack::SwiftConfig[:pass])
|
27
|
+
}.to raise_error Openstack::Swift::AuthenticationError
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should return storage-url, storage-token and auth-token" do
|
31
|
+
subject.auth(Openstack::SwiftConfig[:url], Openstack::SwiftConfig[:user], Openstack::SwiftConfig[:pass]).should have(3).items
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "when authenticated" do
|
36
|
+
let!(:swift_dummy_file){ File.open("/tmp/swift-dummy", "w") {|f| f.puts("test file"*2000)}; "/tmp/swift-dummy" }
|
37
|
+
|
38
|
+
before do
|
39
|
+
@url, _, @token = subject.auth(
|
40
|
+
Openstack::SwiftConfig[:url],
|
41
|
+
Openstack::SwiftConfig[:user],
|
42
|
+
Openstack::SwiftConfig[:pass]
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should return account's headers" do
|
47
|
+
account = subject.account(@url, @token)
|
48
|
+
account.should have_key("x-account-bytes-used")
|
49
|
+
account.should have_key("x-account-object-count")
|
50
|
+
account.should have_key("x-account-container-count")
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should return a list of containers" do
|
54
|
+
subject.containers(@url, @token).should be_a(Array)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should return a list of objects" do
|
58
|
+
subject.objects(@url, @token, "morellon", :delimiter => "/").should be_a(Array)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should download an object" do
|
62
|
+
subject.download_object(@url, @token, "morellon", "Gemfile").should == "/tmp/swift/morellon/Gemfile"
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should upload an object" do
|
66
|
+
subject.upload_object(@url, @token, "morellon", "/tmp/swift-dummy").code.should == "201"
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should create a new container" do
|
70
|
+
subject.create_container(@url, @token, "pothix_container").should be_true
|
71
|
+
end
|
72
|
+
|
73
|
+
context "whe excluding an object" do
|
74
|
+
before { @container = "morellon" }
|
75
|
+
|
76
|
+
it "should delete an existent object" do
|
77
|
+
subject.upload_object(@url, @token, @container, "/tmp/swift-dummy").should be_true
|
78
|
+
subject.delete_object(@url, @token, @container, "swift-dummy").should be_true
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context "when excluding all objects from a manifest" do
|
83
|
+
it "should remove all objects from a given manifest" do
|
84
|
+
container = "multiseg"
|
85
|
+
subject.upload_object(@url, @token, container, swift_dummy_file, {:segments_size => 1024})
|
86
|
+
manifest = subject.object_stat(@url, @token, container, "swift-dummy")["x-object-manifest"]
|
87
|
+
segment_path = manifest.split("/")[1..-1].join("/") + "/00000000"
|
88
|
+
subject.object_stat(@url, @token, "#{container}_segments", segment_path)["etag"].should_not be_nil
|
89
|
+
subject.delete_objects_from_manifest(@url, @token, container, "swift-dummy").should be_true
|
90
|
+
subject.object_stat(@url, @token, "#{container}_segments", segment_path)["etag"].should be_nil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context "when excluding a container" do
|
95
|
+
before { @container = "pothix_container" }
|
96
|
+
|
97
|
+
it "should delete an existent container" do
|
98
|
+
subject.create_container(@url, @token, @container).should be_true
|
99
|
+
subject.delete_container(@url, @token, @container).should be_true
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should raise an error when the container doesn't exist" do
|
103
|
+
expect {
|
104
|
+
subject.delete_container(@url, @token, @container).should be_true
|
105
|
+
subject.delete_container(@url, @token, @container).should be_true
|
106
|
+
}.to raise_error("Could not delete container '#{@container}'")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should get the file stat" do
|
111
|
+
subject.upload_object(@url, @token, "morellon", "/tmp/swift-dummy")
|
112
|
+
headers = subject.object_stat(@url, @token, "morellon", "swift-dummy")
|
113
|
+
|
114
|
+
headers["last-modified"].should_not be_blank
|
115
|
+
headers["etag"].should_not be_blank
|
116
|
+
headers["content-type"].should_not be_blank
|
117
|
+
headers["date"].should_not be_blank
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe "Openstack::Swift::Client" do
|
5
|
+
let!(:swift_dummy_file) do
|
6
|
+
file_path = "/tmp/swift-dummy"
|
7
|
+
File.open(file_path, "w") {|f| f.puts("testfile "*1000)}
|
8
|
+
file_path
|
9
|
+
end
|
10
|
+
|
11
|
+
context "when authenticating" do
|
12
|
+
it "should return authentication error if one of the parameters is incorrect" do
|
13
|
+
expect {
|
14
|
+
Openstack::Swift::Client.new("http://incorrect.com/swift", Openstack::SwiftConfig[:user], Openstack::SwiftConfig[:pass])
|
15
|
+
}.to raise_error(Openstack::Swift::AuthenticationError)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "shoud not need to authenticate again" do
|
19
|
+
client = Openstack::Swift::Client.new(Openstack::SwiftConfig[:url], Openstack::SwiftConfig[:user], Openstack::SwiftConfig[:pass])
|
20
|
+
Openstack::Swift::Api.should_not_receive(:auth)
|
21
|
+
client.account_info
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "when authenticated" do
|
26
|
+
subject { Openstack::Swift::Client.new(Openstack::SwiftConfig[:url], Openstack::SwiftConfig[:user], Openstack::SwiftConfig[:pass]) }
|
27
|
+
|
28
|
+
it "should try to upload" do
|
29
|
+
expect {
|
30
|
+
subject.upload("pothix", swift_dummy_file, {:segments_size => 1024*2})
|
31
|
+
}.to_not raise_error
|
32
|
+
subject.object_info("pothix", "swift-dummy")["manifest"].should_not be_nil
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should download an splitted file" do
|
36
|
+
content_length = subject.object_info("morellon", "splitted_file")["content_length"].to_i
|
37
|
+
file_path = subject.download("morellon", "splitted_file")
|
38
|
+
File.size(file_path).should == content_length
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should return account's details" do
|
42
|
+
account_info = subject.account_info
|
43
|
+
account_info.should have_key("bytes_used")
|
44
|
+
account_info.should have_key("object_count")
|
45
|
+
account_info.should have_key("container_count")
|
46
|
+
end
|
47
|
+
|
48
|
+
context "when deleting" do
|
49
|
+
it "should call the delete method for a non manifest file" do
|
50
|
+
Openstack::Swift::Api.should_receive(:object_stat).and_return({"x-object-manifest" => nil})
|
51
|
+
Openstack::Swift::Api.should_receive(:delete_object)
|
52
|
+
subject.delete("pothix","swift-dummy")
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should call the delete_objects_from_manifest method for a manifest file" do
|
56
|
+
Openstack::Swift::Api.should_receive(:object_stat).and_return({"x-object-manifest" => "pothix_segments/swift-dummy/1313763802.0/9001/"})
|
57
|
+
Openstack::Swift::Api.should_receive(:delete_objects_from_manifest)
|
58
|
+
subject.delete("pothix","swift-dummy")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
2
|
+
require "rubygems"
|
3
|
+
require "bundler/setup"
|
4
|
+
|
5
|
+
require "openstack-swift"
|
6
|
+
|
7
|
+
Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|file| require file}
|
8
|
+
|
9
|
+
# Using a diferent YAML engine on test environment
|
10
|
+
YAML::ENGINE::yamler = "syck"
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: openstack-swifter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- morellon
|
8
|
+
- pothix
|
9
|
+
- rranshous
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2015-04-03 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: httparty
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ">="
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '0'
|
29
|
+
- !ruby/object:Gem::Dependency
|
30
|
+
name: rspec
|
31
|
+
requirement: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - "~>"
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '2.6'
|
36
|
+
type: :development
|
37
|
+
prerelease: false
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - "~>"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '2.6'
|
43
|
+
- !ruby/object:Gem::Dependency
|
44
|
+
name: ruby-debug19
|
45
|
+
requirement: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
type: :development
|
51
|
+
prerelease: false
|
52
|
+
version_requirements: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
description: Openstack's swift client
|
58
|
+
email:
|
59
|
+
- morellon@gmail.com
|
60
|
+
- pothix@pothix.com
|
61
|
+
executables: []
|
62
|
+
extensions: []
|
63
|
+
extra_rdoc_files: []
|
64
|
+
files:
|
65
|
+
- "./Gemfile"
|
66
|
+
- "./README.rdoc"
|
67
|
+
- "./Rakefile"
|
68
|
+
- "./config/swift.yml"
|
69
|
+
- "./lib/openstack-swift.rb"
|
70
|
+
- "./lib/openstack-swift/api.rb"
|
71
|
+
- "./lib/openstack-swift/client.rb"
|
72
|
+
- "./lib/openstack-swift/errors.rb"
|
73
|
+
- "./lib/openstack-swift/swift_config.rb"
|
74
|
+
- "./lib/openstack-swift/version.rb"
|
75
|
+
- "./openstack-swift.gemspec"
|
76
|
+
- "./spec/openstack-swift/api_spec.rb"
|
77
|
+
- "./spec/openstack-swift/client_spec.rb"
|
78
|
+
- "./spec/spec_helper.rb"
|
79
|
+
homepage: http://github.com/rranshous/openstack-swift
|
80
|
+
licenses: []
|
81
|
+
metadata: {}
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options: []
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
requirements: []
|
97
|
+
rubyforge_project: openstack-swift
|
98
|
+
rubygems_version: 2.4.5
|
99
|
+
signing_key:
|
100
|
+
specification_version: 4
|
101
|
+
summary: Openstack's swift client
|
102
|
+
test_files: []
|