thrift_server 0.1.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +2 -0
  3. data/Makefile +27 -14
  4. data/README.md +142 -43
  5. data/circle.yml +14 -0
  6. data/echo_client.rb +22 -3
  7. data/echo_server.rb +23 -16
  8. data/echo_service.thrift +11 -0
  9. data/lib/thrift_server.rb +112 -36
  10. data/lib/thrift_server/instrumentation_middleware.rb +33 -0
  11. data/lib/thrift_server/log_subscriber.rb +60 -0
  12. data/lib/thrift_server/publisher.rb +25 -0
  13. data/lib/thrift_server/rpc_metrics_subscriber.rb +25 -0
  14. data/lib/thrift_server/server_metrics_subscriber.rb +32 -0
  15. data/lib/thrift_server/thread_pool_server.rb +115 -0
  16. data/lib/thrift_server/threaded_server.rb +87 -0
  17. data/lib/thrift_server/validation_middleware.rb +14 -0
  18. data/lib/thrift_server/version.rb +2 -2
  19. data/script/circleci/cache-image +10 -0
  20. data/test/log_subscriber_test.rb +86 -0
  21. data/test/processor_test.rb +189 -0
  22. data/test/rpc_metrics_subscriber_test.rb +44 -0
  23. data/test/server_metrics_subscriber_test.rb +51 -0
  24. data/test/support/log_yielder.rb +32 -0
  25. data/test/support/server_tests.rb +87 -0
  26. data/test/test_helper.rb +32 -23
  27. data/test/thread_pool_server_test.rb +101 -0
  28. data/test/threaded_server_test.rb +56 -0
  29. data/test/validation_middleware_test.rb +71 -0
  30. data/thrift_server.gemspec +2 -2
  31. metadata +34 -22
  32. data/lib/thrift_server/error_tracking_middleware.rb +0 -12
  33. data/lib/thrift_server/honeybadger_error_tracker.rb +0 -11
  34. data/lib/thrift_server/logging_middleware.rb +0 -13
  35. data/lib/thrift_server/metrics_middleware.rb +0 -16
  36. data/script/buildbox/ci +0 -5
  37. data/script/buildbox/step_failed +0 -5
  38. data/test/acceptance_test.rb +0 -225
  39. data/test/error_tracking_middleware_test.rb +0 -25
  40. data/test/honeybadger_error_tracking_test.rb +0 -12
  41. data/test/logging_middleware_test.rb +0 -42
  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
@@ -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
- def Processor(*rpcs)
29
- Class.new do
30
- def initialize(handler)
31
- @handler = handler
32
- end
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
- rpcs.each do |method_name|
35
- define_method "process_#{method_name}" do |*args|
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
- # Monkey patch attr readers to test various server settings
45
- module Thrift
46
- class ServerSocket
47
- attr_reader :port
48
+ class Ping_result
49
+ FIELDS = {
50
+ 'EXCEPTION' => { name: 'ping_test', class: TestException }
51
+ }
48
52
  end
53
+ end
49
54
 
50
- class ThreadPoolServer
51
- attr_reader :server_transport, :transport_factory, :protocol_factory
52
- def threads
53
- @thread_q.max
54
- end
55
+ module TestService
56
+ class Processor < GenericService::Processor
57
+ include Thrift::Processor
55
58
 
56
- def port
57
- server_transport.port
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