routemaster-drain 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.env.test +6 -0
  3. data/.gitignore +7 -0
  4. data/.gitmodules +3 -0
  5. data/.rspec +3 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +16 -0
  8. data/.yardopts +6 -0
  9. data/Gemfile +17 -0
  10. data/Gemfile.lock +122 -0
  11. data/Guardfile +10 -0
  12. data/LICENSE.txt +22 -0
  13. data/README.md +261 -0
  14. data/Rakefile +1 -0
  15. data/lib/routemaster/cache.rb +133 -0
  16. data/lib/routemaster/config.rb +57 -0
  17. data/lib/routemaster/dirty/filter.rb +49 -0
  18. data/lib/routemaster/dirty/map.rb +63 -0
  19. data/lib/routemaster/dirty/state.rb +33 -0
  20. data/lib/routemaster/drain.rb +5 -0
  21. data/lib/routemaster/drain/basic.rb +40 -0
  22. data/lib/routemaster/drain/caching.rb +43 -0
  23. data/lib/routemaster/drain/mapping.rb +43 -0
  24. data/lib/routemaster/drain/terminator.rb +31 -0
  25. data/lib/routemaster/fetcher.rb +63 -0
  26. data/lib/routemaster/jobs/cache.rb +12 -0
  27. data/lib/routemaster/jobs/cache_and_sweep.rb +16 -0
  28. data/lib/routemaster/middleware/authenticate.rb +59 -0
  29. data/lib/routemaster/middleware/cache.rb +29 -0
  30. data/lib/routemaster/middleware/dirty.rb +33 -0
  31. data/lib/routemaster/middleware/filter.rb +26 -0
  32. data/lib/routemaster/middleware/parse.rb +43 -0
  33. data/lib/routemaster/middleware/root_post_only.rb +18 -0
  34. data/lib/routemaster/redis_broker.rb +42 -0
  35. data/routemaster-drain.gemspec +28 -0
  36. data/spec/routemaster/cache_spec.rb +115 -0
  37. data/spec/routemaster/dirty/filter_spec.rb +77 -0
  38. data/spec/routemaster/dirty/map_spec.rb +122 -0
  39. data/spec/routemaster/dirty/state_spec.rb +41 -0
  40. data/spec/routemaster/drain/basic_spec.rb +37 -0
  41. data/spec/routemaster/drain/caching_spec.rb +47 -0
  42. data/spec/routemaster/drain/mapping_spec.rb +51 -0
  43. data/spec/routemaster/drain/terminator_spec.rb +61 -0
  44. data/spec/routemaster/fetcher_spec.rb +56 -0
  45. data/spec/routemaster/middleware/authenticate_spec.rb +59 -0
  46. data/spec/routemaster/middleware/cache_spec.rb +35 -0
  47. data/spec/routemaster/middleware/dirty_spec.rb +33 -0
  48. data/spec/routemaster/middleware/filter_spec.rb +35 -0
  49. data/spec/routemaster/middleware/parse_spec.rb +69 -0
  50. data/spec/routemaster/middleware/root_post_only_spec.rb +30 -0
  51. data/spec/spec_helper.rb +16 -0
  52. data/spec/support/events.rb +9 -0
  53. data/spec/support/rack_test.rb +23 -0
  54. data/spec/support/uses_dotenv.rb +11 -0
  55. data/spec/support/uses_redis.rb +15 -0
  56. data/spec/support/uses_webmock.rb +12 -0
  57. data/test.rb +17 -0
  58. metadata +247 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cc848f2c1b75d8d36a4807ac8d8f0d871f9fca29
