blobstore_client 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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