blobstore_client 0.4.0 → 0.5.0

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.
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (c) 2009-2012 VMware, Inc.
4
+ #
5
+ # Usage example:
6
+ #
7
+ # => Debugger enabled
8
+ # => Welcome to BOSH blobstore client console
9
+ # You can use 'bsc' to access blobstore client methods
10
+ # irb(main):001:0> oid = bsc.create("test data content")
11
+ # => "eyJvaWQiOiJlNGQ5MTUzMy1iOTZiLVlYjc1YzQ1NTAi%0ALCJwdXJsIjpudWxsfQ==%0A"
12
+ # irb(main):002:0> bsc.get(oid)
13
+ # => "test data content"
14
+ # irb(main):003:0> bsc.delete(oid)
15
+ # => true
16
+
17
+ gemfile = File.expand_path("../../Gemfile", __FILE__)
18
+
19
+ if File.exists?(gemfile)
20
+ ENV["BUNDLE_GEMFILE"] = gemfile
21
+ require "rubygems"
22
+ require "bundler/setup"
23
+ end
24
+
25
+ $:.unshift(File.expand_path("../../lib", __FILE__))
26
+ require "blobstore_client"
27
+ require "irb"
28
+ require "irb/completion"
29
+ require "ostruct"
30
+ require "optparse"
31
+
32
+ @provider = nil
33
+ config_file = nil
34
+
35
+ opts_parser = OptionParser.new do |opts|
36
+ opts.on("-p", "--provider PROVIDER") { |p| @provider = p }
37
+ opts.on("-c", "--config FILE") { |file| config_file = file }
38
+ end
39
+ opts_parser.parse!
40
+
41
+ unless @provider && config_file
42
+ puts opts_parser
43
+ exit(1)
44
+ end
45
+
46
+ @config = YAML.load_file(config_file)
47
+
48
+ module ConsoleHelpers
49
+ def bsc
50
+ @bsc ||= Bosh::Blobstore::Client.create(@provider, @config)
51
+ end
52
+ end
53
+
54
+ include ConsoleHelpers
55
+
56
+ begin
57
+ require 'ruby-debug'
58
+ puts "=> Debugger enabled"
59
+ rescue LoadError
60
+ puts "=> ruby-debug not found, debugger disabled"
61
+ end
62
+
63
+ puts "=> Welcome to BOSH blobstore client console"
64
+ puts "You can use 'bsc' to access blobstore client methods"
65
+
66
+ IRB.start
@@ -0,0 +1,160 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ require "base64"
4
+ require "fog"
5
+ require "multi_json"
6
+ require "uri"
7
+ require "uuidtools"
8
+
9
+ module Bosh
10
+ module Blobstore
11
+
12
+ class SwiftBlobstoreClient < BaseClient
13
+
14
+ # Blobstore client for Swift
15
+ # @param [Hash] options Swift BlobStore options
16
+ # @option options [Symbol] container_name
17
+ # @option options [Symbol] swift_provider
18
+ def initialize(options)
19
+ super(options)
20
+ @http_client = HTTPClient.new
21
+ end
22
+
23
+ def container
24
+ return @container if @container
25
+
26
+ validate_options(@options)
27
+
28
+ swift_provider = @options[:swift_provider]
29
+ swift_options = {:provider => swift_provider}
30
+ swift_options.merge!(@options[swift_provider.to_sym])
31
+ swift = Fog::Storage.new(swift_options)
32
+
33
+ container_name = @options[:container_name]
34
+ @container = swift.directories.get(container_name)
35
+ if @container.nil?
36
+ raise NotFound, "Swift container '#{container_name}' not found"
37
+ end
38
+ @container
39
+ end
40
+
41
+ def create_file(file)
42
+ object_id = generate_object_id
43
+ object = container.files.create(:key => object_id,
44
+ :body => file,
45
+ :public => true)
46
+ encode_object_id(object_id, object.public_url)
47
+ rescue Exception => e
48
+ raise BlobstoreError, "Failed to create object: #{e.message}"
49
+ end
50
+
51
+ def get_file(object_id, file)
52
+ object_info = decode_object_id(object_id)
53
+ if object_info["purl"]
54
+ response = @http_client.get(object_info["purl"]) do |block|
55
+ file.write(block)
56
+ end
57
+ if response.status != 200
58
+ raise BlobstoreError, "Could not fetch object, %s/%s" %
59
+ [response.status, response.content]
60
+ end
61
+ else
62
+ object = container.files.get(object_info["oid"]) do |block|
63
+ file.write(block)
64
+ end
65
+ if object.nil?
66
+ raise NotFound, "Swift object '#{object_id}' not found"
67
+ end
68
+ end
69
+ rescue Exception => e
70
+ raise BlobstoreError,
71
+ "Failed to find object '#{object_id}': #{e.message}"
72
+ end
73
+
74
+ def delete(object_id)
75
+ object_info = decode_object_id(object_id)
76
+ object = container.files.get(object_info["oid"])
77
+ if object.nil?
78
+ raise NotFound, "Swift object '#{object_id}' not found"
79
+ else
80
+ object.destroy
81
+ end
82
+ rescue Exception => e
83
+ raise BlobstoreError,
84
+ "Failed to delete object '#{object_id}': #{e.message}"
85
+ end
86
+
87
+ private
88
+
89
+ def generate_object_id
90
+ UUIDTools::UUID.random_create.to_s
91
+ end
92
+
93
+ def encode_object_id(object_id, public_url = nil)
94
+ json = MultiJson.encode({:oid => object_id, :purl => public_url})
95
+ URI::escape(Base64.encode64(json))
96
+ end
97
+
98
+ def decode_object_id(object_id)
99
+ begin
100
+ object_info = MultiJson.decode(Base64.decode64(URI::unescape(object_id)))
101
+ rescue MultiJson::DecodeError => e
102
+ raise BlobstoreError, "Failed to parse object_id: #{e.message}"
103
+ end
104
+
105
+ if !object_info.kind_of?(Hash) || object_info["oid"].nil?
106
+ raise BlobstoreError, "Invalid object_id: #{object_info.inspect}"
107
+ end
108
+ object_info
109
+ end
110
+
111
+ def validate_options(options)
112
+ unless options.is_a?(Hash)
113
+ raise "Invalid options format, Hash expected, #{options.class} given"
114
+ end
115
+ unless options.has_key?(:container_name)
116
+ raise "Swift container name is missing"
117
+ end
118
+ unless options.has_key?(:swift_provider)
119
+ raise "Swift provider is missing"
120
+ end
121
+ case options[:swift_provider]
122
+ when "hp"
123
+ unless options.has_key?(:hp)
124
+ raise "HP options are missing"
125
+ end
126
+ unless options[:hp].is_a?(Hash)
127
+ raise "Invalid HP options, Hash expected,
128
+ #{options[:hp].class} given"
129
+ end
130
+ unless options[:hp].has_key?(:hp_account_id)
131
+ raise "HP account ID is missing"
132
+ end
133
+ unless options[:hp].has_key?(:hp_secret_key)
134
+ raise "HP secret key is missing"
135
+ end
136
+ unless options[:hp].has_key?(:hp_tenant_id)
137
+ raise "HP tenant ID is missing"
138
+ end
139
+ when "rackspace"
140
+ unless options.has_key?(:rackspace)
141
+ raise "Rackspace options are missing"
142
+ end
143
+ unless options[:rackspace].is_a?(Hash)
144
+ raise "Invalid Rackspace options, Hash expected,
145
+ #{options[:rackspace].class} given"
146
+ end
147
+ unless options[:rackspace].has_key?(:rackspace_username)
148
+ raise "Rackspace username is missing"
149
+ end
150
+ unless options[:rackspace].has_key?(:rackspace_api_key)
151
+ raise "Rackspace API key is missing"
152
+ end
153
+ else
154
+ raise "Swift provider #{options[:swift_provider]} not supported"
155
+ end
156
+ end
157
+
158
+ end
159
+ end
160
+ end
@@ -3,7 +3,7 @@
3
3
  module Bosh
