thrift_server 0.1.1 → 1.0.0
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/Dockerfile +2 -0
- data/Makefile +27 -14
- data/README.md +142 -43
- data/circle.yml +14 -0
- data/echo_client.rb +22 -3
- data/echo_server.rb +23 -16
- data/echo_service.thrift +11 -0
- data/lib/thrift_server.rb +112 -36
- data/lib/thrift_server/instrumentation_middleware.rb +33 -0
- data/lib/thrift_server/log_subscriber.rb +60 -0
- data/lib/thrift_server/publisher.rb +25 -0
- data/lib/thrift_server/rpc_metrics_subscriber.rb +25 -0
- data/lib/thrift_server/server_metrics_subscriber.rb +32 -0
- data/lib/thrift_server/thread_pool_server.rb +115 -0
- data/lib/thrift_server/threaded_server.rb +87 -0
- data/lib/thrift_server/validation_middleware.rb +14 -0
- data/lib/thrift_server/version.rb +2 -2
- data/script/circleci/cache-image +10 -0
- data/test/log_subscriber_test.rb +86 -0
- data/test/processor_test.rb +189 -0
- data/test/rpc_metrics_subscriber_test.rb +44 -0
- data/test/server_metrics_subscriber_test.rb +51 -0
- data/test/support/log_yielder.rb +32 -0
- data/test/support/server_tests.rb +87 -0
- data/test/test_helper.rb +32 -23
- data/test/thread_pool_server_test.rb +101 -0
- data/test/threaded_server_test.rb +56 -0
- data/test/validation_middleware_test.rb +71 -0
- data/thrift_server.gemspec +2 -2
- metadata +34 -22
- data/lib/thrift_server/error_tracking_middleware.rb +0 -12
- data/lib/thrift_server/honeybadger_error_tracker.rb +0 -11
- data/lib/thrift_server/logging_middleware.rb +0 -13
- data/lib/thrift_server/metrics_middleware.rb +0 -16
- data/script/buildbox/ci +0 -5
- data/script/buildbox/step_failed +0 -5
- data/test/acceptance_test.rb +0 -225
- data/test/error_tracking_middleware_test.rb +0 -25
- data/test/honeybadger_error_tracking_test.rb +0 -12
- data/test/logging_middleware_test.rb +0 -42
- data/test/metrics_middleware_test.rb +0 -70
@@ -0,0 +1,189 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class ProcessorTest < MiniTest::Unit::TestCase
|
4
|
+
attr_reader :service
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@service = TestService
|
8
|
+
end
|
9
|
+
|
10
|
+
def wrap(service, &block)
|
11
|
+
ThriftServer.wrap service, &block
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_wrap_accepts_processor_itself
|
15
|
+
handler = stub
|
16
|
+
handler.expects(:getItems).with(:request).returns(:response)
|
17
|
+
|
18
|
+
stack = wrap(TestService::Processor).new(handler)
|
19
|
+
|
20
|
+
assert_equal :response, stack.process_getItems(:request)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_wraps_methods_defined_by_the_protocol
|
24
|
+
handler = stub
|
25
|
+
handler.expects(:getItems).with(:request).returns(:response)
|
26
|
+
|
27
|
+
stack = wrap(service).new(handler)
|
28
|
+
|
29
|
+
assert_equal :response, stack.process_getItems(:request)
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_can_add_new_middleware_after_building
|
33
|
+
handler = mock
|
34
|
+
handler.expects(:getItems).with(:modified_args)
|
35
|
+
|
36
|
+
stack = wrap(service).new(handler)
|
37
|
+
|
38
|
+
test_middleware = Class.new do
|
39
|
+
def initialize(app)
|
40
|
+
@app = app
|
41
|
+
end
|
42
|
+
|
43
|
+
def call(env)
|
44
|
+
env.args = :modified_args
|
45
|
+
@app.call env
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
stack.use test_middleware
|
50
|
+
|
51
|
+
stack.process_getItems :request
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_correctly_detects_protocol_exceptions
|
55
|
+
stack = wrap(service).new(stub)
|
56
|
+
|
57
|
+
test_middleware = Class.new do
|
58
|
+
def initialize(app)
|
59
|
+
@app = app
|
60
|
+
end
|
61
|
+
|
62
|
+
def call(rpc)
|
63
|
+
if rpc.exceptions[:getItems_test] == TestException
|
64
|
+
:ok
|
65
|
+
else
|
66
|
+
:missing_exception
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
stack.use test_middleware
|
72
|
+
|
73
|
+
assert_equal :ok, stack.process_getItems(:request)
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_correctly_detects_protocol_exceptions_from_inherited_service_in_other_namespace
|
77
|
+
stack = wrap(service).new(stub)
|
78
|
+
|
79
|
+
test_middleware = Class.new do
|
80
|
+
def initialize(app)
|
81
|
+
@app = app
|
82
|
+
end
|
83
|
+
|
84
|
+
def call(rpc)
|
85
|
+
if rpc.exceptions[:ping_test] == TestException
|
86
|
+
:ok
|
87
|
+
else
|
88
|
+
:missing_exception
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
stack.use test_middleware
|
94
|
+
|
95
|
+
assert_equal :ok, stack.process_ping(:request)
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_cannot_add_middleware_to_stack_after_first_rpc
|
99
|
+
handler = stub getItems: :response
|
100
|
+
|
101
|
+
stack = wrap(service).new(handler)
|
102
|
+
|
103
|
+
stack.process_getItems :request
|
104
|
+
|
105
|
+
ex = assert_raises RuntimeError do
|
106
|
+
stack.use ->(rpc) { rpc }
|
107
|
+
end
|
108
|
+
|
109
|
+
assert_match /frozen/, ex.to_s
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_subscribers_receive_incoming_rpcs
|
113
|
+
handler = stub
|
114
|
+
handler.expects(:getItems).with(:request).returns(:response)
|
115
|
+
|
116
|
+
subscriber = stub
|
117
|
+
processor = wrap(service).new handler
|
118
|
+
|
119
|
+
processor.subscribe subscriber
|
120
|
+
|
121
|
+
subscriber.expects(:rpc_incoming).with do |request, meta|
|
122
|
+
request.name == :getItems
|
123
|
+
end
|
124
|
+
|
125
|
+
processor.process_getItems(:request)
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_subscribers_receive_successful_rpcs
|
129
|
+
handler = stub
|
130
|
+
handler.expects(:getItems).with(:request).returns(:response)
|
131
|
+
|
132
|
+
subscriber = stub
|
133
|
+
processor = wrap(service).new handler
|
134
|
+
|
135
|
+
processor.subscribe subscriber
|
136
|
+
|
137
|
+
subscriber.expects(:rpc_ok).with do |request, response, meta|
|
138
|
+
request.name == :getItems &&
|
139
|
+
response == :response &&
|
140
|
+
meta.fetch(:latency).is_a?(Float)
|
141
|
+
end
|
142
|
+
|
143
|
+
processor.process_getItems(:request)
|
144
|
+
end
|
145
|
+
|
146
|
+
def test_subscribers_receive_rpc_errors
|
147
|
+
error = StandardError.new
|
148
|
+
|
149
|
+
handler = stub
|
150
|
+
handler.expects(:getItems).with(:request).raises(error)
|
151
|
+
|
152
|
+
subscriber = stub
|
153
|
+
processor = wrap(service).new handler
|
154
|
+
|
155
|
+
processor.subscribe subscriber
|
156
|
+
|
157
|
+
subscriber.expects(:rpc_error).with do |request, ex, meta|
|
158
|
+
request.name == :getItems &&
|
159
|
+
ex == error &&
|
160
|
+
meta.fetch(:latency).is_a?(Float)
|
161
|
+
end
|
162
|
+
|
163
|
+
assert_raises StandardError do
|
164
|
+
processor.process_getItems :request
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def test_subscribers_receive_documented_rpc_protocol_exceptions
|
169
|
+
error = TestException.new :placeholder
|
170
|
+
|
171
|
+
handler = stub
|
172
|
+
handler.expects(:getItems).with(:request).raises(error)
|
173
|
+
|
174
|
+
subscriber = stub
|
175
|
+
processor = wrap(service).new handler
|
176
|
+
|
177
|
+
processor.subscribe subscriber
|
178
|
+
|
179
|
+
subscriber.expects(:rpc_exception).with do |request, ex, meta|
|
180
|
+
request.name == :getItems &&
|
181
|
+
ex == error &&
|
182
|
+
meta.fetch(:latency).is_a?(Float)
|
183
|
+
end
|
184
|
+
|
185
|
+
assert_raises TestException do
|
186
|
+
processor.process_getItems :request
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class RpcMetricsSubscriberTest < MiniTest::Unit::TestCase
|
4
|
+
TestError = Class.new StandardError
|
5
|
+
|
6
|
+
attr_reader :subscriber, :rpc, :statsd
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@statsd = mock
|
10
|
+
@subscriber = ThriftServer::RpcMetricsSubscriber.new statsd
|
11
|
+
|
12
|
+
@rpc = ThriftServer::RPC.new :foo, :bar
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_rpc_incoming
|
16
|
+
statsd.expects(:increment).with('rpc.foo.incoming')
|
17
|
+
|
18
|
+
subscriber.rpc_incoming rpc
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_rpc_ok
|
22
|
+
statsd.expects(:increment).with('rpc.foo.success')
|
23
|
+
statsd.expects(:timing).with('rpc.foo.latency', 5)
|
24
|
+
|
25
|
+
subscriber.rpc_ok rpc, :response, latency: 5
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_rpc_exception
|
29
|
+
rpc.exceptions = { test: TestException }
|
30
|
+
|
31
|
+
statsd.expects(:increment).with('rpc.foo.exception')
|
32
|
+
statsd.expects(:increment).with('rpc.foo.exception.test')
|
33
|
+
statsd.expects(:timing).with('rpc.foo.latency', 5)
|
34
|
+
|
35
|
+
subscriber.rpc_exception rpc, TestException.new(:placeholder), latency: 5
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_rpc_error
|
39
|
+
statsd.expects(:increment).with('rpc.foo.error')
|
40
|
+
statsd.expects(:timing).with('rpc.foo.latency', 5)
|
41
|
+
|
42
|
+
subscriber.rpc_error rpc, :response, latency: 5
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class ServerMetricsSubscriberTest < MiniTest::Unit::TestCase
|
4
|
+
TestError = Class.new StandardError
|
5
|
+
|
6
|
+
attr_reader :subscriber, :rpc, :statsd
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@statsd = mock
|
10
|
+
@subscriber = ThriftServer::ServerMetricsSubscriber.new statsd
|
11
|
+
|
12
|
+
@rpc = ThriftServer::RPC.new :foo, :bar
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_server_connection_opened
|
16
|
+
statsd.expects(:gauge).with('server.connection.active', '+1')
|
17
|
+
subscriber.server_connection_opened :addr
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_server_connection_closed
|
21
|
+
statsd.expects(:gauge).with('server.connection.active', '-1')
|
22
|
+
subscriber.server_connection_closed :addr
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_rpc_incoming
|
26
|
+
statsd.expects(:increment).with('rpc.incoming')
|
27
|
+
|
28
|
+
subscriber.rpc_incoming rpc
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_rpc_ok
|
32
|
+
statsd.expects(:increment).with('rpc.success')
|
33
|
+
statsd.expects(:timing).with('rpc.latency', 5)
|
34
|
+
|
35
|
+
subscriber.rpc_ok rpc, :response, latency: 5
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_exception
|
39
|
+
statsd.expects(:increment).with('rpc.exception')
|
40
|
+
statsd.expects(:timing).with('rpc.latency', 5)
|
41
|
+
|
42
|
+
subscriber.rpc_exception rpc, :response, latency: 5
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_error
|
46
|
+
statsd.expects(:increment).with('rpc.error')
|
47
|
+
statsd.expects(:timing).with('rpc.latency', 5)
|
48
|
+
|
49
|
+
subscriber.rpc_error rpc, :response, latency: 5
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# Make it easy to assert on a string line generated through the standard lib
|
2
|
+
# progname & block syntax. This class will automatically yield the block and
|
3
|
+
# concat it the mock receives a single line. This also ensures the block
|
4
|
+
# is always executed, making sure it's free of errors
|
5
|
+
class LogYielder
|
6
|
+
include Concord.new(:log)
|
7
|
+
|
8
|
+
def info(msg)
|
9
|
+
if msg && block_given?
|
10
|
+
log.info "#{msg} #{yield}"
|
11
|
+
else
|
12
|
+
log.info msg
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def error(msg)
|
17
|
+
if msg && block_given?
|
18
|
+
log.error "#{msg} #{yield}"
|
19
|
+
else
|
20
|
+
log.error msg
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def debug(msg)
|
25
|
+
if msg && block_given?
|
26
|
+
log.debug "#{msg} #{yield}"
|
27
|
+
else
|
28
|
+
log.debug msg
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module ServerTests
|
2
|
+
def test_defaults_to_port_9090
|
3
|
+
server = build service, stub
|
4
|
+
assert_equal 9090, server.port
|
5
|
+
end
|
6
|
+
|
7
|
+
def test_accepts_port_options
|
8
|
+
server = build(service, stub, {
|
9
|
+
port: 5000
|
10
|
+
})
|
11
|
+
|
12
|
+
assert_equal 5000, server.port
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_creates_server_with_framed_transport
|
16
|
+
server = build service, stub
|
17
|
+
|
18
|
+
assert_instance_of Thrift::FramedTransportFactory, server.transport
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_uses_server_socket_transport
|
22
|
+
server = build service, stub
|
23
|
+
|
24
|
+
assert_instance_of Thrift::ServerSocket, server.server_transport
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_creates_server_with_binary_protocol
|
28
|
+
server = build service, stub
|
29
|
+
|
30
|
+
assert_instance_of Thrift::BinaryProtocolFactory, server.protocol
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_subscribers_receive_server_start
|
34
|
+
subscriber = mock
|
35
|
+
subscriber.expects(:server_start)
|
36
|
+
|
37
|
+
server = build(service, stub) do |server|
|
38
|
+
server.subscribe subscriber
|
39
|
+
end
|
40
|
+
|
41
|
+
server.start dry_run: true
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_shortcut_method_for_attaching_log_subscriber
|
45
|
+
ThriftServer::LogSubscriber.expects(:new).with(:stdout).returns(:generic)
|
46
|
+
|
47
|
+
server = build(service, stub) do |server|
|
48
|
+
server.log :stdout
|
49
|
+
end
|
50
|
+
|
51
|
+
assert_includes server.publisher, :generic
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_shortcut_method_for_attaching_metrics
|
55
|
+
ThriftServer::ServerMetricsSubscriber.expects(:new).with(:statsd).returns(:server)
|
56
|
+
ThriftServer::RpcMetricsSubscriber.expects(:new).with(:statsd).returns(:rpc)
|
57
|
+
|
58
|
+
server = build(service, stub) do |server|
|
59
|
+
server.metrics :statsd
|
60
|
+
end
|
61
|
+
|
62
|
+
assert_includes server.publisher, :server
|
63
|
+
assert_includes server.publisher, :rpc
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_attaching_subscribers_to_server
|
67
|
+
server = build(service, stub) do |server|
|
68
|
+
server.subscribe :tester
|
69
|
+
end
|
70
|
+
|
71
|
+
assert_includes server.publisher, :tester
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_adding_middleware_to_server
|
75
|
+
test_middleware = Class.new do
|
76
|
+
include Concord.new(:app)
|
77
|
+
|
78
|
+
def call(env)
|
79
|
+
app.call env
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
server = build(service, stub) do |server|
|
84
|
+
server.use test_middleware
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -7,6 +7,9 @@ require 'logger-better'
|
|
7
7
|
require 'minitest/autorun'
|
8
8
|
require 'mocha/mini_test'
|
9
9
|
|
10
|
+
require_relative 'support/server_tests'
|
11
|
+
require_relative 'support/log_yielder'
|
12
|
+
|
10
13
|
TestError = Class.new StandardError
|
11
14
|
|
12
15
|
class FakeStatsd
|
@@ -25,36 +28,42 @@ class NullErrorTracker
|
|
25
28
|
end
|
26
29
|
end
|
27
30
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
31
|
+
TestException = Class.new Thrift::Exception
|
32
|
+
|
33
|
+
class SimulatedResult
|
34
|
+
FIELDS = {
|
35
|
+
'EXCEPTION' => { name: 'test', class: TestException }
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
module GenericService
|
40
|
+
class Processor
|
41
|
+
include Thrift::Processor
|
33
42
|
|
34
|
-
|
35
|
-
|
36
|
-
# nothing to do, this is just a stub to create objects inline
|
37
|
-
# with the generate thrift processors
|
38
|
-
@handler.send method_name, *args
|
39
|
-
end
|
43
|
+
def process_ping(*args)
|
44
|
+
@handler.ping(*args)
|
40
45
|
end
|
41
46
|
end
|
42
|
-
end
|
43
47
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
+
class Ping_result
|
49
|
+
FIELDS = {
|
50
|
+
'EXCEPTION' => { name: 'ping_test', class: TestException }
|
51
|
+
}
|
48
52
|
end
|
53
|
+
end
|
49
54
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
@thread_q.max
|
54
|
-
end
|
55
|
+
module TestService
|
56
|
+
class Processor < GenericService::Processor
|
57
|
+
include Thrift::Processor
|
55
58
|
|
56
|
-
def
|
57
|
-
|
59
|
+
def process_getItems(*args)
|
60
|
+
@handler.getItems(*args)
|
58
61
|
end
|
59
62
|
end
|
63
|
+
|
64
|
+
class GetItems_result
|
65
|
+
FIELDS = {
|
66
|
+
'EXCEPTION' => { name: 'getItems_test', class: TestException }
|
67
|
+
}
|
68
|
+
end
|
60
69
|
end
|