openstack-swifter 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,4 @@
1
+ source "http://gems.locaweb.com.br"
2
+
3
+ # Specify your gem's dependencies in openstack-swift.gemspec
4
+ gemspec
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
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
data/config/swift.yml ADDED
@@ -0,0 +1,4 @@
1
+ :swift:
2
+ :url: "https://sw:8080/auth/v1.0"
3
+ :user: "system:root"
4
+ :pass: "testpass"
@@ -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,5 @@
1
+ # -*- coding: UTF-8 -*-
2
+ module Openstack::Swift
3
+ class AuthenticationError < Exception; end
4
+ class ObjectNotFoundError < Exception; end
5
+ 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,6 @@
1
+ # -*- coding: UTF-8 -*-
2
+ module Openstack
3
+ module Swift
4
+ VERSION = "0.3.1"
5
+ end
6
+ 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
@@ -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: []