azure_client 0.1.1 → 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +1 -1
- data/lib/azure_client.rb +2 -0
- data/lib/azure_client/blob.rb +4 -0
- data/lib/azure_client/buffered_queue.rb +2 -2
- data/lib/azure_client/client.rb +22 -6
- data/lib/azure_client/compression.rb +18 -0
- data/lib/azure_client/container.rb +73 -12
- data/lib/azure_client/version.rb +1 -1
- data/spec/azure_client/container_spec.rb +75 -0
- metadata +6 -3
data/Gemfile.lock
CHANGED
data/lib/azure_client.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# standard
|
2
2
|
require 'json'
|
3
3
|
require 'base64'
|
4
|
+
require "zlib"
|
4
5
|
|
5
6
|
# azure
|
6
7
|
require 'azure'
|
@@ -10,6 +11,7 @@ require 'azure_client/buffered_queue'
|
|
10
11
|
require 'azure_client/buffered_queue_factory'
|
11
12
|
require 'azure_client/buffered_queue_message'
|
12
13
|
require 'azure_client/client'
|
14
|
+
require 'azure_client/compression'
|
13
15
|
require 'azure_client/container'
|
14
16
|
require 'azure_client/exponential_retry_policy'
|
15
17
|
require 'azure_client/linear_retry_policy'
|
data/lib/azure_client/blob.rb
CHANGED
@@ -11,6 +11,10 @@ module AzureClient
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def get_content
|
14
|
+
content_encoding = @blob_service.get_blob_properties(@container_name, @name).properties[:content_encoding]
|
15
|
+
if (content_encoding && content_encoding.downcase == "gzip")
|
16
|
+
return Compression.decompress(@content)
|
17
|
+
end
|
14
18
|
@content
|
15
19
|
end
|
16
20
|
|
@@ -9,7 +9,7 @@ module AzureClient
|
|
9
9
|
@retry_policy = retry_policy
|
10
10
|
end
|
11
11
|
|
12
|
-
def add_message(content, metadata = "", retry_policy = @retry_policy)
|
12
|
+
def add_message(content, metadata = "", options = {}, retry_policy = @retry_policy)
|
13
13
|
payload = {"content" => content, "metadata" => metadata}.to_json
|
14
14
|
queue = select_queue
|
15
15
|
if is_allowed_payload_size(payload)
|
@@ -18,7 +18,7 @@ module AzureClient
|
|
18
18
|
#pick random blob name
|
19
19
|
blob_name = name + (0...9).map{ ('a'..'z').to_a[rand(26)] }.join
|
20
20
|
reference = {"type" => "azure_blob_reference", "name" => blob_name, "metadata" => metadata}
|
21
|
-
@container.store_blob(blob_name, content,
|
21
|
+
@container.store_blob(blob_name, content, options, retry_policy)
|
22
22
|
queue.add_message(reference.to_json, retry_policy)
|
23
23
|
end
|
24
24
|
end
|
data/lib/azure_client/client.rb
CHANGED
@@ -14,16 +14,32 @@ module AzureClient
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def get_container(name, retry_policy = ExponentialRetryPolicy.new(5,1,2))
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
retry_policy.retry {
|
18
|
+
begin
|
19
|
+
blob_service.get_container_properties(name)
|
20
|
+
rescue Exception => e
|
21
|
+
if e.status_code == 404
|
22
|
+
blob_service.create_container(name)
|
23
|
+
else
|
24
|
+
raise e
|
25
|
+
end
|
26
|
+
end
|
27
|
+
}
|
20
28
|
return Container.new(name, blob_service, retry_policy)
|
21
29
|
end
|
22
30
|
|
23
31
|
def get_table(name, retry_policy = ExponentialRetryPolicy.new(2,1,2))
|
24
|
-
|
25
|
-
|
26
|
-
|
32
|
+
retry_policy.retry {
|
33
|
+
begin
|
34
|
+
table_service.get_table(name)
|
35
|
+
rescue StandardError => e
|
36
|
+
if e.status_code == 404
|
37
|
+
table_service.create_table(name)
|
38
|
+
else
|
39
|
+
raise e
|
40
|
+
end
|
41
|
+
end
|
42
|
+
}
|
27
43
|
return Table.new(name, table_service, retry_policy)
|
28
44
|
end
|
29
45
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module AzureClient
|
2
|
+
class Compression
|
3
|
+
|
4
|
+
def self.compress(content)
|
5
|
+
stream = StringIO.new("w")
|
6
|
+
gz = Zlib::GzipWriter.new(stream)
|
7
|
+
gz.write(content)
|
8
|
+
gz.close
|
9
|
+
compressed = stream.string
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.decompress(compressed)
|
13
|
+
gz = Zlib::GzipReader.new(StringIO.new(compressed))
|
14
|
+
content = gz.read
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -1,5 +1,8 @@
|
|
1
1
|
module AzureClient
|
2
2
|
class Container
|
3
|
+
BLOB_SIZE_LIMIT = 1024 * 1024 * 60 # actual limit is 64 MB
|
4
|
+
BLOCK_SIZE_LIMIT = 1024 * 1024 * 3 # actual limit is 4 MB
|
5
|
+
|
3
6
|
attr_reader :name
|
4
7
|
|
5
8
|
def initialize(name, blob_service, retry_policy)
|
@@ -7,12 +10,22 @@ module AzureClient
|
|
7
10
|
@blob_service = blob_service
|
8
11
|
@retry_policy = retry_policy
|
9
12
|
end
|
13
|
+
|
14
|
+
def list_blobs(options = {}, retry_policy = @retry_policy)
|
15
|
+
retry_policy.retry {
|
16
|
+
@blob_service.list_blobs(name, options)
|
17
|
+
}
|
18
|
+
end
|
10
19
|
|
11
20
|
#will overwrite content if blob with same name already exists
|
12
21
|
def store_blob(blob_name, content, options = {}, retry_policy = @retry_policy)
|
13
|
-
|
14
|
-
|
15
|
-
|
22
|
+
if content.kind_of?(String)
|
23
|
+
store_string_to_blob(blob_name, content, options, retry_policy)
|
24
|
+
elsif content.kind_of?(IO)
|
25
|
+
store_io_to_blob(blob_name, content, options, retry_policy)
|
26
|
+
else
|
27
|
+
raise "Trying to upload unknown type #{content.class} into Azure, only String/IO allowed!"
|
28
|
+
end
|
16
29
|
end
|
17
30
|
|
18
31
|
#throws exception if no blob found
|
@@ -57,25 +70,73 @@ module AzureClient
|
|
57
70
|
}
|
58
71
|
end
|
59
72
|
|
60
|
-
def poll_blob(blob_name, key, val,
|
61
|
-
|
73
|
+
def poll_blob(blob_name, key, val, polling_policy = ExponentialRetryPolicy.new(75,5,1.1))
|
74
|
+
start_time = Time.now
|
62
75
|
#create blob if it does not exist
|
63
76
|
begin
|
64
77
|
blob = get_blob(blob_name)
|
65
78
|
rescue
|
66
79
|
store_blob(blob_name,"")
|
67
80
|
end
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
puts "*** polling Azure blob #{blob_name} for #{time} seconds"
|
81
|
+
|
82
|
+
polling_policy.retry {
|
83
|
+
puts "*** polling Azure blob #{blob_name} for #{Time.now - start_time} seconds"
|
72
84
|
metadata = get_blob_metadata(blob_name)
|
73
|
-
|
74
|
-
|
75
|
-
|
85
|
+
if metadata && metadata[key] == val then
|
86
|
+
return val
|
87
|
+
else
|
88
|
+
raise "Polled the blob #{blob_name} for #{Time.now - start_time} seconds. Time out!"
|
89
|
+
end
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def store_string_to_blob(blob_name, content, options = {}, retry_policy = @retry_policy)
|
96
|
+
if options[:compressed]
|
97
|
+
content = Compression.compress(content)
|
98
|
+
options.merge!(:content_encoding => "gzip")
|
99
|
+
end
|
100
|
+
|
101
|
+
#use block list if content is too big
|
102
|
+
if content.length > BLOB_SIZE_LIMIT
|
103
|
+
block_number = content.length / BLOCK_SIZE_LIMIT
|
104
|
+
block_list = (0..block_number).map{|n| [pad_zero(n), :latest]}
|
105
|
+
block_list.each do |block|
|
106
|
+
retry_policy.retry {
|
107
|
+
@blob_service.create_blob_block(name, blob_name, block[0], content[n*BLOCK_SIZE_LIMIT..(n+1)*BLOCK_SIZE_LIMIT-1], options)
|
108
|
+
}
|
76
109
|
end
|
110
|
+
retry_policy.retry {
|
111
|
+
@blob_service.commit_blob_blocks(name, blob_name, block_list, options)
|
112
|
+
}
|
113
|
+
else
|
114
|
+
retry_policy.retry {
|
115
|
+
@blob_service.create_block_blob(name, blob_name, content, options)
|
116
|
+
}
|
77
117
|
end
|
78
118
|
end
|
79
119
|
|
120
|
+
def store_io_to_blob(blob_name, io, options = {}, retry_policy = @retry_policy)
|
121
|
+
#always use block list if content is too big
|
122
|
+
block_id = 0
|
123
|
+
block_list = []
|
124
|
+
while !io.eof?
|
125
|
+
retry_policy.retry {
|
126
|
+
str = io.read(BLOCK_SIZE_LIMIT)
|
127
|
+
@blob_service.create_blob_block(name, blob_name, pad_zero(block_id), str, options)
|
128
|
+
}
|
129
|
+
block_list << [pad_zero(block_id), :latest]
|
130
|
+
block_id += 1
|
131
|
+
end
|
132
|
+
|
133
|
+
retry_policy.retry {
|
134
|
+
@blob_service.commit_blob_blocks(name, blob_name, block_list, options)
|
135
|
+
}
|
136
|
+
end
|
137
|
+
|
138
|
+
def pad_zero(number)
|
139
|
+
"%06d" % number
|
140
|
+
end
|
80
141
|
end
|
81
142
|
end
|
data/lib/azure_client/version.rb
CHANGED
@@ -0,0 +1,75 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'spec_helper')
|
2
|
+
|
3
|
+
describe 'Container' do
|
4
|
+
before(:each) do
|
5
|
+
@storage = get_client
|
6
|
+
@container_name = (0...9).map{ ('a'..'z').to_a[rand(26)] }.join
|
7
|
+
@container = @storage.get_container(@container_name)
|
8
|
+
end
|
9
|
+
|
10
|
+
after(:each) do
|
11
|
+
@container.delete
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should add have a name' do
|
15
|
+
@container.name.should == @container_name
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'stores and gets blobs' do
|
19
|
+
@container.store_blob("blob1", "blob1_content")
|
20
|
+
blob = @container.get_blob("blob1")
|
21
|
+
blob.get_content.should == "blob1_content"
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'stores and gets compressed blobs' do
|
25
|
+
container = @storage.get_container("test")
|
26
|
+
container.store_blob("blob1", "blob_content", {:compressed => true})
|
27
|
+
blob = container.get_blob("blob1")
|
28
|
+
blob.get_content.should == "blob_content"
|
29
|
+
end
|
30
|
+
|
31
|
+
# it 'deletes blobs' do
|
32
|
+
# @container.store_blob("blob1", "blob1_content")
|
33
|
+
# @container.delete_blob("blob1")
|
34
|
+
# expect { @container.get_blob("blob1") }.to raise_error
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# it 'acquires leases' do
|
38
|
+
# @container.store_blob("blob1", "blob1_content")
|
39
|
+
# lease = @container.get_blob_lease('blob1')
|
40
|
+
# lease.should_not be_nil
|
41
|
+
# properties = @container.get_blob_properties('blob1')
|
42
|
+
# properties[:lease_status].should == 'locked'
|
43
|
+
# properties[:lease_state].should == 'leased'
|
44
|
+
# expect { @container.get_blob_lease('blob1') }.to raise_error
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# it 'retrieves blob properties' do
|
48
|
+
# @container.store_blob('blob1', 'blob1_content')
|
49
|
+
# properties = nil
|
50
|
+
# expect { properties = @container.get_blob_properties('blob1') }.to_not raise_error
|
51
|
+
# properties.should respond_to(:[])
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# it 'sets blob metadata' do
|
55
|
+
# @container.store_blob('blob1', 'blob1_content')
|
56
|
+
# metadata = { meta: 'data' }
|
57
|
+
# expect { @container.set_blob_metadata('blob1', metadata) }.to_not raise_error
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# it 'retrieves set metadata' do
|
61
|
+
# @container.store_blob('blob1', 'blob1_content')
|
62
|
+
# metadata = { meta: 'data' }
|
63
|
+
# @container.set_blob_metadata('blob1', metadata)
|
64
|
+
# @container.get_blob_metadata('blob1')['meta'].should == 'data'
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# it 'polls blobs' do
|
68
|
+
# res = ""
|
69
|
+
# Thread.new { res = @container.poll_blob("blob2","state","Ready") }
|
70
|
+
# sleep(6.0)
|
71
|
+
# @container.set_blob_metadata("blob2", {"state" => "Ready"})
|
72
|
+
# sleep (6.0)
|
73
|
+
# res.should == "Ready"
|
74
|
+
# end
|
75
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: azure_client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.6
|
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: 2013-
|
12
|
+
date: 2013-12-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rest-client
|
@@ -94,6 +94,7 @@ files:
|
|
94
94
|
- lib/azure_client/buffered_queue_factory.rb
|
95
95
|
- lib/azure_client/buffered_queue_message.rb
|
96
96
|
- lib/azure_client/client.rb
|
97
|
+
- lib/azure_client/compression.rb
|
97
98
|
- lib/azure_client/container.rb
|
98
99
|
- lib/azure_client/exponential_retry_policy.rb
|
99
100
|
- lib/azure_client/linear_retry_policy.rb
|
@@ -102,6 +103,7 @@ files:
|
|
102
103
|
- lib/azure_client/table.rb
|
103
104
|
- lib/azure_client/table_entity.rb
|
104
105
|
- lib/azure_client/version.rb
|
106
|
+
- spec/azure_client/container_spec.rb
|
105
107
|
homepage: ''
|
106
108
|
licenses: []
|
107
109
|
post_install_message:
|
@@ -126,4 +128,5 @@ rubygems_version: 1.8.24
|
|
126
128
|
signing_key:
|
127
129
|
specification_version: 3
|
128
130
|
summary: ''
|
129
|
-
test_files:
|
131
|
+
test_files:
|
132
|
+
- spec/azure_client/container_spec.rb
|