istox_gruf 2.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +246 -0
  3. data/CODE_OF_CONDUCT.md +46 -0
  4. data/README.md +544 -0
  5. data/bin/gruf +29 -0
  6. data/lib/gruf.rb +50 -0
  7. data/lib/gruf/cli/executor.rb +99 -0
  8. data/lib/gruf/client.rb +217 -0
  9. data/lib/gruf/client/error.rb +66 -0
  10. data/lib/gruf/client/error_factory.rb +105 -0
  11. data/lib/gruf/configuration.rb +137 -0
  12. data/lib/gruf/controllers/base.rb +102 -0
  13. data/lib/gruf/controllers/request.rb +121 -0
  14. data/lib/gruf/controllers/service_binder.rb +117 -0
  15. data/lib/gruf/error.rb +230 -0
  16. data/lib/gruf/errors/debug_info.rb +56 -0
  17. data/lib/gruf/errors/field.rb +56 -0
  18. data/lib/gruf/errors/helpers.rb +44 -0
  19. data/lib/gruf/hooks/base.rb +34 -0
  20. data/lib/gruf/hooks/executor.rb +47 -0
  21. data/lib/gruf/hooks/registry.rb +159 -0
  22. data/lib/gruf/instrumentable_grpc_server.rb +64 -0
  23. data/lib/gruf/integrations/rails/railtie.rb +10 -0
  24. data/lib/gruf/interceptors/active_record/connection_reset.rb +48 -0
  25. data/lib/gruf/interceptors/authentication/basic.rb +87 -0
  26. data/lib/gruf/interceptors/base.rb +53 -0
  27. data/lib/gruf/interceptors/client_interceptor.rb +125 -0
  28. data/lib/gruf/interceptors/context.rb +56 -0
  29. data/lib/gruf/interceptors/instrumentation/output_metadata_timer.rb +61 -0
  30. data/lib/gruf/interceptors/instrumentation/request_logging/formatters/base.rb +41 -0
  31. data/lib/gruf/interceptors/instrumentation/request_logging/formatters/logstash.rb +43 -0
  32. data/lib/gruf/interceptors/instrumentation/request_logging/formatters/plain.rb +48 -0
  33. data/lib/gruf/interceptors/instrumentation/request_logging/interceptor.rb +225 -0
  34. data/lib/gruf/interceptors/instrumentation/statsd.rb +82 -0
  35. data/lib/gruf/interceptors/registry.rb +161 -0
  36. data/lib/gruf/interceptors/server_interceptor.rb +34 -0
  37. data/lib/gruf/interceptors/timer.rb +85 -0
  38. data/lib/gruf/loggable.rb +30 -0
  39. data/lib/gruf/logging.rb +53 -0
  40. data/lib/gruf/outbound/request_context.rb +71 -0
  41. data/lib/gruf/response.rb +71 -0
  42. data/lib/gruf/serializers/errors/base.rb +57 -0
  43. data/lib/gruf/serializers/errors/json.rb +43 -0
  44. data/lib/gruf/server.rb +294 -0
  45. data/lib/gruf/synchronized_client.rb +97 -0
  46. data/lib/gruf/timer.rb +78 -0
  47. data/lib/gruf/version.rb +20 -0
  48. metadata +203 -0
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ module Gruf
19
+ module Errors
20
+ ##
21
+ # Helper module for standardizing error interaction
22
+ #
23
+ module Helpers
24
+ delegate :add_field_error, :set_debug_info, :has_field_errors?, to: :error
25
+
26
+ ##
27
+ # Will issue a GRPC BadStatus exception, with a code based on the code passed.
28
+ #
29
+ # @param [Symbol] error_code The network error code that maps to gRPC status codes
30
+ # @param [Symbol] app_code The application-specific code for the error
31
+ # @param [String] message (Optional) A detail message about the error
32
+ # @param [Hash] metadata (Optional) Any metadata to inject into the trailing metadata for the response
33
+ #
34
+ def fail!(error_code, app_code = nil, message = '', metadata = {})
35
+ e = error
36
+ e.code = error_code.to_sym
37
+ e.app_code = app_code ? app_code.to_sym : e.code
38
+ e.message = message.to_s
39
+ e.metadata = metadata
40
+ e.fail!(request.active_call)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ module Gruf
19
+ module Hooks
20
+ ##
21
+ # Base class for a hook that allows execution at various points of Gruf server processes
22
+ #
23
+ class Base
24
+ include Gruf::Loggable
25
+
26
+ ##
27
+ # @param [Hash] options
28
+ #
29
+ def initialize(options: nil)
30
+ @options = options || {}
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ module Gruf
19
+ module Hooks
20
+ ##
21
+ # Base class for a hook that allows execution at various points of gRPC server processes
22
+ #
23
+ class Executor
24
+ include Gruf::Loggable
25
+
26
+ def initialize(hooks: nil)
27
+ @hooks = hooks || Gruf.hooks&.prepare || []
28
+ end
29
+
30
+ ##
31
+ # Execute a hook point for each registered hook in the registry
32
+ #
33
+ # @param [Symbol] name
34
+ # @param [Hash] arguments
35
+ #
36
+ def call(name, arguments = {})
37
+ name = name.to_sym
38
+
39
+ @hooks.each do |hook|
40
+ next unless hook.respond_to?(name)
41
+
42
+ hook.send(name, arguments)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ module Gruf
19
+ module Hooks
20
+ ##
21
+ # Handles registration of hooks
22
+ #
23
+ class Registry
24
+ class HookNotFoundError < StandardError; end
25
+
26
+ def initialize
27
+ @registry = []
28
+ end
29
+
30
+ ##
31
+ # Add a hook to the registry
32
+ #
33
+ # @param [Class] hook_class The class of the hook to add
34
+ # @param [Hash] options A hash of options to pass into the hook during initialization
35
+ #
36
+ def use(hook_class, options = {})
37
+ hooks_mutex do
38
+ @registry << {
39
+ klass: hook_class,
40
+ options: options
41
+ }
42
+ end
43
+ end
44
+
45
+ ##
46
+ # Remove a hook from the registry
47
+ #
48
+ # @param [Class] hook_class The hook class to remove
49
+ # @raise [HookNotFoundError] if the hook is not found
50
+ #
51
+ def remove(hook_class)
52
+ pos = @registry.find_index { |opts| opts.fetch(:klass, '') == hook_class }
53
+ raise HookNotFoundError if pos.nil?
54
+
55
+ @registry.delete_at(pos)
56
+ end
57
+
58
+ ##
59
+ # Insert a hook before another specified hook
60
+ #
61
+ # @param [Class] before_class The hook to insert before
62
+ # @param [Class] hook_class The class of the hook to add
63
+ # @param [Hash] options A hash of options to pass into the hook during initialization
64
+ # @raise [HookNotFoundError] if the before hook is not found
65
+ #
66
+ def insert_before(before_class, hook_class, options = {})
67
+ hooks_mutex do
68
+ pos = @registry.find_index { |opts| opts.fetch(:klass, '') == before_class }
69
+ raise HookNotFoundError if pos.nil?
70
+
71
+ @registry.insert(
72
+ pos,
73
+ klass: hook_class,
74
+ options: options
75
+ )
76
+ end
77
+ end
78
+
79
+ ##
80
+ # Insert a hook after another specified hook
81
+ #
82
+ # @param [Class] after_class The hook to insert after
83
+ # @param [Class] hook_class The class of the hook to add
84
+ # @param [Hash] options A hash of options to pass into the hook during initialization
85
+ # @raise [HookNotFoundError] if the after hook is not found
86
+ #
87
+ def insert_after(after_class, hook_class, options = {})
88
+ hooks_mutex do
89
+ pos = @registry.find_index { |opts| opts.fetch(:klass, '') == after_class }
90
+ raise HookNotFoundError if pos.nil?
91
+
92
+ @registry.insert(
93
+ (pos + 1),
94
+ klass: hook_class,
95
+ options: options
96
+ )
97
+ end
98
+ end
99
+
100
+ ##
101
+ # Return a list of the hook classes in the registry in their execution order
102
+ #
103
+ # @return [Array<Class>]
104
+ #
105
+ def list
106
+ hooks_mutex do
107
+ @registry.map { |h| h[:klass] }
108
+ end
109
+ end
110
+
111
+ ##
112
+ # Lazily load and return all hooks for the given request
113
+ #
114
+ # @return [Array<Gruf::Hooks::Base>]
115
+ #
116
+ def prepare
117
+ is = []
118
+ hooks_mutex do
119
+ @registry.each do |o|
120
+ is << o[:klass].new(options: o[:options])
121
+ end
122
+ end
123
+ is
124
+ end
125
+
126
+ ##
127
+ # Clear the registry
128
+ #
129
+ def clear
130
+ hooks_mutex do
131
+ @registry = []
132
+ end
133
+ end
134
+
135
+ ##
136
+ # @return [Integer] The number of hooks currently loaded
137
+ #
138
+ def count
139
+ hooks_mutex do
140
+ @registry ||= []
141
+ @registry.count
142
+ end
143
+ end
144
+
145
+ private
146
+
147
+ ##
148
+ # Handle mutations to the hook registry in a thread-safe manner
149
+ #
150
+ def hooks_mutex(&block)
151
+ @hooks_mutex ||= begin
152
+ require 'monitor'
153
+ Monitor.new
154
+ end
155
+ @hooks_mutex.synchronize(&block)
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gruf
4
+ ##
5
+ # A subclass of GRPC::RpcServer that can be used for enhanced monitoring
6
+ # of thread pool. Note that since we are reaching into the internals of
7
+ # GRPC::RpcServer, we need to watch the evolution of that class.
8
+ #
9
+ class InstrumentableGrpcServer < GRPC::RpcServer
10
+ ##
11
+ # Add an event_listener_proc that, if supplied, will be called
12
+ # when interesting events happen in the server.
13
+ #
14
+ def initialize(pool_size: DEFAULT_POOL_SIZE,
15
+ max_waiting_requests: DEFAULT_MAX_WAITING_REQUESTS,
16
+ poll_period: DEFAULT_POLL_PERIOD,
17
+ pool_keep_alive: Pool::DEFAULT_KEEP_ALIVE,
18
+ connect_md_proc: nil,
19
+ server_args: {},
20
+ interceptors: [],
21
+ event_listener_proc: nil)
22
+ # Call the base class initializer
23
+ super(
24
+ pool_size: pool_size,
25
+ max_waiting_requests: max_waiting_requests,
26
+ poll_period: poll_period,
27
+ pool_keep_alive: pool_keep_alive,
28
+ connect_md_proc: connect_md_proc,
29
+ server_args: server_args,
30
+ interceptors: interceptors
31
+ )
32
+
33
+ # Save event listener for later
34
+ @event_listener_proc = event_listener_proc
35
+ end
36
+
37
+ ##
38
+ # Notify the event listener of something interesting
39
+ #
40
+ def notify(event)
41
+ return if @event_listener_proc.nil? || !@event_listener_proc.respond_to?(:call)
42
+
43
+ @event_listener_proc.call(event)
44
+ end
45
+
46
+ ##
47
+ # Hook into the thread pool availability check for monitoring
48
+ #
49
+ def available?(an_rpc)
50
+ super.tap do |obj|
51
+ notify(:thread_pool_exhausted) unless obj
52
+ end
53
+ end
54
+
55
+ ##
56
+ # Hook into the method implementation check for monitoring
57
+ #
58
+ def implemented?(an_rpc)
59
+ super.tap do |obj|
60
+ notify(:unimplemented) unless obj
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gruf
4
+ module Integrations
5
+ module Rails
6
+ class Railtie < ::Rails::Railtie
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ module Gruf
19
+ module Interceptors
20
+ module ActiveRecord
21
+ ##
22
+ # Resets the ActiveRecord connection to maintain accurate connected state in the thread pool
23
+ #
24
+ class ConnectionReset < ::Gruf::Interceptors::ServerInterceptor
25
+ ##
26
+ # Reset any ActiveRecord connections after a gRPC service is called. Because of the way gRPC manages its
27
+ # connection pool, we need to ensure that this is done to properly
28
+ #
29
+ def call
30
+ ::ActiveRecord::Base.establish_connection if enabled? && !::ActiveRecord::Base.connection.active?
31
+
32
+ yield
33
+ ensure
34
+ ::ActiveRecord::Base.clear_active_connections! if enabled?
35
+ end
36
+
37
+ private
38
+
39
+ ##
40
+ # @return [Boolean] If AR is loaded, we can enable this hook safely
41
+ #
42
+ def enabled?
43
+ defined?(::ActiveRecord::Base)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ require 'base64'
19
+
20
+ module Gruf
21
+ module Interceptors
22
+ module Authentication
23
+ ##
24
+ # Handles basic authentication for gRPC requests
25
+ #
26
+ class Basic < Gruf::Interceptors::ServerInterceptor
27
+ ##
28
+ # Validate authentication
29
+ #
30
+ def call
31
+ fail!(:unauthenticated, :unauthenticated) unless bypass? || valid?
32
+ yield
33
+ end
34
+
35
+ private
36
+
37
+ ##
38
+ # @return [Boolean] If this method is in the excluded list, bypass
39
+ #
40
+ def bypass?
41
+ options.fetch(:excluded_methods, []).include?(request.method_name)
42
+ end
43
+
44
+ ##
45
+ # @return [Boolean] True if the basic authentication was valid
46
+ #
47
+ def valid?
48
+ server_credentials.any? do |cred|
49
+ username = cred.fetch(:username, '').to_s
50
+ password = cred.fetch(:password, '').to_s
51
+ if username.empty?
52
+ request_password == password
53
+ else
54
+ request_credentials == "#{username}:#{password}"
55
+ end
56
+ end
57
+ end
58
+
59
+ ##
60
+ # @return [Array<Hash>] An array of valid server credentials for this service
61
+ #
62
+ def server_credentials
63
+ options.fetch(:credentials, [])
64
+ end
65
+
66
+ ##
67
+ # @return [String] The decoded request credentials
68
+ #
69
+ def request_credentials
70
+ credentials = if request.active_call.respond_to?(:metadata)
71
+ request.active_call.metadata['authorization'].to_s
72
+ else
73
+ ''
74
+ end
75
+ Base64.decode64(credentials.to_s.gsub('Basic ', '').strip)
76
+ end
77
+
78
+ ##
79
+ # @return [String] The decoded request password
80
+ #
81
+ def request_password
82
+ @request_password ||= request_credentials.split(':').last
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end