alephant-broker 3.14.0 → 3.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +22 -9
- data/README.md +35 -6
- data/alephant-broker.gemspec +3 -2
- data/lib/alephant/broker/cache.rb +1 -0
- data/lib/alephant/broker/cache/cached_object.rb +71 -0
- data/lib/alephant/broker/cache/client.rb +29 -17
- data/lib/alephant/broker/cache/null_client.rb +12 -4
- data/lib/alephant/broker/component.rb +3 -1
- data/lib/alephant/broker/component_meta.rb +2 -0
- data/lib/alephant/broker/error_component.rb +1 -1
- data/lib/alephant/broker/load_strategy.rb +1 -0
- data/lib/alephant/broker/load_strategy/http.rb +2 -3
- data/lib/alephant/broker/load_strategy/revalidate/fetcher.rb +51 -0
- data/lib/alephant/broker/load_strategy/revalidate/refresher.rb +75 -0
- data/lib/alephant/broker/load_strategy/revalidate/strategy.rb +86 -0
- data/lib/alephant/broker/load_strategy/s3/base.rb +2 -2
- data/lib/alephant/broker/response/asset.rb +1 -1
- data/lib/alephant/broker/response/base.rb +7 -8
- data/lib/alephant/broker/response/batch.rb +1 -1
- data/lib/alephant/broker/version.rb +1 -1
- data/spec/alephant/broker/cache/cached_object_spec.rb +107 -0
- data/spec/alephant/broker/load_strategy/http_spec.rb +5 -7
- data/spec/alephant/broker/load_strategy/revalidate/fetcher_spec.rb +88 -0
- data/spec/alephant/broker/load_strategy/revalidate/refresher_spec.rb +69 -0
- data/spec/alephant/broker/load_strategy/revalidate/strategy_spec.rb +185 -0
- data/spec/fixtures/json/batch_compiled_no_sequence.json +1 -0
- data/spec/integration/not_modified_response_spec.rb +4 -6
- data/spec/integration/rack_spec.rb +0 -3
- data/spec/integration/revalidate_spec.rb +174 -0
- data/spec/spec_helper.rb +1 -0
- metadata +99 -55
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 18fa7932242b113184d3dd00fcea6fd0700d6bd8
|
4
|
+
data.tar.gz: 4b8c2034dfe9275d18aa1163bc65bd47d8463cb8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3183c49f2222401e560b929fed1887e3b67c169bff1b33764489955a7cf14fcb144d40d79e9006cb1f42d282989ee1cafec562bfebb81845a42577f3c76df24e
|
7
|
+
data.tar.gz: 5fe75198d006255e0573c4ac4fec30e49b622e75f4059ea1089edcc53fa7f61cfebd12b7c79ab9363b59ba25f61f9b31b7900e6be28457351399a518c4831b89
|
data/.travis.yml
CHANGED
@@ -1,11 +1,24 @@
|
|
1
|
+
sudo: false
|
2
|
+
cache: bundler
|
1
3
|
language: ruby
|
4
|
+
|
5
|
+
# Ruby build matrix
|
2
6
|
rvm:
|
3
|
-
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
- 2.0
|
8
|
+
- 2.1
|
9
|
+
- 2.2
|
10
|
+
- 2.3.0
|
11
|
+
- 2.3.1
|
12
|
+
- jruby
|
13
|
+
|
14
|
+
# Environment variables
|
15
|
+
env:
|
16
|
+
- RUBYOPT="-W0"
|
17
|
+
|
18
|
+
# Ensure we don't build for *every* commit (doesn't apply to PR builds)
|
19
|
+
branches:
|
20
|
+
only:
|
21
|
+
- master
|
22
|
+
|
23
|
+
script:
|
24
|
+
- bundle exec rspec --format documentation
|
data/README.md
CHANGED
@@ -28,7 +28,6 @@ The **Broker** is capable of retrieving rendered templates from either [S3](http
|
|
28
28
|
|
29
29
|
```ruby
|
30
30
|
require 'alephant/broker'
|
31
|
-
require 'alephant/broker/load_strategy/s3'
|
32
31
|
|
33
32
|
config = {
|
34
33
|
:s3_bucket_id => 'test_bucket',
|
@@ -55,7 +54,6 @@ end
|
|
55
54
|
|
56
55
|
```ruby
|
57
56
|
require 'alephant/broker'
|
58
|
-
require 'alephant/broker/load_strategy/http'
|
59
57
|
|
60
58
|
class UrlGenerator < Alephant::Broker::LoadStrategy::HTTP::URL
|
61
59
|
def generate(id, options)
|
@@ -87,7 +85,7 @@ The HTML load strategy relies upon being given a URLGenerator, which is used to
|
|
87
85
|
* include a `#generate` method which takes `id` (string) and `options` (hash) as parameters.
|
88
86
|
|
89
87
|
```ruby
|
90
|
-
require 'alephant/broker
|
88
|
+
require 'alephant/broker'
|
91
89
|
require 'rack'
|
92
90
|
|
93
91
|
class UrlGenerator < Alephant::Broker::LoadStrategy::HTTP::URL
|
@@ -103,6 +101,40 @@ class UrlGenerator < Alephant::Broker::LoadStrategy::HTTP::URL
|
|
103
101
|
end
|
104
102
|
```
|
105
103
|
|
104
|
+
##### Revalidate Strategy
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
require 'alephant/broker'
|
108
|
+
|
109
|
+
config = {
|
110
|
+
:s3_bucket_id => 'test_bucket',
|
111
|
+
:s3_object_path => 'foo',
|
112
|
+
:lookup_table_name => 'test_lookup',
|
113
|
+
:sqs_queue_name => 'test_queue',
|
114
|
+
:elasticache_config_endpoint => 'test_elisticache'
|
115
|
+
:revalidate_cache_ttl => 30
|
116
|
+
}
|
117
|
+
|
118
|
+
Alephant::Broker::Application.new(
|
119
|
+
Alephant::Broker::LoadStrategy::Revalidate::Strategy.new,
|
120
|
+
config
|
121
|
+
).call(request).tap do |response|
|
122
|
+
puts "status: #{response.status}"
|
123
|
+
puts "content: #{response.content}"
|
124
|
+
end
|
125
|
+
```
|
126
|
+
|
127
|
+
The "Revalidate" strategy uses the following process flow:
|
128
|
+
|
129
|
+
![https://raw.githubusercontent.com/BBC-News/Documentation/master/Alephant/Diagrams/Alephant%20Revalidate%20Broker.png?token=AADB7YskdW22P6z-hDjfk12UiR7bZA8Jks5XkI7EwA%3D%3D](https://raw.githubusercontent.com/BBC-News/Documentation/master/Alephant/Diagrams/Alephant%20Revalidate%20Broker.png?token=AADB7YskdW22P6z-hDjfk12UiR7bZA8Jks5XkI7EwA%3D%3D)
|
130
|
+
|
131
|
+
And uses the following AWS resources:
|
132
|
+
|
133
|
+
* S3 - for storage of rendered components
|
134
|
+
* DynamoDB - for recording S3 component locations
|
135
|
+
* Elasticache - for caching S3 content and recording "inflight" messages
|
136
|
+
* SQS - for communication between the broker and renderer
|
137
|
+
|
106
138
|
### Rack App
|
107
139
|
|
108
140
|
Create **config.ru** using example below, and then run:
|
@@ -144,6 +176,3 @@ This version is added as a header to the response in the following format:
|
|
144
176
|
5. Create a new [Pull Request](https://github.com/BBC-News/alephant-broker/pulls).
|
145
177
|
|
146
178
|
Feel free to create a new [issue](https://github.com/BBC-News/alephant-broker/issues/new) if you find a bug.
|
147
|
-
|
148
|
-
|
149
|
-
|
data/alephant-broker.gemspec
CHANGED
@@ -26,13 +26,14 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.add_development_dependency "pry-remote"
|
27
27
|
spec.add_development_dependency "pry-nav"
|
28
28
|
spec.add_development_dependency "rake-rspec", ">= 0.0.2"
|
29
|
-
|
30
29
|
spec.add_development_dependency "bundler", "~> 1.5"
|
31
30
|
spec.add_development_dependency "rake"
|
31
|
+
spec.add_development_dependency 'rack', '< 2.0.0' # NOTE: rack 2.0 requires Ruby 2.2+
|
32
32
|
spec.add_development_dependency "rack-test"
|
33
33
|
spec.add_development_dependency "simplecov"
|
34
|
+
spec.add_development_dependency "timecop"
|
34
35
|
|
35
|
-
spec.add_runtime_dependency "alephant-lookup"
|
36
|
+
spec.add_runtime_dependency "alephant-lookup", ">= 2.0.2"
|
36
37
|
spec.add_runtime_dependency "alephant-storage", ">= 1.1.1"
|
37
38
|
spec.add_runtime_dependency "alephant-logger", "1.2.0"
|
38
39
|
spec.add_runtime_dependency 'alephant-sequencer'
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'alephant/logger'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module Alephant
|
5
|
+
module Broker
|
6
|
+
module Cache
|
7
|
+
class CachedObject
|
8
|
+
include Logger
|
9
|
+
attr_reader :s3_obj
|
10
|
+
|
11
|
+
DEFAULT_TTL = 10
|
12
|
+
|
13
|
+
def initialize(obj)
|
14
|
+
logger.info(event: 'SettingCachedObject',
|
15
|
+
content: obj,
|
16
|
+
method: "#{self.class}#initialize")
|
17
|
+
|
18
|
+
@s3_obj = obj
|
19
|
+
end
|
20
|
+
|
21
|
+
def update(obj)
|
22
|
+
logger.info(event: 'UpdatingCachedObject',
|
23
|
+
old_content: @s3_obj,
|
24
|
+
new_content: obj,
|
25
|
+
method: "#{self.class}#update")
|
26
|
+
|
27
|
+
@s3_obj = obj
|
28
|
+
end
|
29
|
+
|
30
|
+
def updated
|
31
|
+
time = metadata[:'head_Last-Modified']
|
32
|
+
Time.parse(time)
|
33
|
+
rescue TypeError, ArgumentError => error
|
34
|
+
logger.info(event: 'ErrorCaught', method: "#{self.class}#updated", error: error)
|
35
|
+
Time.now
|
36
|
+
end
|
37
|
+
|
38
|
+
def ttl
|
39
|
+
Integer(metadata[:ttl] || metadata['ttl'])
|
40
|
+
rescue TypeError => error
|
41
|
+
logger.info(event: 'ErrorCaught', method: "#{self.class}#ttl", error: error)
|
42
|
+
Integer(Broker.config[:revalidate_cache_ttl] || DEFAULT_TTL)
|
43
|
+
end
|
44
|
+
|
45
|
+
def expired?
|
46
|
+
result = (updated + ttl) < Time.now
|
47
|
+
|
48
|
+
logger.info(event: 'Expired?',
|
49
|
+
updated: updated,
|
50
|
+
ttl: ttl,
|
51
|
+
updated_plus_ttl: (updated + ttl),
|
52
|
+
now: Time.now,
|
53
|
+
result: result,
|
54
|
+
method: "#{self.class}#expired?")
|
55
|
+
|
56
|
+
result
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_h(obj = nil)
|
60
|
+
obj || s3_obj
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def metadata
|
66
|
+
s3_obj.fetch(:meta, {})
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'dalli-elasticache'
|
2
|
+
require 'alephant/logger'
|
3
3
|
|
4
4
|
module Alephant
|
5
5
|
module Broker
|
@@ -11,45 +11,57 @@ module Alephant
|
|
11
11
|
|
12
12
|
def initialize
|
13
13
|
if config_endpoint.nil?
|
14
|
-
logger.debug
|
15
|
-
logger.metric
|
14
|
+
logger.debug 'Broker::Cache::Client#initialize: No config endpoint, NullClient used'
|
15
|
+
logger.metric 'NoConfigEndpoint'
|
16
16
|
@client = NullClient.new
|
17
17
|
else
|
18
|
-
@elasticache ||= ::Dalli::ElastiCache.new(config_endpoint, :
|
18
|
+
@elasticache ||= ::Dalli::ElastiCache.new(config_endpoint, expires_in: ttl)
|
19
19
|
@client ||= @elasticache.client
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
23
|
def get(key)
|
24
|
-
versioned_key = versioned
|
25
|
-
result
|
26
|
-
|
27
|
-
logger.
|
28
|
-
|
29
|
-
|
24
|
+
versioned_key = versioned(key)
|
25
|
+
result = @client.get(versioned_key)
|
26
|
+
|
27
|
+
logger.info("Broker::Cache::Client#get key: #{versioned_key} - #{result ? 'hit' : 'miss'}")
|
28
|
+
logger.metric('GetKeyMiss') unless result
|
29
|
+
|
30
|
+
return result if result
|
31
|
+
|
32
|
+
set(key, yield) if block_given?
|
33
|
+
rescue StandardError => error
|
34
|
+
logger.info(event: 'ErrorCaught', method: "#{self.class}#get", error: error)
|
30
35
|
yield if block_given?
|
31
36
|
end
|
32
37
|
|
33
|
-
def set(key, value,
|
34
|
-
|
38
|
+
def set(key, value, custom_ttl = nil)
|
39
|
+
versioned_key = versioned(key)
|
40
|
+
set_ttl = custom_ttl || ttl
|
41
|
+
|
42
|
+
logger.info("#{self.class}#set - key: #{versioned_key}, value: #{value}, ttl: #{set_ttl}")
|
43
|
+
|
44
|
+
@client.set(versioned_key, value, set_ttl)
|
45
|
+
|
46
|
+
value
|
35
47
|
end
|
36
48
|
|
37
49
|
private
|
38
50
|
|
39
51
|
def config_endpoint
|
40
|
-
Broker.config[
|
52
|
+
Broker.config[:elasticache_config_endpoint] || Broker.config['elasticache_config_endpoint']
|
41
53
|
end
|
42
54
|
|
43
55
|
def ttl
|
44
|
-
Broker.config[
|
56
|
+
Broker.config[:elasticache_ttl] || Broker.config['elasticache_ttl'] || DEFAULT_TTL
|
45
57
|
end
|
46
58
|
|
47
59
|
def versioned(key)
|
48
|
-
[key, cache_version].compact.join(
|
60
|
+
[key, cache_version].compact.join('_')
|
49
61
|
end
|
50
62
|
|
51
63
|
def cache_version
|
52
|
-
Broker.config[
|
64
|
+
Broker.config[:elasticache_cache_version] || Broker.config['elasticache_cache_version']
|
53
65
|
end
|
54
66
|
end
|
55
67
|
end
|
@@ -2,12 +2,20 @@ module Alephant
|
|
2
2
|
module Broker
|
3
3
|
module Cache
|
4
4
|
class NullClient
|
5
|
-
def
|
6
|
-
|
5
|
+
def initialize
|
6
|
+
@cache = {}
|
7
7
|
end
|
8
8
|
|
9
|
-
def
|
10
|
-
|
9
|
+
def get(key)
|
10
|
+
data = @cache[key]
|
11
|
+
|
12
|
+
return data if data
|
13
|
+
|
14
|
+
set(key, yield) if block_given?
|
15
|
+
end
|
16
|
+
|
17
|
+
def set(key, value, _ttl = nil)
|
18
|
+
@cache[key] = value
|
11
19
|
end
|
12
20
|
end
|
13
21
|
end
|
@@ -23,7 +23,7 @@ module Alephant
|
|
23
23
|
fetch_object(component_meta)
|
24
24
|
rescue
|
25
25
|
logger.metric "CacheMiss"
|
26
|
-
cache.set(component_meta.
|
26
|
+
cache.set(component_meta.component_key, content(component_meta))
|
27
27
|
end
|
28
28
|
|
29
29
|
private
|
@@ -35,7 +35,7 @@ module Alephant
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def fetch_object(component_meta)
|
38
|
-
cache.get(component_meta.
|
38
|
+
cache.get(component_meta.component_key) { content component_meta }
|
39
39
|
end
|
40
40
|
|
41
41
|
def content(component_meta)
|
@@ -52,7 +52,6 @@ module Alephant
|
|
52
52
|
|
53
53
|
def request(component_meta)
|
54
54
|
before = Time.new
|
55
|
-
component_meta.cached = false
|
56
55
|
|
57
56
|
Faraday.get(url_for(component_meta)).tap do |r|
|
58
57
|
unless r.success?
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'alephant/broker/errors'
|
2
|
+
|
3
|
+
module Alephant
|
4
|
+
module Broker
|
5
|
+
module LoadStrategy
|
6
|
+
module Revalidate
|
7
|
+
class Fetcher
|
8
|
+
include Logger
|
9
|
+
|
10
|
+
attr_reader :component_meta
|
11
|
+
|
12
|
+
def initialize(component_meta)
|
13
|
+
@component_meta = component_meta
|
14
|
+
end
|
15
|
+
|
16
|
+
def fetch
|
17
|
+
Alephant::Broker::Cache::CachedObject.new(s3.get(s3_path))
|
18
|
+
rescue AWS::S3::Errors::NoSuchKey, InvalidCacheKey => error
|
19
|
+
logger.info(event: 'ErrorCaught', method: "#{self.class}#fetch", error: error)
|
20
|
+
logger.metric('S3InvalidCacheKey')
|
21
|
+
raise Alephant::Broker::Errors::ContentNotFound
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def s3_path
|
27
|
+
lookup_read = lookup.read(component_meta.id, component_meta.options, 1)
|
28
|
+
|
29
|
+
raise InvalidCacheKey if lookup_read.location.nil?
|
30
|
+
|
31
|
+
lookup_read.location
|
32
|
+
end
|
33
|
+
|
34
|
+
def s3
|
35
|
+
@s3 ||= Alephant::Storage.new(
|
36
|
+
Broker.config[:s3_bucket_id],
|
37
|
+
Broker.config[:s3_object_path]
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
def lookup
|
42
|
+
@lookup ||= Alephant::Lookup.create(
|
43
|
+
Broker.config[:lookup_table_name],
|
44
|
+
Broker.config
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Alephant
|
2
|
+
module Broker
|
3
|
+
module LoadStrategy
|
4
|
+
module Revalidate
|
5
|
+
class Refresher
|
6
|
+
include Logger
|
7
|
+
|
8
|
+
INFLIGHT_CACHE_TTL = 120 # expire the inflight key after 2 minutes
|
9
|
+
|
10
|
+
attr_reader :component_meta
|
11
|
+
|
12
|
+
def initialize(component_meta)
|
13
|
+
@component_meta = component_meta
|
14
|
+
end
|
15
|
+
|
16
|
+
def refresh
|
17
|
+
inflight = cache.get(inflight_cache_key)
|
18
|
+
|
19
|
+
logger.info(event: 'Inflight?', cache_val: inflight, method: "#{self.class}#refresh")
|
20
|
+
|
21
|
+
return if inflight
|
22
|
+
|
23
|
+
logger.info(event: 'QueueMessage', message: message, method: "#{self.class}#refresh")
|
24
|
+
|
25
|
+
queue.send_message(message)
|
26
|
+
cache.set(inflight_cache_key, true, INFLIGHT_CACHE_TTL)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def message
|
32
|
+
::JSON.generate(id: component_meta.id,
|
33
|
+
batch_id: component_meta.batch_id,
|
34
|
+
options: component_meta.options,
|
35
|
+
timestamp: Time.now.to_s)
|
36
|
+
end
|
37
|
+
|
38
|
+
def queue
|
39
|
+
@queue ||= proc do
|
40
|
+
client = AWS::SQS.new
|
41
|
+
url = client.queues.url_for(Broker.config[:sqs_queue_name], queue_options)
|
42
|
+
|
43
|
+
client.queues[url]
|
44
|
+
end.call
|
45
|
+
end
|
46
|
+
|
47
|
+
def queue_options
|
48
|
+
opts = {}
|
49
|
+
opts[:queue_owner_aws_account_id] = aws_acc_id if aws_acc_id
|
50
|
+
|
51
|
+
logger.info(event: 'SQSQueueOptionsConfigured', options: opts, method: "#{self.class}#queue_options")
|
52
|
+
|
53
|
+
opts
|
54
|
+
end
|
55
|
+
|
56
|
+
def aws_acc_id
|
57
|
+
Broker.config[:aws_account_id]
|
58
|
+
end
|
59
|
+
|
60
|
+
def cache
|
61
|
+
@cache ||= Cache::Client.new
|
62
|
+
end
|
63
|
+
|
64
|
+
def cache_key
|
65
|
+
component_meta.component_key
|
66
|
+
end
|
67
|
+
|
68
|
+
def inflight_cache_key
|
69
|
+
"inflight-#{cache_key}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|