kessel-sdk 1.11.0 → 1.13.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/README.md +35 -1
- data/lib/kessel/auth.rb +187 -10
- data/lib/kessel/inventory/GUIDELINES.md +19 -0
- data/lib/kessel/inventory.rb +51 -1
- data/lib/kessel/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6a800ed3c85f59d9a3c3693b03a930d0c15c3f9469afcf7415439c1df6ce0ed5
|
|
4
|
+
data.tar.gz: 7af05d6be0c40cac48b26ce2a4e62168561b9b8291bbb320984676cc1a95385c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a2606225a968c861b8097c89f11076e111b48f2873cddcdce7784bb08e67c51df5cc7ef313686ebf4991c3d357323f8e35d751e68be2af27130aaa9faed99915
|
|
7
|
+
data.tar.gz: f9707c137cffaf0d86546da1986078baa60c887adde5e65835403e8fecd6f19ce3b781a9330c59455da3046e6cab80ef3163aea01b01cfad6e786657b5415689
|
data/README.md
CHANGED
|
@@ -97,7 +97,8 @@ discovery = fetch_oidc_discovery('https://sso.example.com/auth/realms/my-realm')
|
|
|
97
97
|
oauth = OAuth2ClientCredentials.new(
|
|
98
98
|
client_id: 'my-app',
|
|
99
99
|
client_secret: 'my-secret',
|
|
100
|
-
token_endpoint: discovery.token_endpoint
|
|
100
|
+
token_endpoint: discovery.token_endpoint,
|
|
101
|
+
retry: { max_retries: 3, base_delay: 0.5, max_delay: 2.0, jitter: :full }
|
|
101
102
|
)
|
|
102
103
|
|
|
103
104
|
# Build the client -- tokens are cached and refreshed automatically
|
|
@@ -106,6 +107,13 @@ client = KesselInventoryService::ClientBuilder.new('kessel.example.com:443')
|
|
|
106
107
|
.build
|
|
107
108
|
```
|
|
108
109
|
|
|
110
|
+
Token fetch retries apply only to the token endpoint, not OIDC discovery. By default, network/timeout errors and HTTP
|
|
111
|
+
429/5xx responses receive four total attempts (initial plus three retries), with full-jitter delay caps of 0.5, 1,
|
|
112
|
+
and 2 seconds and total added sleep below 3.5 seconds. `retry` accepts non-negative integer `max_retries` (0
|
|
113
|
+
disables retries), positive finite-second `base_delay` and `max_delay`, and `jitter: :full` or `:none`; defaults are
|
|
114
|
+
`3`, `0.5`, `2.0`, and `:full`. Invalid retry configuration raises `ArgumentError`. Permanent OAuth/configuration
|
|
115
|
+
failures are not retried. Refresh is synchronous, so a transient SSO failure may delay an RPC.
|
|
116
|
+
|
|
109
117
|
#### Custom or No Credentials
|
|
110
118
|
|
|
111
119
|
```ruby
|
|
@@ -122,6 +130,31 @@ client = KesselInventoryService::ClientBuilder.new(target)
|
|
|
122
130
|
|
|
123
131
|
Build the client **once at application startup and reuse it**. The underlying gRPC channel manages its own HTTP/2 connection pool.
|
|
124
132
|
|
|
133
|
+
### HTTP/2 Keepalive
|
|
134
|
+
|
|
135
|
+
All inventory clients now use these transport keepalive defaults, including clients created by existing builder chains:
|
|
136
|
+
|
|
137
|
+
- Ping interval: 45 seconds
|
|
138
|
+
- Ping acknowledgement timeout: 10 seconds
|
|
139
|
+
- Permit pings while there are no active calls: enabled
|
|
140
|
+
|
|
141
|
+
Override these values with the fluent `keepalive` method. Durations are finite positive real numbers in seconds and
|
|
142
|
+
are converted to integer milliseconds by truncating any fractional millisecond downward. The resulting value must be
|
|
143
|
+
between 1 and 2,147,483,647 milliseconds. Passing `nil` leaves that setting unchanged; `permit_without_calls` accepts
|
|
144
|
+
only `true` or `false` (including `false` as an explicit override).
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
client = Kessel::Inventory::V1beta2::KesselInventoryService::ClientBuilder
|
|
148
|
+
.new(ENV.fetch('KESSEL_ENDPOINT', nil))
|
|
149
|
+
.keepalive(interval: 60, timeout: 10, permit_without_calls: false)
|
|
150
|
+
.build
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The Ruby SDK sets `grpc.http2.max_pings_without_data` internally to avoid a local cap; it is not a public configuration
|
|
154
|
+
option. Before rollout, confirm that the Kessel server and any gateway enforce compatible keepalive policies
|
|
155
|
+
(RHCLOUD-51673). Keepalive is a transport-level ping mechanism, not an RPC health check and does not provide retries
|
|
156
|
+
or load-balancer guarantees. The builder still returns a gRPC stub; build it once and reuse it as usual.
|
|
157
|
+
|
|
125
158
|
### Check Permissions
|
|
126
159
|
|
|
127
160
|
```ruby
|
|
@@ -339,6 +372,7 @@ The `examples/` directory contains working examples. Set up environment variable
|
|
|
339
372
|
| `check_for_update_bulk.rb` | Bulk strongly consistent update checks |
|
|
340
373
|
| `delete_resource.rb` | Deleting resources |
|
|
341
374
|
| `fetch_workspaces.rb` | Fetching workspaces via RBAC HTTP API |
|
|
375
|
+
| `keepalive.rb` | Configuring HTTP/2 keepalive on an inventory client |
|
|
342
376
|
| `list_workspaces.rb` | Listing workspaces with auto-pagination |
|
|
343
377
|
| `report_resource.rb` | Reporting resource state |
|
|
344
378
|
| `console_principal.rb` | Building principals from `x-rh-identity` headers |
|
data/lib/kessel/auth.rb
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require 'grpc'
|
|
4
4
|
require 'kessel/version'
|
|
5
|
+
require 'socket'
|
|
6
|
+
require 'timeout'
|
|
5
7
|
|
|
6
8
|
module Kessel
|
|
7
9
|
# OpenID Connect authentication module for Kessel services.
|
|
@@ -105,6 +107,7 @@ module Kessel
|
|
|
105
107
|
#
|
|
106
108
|
# # Get current access token (automatically cached and refreshed)
|
|
107
109
|
# token = oauth.get_token
|
|
110
|
+
# rubocop:disable Metrics/ClassLength
|
|
108
111
|
class OAuth2ClientCredentials
|
|
109
112
|
include Kessel::Auth
|
|
110
113
|
|
|
@@ -113,9 +116,14 @@ module Kessel
|
|
|
113
116
|
# @param client_id [String] OIDC client identifier
|
|
114
117
|
# @param client_secret [String] OIDC client secret
|
|
115
118
|
# @param token_endpoint [String] OIDC token endpoint URL
|
|
119
|
+
# @param retry [Hash] Optional token-endpoint retry settings. Keys are
|
|
120
|
+
# `max_retries` (non-negative Integer; 0 disables retries), `base_delay`
|
|
121
|
+
# and `max_delay` (positive finite seconds), and `jitter` (`:full` or
|
|
122
|
+
# `:none`). Defaults are 3, 0.5, 2.0, and `:full`, respectively.
|
|
116
123
|
#
|
|
117
124
|
# @raise [OAuthDependencyError] if the openid_connect gem is not available
|
|
118
125
|
# @raise [OAuthAuthenticationError] if authentication fails
|
|
126
|
+
# @raise [ArgumentError] if retry settings are invalid
|
|
119
127
|
#
|
|
120
128
|
# @example
|
|
121
129
|
# oauth = OAuth2ClientCredentials.new(
|
|
@@ -123,13 +131,20 @@ module Kessel
|
|
|
123
131
|
# client_secret: 'secret',
|
|
124
132
|
# token_endpoint: 'https://my-domain/auth/realms/my-realm/protocol/openid-connect/token'
|
|
125
133
|
# )
|
|
126
|
-
def initialize(client_id:, client_secret:, token_endpoint
|
|
134
|
+
def initialize(client_id:, client_secret:, token_endpoint:, **options)
|
|
135
|
+
validate_retry_options!(options)
|
|
136
|
+
retry_config = normalize_retry_config(options.fetch(:retry, {}))
|
|
127
137
|
check_dependencies!
|
|
128
138
|
|
|
129
139
|
@client_id = client_id
|
|
130
140
|
@client_secret = client_secret
|
|
131
141
|
@token_endpoint = token_endpoint
|
|
142
|
+
@retry_config = retry_config
|
|
132
143
|
@token_mutex = Mutex.new
|
|
144
|
+
@generation_state_mutex = Mutex.new
|
|
145
|
+
@generation_users = Hash.new(0)
|
|
146
|
+
@generation_failures = {}
|
|
147
|
+
@cached_token_generation = nil
|
|
133
148
|
@generation = 0
|
|
134
149
|
end
|
|
135
150
|
|
|
@@ -147,23 +162,80 @@ module Kessel
|
|
|
147
162
|
def get_token(force_refresh: false)
|
|
148
163
|
return @cached_token if !force_refresh && token_valid?
|
|
149
164
|
|
|
150
|
-
generation =
|
|
165
|
+
generation = register_generation
|
|
151
166
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
167
|
+
begin
|
|
168
|
+
@token_mutex.synchronize do
|
|
169
|
+
failure = generation_failure(generation)
|
|
155
170
|
|
|
156
|
-
|
|
157
|
-
|
|
171
|
+
# A later successful generation may have recovered after this caller's failure.
|
|
172
|
+
return @cached_token if newer_cached_token?(generation)
|
|
173
|
+
raise failure if failure
|
|
174
|
+
|
|
175
|
+
begin
|
|
176
|
+
token = refresh
|
|
177
|
+
rescue StandardError => e
|
|
178
|
+
record_failure_and_advance(generation, e)
|
|
179
|
+
raise
|
|
180
|
+
end
|
|
181
|
+
cache_token_and_advance(token)
|
|
158
182
|
|
|
159
|
-
|
|
183
|
+
@cached_token
|
|
184
|
+
end
|
|
160
185
|
rescue StandardError => e
|
|
161
|
-
raise OAuthAuthenticationError, "Failed to obtain client credentials token: #{e.message}"
|
|
186
|
+
raise OAuthAuthenticationError, "Failed to obtain client credentials token: #{e.message}", cause: e
|
|
187
|
+
ensure
|
|
188
|
+
unregister_generation(generation)
|
|
162
189
|
end
|
|
163
190
|
end
|
|
164
191
|
|
|
165
192
|
private
|
|
166
193
|
|
|
194
|
+
def register_generation
|
|
195
|
+
@generation_state_mutex.synchronize do
|
|
196
|
+
generation = @generation
|
|
197
|
+
@generation_users[generation] += 1
|
|
198
|
+
generation
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def generation_failure(generation)
|
|
203
|
+
@generation_state_mutex.synchronize { @generation_failures[generation] }
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def record_failure_and_advance(generation, error)
|
|
207
|
+
@generation_state_mutex.synchronize do
|
|
208
|
+
@generation_failures[generation] = error
|
|
209
|
+
@generation += 1
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def advance_generation
|
|
214
|
+
@generation_state_mutex.synchronize { @generation += 1 }
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def cache_token_and_advance(token)
|
|
218
|
+
@generation_state_mutex.synchronize do
|
|
219
|
+
@cached_token = token
|
|
220
|
+
@generation += 1
|
|
221
|
+
@cached_token_generation = @generation
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def newer_cached_token?(generation)
|
|
226
|
+
!!(@cached_token_generation && @cached_token_generation > generation && token_valid?)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def unregister_generation(generation)
|
|
230
|
+
@generation_state_mutex.synchronize do
|
|
231
|
+
@generation_users[generation] -= 1
|
|
232
|
+
next unless @generation_users[generation].zero?
|
|
233
|
+
|
|
234
|
+
@generation_users.delete(generation)
|
|
235
|
+
@generation_failures.delete(generation)
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
167
239
|
def refresh
|
|
168
240
|
client = create_oidc_client
|
|
169
241
|
|
|
@@ -173,13 +245,117 @@ module Kessel
|
|
|
173
245
|
client_secret: @client_secret
|
|
174
246
|
}
|
|
175
247
|
|
|
176
|
-
token_data = client
|
|
248
|
+
token_data = access_token_with_retries(client, request_params)
|
|
177
249
|
RefreshTokenResponse.new(
|
|
178
250
|
access_token: token_data.access_token,
|
|
179
251
|
expires_at: Time.now + (token_data.expires_in || DEFAULT_EXPIRES_IN)
|
|
180
252
|
).freeze
|
|
181
253
|
end
|
|
182
254
|
|
|
255
|
+
def access_token_with_retries(client, request_params)
|
|
256
|
+
retry_index = 0
|
|
257
|
+
|
|
258
|
+
begin
|
|
259
|
+
client.access_token!(request_params)
|
|
260
|
+
rescue StandardError => e
|
|
261
|
+
raise unless retryable_token_error?(e) && retry_index < @retry_config[:max_retries]
|
|
262
|
+
|
|
263
|
+
sleep(retry_delay(retry_index))
|
|
264
|
+
retry_index += 1
|
|
265
|
+
retry
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def validate_retry_options!(options)
|
|
270
|
+
unknown_keys = options.keys - [:retry]
|
|
271
|
+
return if unknown_keys.empty?
|
|
272
|
+
|
|
273
|
+
raise ArgumentError, "unknown keyword: #{unknown_keys.first.inspect}"
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def normalize_retry_config(retry_options)
|
|
277
|
+
unless retry_options.is_a?(Hash) && retry_options.keys.all?(Symbol)
|
|
278
|
+
raise ArgumentError, 'retry must be a symbol-keyed Hash'
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
unknown_keys = retry_options.keys - %i[max_retries base_delay max_delay jitter]
|
|
282
|
+
raise ArgumentError, "unknown retry option: #{unknown_keys.first.inspect}" unless unknown_keys.empty?
|
|
283
|
+
|
|
284
|
+
config = {
|
|
285
|
+
max_retries: 3,
|
|
286
|
+
base_delay: 0.5,
|
|
287
|
+
max_delay: 2.0,
|
|
288
|
+
jitter: :full
|
|
289
|
+
}.merge(retry_options)
|
|
290
|
+
validate_retry_config_values!(config)
|
|
291
|
+
config.freeze
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def validate_retry_config_values!(config)
|
|
295
|
+
unless config[:max_retries].is_a?(Integer) && config[:max_retries] >= 0
|
|
296
|
+
raise ArgumentError, 'retry max_retries must be a non-negative Integer'
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
%i[base_delay max_delay].each do |key|
|
|
300
|
+
next if valid_delay?(config[key])
|
|
301
|
+
|
|
302
|
+
raise ArgumentError, "retry #{key} must be a positive finite Integer or Float"
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
return if %i[full none].include?(config[:jitter])
|
|
306
|
+
|
|
307
|
+
raise ArgumentError, 'retry jitter must be :full or :none'
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def valid_delay?(value)
|
|
311
|
+
(value.is_a?(Integer) || value.is_a?(Float)) && value.positive? &&
|
|
312
|
+
(!value.is_a?(Float) || value.finite?)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def retry_delay(retry_index)
|
|
316
|
+
cap = [@retry_config[:max_delay], @retry_config[:base_delay] * (2**retry_index)].min
|
|
317
|
+
return cap if @retry_config[:jitter] == :none
|
|
318
|
+
|
|
319
|
+
rand(cap.to_f)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def retryable_token_error?(error)
|
|
323
|
+
retryable_rack_oauth_error?(error) || retryable_transient_error?(error)
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def retryable_rack_oauth_error?(error)
|
|
327
|
+
return false unless defined?(::Rack::OAuth2::Client::Error)
|
|
328
|
+
return false unless error.is_a?(::Rack::OAuth2::Client::Error)
|
|
329
|
+
|
|
330
|
+
status = error.status
|
|
331
|
+
status == 429 || (status.is_a?(Integer) && status.between?(500, 599))
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def retryable_transient_error?(error)
|
|
335
|
+
transient_error_classes.any? { |error_class| error.is_a?(error_class) }
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def transient_error_classes
|
|
339
|
+
%w[
|
|
340
|
+
Faraday::ConnectionFailed
|
|
341
|
+
Faraday::TimeoutError
|
|
342
|
+
Timeout::Error
|
|
343
|
+
SocketError
|
|
344
|
+
EOFError
|
|
345
|
+
Errno::ECONNREFUSED
|
|
346
|
+
Errno::ECONNRESET
|
|
347
|
+
Errno::ETIMEDOUT
|
|
348
|
+
Errno::EHOSTUNREACH
|
|
349
|
+
Errno::ENETUNREACH
|
|
350
|
+
].map { |name| optional_error_class(name) }.compact
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def optional_error_class(name)
|
|
354
|
+
Object.const_get(name, false)
|
|
355
|
+
rescue NameError
|
|
356
|
+
nil
|
|
357
|
+
end
|
|
358
|
+
|
|
183
359
|
# Checks if we have a valid cached token.
|
|
184
360
|
#
|
|
185
361
|
# @return [Boolean] true if token exists and not expired
|
|
@@ -206,5 +382,6 @@ module Kessel
|
|
|
206
382
|
)
|
|
207
383
|
end
|
|
208
384
|
end
|
|
385
|
+
# rubocop:enable Metrics/ClassLength
|
|
209
386
|
end
|
|
210
387
|
end
|
|
@@ -59,6 +59,25 @@ client = KesselInventoryService::ClientBuilder.new(target)
|
|
|
59
59
|
|
|
60
60
|
`build` returns a single gRPC stub instance (not a tuple). The underlying gRPC channel manages its own HTTP/2 connection pool. Build once at application startup and reuse -- do not create a new stub per request.
|
|
61
61
|
|
|
62
|
+
### HTTP/2 Keepalive
|
|
63
|
+
|
|
64
|
+
Every `ClientBuilder` passes these channel arguments to its service stub, including builders that do not call `keepalive`:
|
|
65
|
+
|
|
66
|
+
- `grpc.keepalive_time_ms`: 45,000 (45 seconds)
|
|
67
|
+
- `grpc.keepalive_timeout_ms`: 10,000 (10 seconds)
|
|
68
|
+
- `grpc.keepalive_permit_without_calls`: 1 (enabled)
|
|
69
|
+
- `grpc.http2.max_pings_without_data`: 0 (internal Ruby gRPC setting; not exposed as a public option)
|
|
70
|
+
|
|
71
|
+
`keepalive(interval: nil, timeout: nil, permit_without_calls: nil)` is fluent. Duration keywords are finite positive real
|
|
72
|
+
numbers in seconds, converted to integer milliseconds by truncating fractional milliseconds downward; the result must
|
|
73
|
+
be 1 through 2,147,483,647. `nil` leaves the existing setting unchanged, and `permit_without_calls` accepts only the
|
|
74
|
+
actual boolean values `true` and `false`. Each call validates every supplied value before changing any builder state.
|
|
75
|
+
|
|
76
|
+
These are transport ping settings, not RPC health checks, retries, or load-balancer guarantees. Check server and gateway
|
|
77
|
+
keepalive enforcement compatibility before rollout (RHCLOUD-51673); in particular, the 45-second interval may be
|
|
78
|
+
affected by an infrastructure-enforced minimum. Ruby's `grpc.http2.max_pings_without_data` value is internal and is not a
|
|
79
|
+
cross-SDK configuration knob.
|
|
80
|
+
|
|
62
81
|
## Service Wiring Pattern
|
|
63
82
|
|
|
64
83
|
Every gRPC service module must follow this exact pattern:
|
data/lib/kessel/inventory.rb
CHANGED
|
@@ -14,9 +14,24 @@ module Kessel
|
|
|
14
14
|
class ClientBuilder
|
|
15
15
|
include Kessel::GRPC
|
|
16
16
|
|
|
17
|
+
MAX_KEEPALIVE_MILLISECONDS = 2_147_483_647
|
|
18
|
+
private_constant :MAX_KEEPALIVE_MILLISECONDS
|
|
19
|
+
|
|
17
20
|
def initialize(target)
|
|
18
21
|
@target = target
|
|
19
22
|
raise 'Invalid target type' if @target.nil? || !@target.is_a?(String)
|
|
23
|
+
|
|
24
|
+
@channel_args = {
|
|
25
|
+
'grpc.keepalive_time_ms' => 45_000,
|
|
26
|
+
'grpc.keepalive_timeout_ms' => 10_000,
|
|
27
|
+
'grpc.keepalive_permit_without_calls' => 1,
|
|
28
|
+
'grpc.http2.max_pings_without_data' => 0
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def keepalive(interval: nil, timeout: nil, permit_without_calls: nil)
|
|
33
|
+
@channel_args.merge!(keepalive_channel_args(interval, timeout, permit_without_calls))
|
|
34
|
+
self
|
|
20
35
|
end
|
|
21
36
|
|
|
22
37
|
def oauth2_client_authenticated(oauth2_client_credentials:, channel_credentials: nil)
|
|
@@ -52,7 +67,7 @@ module Kessel
|
|
|
52
67
|
|
|
53
68
|
credentials = @channel_credentials
|
|
54
69
|
credentials = credentials.compose(@call_credentials) unless @call_credentials.nil?
|
|
55
|
-
self.class.stub_class.new(@target, credentials)
|
|
70
|
+
self.class.stub_class.new(@target, credentials, channel_args: @channel_args.dup)
|
|
56
71
|
end
|
|
57
72
|
|
|
58
73
|
private
|
|
@@ -66,6 +81,41 @@ module Kessel
|
|
|
66
81
|
|
|
67
82
|
raise 'Invalid credential configuration: can not authenticate with insecure channel'
|
|
68
83
|
end
|
|
84
|
+
|
|
85
|
+
def keepalive_channel_args(interval, timeout, permit_without_calls)
|
|
86
|
+
channel_args = {}
|
|
87
|
+
channel_args['grpc.keepalive_time_ms'] = duration_to_milliseconds(interval, 'interval') unless interval.nil?
|
|
88
|
+
channel_args['grpc.keepalive_timeout_ms'] = duration_to_milliseconds(timeout, 'timeout') unless timeout.nil?
|
|
89
|
+
|
|
90
|
+
unless permit_without_calls.nil?
|
|
91
|
+
unless permit_without_calls.equal?(true) || permit_without_calls.equal?(false)
|
|
92
|
+
raise 'Invalid keepalive permit_without_calls: must be true, false, or nil'
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
channel_args['grpc.keepalive_permit_without_calls'] = permit_without_calls ? 1 : 0
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
channel_args
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def duration_to_milliseconds(value, name)
|
|
102
|
+
unless value.is_a?(Numeric) && !value.is_a?(Complex) && value.respond_to?(:finite?) && value.finite?
|
|
103
|
+
raise "Invalid keepalive #{name}: must be a finite real number of seconds"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
scaled_milliseconds = value * 1000
|
|
107
|
+
unless valid_scaled_keepalive_milliseconds?(scaled_milliseconds)
|
|
108
|
+
raise "Invalid keepalive #{name}: must convert to 1..#{MAX_KEEPALIVE_MILLISECONDS} milliseconds"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
scaled_milliseconds.floor
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def valid_scaled_keepalive_milliseconds?(milliseconds)
|
|
115
|
+
milliseconds.respond_to?(:finite?) &&
|
|
116
|
+
milliseconds.finite? &&
|
|
117
|
+
(1...(MAX_KEEPALIVE_MILLISECONDS + 1)).cover?(milliseconds)
|
|
118
|
+
end
|
|
69
119
|
end
|
|
70
120
|
end
|
|
71
121
|
end
|
data/lib/kessel/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kessel-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.13.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Project Kessel
|
|
@@ -314,7 +314,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
314
314
|
- !ruby/object:Gem::Version
|
|
315
315
|
version: '0'
|
|
316
316
|
requirements: []
|
|
317
|
-
rubygems_version: 4.0.
|
|
317
|
+
rubygems_version: 4.0.22
|
|
318
318
|
specification_version: 4
|
|
319
319
|
summary: Ruby SDK for Project Kessel
|
|
320
320
|
test_files: []
|