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 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: []