blobsterix 0.0.9

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