kessel-sdk 1.12.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 997a5d14ffc861c340630e331dfd5c34f367ed7f68b9f760363a817180c10711
4
- data.tar.gz: 6144712b5dec3a5355bc3f7e6a35953a0b4a2c2beeee56b017eebc659c03f3bc
3
+ metadata.gz: 6a800ed3c85f59d9a3c3693b03a930d0c15c3f9469afcf7415439c1df6ce0ed5
4
+ data.tar.gz: 7af05d6be0c40cac48b26ce2a4e62168561b9b8291bbb320984676cc1a95385c
5
5
  SHA512:
6
- metadata.gz: 7397b29d146033e8d2060af9e25195debd851f052b4c3fad903277bd13b25ed8a1588f5a712bc8952e18fc75315cb79bd32c3a8732c07604f0cf51d2689ba843
7
- data.tar.gz: 0c3b20634ed5d7529d66ba4b3e06c15e677eddfaf676618eaa4864205d7157a3b57d9cbab91b77de8e3adbbdbb707e255ef3055e986de6064a87b9c284b0e7dc
6
+ metadata.gz: a2606225a968c861b8097c89f11076e111b48f2873cddcdce7784bb08e67c51df5cc7ef313686ebf4991c3d357323f8e35d751e68be2af27130aaa9faed99915
7
+ data.tar.gz: f9707c137cffaf0d86546da1986078baa60c887adde5e65835403e8fecd6f19ce3b781a9330c59455da3046e6cab80ef3163aea01b01cfad6e786657b5415689
data/README.md CHANGED
@@ -130,6 +130,31 @@ client = KesselInventoryService::ClientBuilder.new(target)
130
130
 
131
131
  Build the client **once at application startup and reuse it**. The underlying gRPC channel manages its own HTTP/2 connection pool.
132
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
+
133
158
  ### Check Permissions
134
159
 
135
160
  ```ruby
@@ -347,6 +372,7 @@ The `examples/` directory contains working examples. Set up environment variable
347
372
  | `check_for_update_bulk.rb` | Bulk strongly consistent update checks |
348
373
  | `delete_resource.rb` | Deleting resources |
349
374
  | `fetch_workspaces.rb` | Fetching workspaces via RBAC HTTP API |
375
+ | `keepalive.rb` | Configuring HTTP/2 keepalive on an inventory client |
350
376
  | `list_workspaces.rb` | Listing workspaces with auto-pagination |
351
377
  | `report_resource.rb` | Reporting resource state |
352
378
  | `console_principal.rb` | Building principals from `x-rh-identity` headers |
@@ -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:
@@ -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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Kessel
4
4
  module Inventory
5
- VERSION = '1.12.0'
5
+ VERSION = '1.13.0'
6
6
  end
7
7
  end
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.12.0
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.7
317
+ rubygems_version: 4.0.22
318
318
  specification_version: 4
319
319
  summary: Ruby SDK for Project Kessel
320
320
  test_files: []