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.
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