fastly_nsq 0.13.2 → 1.0.2
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 +5 -5
- data/.env +4 -0
- data/.overcommit.yml +3 -3
- data/.rubocop.yml +11 -1
- data/.travis.yml +8 -1
- data/Gemfile +9 -0
- data/README.md +52 -82
- data/Rakefile +2 -0
- data/bin/fastly_nsq +1 -0
- data/docker-compose.yml +23 -0
- data/examples/.sample.env +0 -3
- data/fastly_nsq.gemspec +7 -8
- data/lib/fastly_nsq.rb +44 -50
- data/lib/fastly_nsq/cli.rb +20 -14
- data/lib/fastly_nsq/consumer.rb +26 -30
- data/lib/fastly_nsq/feeder.rb +16 -0
- data/lib/fastly_nsq/http/nsqd.rb +7 -1
- data/lib/fastly_nsq/http/nsqlookupd.rb +1 -1
- data/lib/fastly_nsq/launcher.rb +31 -23
- data/lib/fastly_nsq/listener.rb +34 -103
- data/lib/fastly_nsq/manager.rb +48 -72
- data/lib/fastly_nsq/message.rb +2 -0
- data/lib/fastly_nsq/messenger.rb +5 -5
- data/lib/fastly_nsq/priority_queue.rb +12 -0
- data/lib/fastly_nsq/priority_thread_pool.rb +32 -0
- data/lib/fastly_nsq/producer.rb +52 -32
- data/lib/fastly_nsq/testing.rb +239 -0
- data/lib/fastly_nsq/tls_options.rb +2 -0
- data/lib/fastly_nsq/version.rb +3 -1
- data/spec/{lib/fastly_nsq/cli_spec.rb → cli_spec.rb} +2 -0
- data/spec/consumer_spec.rb +59 -0
- data/spec/fastly_nsq_spec.rb +72 -0
- data/spec/feeder_spec.rb +22 -0
- data/spec/{lib/fastly_nsq/http → http}/nsqd_spec.rb +1 -1
- data/spec/{lib/fastly_nsq/http → http}/nsqlookupd_spec.rb +1 -1
- data/spec/{lib/fastly_nsq/http_spec.rb → http_spec.rb} +3 -1
- data/spec/integration_spec.rb +48 -0
- data/spec/launcher_spec.rb +50 -0
- data/spec/listener_spec.rb +184 -0
- data/spec/manager_spec.rb +111 -0
- data/spec/matchers/delegate.rb +32 -0
- data/spec/{lib/fastly_nsq/message_spec.rb → message_spec.rb} +2 -0
- data/spec/{lib/fastly_nsq/messenger_spec.rb → messenger_spec.rb} +7 -5
- data/spec/priority_thread_pool_spec.rb +19 -0
- data/spec/producer_spec.rb +94 -0
- data/spec/spec_helper.rb +32 -28
- data/spec/support/http.rb +37 -0
- data/spec/support/webmock.rb +22 -0
- data/spec/{lib/fastly_nsq/tls_options_spec.rb → tls_options_spec.rb} +2 -0
- metadata +54 -96
- data/env_configuration_for_local_gem_tests.yml +0 -5
- data/example_config_class.rb +0 -20
- data/examples/Rakefile +0 -41
- data/lib/fastly_nsq/fake_backend.rb +0 -114
- data/lib/fastly_nsq/listener/config.rb +0 -35
- data/lib/fastly_nsq/rake_task.rb +0 -78
- data/lib/fastly_nsq/strategy.rb +0 -36
- data/spec/lib/fastly_nsq/consumer_spec.rb +0 -72
- data/spec/lib/fastly_nsq/fake_backend_spec.rb +0 -135
- data/spec/lib/fastly_nsq/fastly_nsq_spec.rb +0 -10
- data/spec/lib/fastly_nsq/launcher_spec.rb +0 -56
- data/spec/lib/fastly_nsq/listener_spec.rb +0 -213
- data/spec/lib/fastly_nsq/manager_spec.rb +0 -127
- data/spec/lib/fastly_nsq/producer_spec.rb +0 -60
- data/spec/lib/fastly_nsq/rake_task_spec.rb +0 -142
- data/spec/lib/fastly_nsq/strategy_spec.rb +0 -36
- data/spec/lib/fastly_nsq_spec.rb +0 -18
- data/spec/support/env_helpers.rb +0 -15
data/lib/fastly_nsq/message.rb
CHANGED
data/lib/fastly_nsq/messenger.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module FastlyNsq::Messenger
|
2
|
-
DEFAULT_ORIGIN = 'Unknown'
|
4
|
+
DEFAULT_ORIGIN = 'Unknown'
|
3
5
|
@originating_service = DEFAULT_ORIGIN
|
4
6
|
|
5
7
|
module_function
|
6
8
|
|
7
|
-
def deliver(message:,
|
9
|
+
def deliver(message:, topic:, originating_service: nil, meta: {})
|
8
10
|
meta[:originating_service] = originating_service || self.originating_service
|
9
11
|
|
10
12
|
payload = {
|
@@ -12,9 +14,7 @@ module FastlyNsq::Messenger
|
|
12
14
|
meta: meta,
|
13
15
|
}
|
14
16
|
|
15
|
-
producer_for(topic:
|
16
|
-
producer.write payload.to_json
|
17
|
-
end
|
17
|
+
producer_for(topic: topic) { |producer| producer.write payload.to_json }
|
18
18
|
end
|
19
19
|
|
20
20
|
def originating_service=(service)
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class FastlyNsq::PriorityThreadPool < Concurrent::ThreadPoolExecutor
|
4
|
+
alias max_threads max_length
|
5
|
+
|
6
|
+
def initialize(*)
|
7
|
+
super
|
8
|
+
|
9
|
+
@queue = FastlyNsq::PriorityQueue.new(:max)
|
10
|
+
end
|
11
|
+
|
12
|
+
# tries to enqueue task
|
13
|
+
# @return [true, false] if enqueued
|
14
|
+
#
|
15
|
+
# @!visibility private
|
16
|
+
def ns_enqueue(*args, &task)
|
17
|
+
if !ns_limited_queue? || @queue.size < @max_queue
|
18
|
+
@queue.push([task, args[1..-1]], args[0])
|
19
|
+
true
|
20
|
+
else
|
21
|
+
false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# tries to assign task to a worker, tries to get one from @ready or to create new one
|
26
|
+
# @return [true, false] if task is assigned to a worker
|
27
|
+
#
|
28
|
+
# @!visibility private
|
29
|
+
def ns_assign_worker(*args, &task)
|
30
|
+
super(args[1..-1], &task)
|
31
|
+
end
|
32
|
+
end
|
data/lib/fastly_nsq/producer.rb
CHANGED
@@ -1,41 +1,61 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
module FastlyNsq
|
4
|
-
class Producer
|
5
|
-
extend Forwardable
|
6
|
-
def_delegator :connection, :terminate
|
7
|
-
def_delegator :connection, :write
|
8
|
-
|
9
|
-
def initialize(topic:, tls_options: nil, connector: nil)
|
10
|
-
@topic = topic
|
11
|
-
@tls_options = TlsOptions.as_hash(tls_options)
|
12
|
-
@connector = connector
|
13
|
-
Timeout.timeout(5) do
|
14
|
-
sleep(0.1) until connection.connected?
|
15
|
-
end
|
16
|
-
rescue Timeout::Error => error
|
17
|
-
FastlyNsq.logger.error "Producer for #{topic} failed to connect!"
|
18
|
-
connection.terminate
|
19
|
-
raise error
|
20
|
-
end
|
1
|
+
# frozen_string_literal: true
|
21
2
|
|
22
|
-
|
3
|
+
class FastlyNsq::Producer
|
4
|
+
DEFAULT_CONNECTION_TIMEOUT = 5 # seconds
|
23
5
|
|
24
|
-
|
6
|
+
attr_reader :topic, :connect_timeout, :connection, :logger
|
25
7
|
|
26
|
-
|
27
|
-
|
28
|
-
|
8
|
+
def initialize(topic:, tls_options: nil, logger: FastlyNsq.logger, connect_timeout: DEFAULT_CONNECTION_TIMEOUT)
|
9
|
+
@topic = topic
|
10
|
+
@tls_options = FastlyNsq::TlsOptions.as_hash(tls_options)
|
11
|
+
@connect_timeout = connect_timeout
|
12
|
+
@logger = logger
|
29
13
|
|
30
|
-
|
31
|
-
|
32
|
-
|
14
|
+
connect
|
15
|
+
end
|
16
|
+
|
17
|
+
def terminate
|
18
|
+
connection.terminate
|
19
|
+
@connection = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def connected?
|
23
|
+
return false unless connection
|
24
|
+
|
25
|
+
connection.connected?
|
26
|
+
end
|
33
27
|
|
34
|
-
|
35
|
-
|
36
|
-
|
28
|
+
def write(message)
|
29
|
+
raise FastlyNsq::NotConnectedError unless connected?
|
30
|
+
connection.write message
|
31
|
+
end
|
32
|
+
|
33
|
+
def connect
|
34
|
+
lookupd = FastlyNsq.lookupd_http_addresses
|
35
|
+
|
36
|
+
@connection ||= Nsq::Producer.new(
|
37
|
+
tls_options.merge(
|
38
|
+
nsqlookupd: lookupd,
|
37
39
|
topic: topic,
|
38
|
-
|
40
|
+
),
|
41
|
+
)
|
42
|
+
|
43
|
+
timeout_args = [connect_timeout, FastlyNsq::ConnectionFailed]
|
44
|
+
|
45
|
+
if RUBY_VERSION > '2.4.0'
|
46
|
+
timeout_args << "Failed connection to #{lookupd} within #{connect_timeout} seconds"
|
39
47
|
end
|
48
|
+
|
49
|
+
Timeout.timeout(*timeout_args) { Thread.pass until connection.connected? }
|
50
|
+
|
51
|
+
true
|
52
|
+
rescue FastlyNsq::ConnectionFailed
|
53
|
+
logger.error { "Producer for #{topic} failed to connect!" }
|
54
|
+
terminate
|
55
|
+
raise
|
40
56
|
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
attr_reader :tls_options
|
41
61
|
end
|
@@ -0,0 +1,239 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FastlyNsq
|
4
|
+
class Testing
|
5
|
+
class << self
|
6
|
+
attr_accessor :__test_mode
|
7
|
+
|
8
|
+
def __set_test_mode(mode)
|
9
|
+
if block_given?
|
10
|
+
current_mode = __test_mode
|
11
|
+
begin
|
12
|
+
self.__test_mode = mode
|
13
|
+
yield
|
14
|
+
ensure
|
15
|
+
self.__test_mode = current_mode
|
16
|
+
end
|
17
|
+
else
|
18
|
+
self.__test_mode = mode
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def disable!(&block)
|
23
|
+
__set_test_mode(:disable, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def fake!(&block)
|
27
|
+
__set_test_mode(:fake, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
def inline!(&block)
|
31
|
+
__set_test_mode(:inline, &block)
|
32
|
+
end
|
33
|
+
|
34
|
+
def enabled?
|
35
|
+
__test_mode != :disable
|
36
|
+
end
|
37
|
+
|
38
|
+
def disabled?
|
39
|
+
__test_mode == :disable
|
40
|
+
end
|
41
|
+
|
42
|
+
def fake?
|
43
|
+
__test_mode == :fake
|
44
|
+
end
|
45
|
+
|
46
|
+
def inline?
|
47
|
+
__test_mode == :inline
|
48
|
+
end
|
49
|
+
|
50
|
+
def reset!
|
51
|
+
return unless enabled?
|
52
|
+
FastlyNsq::Messages.messages.clear
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
module Messages
|
58
|
+
def self.messages
|
59
|
+
@messages ||= Hash.new { |h, k| h[k] = [] }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class TestMessage
|
64
|
+
attr_reader :raw_body
|
65
|
+
|
66
|
+
def initialize(raw_body)
|
67
|
+
@raw_body = raw_body
|
68
|
+
end
|
69
|
+
|
70
|
+
def body
|
71
|
+
JSON.parse(JSON.dump(raw_body))
|
72
|
+
rescue JSON::ParserError
|
73
|
+
raw_body
|
74
|
+
end
|
75
|
+
|
76
|
+
def finish
|
77
|
+
FastlyNsq::Messages.messages.find { |_, ms| ms.delete(self) }
|
78
|
+
end
|
79
|
+
|
80
|
+
def requeue(*)
|
81
|
+
# sure
|
82
|
+
true
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
module ProducerTesting
|
87
|
+
def connected?
|
88
|
+
return super unless FastlyNsq::Testing.enabled?
|
89
|
+
@connected = true if @connected.nil?
|
90
|
+
@connected
|
91
|
+
end
|
92
|
+
|
93
|
+
def terminate
|
94
|
+
return super unless FastlyNsq::Testing.enabled?
|
95
|
+
|
96
|
+
@connected = false
|
97
|
+
end
|
98
|
+
|
99
|
+
def write(message)
|
100
|
+
return super unless FastlyNsq::Testing.enabled?
|
101
|
+
|
102
|
+
raise FastlyNsq::NotConnectedError unless connected?
|
103
|
+
|
104
|
+
test_message = TestMessage.new(message)
|
105
|
+
FastlyNsq::Messages.messages[topic] << test_message
|
106
|
+
|
107
|
+
if FastlyNsq::Testing.inline?
|
108
|
+
listener = FastlyNsq.manager.topic_listeners[topic]
|
109
|
+
return unless listener
|
110
|
+
listener.call test_message
|
111
|
+
end
|
112
|
+
|
113
|
+
true
|
114
|
+
end
|
115
|
+
|
116
|
+
def connection
|
117
|
+
return super unless FastlyNsq::Testing.enabled?
|
118
|
+
return nil unless connected?
|
119
|
+
self
|
120
|
+
end
|
121
|
+
|
122
|
+
def connect
|
123
|
+
return super unless FastlyNsq::Testing.enabled?
|
124
|
+
@connected = true
|
125
|
+
end
|
126
|
+
|
127
|
+
def messages
|
128
|
+
raise NoMethodError unless FastlyNsq::Testing.enabled?
|
129
|
+
|
130
|
+
FastlyNsq::Messages.messages[topic]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
Producer.prepend(ProducerTesting)
|
135
|
+
|
136
|
+
module ListenerTesting
|
137
|
+
module ClassMethods
|
138
|
+
def messages(topic = nil)
|
139
|
+
return FastlyNsq::Messages.messages.values.flatten unless topic
|
140
|
+
|
141
|
+
FastlyNsq::Messages.messages[topic]
|
142
|
+
end
|
143
|
+
|
144
|
+
def drain(topic = nil)
|
145
|
+
topics = topic ? [topic] : FastlyNsq::Messages.messages.keys
|
146
|
+
topics.each do |t|
|
147
|
+
messages = FastlyNsq::Messages.messages[t]
|
148
|
+
next unless messages.any?
|
149
|
+
listener = FastlyNsq.manager.topic_listeners[t]
|
150
|
+
next unless listener
|
151
|
+
|
152
|
+
messages.dup.each { |message| listener.call(message) }
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def clear
|
157
|
+
FastlyNsq::Messages.messages.clear
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def self.prepended(klass)
|
162
|
+
klass.prepend(ClassMethods)
|
163
|
+
super
|
164
|
+
end
|
165
|
+
|
166
|
+
def terminate
|
167
|
+
return super unless FastlyNsq::Testing.enabled?
|
168
|
+
|
169
|
+
@connected = false
|
170
|
+
end
|
171
|
+
|
172
|
+
def connected?
|
173
|
+
return super unless FastlyNsq::Testing.enabled?
|
174
|
+
@connected = true if @connected.nil?
|
175
|
+
|
176
|
+
@connected
|
177
|
+
end
|
178
|
+
|
179
|
+
def drain
|
180
|
+
raise NoMethodError unless FastlyNsq::Testing.enabled?
|
181
|
+
|
182
|
+
self.class.drain(topic)
|
183
|
+
end
|
184
|
+
|
185
|
+
def messages
|
186
|
+
raise NoMethodError unless FastlyNsq::Testing.enabled?
|
187
|
+
|
188
|
+
self.class.messages(topic)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
FastlyNsq::Listener.prepend(ListenerTesting)
|
193
|
+
|
194
|
+
module ConsumerTesting
|
195
|
+
module ClassMethods
|
196
|
+
def messages(topic = nil)
|
197
|
+
return FastlyNsq::Messages.messages.values.flatten unless topic
|
198
|
+
|
199
|
+
FastlyNsq::Messages.messages[topic]
|
200
|
+
end
|
201
|
+
|
202
|
+
def clear
|
203
|
+
FastlyNsq::Messages.messages.clear
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def self.prepended(klass)
|
208
|
+
klass.prepend(ClassMethods)
|
209
|
+
super
|
210
|
+
end
|
211
|
+
|
212
|
+
def terminate
|
213
|
+
FastlyNsq::Testing.enabled? || super
|
214
|
+
end
|
215
|
+
|
216
|
+
def empty?
|
217
|
+
FastlyNsq::Testing.enabled? ? messages.empty? : super
|
218
|
+
end
|
219
|
+
|
220
|
+
def size
|
221
|
+
FastlyNsq::Testing.enabled? ? messages.size : super
|
222
|
+
end
|
223
|
+
|
224
|
+
def terminated?
|
225
|
+
FastlyNsq::Testing.enabled? ? false : super
|
226
|
+
end
|
227
|
+
|
228
|
+
def messages
|
229
|
+
raise NoMethodError unless FastlyNsq::Testing.enabled?
|
230
|
+
|
231
|
+
FastlyNsq::Messages.messages[topic]
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
FastlyNsq::Consumer.prepend(ConsumerTesting)
|
236
|
+
end
|
237
|
+
|
238
|
+
# Default to fake testing
|
239
|
+
FastlyNsq::Testing.fake!
|
data/lib/fastly_nsq/version.rb
CHANGED
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe FastlyNsq::Consumer do
|
6
|
+
let!(:topic) { 'fnsq' }
|
7
|
+
let!(:channel) { 'fnsq' }
|
8
|
+
let!(:queue) { nil }
|
9
|
+
|
10
|
+
subject { described_class.new(topic: topic, channel: channel, queue: queue) }
|
11
|
+
|
12
|
+
before { reset_topic(topic, channel: channel) }
|
13
|
+
before { expect { subject }.to eventually(be_connected).within(5) }
|
14
|
+
|
15
|
+
after { subject.terminate if subject.connected? }
|
16
|
+
|
17
|
+
it { should be_connected }
|
18
|
+
|
19
|
+
it 'should terminate' do
|
20
|
+
expect { subject.terminate }.to change(subject, :connected?).to(false)
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'with a specified queue' do
|
24
|
+
let!(:queue) { Queue.new }
|
25
|
+
|
26
|
+
it 'passes #queue to Nsq::Consumer' do
|
27
|
+
message = 'foo'
|
28
|
+
|
29
|
+
FastlyNsq::Messenger.deliver(message: message, topic: topic)
|
30
|
+
|
31
|
+
expect { queue.size }.to eventually(eq 1).within(15)
|
32
|
+
fastly_message = FastlyNsq::Message.new(queue.pop)
|
33
|
+
|
34
|
+
expect(fastly_message.data).to eq(message)
|
35
|
+
fastly_message.finish
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it { should delegate(:size).to(:connection) }
|
40
|
+
it { should delegate(:terminate).to(:connection) }
|
41
|
+
it { should delegate(:pop).to(:connection) }
|
42
|
+
it { should delegate(:pop_without_blocking).to(:connection) }
|
43
|
+
|
44
|
+
describe 'with a message' do
|
45
|
+
let(:message) { 'foo' }
|
46
|
+
|
47
|
+
before { FastlyNsq::Messenger.deliver(message: message, topic: topic) }
|
48
|
+
|
49
|
+
it 'should not be empty' do
|
50
|
+
expect { subject }.to eventually(be_empty).within(15)
|
51
|
+
end
|
52
|
+
|
53
|
+
describe 'that has finished' do
|
54
|
+
before { subject.pop.finish }
|
55
|
+
|
56
|
+
it { should be_empty }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|