blobsterix 0.0.9

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.
Files changed (67) hide show
  1. data/.gitignore +27 -0
  2. data/CHANGELOG.txt +13 -0
  3. data/Gemfile +16 -0
  4. data/LICENSE +22 -0
  5. data/README.md +122 -0
  6. data/Rakefile +13 -0
  7. data/bin/blobsterix +152 -0
  8. data/bin/test +26 -0
  9. data/blobsterix.gemspec +39 -0
  10. data/config/lighttpd.conf +50 -0
  11. data/lib/blobsterix.rb +213 -0
  12. data/lib/blobsterix/blob/blob_api.rb +55 -0
  13. data/lib/blobsterix/blob/blob_url_helper.rb +55 -0
  14. data/lib/blobsterix/helper/accept_type.rb +62 -0
  15. data/lib/blobsterix/helper/blob_access.rb +73 -0
  16. data/lib/blobsterix/helper/config_loader.rb +33 -0
  17. data/lib/blobsterix/helper/data_response.rb +54 -0
  18. data/lib/blobsterix/helper/http.rb +47 -0
  19. data/lib/blobsterix/helper/logable.rb +11 -0
  20. data/lib/blobsterix/helper/murmur.rb +137 -0
  21. data/lib/blobsterix/helper/status_info.rb +42 -0
  22. data/lib/blobsterix/helper/template_renderer.rb +39 -0
  23. data/lib/blobsterix/mimemagic/magic.rb +138 -0
  24. data/lib/blobsterix/mimemagic/tables.rb +1770 -0
  25. data/lib/blobsterix/mimemagic/version.rb +5 -0
  26. data/lib/blobsterix/router/app_router.rb +134 -0
  27. data/lib/blobsterix/s3/s3_api.rb +92 -0
  28. data/lib/blobsterix/s3/s3_url_helper.rb +93 -0
  29. data/lib/blobsterix/service.rb +34 -0
  30. data/lib/blobsterix/status/status_api.rb +62 -0
  31. data/lib/blobsterix/status/status_url_helper.rb +11 -0
  32. data/lib/blobsterix/storage/blob_meta_data.rb +60 -0
  33. data/lib/blobsterix/storage/bucket.rb +36 -0
  34. data/lib/blobsterix/storage/bucket_entry.rb +29 -0
  35. data/lib/blobsterix/storage/bucket_list.rb +26 -0
  36. data/lib/blobsterix/storage/cache.rb +90 -0
  37. data/lib/blobsterix/storage/file_system.rb +132 -0
  38. data/lib/blobsterix/storage/file_system_meta_data.rb +136 -0
  39. data/lib/blobsterix/storage/storage.rb +30 -0
  40. data/lib/blobsterix/transformation/image_transformation.rb +439 -0
  41. data/lib/blobsterix/transformation/transformation.rb +30 -0
  42. data/lib/blobsterix/transformation/transformation_chain.rb +78 -0
  43. data/lib/blobsterix/transformation/transformation_manager.rb +115 -0
  44. data/lib/blobsterix/version.rb +3 -0
  45. data/scripts/download.rb +30 -0
  46. data/scripts/test +6 -0
  47. data/spec/lib/blob/blob_api_spec.rb +81 -0
  48. data/spec/lib/helper/blob_access_spec.rb +72 -0
  49. data/spec/lib/s3/s3_api_spec.rb +183 -0
  50. data/spec/lib/service_spec.rb +12 -0
  51. data/spec/lib/status/status_api_spec.rb +42 -0
  52. data/spec/lib/storage/cache_spec.rb +135 -0
  53. data/spec/lib/storage/file_system_spec.rb +84 -0
  54. data/spec/spec_helper.rb +139 -0
  55. data/templates/app/Gemfile +12 -0
  56. data/templates/app/Rakefile +7 -0
  57. data/templates/app/config.rb +61 -0
  58. data/templates/app/config/environments/development.rb +40 -0
  59. data/templates/app/config/environments/production.rb +40 -0
  60. data/templates/app/storages/.keep +0 -0
  61. data/templates/app/transformators/.keep +0 -0
  62. data/templates/app/views/.keep +0 -0
  63. data/templates/storage_template.rb +30 -0
  64. data/templates/transformation_template.rb +41 -0
  65. data/templates/views/error_page.erb +18 -0
  66. data/templates/views/status_page.erb +31 -0
  67. metadata +325 -0
