alephant-broker 1.2.1 → 1.3.1
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.
- checksums.yaml +4 -4
- data/Rakefile +1 -2
- data/alephant-broker.gemspec +2 -2
- data/lib/alephant/broker.rb +11 -11
- data/lib/alephant/broker/component.rb +20 -67
- data/lib/alephant/broker/component_factory.rb +30 -0
- data/lib/alephant/broker/component_meta.rb +51 -0
- data/lib/alephant/broker/error_component.rb +42 -0
- data/lib/alephant/broker/errors/content_not_found.rb +7 -0
- data/lib/alephant/broker/errors/invalid_cache_key.rb +1 -3
- data/lib/alephant/broker/load_strategy/s3.rb +88 -0
- data/lib/alephant/broker/request/asset.rb +2 -3
- data/lib/alephant/broker/request/batch.rb +4 -5
- data/lib/alephant/broker/request/factory.rb +23 -18
- data/lib/alephant/broker/request/handler.rb +5 -10
- data/lib/alephant/broker/response/asset.rb +17 -10
- data/lib/alephant/broker/response/base.rb +5 -53
- data/lib/alephant/broker/response/batch.rb +12 -9
- data/lib/alephant/broker/response/factory.rb +4 -0
- data/lib/alephant/broker/version.rb +1 -1
- data/spec/fixtures/json/batch.json +1 -0
- data/spec/fixtures/json/batch_compiled.json +1 -0
- data/spec/integration/spec_helper.rb +1 -0
- data/spec/rack_spec.rb +97 -96
- data/spec/spec_helper.rb +4 -0
- metadata +57 -46
@@ -1,18 +1,18 @@
|
|
1
1
|
require 'alephant/logger'
|
2
2
|
require 'alephant/broker/component'
|
3
3
|
|
4
|
-
|
5
4
|
module Alephant
|
6
5
|
module Broker
|
7
6
|
module Request
|
8
7
|
class Batch
|
9
8
|
include Logger
|
10
9
|
|
11
|
-
attr_reader :batch_id, :components
|
10
|
+
attr_reader :batch_id, :components, :load_strategy
|
12
11
|
|
13
|
-
def initialize(env)
|
12
|
+
def initialize(component_factory, env)
|
14
13
|
logger.debug("Request::Batch#initialize(#{env.settings})")
|
15
14
|
|
15
|
+
@component_factory = component_factory
|
16
16
|
@batch_id = env.data['batch_id']
|
17
17
|
@components = components_for env
|
18
18
|
|
@@ -23,14 +23,13 @@ module Alephant
|
|
23
23
|
|
24
24
|
def components_for(env)
|
25
25
|
env.data['components'].map do |c|
|
26
|
-
|
26
|
+
@component_factory.create(
|
27
27
|
c['component'],
|
28
28
|
batch_id,
|
29
29
|
c['options']
|
30
30
|
)
|
31
31
|
end
|
32
32
|
end
|
33
|
-
|
34
33
|
end
|
35
34
|
end
|
36
35
|
end
|
@@ -1,26 +1,31 @@
|
|
1
1
|
require 'alephant/broker/request'
|
2
|
+
require 'alephant/broker/component_factory'
|
2
3
|
|
3
|
-
module Alephant
|
4
|
-
|
4
|
+
module Alephant
|
5
|
+
module Broker
|
6
|
+
module Request
|
7
|
+
class Factory
|
8
|
+
def self.request_type_from(env)
|
9
|
+
env.path.split('/')[1]
|
10
|
+
end
|
5
11
|
|
6
|
-
|
7
|
-
|
8
|
-
end
|
12
|
+
def self.request_for(load_strategy, env)
|
13
|
+
component_factory = ComponentFactory.new load_strategy
|
9
14
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
15
|
+
case request_type_from(env)
|
16
|
+
when 'multi'
|
17
|
+
Multi.new(env)
|
18
|
+
when 'component'
|
19
|
+
Asset.new(component_factory, env)
|
20
|
+
when 'components'
|
21
|
+
Batch.new(component_factory, env)
|
22
|
+
when 'status'
|
23
|
+
Status.new
|
24
|
+
else
|
25
|
+
NotFound.new
|
26
|
+
end
|
27
|
+
end
|
22
28
|
end
|
23
29
|
end
|
24
30
|
end
|
25
31
|
end
|
26
|
-
|
@@ -4,6 +4,7 @@ require 'alephant/broker/request'
|
|
4
4
|
require 'alephant/broker/response'
|
5
5
|
require 'alephant/broker/request/factory'
|
6
6
|
require 'alephant/broker/response/factory'
|
7
|
+
require 'alephant/broker/errors/content_not_found'
|
7
8
|
|
8
9
|
module Alephant
|
9
10
|
module Broker
|
@@ -11,23 +12,17 @@ module Alephant
|
|
11
12
|
class Handler
|
12
13
|
extend Logger
|
13
14
|
|
14
|
-
def self.request_for(env)
|
15
|
-
Request::Factory.request_for env
|
15
|
+
def self.request_for(load_strategy, env)
|
16
|
+
Request::Factory.request_for(load_strategy, env)
|
16
17
|
end
|
17
18
|
|
18
19
|
def self.response_for(request)
|
19
20
|
Response::Factory.response_for request
|
20
21
|
end
|
21
22
|
|
22
|
-
def self.process(env)
|
23
|
-
|
24
|
-
response_for request_for(env)
|
25
|
-
rescue Exception => e
|
26
|
-
logger.warn("Broker.requestHandler.process: Exception raised (#{e.message}, #{e.backtrace.join('\n')})")
|
27
|
-
Response::Factory.error
|
28
|
-
end
|
23
|
+
def self.process(load_strategy, env)
|
24
|
+
response_for request_for(load_strategy, env)
|
29
25
|
end
|
30
|
-
|
31
26
|
end
|
32
27
|
end
|
33
28
|
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'alephant/broker/errors/invalid_cache_key'
|
2
1
|
require 'alephant/logger'
|
3
2
|
|
4
3
|
module Alephant
|
@@ -7,23 +6,31 @@ module Alephant
|
|
7
6
|
class Asset < Base
|
8
7
|
include Logger
|
9
8
|
|
10
|
-
attr_reader :component
|
11
|
-
|
12
9
|
def initialize(component)
|
13
10
|
@component = component
|
14
|
-
super
|
11
|
+
super component.status
|
15
12
|
end
|
16
13
|
|
17
14
|
def setup
|
18
|
-
|
15
|
+
@headers = @component.headers
|
16
|
+
@content = @component.content
|
17
|
+
log if @component.is_a? Component
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
19
21
|
|
20
|
-
|
21
|
-
@
|
22
|
-
@status = loaded_content[:status]
|
23
|
-
@sequence = component.version.nil? ? 'not available' : component.version
|
24
|
-
@cached = component.cached
|
22
|
+
def batched
|
23
|
+
@component.batch_id.nil? ? '' : 'batched'
|
25
24
|
end
|
26
25
|
|
26
|
+
def details
|
27
|
+
c = @component
|
28
|
+
"#{c.id}/#{c.opts_hash}/#{c.headers} #{batched} #{c.options}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def log
|
32
|
+
logger.info "Broker: Component loaded! #{details} (200)"
|
33
|
+
end
|
27
34
|
end
|
28
35
|
end
|
29
36
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'alephant/broker/errors/invalid_cache_key'
|
1
2
|
require 'aws-sdk'
|
2
3
|
require 'ostruct'
|
3
4
|
|
@@ -5,8 +6,7 @@ module Alephant
|
|
5
6
|
module Broker
|
6
7
|
module Response
|
7
8
|
class Base
|
8
|
-
attr_reader :headers
|
9
|
-
attr_accessor :status, :content, :content_type, :version, :sequence, :cached
|
9
|
+
attr_reader :content, :headers, :status
|
10
10
|
|
11
11
|
STATUS_CODE_MAPPING = {
|
12
12
|
200 => 'ok',
|
@@ -15,64 +15,16 @@ module Alephant
|
|
15
15
|
}
|
16
16
|
|
17
17
|
def initialize(status = 200, content_type = "text/html")
|
18
|
-
@
|
19
|
-
@
|
20
|
-
@
|
21
|
-
@cached = false
|
22
|
-
@content_type = content_type
|
23
|
-
@status = status
|
24
|
-
@content = STATUS_CODE_MAPPING[status]
|
18
|
+
@content = STATUS_CODE_MAPPING[status]
|
19
|
+
@headers = { "Content-Type" => content_type }
|
20
|
+
@status = status
|
25
21
|
|
26
22
|
setup
|
27
23
|
end
|
28
24
|
|
29
|
-
def to_h
|
30
|
-
{
|
31
|
-
:status => @status,
|
32
|
-
:content => @content,
|
33
|
-
:content_type => @content_type,
|
34
|
-
:version => @version,
|
35
|
-
:sequence => @sequence,
|
36
|
-
:cached => @cached
|
37
|
-
}
|
38
|
-
end
|
39
|
-
|
40
25
|
protected
|
41
26
|
|
42
27
|
def setup; end
|
43
|
-
|
44
|
-
def load(component)
|
45
|
-
begin
|
46
|
-
|
47
|
-
data = OpenStruct.new(:status => 200, :content_type => content_type)
|
48
|
-
component.load
|
49
|
-
|
50
|
-
data.content_type = component.content_type
|
51
|
-
data.body = component.content.force_encoding('UTF-8')
|
52
|
-
rescue AWS::S3::Errors::NoSuchKey, InvalidCacheKey => e
|
53
|
-
data.body = "Not found"
|
54
|
-
data.status = 404
|
55
|
-
rescue StandardError => e
|
56
|
-
data.body = "#{error_for(e)}"
|
57
|
-
data.status = 500
|
58
|
-
end
|
59
|
-
|
60
|
-
log(component, data.status, e)
|
61
|
-
data.marshal_dump
|
62
|
-
end
|
63
|
-
|
64
|
-
def log(c, status, e = nil)
|
65
|
-
logger.info("Broker: Component loaded: #{details_for(c)} (#{status}) #{error_for(e)}")
|
66
|
-
end
|
67
|
-
|
68
|
-
def details_for(c)
|
69
|
-
"#{c.id}/#{c.opts_hash}/#{c.version} #{c.batch_id.nil? ? '' : "batched"} (#{c.options})"
|
70
|
-
end
|
71
|
-
|
72
|
-
def error_for(e)
|
73
|
-
e.nil? ? nil : "#{e.message}\n#{e.backtrace.join('\n')}"
|
74
|
-
end
|
75
|
-
|
76
28
|
end
|
77
29
|
end
|
78
30
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'alephant/logger'
|
2
|
-
require '
|
2
|
+
require 'pmap'
|
3
3
|
|
4
4
|
module Alephant
|
5
5
|
module Broker
|
@@ -18,22 +18,25 @@ module Alephant
|
|
18
18
|
|
19
19
|
def setup
|
20
20
|
@content = JSON.generate({
|
21
|
-
|
22
|
-
|
21
|
+
'batch_id' => batch_id,
|
22
|
+
'components' => json
|
23
23
|
})
|
24
24
|
end
|
25
25
|
|
26
26
|
private
|
27
27
|
|
28
28
|
def json
|
29
|
-
logger.info
|
30
|
-
result = components.pmap do |
|
29
|
+
logger.info "Broker: Batch load started (#{batch_id})"
|
30
|
+
result = components.pmap do |component|
|
31
31
|
{
|
32
|
-
'component'
|
33
|
-
'options'
|
34
|
-
|
32
|
+
'component' => component.id,
|
33
|
+
'options' => component.options,
|
34
|
+
'status' => component.status,
|
35
|
+
'content_type' => component.content_type,
|
36
|
+
'body' => component.content
|
37
|
+
}
|
35
38
|
end
|
36
|
-
logger.info
|
39
|
+
logger.info "Broker: Batch load done (#{batch_id})"
|
37
40
|
|
38
41
|
result
|
39
42
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
{"batch_id":"baz","components":[{"component":"ni_council_results_table"},{"component":"ni_council_results_table"}]}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"batch_id":"baz","components":[{"component":"ni_council_results_table","options":{},"status":200,"content_type":"foo/bar","body":"Test"},{"component":"ni_council_results_table","options":{},"status":200,"content_type":"foo/bar","body":"Test"}]}
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative '../spec_helper'
|
data/spec/rack_spec.rb
CHANGED
@@ -1,23 +1,37 @@
|
|
1
|
-
ENV['RACK_ENV'] = 'test'
|
2
|
-
|
3
1
|
require 'spec_helper'
|
4
|
-
require 'rack/test'
|
5
|
-
require 'alephant/broker'
|
6
|
-
|
7
|
-
RSpec.configure do |conf|
|
8
|
-
conf.include Rack::Test::Methods
|
9
|
-
end
|
10
2
|
|
11
|
-
describe
|
12
|
-
|
13
|
-
|
3
|
+
describe Alephant::Broker::Application do
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
let(:app) do
|
7
|
+
described_class.new(
|
8
|
+
Alephant::Broker::LoadStrategy::S3.new,
|
9
|
+
{
|
10
|
+
:lookup_table_name => 'test_table',
|
11
|
+
:bucket_id => 'test_bucket',
|
12
|
+
:path => 'bucket_path'
|
13
|
+
}
|
14
|
+
)
|
15
|
+
end
|
16
|
+
let(:cache_hash) do
|
17
|
+
{
|
14
18
|
:content_type => 'test/content',
|
15
19
|
:content => 'Test'
|
16
20
|
}
|
21
|
+
end
|
22
|
+
let(:sequencer_double) do
|
23
|
+
instance_double(
|
24
|
+
'Alephant::Sequencer::Sequencer',
|
25
|
+
:get_last_seen => '111'
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
before do
|
30
|
+
allow_any_instance_of(Logger).to receive(:info)
|
31
|
+
allow_any_instance_of(Logger).to receive(:debug)
|
17
32
|
|
18
33
|
allow_any_instance_of(Alephant::Broker::Cache::Client)
|
19
|
-
.to receive(:get)
|
20
|
-
.and_return(cache_hash)
|
34
|
+
.to receive(:get).and_return(cache_hash)
|
21
35
|
|
22
36
|
allow_any_instance_of(Alephant::Broker::Component)
|
23
37
|
.to receive_messages(
|
@@ -27,97 +41,84 @@ describe 'Broker Rack Application' do
|
|
27
41
|
)
|
28
42
|
|
29
43
|
allow_any_instance_of(Alephant::Broker::Response::Asset)
|
30
|
-
.to receive(:status)
|
31
|
-
.and_return(200)
|
32
|
-
end
|
33
|
-
|
34
|
-
def app
|
35
|
-
Alephant::Broker::Application.new({
|
36
|
-
:lookup_table_name => 'test_table',
|
37
|
-
:bucket_id => 'test_bucket',
|
38
|
-
:path => 'bucket_path'
|
39
|
-
})
|
40
|
-
end
|
41
|
-
|
42
|
-
it 'Tests status page' do
|
43
|
-
get '/status'
|
44
|
-
expect(last_response).to be_ok
|
45
|
-
expect(last_response.body).to eq('ok')
|
46
|
-
end
|
47
|
-
|
48
|
-
it "Tests not found page" do
|
49
|
-
get '/some/non-existent-page'
|
50
|
-
expect(last_response.status).to eq(404)
|
51
|
-
expect(last_response.body).to eq('Not found')
|
52
|
-
end
|
53
|
-
|
54
|
-
it "Test asset data is returned" do
|
55
|
-
get '/component/test_component'
|
56
|
-
|
57
|
-
expect(last_response).to be_ok
|
58
|
-
expect(last_response.body).to eq('Test')
|
59
|
-
end
|
44
|
+
.to receive(:status).and_return(200)
|
60
45
|
|
61
|
-
|
62
|
-
get '/component/test_component?variant=test_variant'
|
63
|
-
|
64
|
-
expect(last_response).to be_ok
|
65
|
-
expect(last_response.body).to eq('Test')
|
46
|
+
allow(Alephant::Sequencer).to receive(:create) { sequencer_double }
|
66
47
|
end
|
67
48
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
get '/component/test_component'
|
74
|
-
|
75
|
-
expect(last_response.status).to eq(404)
|
49
|
+
describe 'Status endpoint `/status`' do
|
50
|
+
before { get '/status' }
|
51
|
+
specify { expect(last_response.status).to eql 200 }
|
52
|
+
specify { expect(last_response.body).to eql 'ok' }
|
76
53
|
end
|
77
54
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
get '/component/test_component'
|
84
|
-
|
85
|
-
expect(last_response.status).to eq(500)
|
55
|
+
describe '404 endpoint `/banana`' do
|
56
|
+
before { get '/banana' }
|
57
|
+
specify { expect(last_response.status).to eql 404 }
|
58
|
+
specify { expect(last_response.body).to eq 'Not found' }
|
86
59
|
end
|
87
60
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
61
|
+
describe 'Component endpoint `/component/...`' do
|
62
|
+
let(:batch_json) do
|
63
|
+
IO.read("#{File.dirname(__FILE__)}/fixtures/json/batch.json").strip
|
64
|
+
end
|
65
|
+
let(:batch_compiled_json) do
|
66
|
+
IO.read("#{File.dirname(__FILE__)}/fixtures/json/batch_compiled.json").strip
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'for a valid component ID' do
|
70
|
+
before { get '/component/test_component' }
|
71
|
+
specify { expect(last_response.status).to eql 200 }
|
72
|
+
specify { expect(last_response.body).to eql 'Test' }
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'for valid URL parameters in request' do
|
76
|
+
before { get '/component/test_component?variant=test_variant' }
|
77
|
+
specify { expect(last_response.status).to eq 200 }
|
78
|
+
specify { expect(last_response.body).to eq 'Test' }
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'when using valid batch asset data' do
|
82
|
+
before { post '/components/batch', batch_json, 'CONTENT_TYPE' => 'application/json' }
|
83
|
+
specify { expect(last_response.status).to eql 200 }
|
84
|
+
specify { expect(JSON.parse last_response.body).to eq JSON.parse(batch_compiled_json) }
|
85
|
+
end
|
96
86
|
end
|
97
87
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
88
|
+
describe 'Cached data' do
|
89
|
+
let(:cache_double) do
|
90
|
+
instance_double(
|
91
|
+
'Alephant::Broker::Cache::Client',
|
92
|
+
:set => {
|
93
|
+
:content_type => 'test/html',
|
94
|
+
:content => '<p>Some data</p>'
|
95
|
+
},
|
96
|
+
:get => '<p>Some data</p>'
|
97
|
+
)
|
98
|
+
end
|
99
|
+
let(:lookup_location_double) do
|
100
|
+
instance_double('Alephant::Lookup::Location', location: 'test/location')
|
101
|
+
end
|
102
|
+
let(:lookup_helper_double) do
|
103
|
+
instance_double('Alephant::Lookup::LookupHelper', read: lookup_location_double)
|
104
|
+
end
|
105
|
+
let(:s3_cache_double) do
|
106
|
+
instance_double(
|
107
|
+
'Alephant::Cache',
|
108
|
+
:get => 'test_content'
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'which is old' do
|
113
|
+
before do
|
114
|
+
allow(Alephant::Lookup).to receive(:create) { lookup_helper_double }
|
115
|
+
allow(Alephant::Broker::Cache::Client).to receive(:new) { cache_double }
|
116
|
+
allow(Alephant::Cache).to receive(:new) { s3_cache_double }
|
117
|
+
end
|
118
|
+
it 'should update the cache (call `.set`)' do
|
119
|
+
expect(cache_double).to receive(:set).once
|
120
|
+
end
|
121
|
+
after { get '/component/test_component' }
|
122
|
+
end
|
121
123
|
end
|
122
|
-
|
123
124
|
end
|