4
+ data.tar.gz: 5c627ab91e9497abb0acb81227484650f280853c
5
+ SHA512:
6
+ metadata.gz: fdcd9b0cb52cb767ac97d9b7df8b7c26c1ff92755487b7801748f488b3946c256079e441a8c9264768c858b3b12f70eed38b6a23fd93a2d48526a40642314445
7
+ data.tar.gz: 2cb2c4af8600340854423def3364f3e1d4f3d51627765d2e6f8a4579e70c25ba31ee38058acf7b37941be52b4f00e1ad3d472ed9607590f4f242043b2d65b232
data/.env.test ADDED
@@ -0,0 +1,6 @@
1
+ REDIS_TEST_URL=redis://localhost/13
2
+ ROUTEMASTER_DRAIN_REDIS=redis://localhost/13
3
+ ROUTEMASTER_CACHE_REDIS=redis://localhost/12
4
+ ROUTEMASTER_CACHE_AUTH=example.com:username:s3cr3t
5
+ ROUTEMASTER_DRAIN_TOKENS=d3m0
6
+
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ .bundle/
2
+ doc/
3
+ .yardoc/
4
+ pkg/
5
+ tmp
6
+ *.orig
7
+ tags
data/.gitmodules ADDED
@@ -0,0 +1,3 @@
1
+ [submodule "rebund"]
2
+ path = rebund
3
+ url = https://github.com/mezis/rebund.git
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ -I.
2
+ --color
3
+ --format progress
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.2
data/.travis.yml ADDED
@@ -0,0 +1,16 @@
1
+ language: ruby
2
+ services:
3
+ - redis-server
4
+ rvm:
5
+ - 2.1.1
6
+ - 2.1.2
7
+ script:
8
+ - bundle exec rspec
9
+ install:
10
+ - "./rebund/run download"
11
+ - bundle install --path vendor/bundle
12
+ after_script:
13
+ - "./rebund/run upload"
14
+ env:
15
+ global:
16
+ secure: "kMxJcT8xyQ+QyCeKW5lDP44IU99Y8iN1AWZo9Z1BKN6HigGa8Ua8O/aD8AJjrTfRRSjnM402utmW6mk78RYVxJha3+69jJYr25I9EeyYhV2cuRu+5Ictxfcb9R9VhS0b6LDncz9h55xDS1UA35rvj+EOO4/M9moccw7T2qzPa2U="
data/.yardopts ADDED
@@ -0,0 +1,6 @@
1
+ --no-private
2
+ --protected
3
+ --markup-provider=redcarpet
4
+ --markup=markdown
5
+
6
+ lib/**/*.rb - README.md LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source ENV.fetch('GEM_SOURCE', 'https://rubygems.org')
2
+
3
+ # Specify your gem's dependencies in routemaster_client.gemspec
4
+ gemspec
5
+
6
+ # Just here to avoid a safety warning
7
+ gem 'psych', require: false
8
+
9
+ # Used in builds and tests
10
+ gem 'bundler', require: false
11
+ gem 'rake', require: false
12
+ gem 'guard-rspec', require: false
13
+ gem 'webmock', require: false
14
+ gem 'pry-nav', require: false
15
+ gem 'rack-test', require: false
16
+ gem 'dotenv', require: false
17
+
data/Gemfile.lock ADDED
@@ -0,0 +1,122 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ routemaster-drain (1.0.0)
5
+ faraday
6
+ faraday_middleware
7
+ hashie
8
+ net-http-persistent
9
+ rack
10
+ redis-namespace
11
+ resque
12
+ thread
13
+ wisper
14
+
15
+ GEM
16
+ remote: http://eu.yarp.io/
17
+ specs:
18
+ addressable (2.3.6)
19
+ celluloid (0.16.0)
20
+ timers (~> 4.0.0)
21
+ coderay (1.1.0)
22
+ crack (0.4.2)
23
+ safe_yaml (~> 1.0.0)
24
+ diff-lcs (1.2.5)
25
+ dotenv (0.11.1)
26
+ dotenv-deployment (~> 0.0.2)
27
+ dotenv-deployment (0.0.2)
28
+ faraday (0.9.0)
29
+ multipart-post (>= 1.2, < 3)
30
+ faraday_middleware (0.9.1)
31
+ faraday (>= 0.7.4, < 0.10)
32
+ ffi (1.9.3)
33
+ formatador (0.2.5)
34
+ guard (2.6.1)
35
+ formatador (>= 0.2.4)
36
+ listen (~> 2.7)
37
+ lumberjack (~> 1.0)
38
+ pry (>= 0.9.12)
39
+ thor (>= 0.18.1)
40
+ guard-rspec (4.3.1)
41
+ guard (~> 2.1)
42
+ rspec (>= 2.14, < 4.0)
43
+ hashie (3.3.1)
44
+ hitimes (1.2.2)
45
+ listen (2.7.9)
46
+ celluloid (>= 0.15.2)
47
+ rb-fsevent (>= 0.9.3)
48
+ rb-inotify (>= 0.9)
49
+ lumberjack (1.0.9)
50
+ method_source (0.8.2)
51
+ mono_logger (1.1.0)
52
+ multi_json (1.10.1)
53
+ multipart-post (2.0.0)
54
+ net-http-persistent (2.9.4)
55
+ pry (0.10.1)
56
+ coderay (~> 1.1.0)
57
+ method_source (~> 0.8.1)
58
+ slop (~> 3.4)
59
+ pry-nav (0.2.4)
60
+ pry (>= 0.9.10, < 0.11.0)
61
+ psych (2.0.5)
62
+ rack (1.5.2)
63
+ rack-protection (1.5.3)
64
+ rack
65
+ rack-test (0.6.2)
66
+ rack (>= 1.0)
67
+ rake (10.3.2)
68
+ rb-fsevent (0.9.4)
69
+ rb-inotify (0.9.5)
70
+ ffi (>= 0.5.0)
71
+ redis (3.1.0)
72
+ redis-namespace (1.5.1)
73
+ redis (~> 3.0, >= 3.0.4)
74
+ resque (1.25.2)
75
+ mono_logger (~> 1.0)
76
+ multi_json (~> 1.0)
77
+ redis-namespace (~> 1.3)
78
+ sinatra (>= 0.9.2)
79
+ vegas (~> 0.1.2)
80
+ rspec (3.1.0)
81
+ rspec-core (~> 3.1.0)
82
+ rspec-expectations (~> 3.1.0)
83
+ rspec-mocks (~> 3.1.0)
84
+ rspec-core (3.1.2)
85
+ rspec-support (~> 3.1.0)
86
+ rspec-expectations (3.1.0)
87
+ diff-lcs (>= 1.2.0, < 2.0)
88
+ rspec-support (~> 3.1.0)
89
+ rspec-mocks (3.1.0)
90
+ rspec-support (~> 3.1.0)
91
+ rspec-support (3.1.0)
92
+ safe_yaml (1.0.3)
93
+ sinatra (1.4.5)
94
+ rack (~> 1.4)
95
+ rack-protection (~> 1.4)
96
+ tilt (~> 1.3, >= 1.3.4)
97
+ slop (3.6.0)
98
+ thor (0.19.1)
99
+ thread (0.1.4)
100
+ tilt (1.4.1)
101
+ timers (4.0.0)
102
+ hitimes
103
+ vegas (0.1.11)
104
+ rack (>= 1.0.0)
105
+ webmock (1.18.0)
106
+ addressable (>= 2.3.6)
107
+ crack (>= 0.3.2)
108
+ wisper (1.4.0)
109
+
110
+ PLATFORMS
111
+ ruby
112
+
113
+ DEPENDENCIES
114
+ bundler
115
+ dotenv
116
+ guard-rspec
117
+ pry-nav
118
+ psych
119
+ rack-test
120
+ rake
121
+ routemaster-drain!
122
+ webmock
data/Guardfile ADDED
@@ -0,0 +1,10 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :rspec, cmd: 'bundle exec rspec' do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
9
+ end
10
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 HouseTrip Ltd.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,261 @@
1
+ # routemaster-drain
2
+
3
+ A Rack-based event receiver for the
4
+ [Routemaster](https://github.com/HouseTrip/routemaster) event bus.
5
+
6
+ ![Version](https://badge.fury.io/rb/routemaster-drain.svg)
7
+ &nbsp;
8
+ ![Build](https://travis-ci.org/HouseTrip/routemaster-drain.svg?branch=master)
9
+ &nbsp;
10
+ [![](http://img.shields.io/badge/API%20docs-rubydoc.info-blue.svg)](http://rubydoc.info/github/HouseTrip/routemaster-drain/frames/file/README.md)
11
+
12
+ `routemaster-drain` is a collection of Rack middleware to receive and
13
+ parse Routemaster events, filter them, and preemptively cache the corresponding
14
+ resources.
15
+
16
+ It provides prebuilt middleware stacks (`Basic`, `Mapping`, and `Caching`) for
17
+ typical use cases, illustrated below, or you can easily roll your own by
18
+ combining middleware.
19
+
20
+
21
+ <!-- START doctoc generated TOC please keep comment here to allow auto update -->
22
+ <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
23
+
24
+ - [Installation](#installation)
25
+ - [Illustrated use cases](#illustrated-use-cases)
26
+ - [Simply receive events from Routemaster](#simply-receive-events-from-routemaster)
27
+ - [Receive change notifications without duplicates](#receive-change-notifications-without-duplicates)
28
+ - [Cache data for all notified resources](#cache-data-for-all-notified-resources)
29
+ - [Internals](#internals)
30
+ - [Dirty map](#dirty-map)
31
+ - [Filter](#filter)
32
+ - [Contributing](#contributing)
33
+
34
+ <!-- END doctoc generated TOC please keep comment here to allow auto update -->
35
+
36
+
37
+ ## Installation
38
+
39
+ Add this line to your application's Gemfile:
40
+
41
+ gem 'routemaster-drain'
42
+
43
+ **Configuration**
44
+
45
+ This gem is configured through the environment, making 12factor compliance
46
+ easier.
47
+
48
+ - `ROUTEMASTER_DRAIN_TOKENS`: a comma-separated list of valid authentication
49
+ tokens, used by Routemaster to send you events.
50
+ - `ROUTEMASTER_DRAIN_REDIS`: the URL of the Redis instance used for filtering
51
+ and dirty mapping. Required if you use either feature, ignored otherwise.
52
+ A namespace can be specified.
53
+ Example: `redis://user:s3cr3t@myhost:1234/12/ns`.
54
+ - `ROUTEMASTER_CACHE_REDIS`: the URL of the Redis instance used for caching.
55
+ Required if you use the feature, ignored otherwise. Formatted like
56
+ `ROUTEMASTER_DRAIN_REDIS`.
57
+ - `ROUTEMASTER_CACHE_EXPIRY`: if using the cache, for how long to cache
58
+ entries, in seconds. Default 1 year (31,536,000).
59
+ - `ROUTEMASTER_CACHE_AUTH`: if using the cache, specifies what username/password
60
+ pairs to use to fetch resources. The format is a comma-separated list of
61
+ colon-separate lists of regexp, username, password values. Example:
62
+ `server1:user:p4ss,server2:user:p4ass`.
63
+ - `ROUTEMASTER_QUEUE_NAME`: if using the cache, on which Resque queue the cache
64
+ population jobs should be enqueued.
65
+
66
+
67
+
68
+ ## Illustrated use cases
69
+
70
+
71
+ ### Simply receive events from Routemaster
72
+
73
+ Provide a listener for events:
74
+
75
+ ```ruby
76
+ class Listener
77
+ def on_events_received(batch)
78
+ batch.each do |event|
79
+ puts event.url
80
+ end
81
+ end
82
+ end
83
+ ```
84
+
85
+ Each event is a `Hashie::Mash` and responds to `type` (one of `create`,
86
+ `update`, `delete`, or `noop`), `url` (the resource), and `t` (the event
87
+ timestamp, in milliseconds since the Epoch).
88
+
89
+ Create the app that will process events:
90
+
91
+ ```ruby
92
+ require 'routemaster/drain/basic'
93
+ $app = Routemaster::Drain::Basic.new
94
+ ```
95
+
96
+ Bind the app to your listener:
97
+
98
+ ```ruby
99
+ $app.subscribe(Listener.new)
100
+ ```
101
+
102
+ And finally, mount your app to your subscription path:
103
+
104
+ ```ruby
105
+ # typically in config.ru
106
+ map '/events' do
107
+ run $app
108
+ end
109
+ ```
110
+
111
+ This relies on the excellent event bus from the [wisper
112
+ gem](https://github.com/krisleech/wisper#wisper).
113
+
114
+
115
+ ### Receive change notifications without duplicates
116
+
117
+ When reacting to changes of some resource, it's common to want to avoid
118
+ receiving further change notifications until you've actually processed that
119
+ resource.
120
+
121
+ Possibly you'll want to process changes in batches at regular time intervals.
122
+
123
+ For this purpose, use `Routemaster::Drain::Mapping`:
124
+
125
+ ```ruby
126
+ require 'routemaster/drain/mapping'
127
+ $app = Routemaster::Drain::Mapping.new
128
+ ```
129
+
130
+ And mount it as usual:
131
+
132
+ ```ruby
133
+ # in config.ru
134
+ map('/events') { run $app }
135
+ ```
136
+
137
+ Instead of processing events, you'll check for changes in the dirty map:
138
+
139
+ ```ruby
140
+ require 'routemaster/dirty/map'
141
+ $map = Routemaster::Dirty::Map.new
142
+
143
+ every_5_minutes do
144
+ $map.sweep do |url|
145
+ # do something about this changed resource
146
+ true
147
+ end
148
+ end
149
+ ```
150
+
151
+ Until you've called `#sweep` and your block has returned `true`, you won't be
152
+ bugged again — the dirty map acts as a buffer of changes (see below for
153
+ internals).
154
+
155
+ Notes:
156
+ - You can limit the number of resources to be swept (`$map.sweep(123) { ... }`).
157
+ - You can count the number of resources to be swept with `$map.count`.
158
+ - You're not told _what_ is to be swept; entities won't be swept in the order of
159
+ events received (much like Routemaster does not guarantee ordering).
160
+ - If your sweeper fails, the dirty map will not be cleaned, so you can have
161
+ leftovers. It's good practice to regularly run `$map.sweep { ... }` and perform
162
+ cleanup regularly.
163
+ - The map won't tell you if the resources has been changed, created, or deleted.
164
+ You'll have to figure it out with an API call.
165
+ - You can still attach a listener to the app to get all events.
166
+
167
+
168
+ ### Cache data for all notified resources
169
+
170
+ Another common use case is that you'll actually need the representation of the
171
+ resources Routemaster tells you about.
172
+
173
+ The `Caching` prebuilt app can do that for you, using Resque to populate the
174
+ cache as events are received.
175
+
176
+ For this purpose, use `Routemaster::Drain::Caching`:
177
+
178
+ ```ruby
179
+ require 'routemaster/drain/machine'
180
+ $app = Routemaster::Drain:Caching.new
181
+ ```
182
+
183
+ And mount it as usual:
184
+
185
+ ```ruby
186
+ # in config.ru
187
+ map('/events') { run $app }
188
+ ```
189
+
190
+ You can still attach a listenenr if you want the incoming events. Typically,
191
+ what you'll want is the cache:
192
+
193
+ ```ruby
194
+ require 'routemaster/cache'
195
+ $cache = Routemaster::Cache.new
196
+
197
+ response = @cache.fget('https://example.com/widgets/123')
198
+ puts response.body.id
199
+ ```
200
+
201
+ In this example, is your app was notified by Routemaster about Widget #123, the
202
+ cache will be very likely to be hit; and it will be invalidated automatically
203
+ whenever the drain gets notified about a change on that widget.
204
+
205
+ Note that `Cache#fget` is a future, so you can efficiently query many resources
206
+ and have any `HTTP GET` requests (and cache queries) happen in parallel.
207
+
208
+ See
209
+ [rubydoc](http://rubydoc.info/github/HouseTrip/routemaster-drain/Routemaster/Cache)
210
+ for more details on `Cache`.
211
+
212
+
213
+ ## Internals
214
+
215
+ The more elaborate drains are built with two components which can also be used
216
+ independently,
217
+ [`Dirty::Map`](http://rubydoc.info/github/HouseTrip/routemaster-drain/Routemaster/Dirty/Map)
218
+ and
219
+ [`Dirty::Filter`](http://rubydoc.info/github/HouseTrip/routemaster-drain/Routemaster/Dirty/Filter).
220
+
221
+ ### Dirty map
222
+
223
+ A dirty map collects entities that have been created, updated, or deleted (or
224
+ rather, their URLs). It can be used to delay your service's reaction to events,
225
+ for instance combined with Resque.
226
+
227
+ A dirty map map gets _marked_ when an event about en entity gets processed that
228
+ indicates a state change, and _swept_ to process those changes.
229
+
230
+ Practically, instances of
231
+ [`Routemaster::Dirty::Map`](http://rubydoc.info/github/HouseTrip/routemaster-drain/Routemaster/Dirty/Map)
232
+ will emit a `:dirty_entity` event when a URL is marked as dirty, and can be
233
+ swept when an entity is "cleaned". If a URL is marked multiple times before
234
+ being swept (e.g. for very volatile entities), the event will only by broadcast
235
+ once.
236
+
237
+ To sweep the map, you can for instance listen to this event and call
238
+ [`#sweep_one`](http://rubydoc.info/github/HouseTrip/routemaster-drain/Routemaster/Dirty/Map#sweep_one-instance_method).
239
+
240
+ If you're not in a hurry and would rather run through batches you can call
241
+ [`#sweep`](http://rubydoc.info/github/HouseTrip/routemaster-drain/Routemaster/Dirty/Map#sweep-instance_method)
242
+ which will yield URLs until it runs out of dirty resources.
243
+
244
+ ### Filter
245
+
246
+ [`Routemaster::Dirty::Filter`](http://rubydoc.info/github/HouseTrip/routemaster-drain/Routemaster/Dirty/Filter) is a simple event filter
247
+ that performs reordering. It ignores events older than the latest known
248
+ information on an entity.
249
+
250
+ It stores transient state in Redis and will emit `:entity_changed` events
251
+ whenever an entity has changed. This event can usefully be fed into a dirty map,
252
+ as in `Receiver::Filter` for instance.
253
+
254
+
255
+ ## Contributing
256
+
257
+ 1. Fork it ( http://github.com/HouseTrip/routemaster-drain/fork )
258
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
259
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
260
+ 4. Push to the branch (`git push origin my-new-feature`)
261
+ 5. Create new Pull Request