hivent 1.0.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 +7 -0
- data/.codeclimate.yml +19 -0
- data/.gitignore +14 -0
- data/.rspec +1 -0
- data/.rubocop.yml +1063 -0
- data/.ruby-version +1 -0
- data/.simplecov.template +1 -0
- data/.travis.yml +23 -0
- data/.version +1 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +196 -0
- data/bin/hivent +5 -0
- data/hivent.gemspec +34 -0
- data/lib/hivent.rb +32 -0
- data/lib/hivent/abstract_signal.rb +63 -0
- data/lib/hivent/cli/consumer.rb +60 -0
- data/lib/hivent/cli/runner.rb +50 -0
- data/lib/hivent/cli/start_option_parser.rb +53 -0
- data/lib/hivent/config.rb +22 -0
- data/lib/hivent/config/options.rb +51 -0
- data/lib/hivent/emitter.rb +41 -0
- data/lib/hivent/life_cycle_event_handler.rb +41 -0
- data/lib/hivent/redis/consumer.rb +82 -0
- data/lib/hivent/redis/extensions.rb +26 -0
- data/lib/hivent/redis/lua/consumer.lua +179 -0
- data/lib/hivent/redis/lua/producer.lua +27 -0
- data/lib/hivent/redis/producer.rb +24 -0
- data/lib/hivent/redis/redis.rb +14 -0
- data/lib/hivent/redis/signal.rb +36 -0
- data/lib/hivent/rspec.rb +11 -0
- data/lib/hivent/signal.rb +14 -0
- data/lib/hivent/spec.rb +11 -0
- data/lib/hivent/spec/matchers.rb +14 -0
- data/lib/hivent/spec/matchers/emit.rb +116 -0
- data/lib/hivent/spec/signal.rb +60 -0
- data/lib/hivent/version.rb +6 -0
- data/spec/codeclimate_helper.rb +5 -0
- data/spec/fixtures/cli/bootstrap_consumers.rb +7 -0
- data/spec/fixtures/cli/life_cycle_event_test.rb +8 -0
- data/spec/hivent/abstract_signal_spec.rb +161 -0
- data/spec/hivent/cli/consumer_spec.rb +68 -0
- data/spec/hivent/cli/runner_spec.rb +75 -0
- data/spec/hivent/cli/start_option_parser_spec.rb +48 -0
- data/spec/hivent/life_cycle_event_handler_spec.rb +38 -0
- data/spec/hivent/redis/consumer_spec.rb +348 -0
- data/spec/hivent/redis/signal_spec.rb +155 -0
- data/spec/hivent_spec.rb +100 -0
- data/spec/spec/matchers/emit_spec.rb +66 -0
- data/spec/spec/signal_spec.rb +72 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/support/matchers/exit_with_code.rb +28 -0
- data/spec/support/stdout_helpers.rb +25 -0
- metadata +267 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
local event = ARGV[1]
|
2
|
+
local payload = ARGV[2]
|
3
|
+
local partition_id = ARGV[3]
|
4
|
+
|
5
|
+
local function concat_tables_uniq(table1, table2)
|
6
|
+
for i=1, #table2 do table.insert(table1, table2[i]) end
|
7
|
+
local set = {}
|
8
|
+
for _, l in ipairs(table1) do set[l] = true end
|
9
|
+
local uniq = {}
|
10
|
+
for i, _ in pairs(set) do table.insert(uniq, i) end
|
11
|
+
return uniq
|
12
|
+
end
|
13
|
+
|
14
|
+
local function produce(event, payload, partition_id)
|
15
|
+
local consumers = concat_tables_uniq(redis.call("SMEMBERS", event), redis.call("SMEMBERS", "*"))
|
16
|
+
|
17
|
+
for _, consumer in ipairs(consumers) do
|
18
|
+
local partition_count = tonumber(redis.call("GET", consumer .. ":partition_count"))
|
19
|
+
local partition = partition_id % partition_count
|
20
|
+
local queue = consumer .. ":" .. partition
|
21
|
+
|
22
|
+
redis.call("LPUSH", queue, payload)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
produce(event, payload, tonumber(partition_id))
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Hivent
|
3
|
+
|
4
|
+
module Redis
|
5
|
+
|
6
|
+
class Producer
|
7
|
+
|
8
|
+
include Hivent::Redis::Extensions
|
9
|
+
|
10
|
+
LUA_PRODUCER = File.expand_path("../lua/producer.lua", __FILE__)
|
11
|
+
|
12
|
+
def initialize(redis)
|
13
|
+
@redis = redis
|
14
|
+
end
|
15
|
+
|
16
|
+
def write(name, payload, partition_key)
|
17
|
+
script(LUA_PRODUCER, name, payload, partition_key)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'zlib'
|
3
|
+
require 'hivent/redis/producer'
|
4
|
+
|
5
|
+
module Hivent
|
6
|
+
|
7
|
+
module Redis
|
8
|
+
|
9
|
+
class Signal < AbstractSignal
|
10
|
+
|
11
|
+
MAX_TRIES = 4
|
12
|
+
SLEEP = ->(n) { (n**4) * 0.01 }
|
13
|
+
|
14
|
+
def initialize(*args)
|
15
|
+
super
|
16
|
+
|
17
|
+
@producer = Producer.new(redis)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def send_message(message, key, _version)
|
23
|
+
Retryable.retryable(tries: MAX_TRIES, sleep: SLEEP) do
|
24
|
+
producer.write(name, message.to_json, key)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def redis
|
29
|
+
Hivent::Redis.redis
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/lib/hivent/rspec.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Hivent
|
3
|
+
|
4
|
+
module Signal
|
5
|
+
|
6
|
+
# pretend Signal is a class so that using backends is transparent
|
7
|
+
def self.new(*args)
|
8
|
+
klass = "Hivent::#{Hivent::Config.backend.to_s.camelize}::Signal"
|
9
|
+
klass.constantize.new(*args)
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
data/lib/hivent/spec.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Hivent
|
3
|
+
|
4
|
+
module Spec
|
5
|
+
|
6
|
+
module Matchers
|
7
|
+
|
8
|
+
class Emit
|
9
|
+
|
10
|
+
def initialize(name, meta)
|
11
|
+
@name = name
|
12
|
+
@meta = meta
|
13
|
+
end
|
14
|
+
|
15
|
+
def emitted?
|
16
|
+
before = signals.length
|
17
|
+
|
18
|
+
yield
|
19
|
+
|
20
|
+
signals.length > before
|
21
|
+
end
|
22
|
+
|
23
|
+
def emitted_with_payload?(payload)
|
24
|
+
before = signals_with_payload(payload).length
|
25
|
+
|
26
|
+
yield
|
27
|
+
|
28
|
+
signals_with_payload(payload).length > before
|
29
|
+
end
|
30
|
+
|
31
|
+
def signals
|
32
|
+
messages
|
33
|
+
.lazy
|
34
|
+
.select { |signal| signal[:name] == @name }
|
35
|
+
.select do |signal|
|
36
|
+
@meta.all? do |key, value|
|
37
|
+
!value.present? ||
|
38
|
+
value == signal[:message][:meta][key]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
.to_a
|
42
|
+
end
|
43
|
+
|
44
|
+
def signals_with_payload(payload)
|
45
|
+
signals.select { |signal| deep_include?(signal[:message][:payload], payload) }
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def messages
|
51
|
+
Signal.messages
|
52
|
+
end
|
53
|
+
|
54
|
+
def deep_include?(hash, sub_hash)
|
55
|
+
sub_hash.keys.all? do |key|
|
56
|
+
if hash.has_key?(key) && sub_hash[key].is_a?(Hash) && hash[key].is_a?(Hash)
|
57
|
+
deep_include?(hash[key], sub_hash[key])
|
58
|
+
else
|
59
|
+
hash[key] == sub_hash[key]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
RSpec::Matchers.define :emit do |name, meta = {}|
|
73
|
+
matcher = Hivent::Spec::Matchers::Emit.new(name, meta)
|
74
|
+
|
75
|
+
match do |actual|
|
76
|
+
if actual.is_a?(Proc)
|
77
|
+
if payload.present?
|
78
|
+
matcher.emitted_with_payload?(payload, &actual)
|
79
|
+
else
|
80
|
+
matcher.emitted?(&actual)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
chain :with, :payload
|
86
|
+
|
87
|
+
failure_message do |_actual|
|
88
|
+
message = %{expected to have emitted a signal with name "#{name}"}
|
89
|
+
|
90
|
+
unless meta.empty?
|
91
|
+
message += ", meta #{meta.inspect}"
|
92
|
+
end
|
93
|
+
|
94
|
+
if payload.present?
|
95
|
+
message += " and payload #{payload.inspect}"
|
96
|
+
end
|
97
|
+
|
98
|
+
message
|
99
|
+
end
|
100
|
+
|
101
|
+
failure_message_when_negated do |_actual|
|
102
|
+
message = %{expected not to have emitted a signal with name "#{name}"}
|
103
|
+
|
104
|
+
unless meta.empty?
|
105
|
+
message += ", meta #{meta.inspect}"
|
106
|
+
end
|
107
|
+
|
108
|
+
if payload.present?
|
109
|
+
message += " and payload #{payload.inspect}"
|
110
|
+
end
|
111
|
+
|
112
|
+
message
|
113
|
+
end
|
114
|
+
|
115
|
+
supports_block_expectations
|
116
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'hivent/abstract_signal'
|
3
|
+
|
4
|
+
module Hivent
|
5
|
+
|
6
|
+
module Spec
|
7
|
+
|
8
|
+
class Signal < AbstractSignal
|
9
|
+
|
10
|
+
def self.reset!
|
11
|
+
@store = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.messages
|
15
|
+
@store ||= []
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(*args)
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def messages
|
23
|
+
self.class.messages
|
24
|
+
end
|
25
|
+
|
26
|
+
def emit(_payload, version:, cid: nil, key: nil)
|
27
|
+
super.tap do |message|
|
28
|
+
begin
|
29
|
+
Hivent.emitter.broadcast(message)
|
30
|
+
|
31
|
+
report_success(name, version, message)
|
32
|
+
rescue => e
|
33
|
+
report_failure(e, message)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def life_cycle_event_handler
|
41
|
+
Hivent::Config.life_cycle_event_handler
|
42
|
+
end
|
43
|
+
|
44
|
+
def report_success(name, version, message)
|
45
|
+
life_cycle_event_handler.try(:event_processing_succeeded, name, version, message)
|
46
|
+
end
|
47
|
+
|
48
|
+
def report_failure(e, message)
|
49
|
+
life_cycle_event_handler.try(:event_processing_failed, e, message, message.to_json, "queue")
|
50
|
+
end
|
51
|
+
|
52
|
+
def send_message(message, _key, _version)
|
53
|
+
messages << { name: @name, message: message }
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Hivent::AbstractSignal do
|
5
|
+
|
6
|
+
class MySignal < Hivent::AbstractSignal
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def send_message(message, key, version)
|
11
|
+
# do nothing
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:signal) do
|
17
|
+
MySignal.new(name)
|
18
|
+
end
|
19
|
+
let(:name) { "my_signal" }
|
20
|
+
let(:payload) { { key: "value" } }
|
21
|
+
let(:version) { 1 }
|
22
|
+
let(:cid) { nil }
|
23
|
+
let(:partition_key) { nil }
|
24
|
+
let(:client_id) { "my_client_id" }
|
25
|
+
|
26
|
+
before :each do
|
27
|
+
Hivent.configure do |config|
|
28
|
+
config.backend = :redis
|
29
|
+
config.client_id = client_id
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#emit" do
|
34
|
+
|
35
|
+
subject { signal.emit(payload, version: version, cid: cid, key: partition_key) }
|
36
|
+
|
37
|
+
its([:meta]) { is_expected.to be_present }
|
38
|
+
its([:payload]) { is_expected.to be_present }
|
39
|
+
its([:payload, :key]) { is_expected.to eq("value") }
|
40
|
+
|
41
|
+
context "contains meta data with" do
|
42
|
+
|
43
|
+
context "when correlation ID is omitted" do
|
44
|
+
its([:meta, :cid]) { is_expected.to be_present }
|
45
|
+
end
|
46
|
+
|
47
|
+
context "when correlation ID is passed" do
|
48
|
+
let(:cid) { "cid" }
|
49
|
+
its([:meta, :cid]) { is_expected.to eq(cid) }
|
50
|
+
end
|
51
|
+
|
52
|
+
its([:meta, :producer]) { is_expected.to eq(client_id) }
|
53
|
+
its([:meta, :created_at]) { is_expected.to be_present }
|
54
|
+
its([:meta, :name]) { is_expected.to eq(name) }
|
55
|
+
its([:meta, :version]) { is_expected.to eq(version) }
|
56
|
+
its([:meta, :event_uuid]) { is_expected.to be_present }
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
context "when a key is provided" do
|
61
|
+
let(:partition_key) { SecureRandom.hex }
|
62
|
+
|
63
|
+
it "sends the message using the given key as the partition key" do
|
64
|
+
expect(signal).to receive(:send_message).once
|
65
|
+
.with(anything, Zlib.crc32(partition_key), anything)
|
66
|
+
|
67
|
+
subject
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context "when a key is not provided" do
|
72
|
+
let(:partition_key) { nil }
|
73
|
+
|
74
|
+
it "sends the message using a partition key derived from the message" do
|
75
|
+
allow(signal).to receive(:send_message).once
|
76
|
+
.with(anything, Zlib.crc32(payload.to_json), anything)
|
77
|
+
|
78
|
+
subject
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "#receive" do
|
85
|
+
after :each do
|
86
|
+
Hivent.emitter.remove_listener name
|
87
|
+
Hivent.emitter.events.clear
|
88
|
+
end
|
89
|
+
|
90
|
+
it "receives events for this signal with their payload" do
|
91
|
+
emitted_payload = { foo: "bar" }
|
92
|
+
received_payload = nil
|
93
|
+
|
94
|
+
signal.receive do |payload|
|
95
|
+
received_payload = payload
|
96
|
+
end
|
97
|
+
|
98
|
+
Hivent.emitter.emit(name, emitted_payload)
|
99
|
+
|
100
|
+
expect(received_payload).to equal(emitted_payload)
|
101
|
+
end
|
102
|
+
|
103
|
+
context "when a version is not specified" do
|
104
|
+
let(:version) { nil }
|
105
|
+
|
106
|
+
it "receives events transmitted using only the signal's name" do
|
107
|
+
counter = 0
|
108
|
+
|
109
|
+
signal.receive { counter += 1 }
|
110
|
+
|
111
|
+
expect { Hivent.emitter.emit name }.to change { counter }.by(1)
|
112
|
+
end
|
113
|
+
|
114
|
+
it "stores the event in the consumer" do
|
115
|
+
signal.receive {}
|
116
|
+
expect(Hivent.emitter.events).to include(name: name, version: version)
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
context "when a version is specified" do
|
122
|
+
let(:version) { 2 }
|
123
|
+
|
124
|
+
it "receives events transmitted using the signal's name and version" do
|
125
|
+
counter = 0
|
126
|
+
|
127
|
+
signal.receive(version: version) { counter += 1 }
|
128
|
+
|
129
|
+
expect { Hivent.emitter.emit "#{name}:#{version}" }.to change { counter }.by(1)
|
130
|
+
end
|
131
|
+
|
132
|
+
it "stores the event in the consumer" do
|
133
|
+
signal.receive(version: version) {}
|
134
|
+
expect(Hivent.emitter.events).to include(name: name, version: version)
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
context "with a wildcard signal" do
|
140
|
+
let(:signal) do
|
141
|
+
MySignal.new("*")
|
142
|
+
end
|
143
|
+
|
144
|
+
it "receives events for all signals signal with their payloads" do
|
145
|
+
received_payloads = []
|
146
|
+
|
147
|
+
signal.receive do |payload|
|
148
|
+
received_payloads << payload
|
149
|
+
end
|
150
|
+
|
151
|
+
Hivent.emitter.emit(Hivent::Emitter::WILDCARD, foo: "bar")
|
152
|
+
Hivent.emitter.emit(Hivent::Emitter::WILDCARD, foo: "baz")
|
153
|
+
|
154
|
+
expect(received_payloads).to eq([{ foo: "bar" }, { foo: "baz" }])
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|