4
4
  module Blobstore
5
5
  class Client
6
- VERSION = "0.4.0"
6
+ VERSION = "0.5.0"
7
7
  end
8
8
  end
9
9
  end
@@ -10,6 +10,7 @@ require "blobstore_client/client"
10
10
  require "blobstore_client/base"
11
11
  require "blobstore_client/simple_blobstore_client"
12
12
  require "blobstore_client/s3_blobstore_client"
13
+ require "blobstore_client/swift_blobstore_client"
13
14
  require "blobstore_client/local_client"
14
15
  require "blobstore_client/atmos_blobstore_client"
15
16
 
@@ -20,6 +21,7 @@ module Bosh
20
21
  PROVIDER_MAP = {
21
22
  "simple" => SimpleBlobstoreClient,
22
23
  "s3" => S3BlobstoreClient,
24
+ "swift" => SwiftBlobstoreClient,
23
25
  "atmos" => AtmosBlobstoreClient,
24
26
  "local" => LocalClient
25
27
  }
@@ -30,6 +30,11 @@ describe Bosh::Blobstore::Client do
30
30
  bs.should be_instance_of Bosh::Blobstore::S3BlobstoreClient
31
31
  end
32
32
 
33
+ it "should have an swift provider" do
34
+ bs = Bosh::Blobstore::Client.create('swift', {})
35
+ bs.should be_instance_of Bosh::Blobstore::SwiftBlobstoreClient
36
+ end
37
+
33
38
  it "should raise an exception on an unknown client" do
