azure_client 0.1.1 → 0.1.6
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.
- 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
|