@@ -0,0 +1,12 @@
1
+ require "spec_helper"
2
+
3
+ describe Blobsterix::Service do
4
+ include Goliath::TestHelper
5
+ it "it should route to the status page" do
6
+ with_api( Blobsterix::Service) do |a|
7
+ resp = get_request(:path=>"/status.json") do
8
+ end
9
+ expect(JSON.parse(resp.response)).to include("cache_hits")
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,42 @@
1
+ require "spec_helper"
2
+
3
+ describe Blobsterix::StatusApi do
4
+ include Rack::Test::Methods
5
+ def app
6
+ Blobsterix::StatusApi
7
+ end
8
+ describe 'GET /status' do
9
+ it 'should get status as json' do
10
+ get "/status.json"
11
+ expect(last_response.status).to eql(200)
12
+ body = JSON.parse(last_response.body)
13
+ expect(body).to have_key("cache_accesses")
14
+ expect(body).to have_key("cache_errors")
15
+ expect(body).to have_key("cache_hit_rate")
16
+ expect(body).to have_key("cache_hits")
17
+ expect(body).to have_key("cache_misses")
18
+ expect(body).to have_key("connections")
19
+ expect(body).to have_key("ram_usage")
20
+ expect(body).to have_key("uptime")
21
+ end
22
+ it 'should get status as xml' do
23
+ get "/status.xml"
24
+ expect(last_response.status).to eql(200)
25
+ body = Hash.from_xml(last_response.body)
26
+ expect(body).to have_key(:BlobsterixStatus)
27
+ expect(body[:BlobsterixStatus]).to have_key(:cache_accesses)
28
+ expect(body[:BlobsterixStatus]).to have_key(:cache_errors)
29
+ expect(body[:BlobsterixStatus]).to have_key(:cache_hit_rate)
30
+ expect(body[:BlobsterixStatus]).to have_key(:cache_hits)
31
+ expect(body[:BlobsterixStatus]).to have_key(:cache_misses)
32
+ expect(body[:BlobsterixStatus]).to have_key(:connections)
33
+ expect(body[:BlobsterixStatus]).to have_key(:ram_usage)
34
+ expect(body[:BlobsterixStatus]).to have_key(:uptime)
35
+ end
36
+
37
+ it 'should get status as html' do
38
+ get "/status"
39
+ expect(last_response.status).to eql(200)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,135 @@
1
+ require "spec_helper"
2
+
3
+ describe Blobsterix::Storage::Cache do
4
+ include Blobsterix::SpecHelper
5
+
6
+ let(:data) {"Hi my name is Test"}
7
+ let(:key) {"test.txt"}
8
+ let(:bucket) {"test"}
9
+
10
+ let(:blob_access) {Blobsterix::BlobAccess.new(:bucket => bucket, :id => key)}
11
+ let(:blob_access_1) {Blobsterix::BlobAccess.new(:bucket => bucket, :id => key, :trafo => [["test", ""]])}
12
+ let(:blob_access_2) {Blobsterix::BlobAccess.new(:bucket => bucket, :id => key, :trafo => [["dummy", ""]])}
13
+ let(:blob_access_3) {Blobsterix::BlobAccess.new(:bucket => bucket, :id => key, :trafo => [["test", ""],["dummy", ""]])}
14
+
15
+ describe "invalidation" do
16
+ before :each do
17
+ Blobsterix.cache.put(blob_access, data)
18
+ Blobsterix.cache.put(blob_access_1, data)
19
+ Blobsterix.cache.put(blob_access_2, data)
20
+ Blobsterix.cache.put(blob_access_3, data)
21
+ end
22
+
23
+ after :each do
24
+ clear_cache
25
+ Blobsterix.cache_checker=lambda{|blob_access, last_accessed_at, created_at|
26
+ false
27
+ }
28
+ end
29
+
30
+ it "should follow invalidation structure" do
31
+ Blobsterix.cache_checker=lambda{|blob_access_, last_accessed_at, created_at|
32
+ blob_access_1.equals?(blob_access_) || blob_access_3.equals?(blob_access_)
33
+ }
34
+ Blobsterix.cache.invalidation
35
+ expect(blob_access.get().valid).to eql(true)
36
+ expect(blob_access_1.get().valid).to eql(false)
37
+ expect(blob_access_2.get().valid).to eql(true)
38
+ expect(blob_access_3.get().valid).to eql(false)
39
+ end
40
+
41
+ it "should invalidate all" do
42
+ Blobsterix.cache_checker=lambda{|blob_access_, last_accessed_at, created_at|
43
+ true
44
+ }
45
+ Blobsterix.cache.invalidation
46
+ expect(blob_access.get().valid).to eql(false)
47
+ expect(blob_access_1.get().valid).to eql(false)
48
+ expect(blob_access_2.get().valid).to eql(false)
49
+ expect(blob_access_3.get().valid).to eql(false)
50
+ end
51
+
52
+ it "should invalidate none" do
53
+ Blobsterix.cache.invalidation
54
+ expect(blob_access.get().valid).to eql(true)
55
+ expect(blob_access_1.get().valid).to eql(true)
56
+ expect(blob_access_2.get().valid).to eql(true)
57
+ expect(blob_access_3.get().valid).to eql(true)
58
+ end
59
+ end
60
+
61
+ describe "cache" do
62
+ after :each do
63
+ clear_cache
64
+ end
65
+
66
+ it "should return invalid blob when key doesn't exist" do
67
+ expect(Blobsterix.cache.get(blob_access).valid).to be(false)
68
+ end
69
+
70
+ it "should return valid blob when key exists" do
71
+ Blobsterix.cache.put(blob_access, data)
72
+ metaData = Blobsterix.cache.get(blob_access)
73
+ expect(metaData.valid).to be(true)
74
+ expect(metaData.read).to eql(data)
75
+ end
76
+
77
+ it "should return invalid blob when key is invalidated" do
78
+ Blobsterix.cache.put(blob_access, data)
79
+ metaData = Blobsterix.cache.get(blob_access)
80
+ expect(metaData.valid).to be(true)
81
+ expect(metaData.read).to eql(data)
82
+
83
+ Blobsterix.cache.invalidate(blob_access)
84
+
85
+ expect(Blobsterix.cache.get(blob_access).valid).to be(false)
86
+ end
87
+
88
+ it "should return invalid blob when key is invalidated for all trafos" do
89
+ Blobsterix.cache.put(blob_access, data)
90
+ Blobsterix.cache.put(blob_access_1, data)
91
+
92
+ metaData = Blobsterix.cache.get(blob_access)
93
+ expect(metaData.valid).to be(true)
94
+ expect(metaData.read).to eql(data)
95
+
96
+ metaData = Blobsterix.cache.get(blob_access_1)
97
+ expect(metaData.valid).to be(true)
98
+ expect(metaData.read).to eql(data)
99
+
100
+ Blobsterix.cache.invalidate(blob_access)
101
+
102
+ expect(Blobsterix.cache.get(blob_access).valid).to be(false)
103
+ expect(Blobsterix.cache.get(blob_access_1).valid).to be(false)
104
+ end
105
+
106
+ it "should return invalid blob when key is invalidated for one trafos" do
107
+ Blobsterix.cache.put(blob_access, data)
108
+ Blobsterix.cache.put(blob_access_1, data)
109
+
110
+ metaData = Blobsterix.cache.get(blob_access)
111
+ expect(metaData.valid).to be(true)
112
+ expect(metaData.read).to eql(data)
113
+
114
+ metaData = Blobsterix.cache.get(blob_access_1)
115
+ expect(metaData.valid).to be(true)
116
+ expect(metaData.read).to eql(data)
117
+
118
+ Blobsterix.cache.invalidate(blob_access, true)
119
+
120
+ expect(Blobsterix.cache.get(blob_access).valid).to be(false)
121
+ expect(Blobsterix.cache.get(blob_access_1).valid).to be(true)
122
+ end
123
+
124
+ it "should return invalid blob when key is deleted" do
125
+ Blobsterix.cache.put(blob_access, data)
126
+ metaData = Blobsterix.cache.get(blob_access)
127
+ expect(metaData.valid).to be(true)
128
+ expect(metaData.read).to eql(data)
129
+
130
+ Blobsterix.cache.delete(blob_access)
131
+
132
+ expect(Blobsterix.cache.get(blob_access).valid).to be(false)
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,84 @@
1
+ require "spec_helper"
2
+
3
+ describe Blobsterix::Storage::FileSystem do
4
+ include Blobsterix::SpecHelper
5
+
6
+ let(:data) {"Hi my name is Test"}
7
+ let(:key) {"test.txt"}
8
+ let(:bucket) {"test"}
9
+
10
+
11
+
12
+ describe "bucket" do
13
+ after :each do
14
+ clear_storage
15
+ end
16
+
17
+ it "should return error if bucket doesn't exist" do
18
+ response = Hash.from_xml Blobsterix.storage.list(bucket).to_xml
19
+ expect(response).to have_key(:Error)
20
+ expect(response[:Error]).to eql("no such bucket")
21
+ end
22
+
23
+ it "should return the bucket info if it exists" do
24
+ Blobsterix.storage.create(bucket)
25
+ response = Hash.from_xml Blobsterix.storage.list(bucket).to_xml
26
+ expect(response).to have_key(:ListBucketResult)
27
+ expect(response[:ListBucketResult]).to have_key(:Name)
28
+ expect(response[:ListBucketResult][:Name]).to eql(bucket)
29
+ end
30
+
31
+ it "should return error after bucket is destroyed" do
32
+ Blobsterix.storage.create(bucket)
33
+ response = Hash.from_xml Blobsterix.storage.list(bucket).to_xml
34
+ expect(response).to have_key(:ListBucketResult)
35
+ expect(response[:ListBucketResult]).to have_key(:Name)
36
+ expect(response[:ListBucketResult][:Name]).to eql(bucket)
37
+
38
+ Blobsterix.storage.delete(bucket)
39
+ response = Hash.from_xml Blobsterix.storage.list(bucket).to_xml
40
+ expect(response).to have_key(:Error)
41
+ expect(response[:Error]).to eql("no such bucket")
42
+ end
43
+
44
+ it "should return the key info if it exists when listing bucket" do
45
+ Blobsterix.storage.put(bucket, key, StringIO.new(data, "r"))
46
+ response = Hash.from_xml Blobsterix.storage.list(bucket).to_xml
47
+ expect(response).to have_key(:ListBucketResult)
48
+ expect(response[:ListBucketResult]).to have_key(:Name)
49
+ expect(response[:ListBucketResult][:Name]).to eql(bucket)
50
+ expect(response[:ListBucketResult][:Contents]).to_not be_empty
51
+ expect(response[:ListBucketResult][:Contents][:Key]).to eql(key)
52
+ expect(response[:ListBucketResult][:Contents][:MimeType]).to eql("text/plain")
53
+ expect(response[:ListBucketResult][:Contents][:Size]).to eql(data.length)
54
+ expect(response[:ListBucketResult][:Contents][:ETag]).to eql(Digest::MD5.hexdigest(data))
55
+ end
56
+ end
57
+
58
+ describe "keys" do
59
+ after :each do
60
+ clear_storage
61
+ end
62
+
63
+ it "should return invalid meta blob when key doesn't exist" do
64
+ expect(Blobsterix.storage.get(bucket, key).valid).to be(false)
65
+ end
66
+
67
+ it "should return valid meta blob when key exist" do
68
+ Blobsterix.storage.put(bucket, key, StringIO.new(data, "r"))
69
+ metaData = Blobsterix.storage.get(bucket, key)
70
+ expect(metaData.valid).to be(true)
71
+ expect(metaData.read).to eql(data)
72
+ end
73
+
74
+ it "should return invalid meta blob when key is deleted" do
75
+ Blobsterix.storage.put(bucket, key, StringIO.new(data, "r"))
76
+ metaData = Blobsterix.storage.get(bucket, key)
77
+ expect(metaData.valid).to be(true)
78
+ expect(metaData.read).to eql(data)
79
+
80
+ Blobsterix.storage.delete_key(bucket, key)
81
+ expect(Blobsterix.storage.get(bucket, key).valid).to be(false)
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,139 @@
1
+ require 'simplecov'
2
+ SimpleCov.start do
3
+ command_name "Tests"
4
+ end
5
+
6
+ require 'bundler'
7
+
8
+ Bundler.setup
9
+ Bundler.require
10
+
11
+ require 'rack/test'
12
+ require 'goliath/test_helper'
13
+
14
+ Blobsterix.storage_dir=Blobsterix.root.join("tmp/contents")
15
+ Blobsterix.cache_dir=Blobsterix.root.join("tmp/cache")
16
+ Blobsterix.storage_event_listener=lambda{|a,b|}
17
+ Blobsterix.logger=Logger.new(Blobsterix.root.join("spec.log"))
18
+
19
+ module Blobsterix
20
+ module SpecHelper
21
+ def clear_storage
22
+ FileUtils.rm_rf Dir.glob(Blobsterix.storage_dir.join("**/*"))
23
+ end
24
+ def clear_cache
25
+ FileUtils.rm_rf Dir.glob(Blobsterix.cache_dir.join("**/*"))
26
+ end
27
+ def clear_data
28
+ clear_cache
29
+ clear_storage
30
+ end
31
+ def run_em(&block)
32
+ EM.run {
33
+ f = Fiber.new(&block)
34
+ f.resume
35
+ EM.stop
36
+ }
37
+ end
38
+ class DummyTrafo < Blobsterix::Transformations::Transformation
39
+ def name()
40
+ "dummy"
41
+ end
42
+ def input_type()
43
+ @input_type ||= Blobsterix::AcceptType.new "text/plain"
44
+ end
45
+
46
+ def output_type()
47
+ @output_type ||= Blobsterix::AcceptType.new "text/plain"
48
+ end
49
+
50
+ def transform(input_path, target_path, value)
51
+ File.open(target_path, "w+") {|file|
52
+ file.write("#{value||"dummy"}")
53
+ }
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ class Nokogiri::XML::Node
60
+ TYPENAMES = {1=>'element',2=>'attribute',3=>'text',4=>'cdata',8=>'comment'}
61
+ def to_hash
62
+ {kind:TYPENAMES[node_type],name:name}.tap do |h|
63
+ h.merge! nshref:namespace.href, nsprefix:namespace.prefix if namespace
64
+ h.merge! text:text
65
+ h.merge! attr:attribute_nodes.map(&:to_hash) if element?
66
+ h.merge! kids:children.map(&:to_hash) if element?
67
+ end
68
+ end
69
+ end
70
+ class Nokogiri::XML::Document
71
+ def to_hash; root.to_hash; end
72
+ end
73
+
74
+ class Hash
75
+ class << self
76
+ def from_xml(xml_io)
77
+ begin
78
+ result = Nokogiri::XML(xml_io)
79
+ return { result.root.name.to_sym => xml_node_to_hash(result.root)}
80
+ rescue Exception => e
81
+ # raise your custom exception here
82
+ end
83
+ end
84
+
85
+ def xml_node_to_hash(node)
86
+ # If we are at the root of the document, start the hash
87
+ if node.element?
88
+ result_hash = {}
89
+ if node.attributes != {}
90
+ result_hash[:attributes] = {}
91
+ node.attributes.keys.each do |key|
92
+ result_hash[:attributes][node.attributes[key].name.to_sym] = prepare(node.attributes[key].value)
93
+ end
94
+ end
95
+ if node.children.size > 0
96
+ node.children.each do |child|
97
+ result = xml_node_to_hash(child)
98
+
99
+ if child.name == "text"
100
+ unless child.next_sibling || child.previous_sibling
101
+ return prepare(result)
102
+ end
103
+ elsif result_hash[child.name.to_sym]
104
+ if result_hash[child.name.to_sym].is_a?(Object::Array)
105
+ result_hash[child.name.to_sym] << prepare(result)
106
+ else
107
+ result_hash[child.name.to_sym] = [result_hash[child.name.to_sym]] << prepare(result)
108
+ end
109
+ else
110
+ result_hash[child.name.to_sym] = prepare(result)
111
+ end
112
+ end
113
+
114
+ return result_hash
115
+ else
116
+ return result_hash
117
+ end
118
+ else
119
+ return prepare(node.content.to_s)
120
+ end
121
+ end
122
+
123
+ def prepare(data)
124
+ (data.class == String && data.to_i.to_s == data) ? data.to_i : data
125
+ end
126
+ end
127
+
128
+ def to_struct(struct_name)
129
+ Struct.new(struct_name,*keys).new(*values)
130
+ end
131
+ end
132
+
133
+
134
+
135
+ RSpec.configure do |c|
136
+ c.include Goliath::TestHelper, :example_group => {
137
+ :file_path => /spec\/integration/
138
+ }
139
+ end
@@ -0,0 +1,12 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem "blobsterix"
4
+
5
+ group :development do
6
+ gem "pry"
7
+ gem "capistrano", "2.15.4"
8
+ gem "capistrano-ext", "1.2.1"
9
+ gem "rvm-capistrano", "~> 1.5.1"
10
+ gem 'whenever', '=0.8.2', :require => false
11
+ gem "railsless-deploy", :require => false
12
+ end
@@ -0,0 +1,7 @@
1
+ desc "Invalidate the cache"
2
+ task :invalidate_cache do
3
+ require "blobsterix"
4
+ include Blobsterix::ConfigLoader
5
+ load_blobsterix_config
6
+ Blobsterix.cache.invalidation
7
+ end
@@ -0,0 +1,61 @@
1
+ module Blobsterix
2
+
3
+ # # If this function is uncommented it should return an environment
4
+ # # this env will override the one supplied from the command line
5
+ # def self.env()
6
+ # :development or :production or :test
7
+ # end
8
+
9
+ # # Use this method to set the port. (standard is 9000)
10
+ # def self.port()
11
+ # 9000
12
+ # end
13
+
14
+ # # Use this to set the address. (standard is all)
15
+ # def self.address()
16
+ # "127.0.0.1"
17
+ # end
18
+
19
+ # # Override those methods if you want to change the storage and cache dirs from their default location
20
+ # # For a different way check the environments files
21
+ # def self.storage_dir
22
+ # Blobsterix.root.join("contents")
23
+ # end
24
+ # def self.cache_dir
25
+ # Blobsterix.root.join("cache")
26
+ # end
27
+
28
+ # # Override those if you want to change the default storage and cache system.
29
+ # # For a different way check the environments files
30
+ # def self.storage
31
+ # @@storage ||= Storage::FileSystem.new(Blobsterix.storage_dir)
32
+ # end
33
+ # def self.cache
34
+ # @@cache ||= Storage::Cache.new(Blobsterix.cache_dir)
35
+ # end
36
+
37
+
38
+ # # Override this method if you want to use a different transformation manager.
39
+ # # For a different way check the environments files
40
+ # # normally not needed
41
+ # def self.transformation
42
+ # @@transformation ||= Blobsterix::Transformations::TransformationManager.new
43
+ # end
44
+
45
+ # # Override this method incase you expect the trafo string in a special format.
46
+ # # For a different way check the environments files
47
+ # # The return should be in format of: trafoName_value,trafoName_value,....
48
+ # # Example: scale_2,rotate_20
49
+ # def self.decrypt_trafo(trafo_string,logger)
50
+ # trafo_string
51
+ # end
52
+
53
+ # # Use a specific storage and cache event listener. Check the environment files for a better description
54
+ # def self.storage_event_listener
55
+ # @storage_event_listener||=lambda{|target, blob_access|
56
+ # logger.info("#{target}: #{blob_access}")
57
+ # }
58
+ # end
59
+ end
60
+
61
+ require Blobsterix.root.join('config','environments',"#{Blobsterix.env}.rb")