34
39
  lambda {
35
40
  bs = Bosh::Blobstore::Client.create('foobar', {})
@@ -0,0 +1,315 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Bosh::Blobstore::SwiftBlobstoreClient do
4
+
5
+ def swift_options(container_name, swift_provider, credentials)
6
+ if credentials
7
+ options = {
8
+ "rackspace" => {
9
+ "rackspace_username" => "username",
10
+ "rackspace_api_key" => "api_key"
11
+ },
12
+ "hp" => {
13
+ "hp_account_id" => "account_id",
14
+ "hp_secret_key" => "secret_key",
15
+ "hp_tenant_id" => "tenant_id"
16
+ }
17
+ }
18
+ else
19
+ options = {}
20
+ end
21
+ options["container_name"] = container_name if container_name
22
+ options["swift_provider"] = swift_provider if swift_provider
23
+ options
24
+ end
25
+
26
+ def swift_blobstore(options)
27
+ Bosh::Blobstore::SwiftBlobstoreClient.new(options)
28
+ end
29
+
30
+ before(:each) do
31
+ @swift = mock("swift")
32
+ Fog::Storage.stub!(:new).and_return(@swift)
33
+ @http_client = mock("http-client")
34
+ HTTPClient.stub!(:new).and_return(@http_client)
35
+ end
36
+
37
+ describe "on HP Cloud Storage" do
38
+
39
+ describe "with credentials" do
40
+
41
+ before(:each) do
42
+ @client = swift_blobstore(swift_options("test-container",
43
+ "hp",
44
+ true))
45
+ end
46
+
47
+ it "should create an object" do
48
+ data = "some content"
49
+ directories = double("directories")
50
+ container = double("container")
51
+ files = double("files")
52
+ object = double("object")
53
+
54
+ @client.should_receive(:generate_object_id).and_return("object_id")
55
+ @swift.stub(:directories).and_return(directories)
56
+ directories.should_receive(:get).with("test-container") \
57
+ .and_return(container)
58
+ container.should_receive(:files).and_return(files)
59
+ files.should_receive(:create).with { |opt|
60
+ opt[:key].should eql "object_id"
61
+ #opt[:body].should eql data
62
+ opt[:public].should eql true
63
+ }.and_return(object)
64
+ object.should_receive(:public_url).and_return("public-url")
65
+
66
+ object_id = @client.create(data)
67
+ object_info = MultiJson.decode(Base64.decode64(
68
+ URI::unescape(object_id)))
69
+ object_info["oid"].should eql("object_id")
70
+ object_info["purl"].should eql("public-url")
71
+ end
72
+
73
+ it "should fetch an object without a public url" do
74
+ data = "some content"
75
+ directories = double("directories")
76
+ container = double("container")
77
+ files = double("files")
78
+ object = double("object")
79
+
80
+ @swift.stub(:directories).and_return(directories)
81
+ directories.should_receive(:get).with("test-container") \
82
+ .and_return(container)
83
+ container.should_receive(:files).and_return(files)
84
+ files.should_receive(:get).with("object_id").and_yield(data) \
85
+ .and_return(object)
86
+
87
+ oid = URI::escape(Base64.encode64(MultiJson.encode(
88
+ {:oid => "object_id"})))
89
+ @client.get(oid).should eql(data)
90
+ end
91
+
92
+ it "should fetch an object with a public url" do
93
+ data = "some content"
94
+ response = mock("response")
95
+
96
+ @http_client.should_receive(:get).with("public-url") \
97
+ .and_yield(data).and_return(response)
98
+ response.stub!(:status).and_return(200)
99
+
100
+ oid = URI::escape(Base64.encode64(MultiJson.encode(
101
+ {:oid => "object_id",
102
+ :purl => "public-url"})))
103
+ @client.get(oid).should eql(data)
104
+ end
105
+
106
+ it "should delete an object" do
107
+ directories = double("directories")
108
+ container = double("container")
109
+ files = double("files")
110
+ object = double("object")
111
+
112
+ @swift.stub(:directories).and_return(directories)
113
+ directories.should_receive(:get).with("test-container") \
114
+ .and_return(container)
115
+ container.should_receive(:files).and_return(files)
116
+ files.should_receive(:get).with("object_id").and_return(object)
117
+ object.should_receive(:destroy)
118
+
119
+ oid = URI::escape(Base64.encode64(MultiJson.encode(
120
+ {:oid => "object_id"})))
121
+ @client.delete(oid)
122
+ end
123
+
124
+ end
125
+
126
+ describe "without credentials" do
127
+
128
+ before(:each) do
129
+ @client = swift_blobstore(swift_options("test-container",
130
+ "hp",
131
+ false))
132
+ end
133
+
134
+ it "should refuse to create an object" do
135
+ data = "some content"
136
+
137
+ lambda {
138
+ object_id = @client.create(data)
139
+ }.should raise_error(Bosh::Blobstore::BlobstoreError)
140
+ end
141
+
142
+ it "should refuse to fetch an object without a public url" do
143
+ oid = URI::escape(Base64.encode64(MultiJson.encode(
144
+ {:oid => "object_id"})))
145
+ lambda {
146
+ @client.get(oid)
147
+ }.should raise_error(Bosh::Blobstore::BlobstoreError)
148
+ end
149
+
150
+ it "should fetch an object with a public url" do
151
+ data = "some content"
152
+ response = mock("response")
153
+
154
+ @http_client.should_receive(:get).with("public-url") \
155
+ .and_yield(data).and_return(response)
156
+ response.stub!(:status).and_return(200)
157
+
158
+ oid = URI::escape(Base64.encode64(MultiJson.encode(
159
+ {:oid => "object_id",
160
+ :purl => "public-url"})))
161
+ @client.get(oid).should eql(data)
162
+ end
163
+
164
+ it "should refuse to delete an object" do
165
+ oid = URI::escape(Base64.encode64(MultiJson.encode(
166
+ {:oid => "object_id"})))
167
+ lambda {
168
+ @client.delete(oid)
169
+ }.should raise_error(Bosh::Blobstore::BlobstoreError)
170
+ end
171
+
172
+ end
173
+
174
+ end
175
+
176
+ describe "on Rackspace Cloud Files" do
177
+
178
+ describe "with credentials" do
179
+
180
+ before(:each) do
181
+ @client = swift_blobstore(swift_options("test-container",
182
+ "rackspace",
183
+ true))
184
+ end
185
+
186
+ it "should create an object" do
187
+ data = "some content"
188
+ directories = double("directories")
189
+ container = double("container")
190
+ files = double("files")
191
+ object = double("object")
192
+
193
+ @client.should_receive(:generate_object_id).and_return("object_id")
194
+ @swift.stub(:directories).and_return(directories)
195
+ directories.should_receive(:get).with("test-container") \
196
+ .and_return(container)
197
+ container.should_receive(:files).and_return(files)
198
+ files.should_receive(:create).with { |opt|
199
+ opt[:key].should eql "object_id"
200
+ #opt[:body].should eql data
201
+ opt[:public].should eql true
202
+ }.and_return(object)
203
+ object.should_receive(:public_url).and_return("public-url")
204
+
205
+ object_id = @client.create(data)
206
+ object_info = MultiJson.decode(Base64.decode64(
207
+ URI::unescape(object_id)))
208
+ object_info["oid"].should eql("object_id")
209
+ object_info["purl"].should eql("public-url")
210
+ end
211
+
212
+ it "should fetch an object without a public url" do
213
+ data = "some content"
214
+ directories = double("directories")
215
+ container = double("container")
216
+ files = double("files")
217
+ object = double("object")
218
+
219
+ @swift.stub(:directories).and_return(directories)
220
+ directories.should_receive(:get).with("test-container") \
221
+ .and_return(container)
222
+ container.should_receive(:files).and_return(files)
223
+ files.should_receive(:get).with("object_id").and_yield(data) \
224
+ .and_return(object)
225
+
226
+ oid = URI::escape(Base64.encode64(MultiJson.encode(
227
+ {:oid => "object_id"})))
228
+ @client.get(oid).should eql(data)
229
+ end
230
+
231
+ it "should fetch an object with a public url" do
232
+ data = "some content"
233
+ response = mock("response")
234
+
235
+ @http_client.should_receive(:get).with("public-url") \
236
+ .and_yield(data).and_return(response)
237
+ response.stub!(:status).and_return(200)
238
+
239
+ oid = URI::escape(Base64.encode64(MultiJson.encode(
240
+ {:oid => "object_id",
241
+ :purl => "public-url"})))
242
+ @client.get(oid).should eql(data)
243
+ end
244
+
245
+ it "should delete an object" do
246
+ directories = double("directories")
247
+ container = double("container")
248
+ files = double("files")
249
+ object = double("object")
250
+
251
+ @swift.stub(:directories).and_return(directories)
252
+ directories.should_receive(:get).with("test-container") \
253
+ .and_return(container)
254
+ container.should_receive(:files).and_return(files)
255
+ files.should_receive(:get).with("object_id").and_return(object)
256
+ object.should_receive(:destroy)
257
+
258
+ oid = URI::escape(Base64.encode64(MultiJson.encode(
259
+ {:oid => "object_id"})))
260
+ @client.delete(oid)
261
+ end
262
+
263
+ end
264
+
265
+ describe "without credentials" do
266
+
267
+ before(:each) do
268
+ @client = swift_blobstore(swift_options("test-container",
269
+ "rackspace",
270
+ false))
271
+ end
272
+
273
+ it "should refuse to create an object" do
274
+ data = "some content"
275
+
276
+ lambda {
277
+ object_id = @client.create(data)
278
+ }.should raise_error(Bosh::Blobstore::BlobstoreError)
279
+ end
280
+
281
+ it "should refuse to fetch an object without a public url" do
282
+ oid = URI::escape(Base64.encode64(MultiJson.encode(
283
+ {:oid => "object_id"})))
284
+ lambda {
285
+ @client.get(oid)
286
+ }.should raise_error(Bosh::Blobstore::BlobstoreError)
287
+ end
288
+
289
+ it "should fetch an object with a public url" do
290
+ data = "some content"
291
+ response = mock("response")
292
+
293
+ @http_client.should_receive(:get).with("public-url") \
294
+ .and_yield(data).and_return(response)
295
+ response.stub!(:status).and_return(200)
296
+
297
+ oid = URI::escape(Base64.encode64(MultiJson.encode(
298
+ {:oid => "object_id",
299
+ :purl => "public-url"})))
300
+ @client.get(oid).should eql(data)
301
+ end
302
+
303
+ it "should refuse to delete an object" do
304
+ oid = URI::escape(Base64.encode64(MultiJson.encode(
305
+ {:oid => "object_id"})))
306
+ lambda {
307
+ @client.delete(oid)
308
+ }.should raise_error(Bosh::Blobstore::BlobstoreError)
309
+ end
310
+
311
+ end
312
+
313
+ end
314
+
315
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blobstore_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-08-16 00:00:00.000000000 Z
12
+ date: 2012-11-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: aws-s3
@@ -27,6 +27,22 @@ dependencies:
27
27
  - - ~>
28
28
  - !ruby/object:Gem::Version
29
29
  version: 0.6.2
30
+ - !ruby/object:Gem::Dependency
31
+ name: fog
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 1.6.0
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 1.6.0
30
46
  - !ruby/object:Gem::Dependency
31
47
  name: httpclient
32
48
  requirement: !ruby/object:Gem::Requirement
@@ -109,10 +125,12 @@ dependencies:
109
125
  version: '0.5'
110
126
  description: BOSH blobstore client
111
127
  email: support@vmware.com
112
- executables: []
128
+ executables:
129
+ - blobstore_client_console
113
130
  extensions: []
114
131
  extra_rdoc_files: []
115
132
  files:
133
+ - bin/blobstore_client_console
116
134
  - lib/blobstore_client.rb
117
135
  - lib/blobstore_client/atmos_blobstore_client.rb
118
136
  - lib/blobstore_client/base.rb
@@ -121,6 +139,7 @@ files:
121
139
  - lib/blobstore_client/local_client.rb
122
140
  - lib/blobstore_client/s3_blobstore_client.rb
123
141
  - lib/blobstore_client/simple_blobstore_client.rb
142
+ - lib/blobstore_client/swift_blobstore_client.rb
124
143
  - lib/blobstore_client/version.rb
125
144
  - README
126
145
  - Rakefile
@@ -131,6 +150,7 @@ files:
131
150
  - spec/unit/local_client_spec.rb
132
151
  - spec/unit/s3_blobstore_client_spec.rb
133
152
  - spec/unit/simple_blobstore_client_spec.rb
153
+ - spec/unit/swift_blobstore_client_spec.rb
134
154
  homepage: http://www.vmware.com
135
155
  licenses: []
136
156
  post_install_message:
@@ -145,7 +165,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
145
165
  version: '0'
146
166
  segments:
147
167
  - 0
148
- hash: 4134087797262691141
168
+ hash: -2537561053731340493
149
169
  required_rubygems_version: !ruby/object:Gem::Requirement
150
170
  none: false
151
171
  requirements:
@@ -154,7 +174,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
154
174
  version: '0'
155
175
  segments:
156
176
  - 0
157
- hash: 4134087797262691141
177
+ hash: -2537561053731340493
158
178
  requirements: []
159
179
  rubyforge_project:
160
180
  rubygems_version: 1.8.24
@@ -169,3 +189,4 @@ test_files:
169
189
  - spec/unit/local_client_spec.rb
170
190
  - spec/unit/s3_blobstore_client_spec.rb
171
191
  - spec/unit/simple_blobstore_client_spec.rb
192
+ - spec/unit/swift_blobstore_client_spec.rb