nonnative 3.11.0 → 3.14.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/.circleci/config.yml +2 -2
- data/AGENTS.md +26 -0
- data/Gemfile.lock +1 -1
- data/README.md +51 -9
- data/lib/nonnative/configuration.rb +3 -3
- data/lib/nonnative/configuration_process.rb +13 -4
- data/lib/nonnative/configuration_proxy.rb +2 -2
- data/lib/nonnative/configuration_readiness.rb +35 -5
- data/lib/nonnative/configuration_service.rb +24 -0
- data/lib/nonnative/configuration_service_readiness.rb +48 -0
- data/lib/nonnative/cucumber.rb +1 -0
- data/lib/nonnative/fault_injection_proxy.rb +12 -0
- data/lib/nonnative/grpc_health.rb +59 -0
- data/lib/nonnative/grpc_probe.rb +49 -0
- data/lib/nonnative/http_probe.rb +4 -3
- data/lib/nonnative/pool.rb +19 -1
- data/lib/nonnative/ports.rb +18 -3
- data/lib/nonnative/service.rb +14 -0
- data/lib/nonnative/socket_pair.rb +1 -0
- data/lib/nonnative/socket_pair_factory.rb +5 -1
- data/lib/nonnative/tcp_probe.rb +32 -0
- data/lib/nonnative/timeout_socket_pair.rb +30 -0
- data/lib/nonnative/version.rb +1 -1
- data/lib/nonnative.rb +18 -5
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 802f0ee020ba8e7c6cef867895d30c6491ace313fec8103aa6b99726c8402666
|
|
4
|
+
data.tar.gz: 70d522685a3c9cfbc872ee36549d9ba8274be60138a79db68e64c83115fd7cb5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a0b3e8502437cfd6d3a556a8a6f10901a2a89d85a4689fbad3c68d6819e03b4cccf5a51de3cfd66b86827d66f768891f68ee41c91b3ea504345c7f25d22178c8
|
|
7
|
+
data.tar.gz: b16ef6cfe6099e9a62be954178c71a743fb297ece661be2651239c8501707af30e1609e58abfe99bc58d007777a45c99505eb374484c0ba9788e886d0b876e42
|
data/.circleci/config.yml
CHANGED
|
@@ -14,10 +14,10 @@ commands:
|
|
|
14
14
|
executors:
|
|
15
15
|
ruby:
|
|
16
16
|
docker:
|
|
17
|
-
- image: alexfalkowski/ruby:3.
|
|
17
|
+
- image: alexfalkowski/ruby:3.19
|
|
18
18
|
release:
|
|
19
19
|
docker:
|
|
20
|
-
- image: alexfalkowski/release:8.
|
|
20
|
+
- image: alexfalkowski/release:8.20
|
|
21
21
|
alpine:
|
|
22
22
|
docker:
|
|
23
23
|
- image: alpine:latest
|
data/AGENTS.md
CHANGED
|
@@ -79,6 +79,32 @@ of dependencies.
|
|
|
79
79
|
thread. Do not flag the listener `join` as an unbounded reliability gap unless
|
|
80
80
|
the task is explicitly about proxy shutdown behavior or there is a normal-use
|
|
81
81
|
reproducer, failing CI evidence, or platform-specific hang evidence.
|
|
82
|
+
- Service readiness is intentionally TCP-only for externally managed
|
|
83
|
+
dependencies. Do not flag missing HTTP/gRPC service readiness as a feature gap
|
|
84
|
+
unless the task is explicitly about changing the `Nonnative::Service`
|
|
85
|
+
readiness model. HTTP/gRPC readiness belongs to managed processes, where the
|
|
86
|
+
user explicitly models the application health endpoints.
|
|
87
|
+
- Process runners intentionally inherit the parent working directory. Nonnative
|
|
88
|
+
is normally run from the test folder that owns `test/nonnative.yml`, relative
|
|
89
|
+
config paths, logs, and reports. Do not flag missing process cwd/chdir support
|
|
90
|
+
as a feature gap unless the task is explicitly about changing process working
|
|
91
|
+
directory behavior.
|
|
92
|
+
- Message-specific start/stop assertions and attempted-stop success assertions
|
|
93
|
+
in `features/step_definitions/lifecycle_steps.rb` are repository-local
|
|
94
|
+
Cucumber helpers. Do not flag their absence from `lib/nonnative/cucumber.rb`
|
|
95
|
+
as a feature gap unless downstream usage evidence exists or the task is
|
|
96
|
+
explicitly about expanding public lifecycle assertion steps.
|
|
97
|
+
- Liveness, readiness, and metrics endpoint assertion steps in
|
|
98
|
+
`features/step_definitions/servers_steps.rb` are repository-local Cucumber
|
|
99
|
+
helpers for testing nonnative's own framework assumptions. Do not flag their
|
|
100
|
+
absence from `lib/nonnative/cucumber.rb` as a feature gap unless downstream
|
|
101
|
+
usage evidence exists or the task is explicitly about expanding public
|
|
102
|
+
observability assertion steps.
|
|
103
|
+
- `Nonnative::Configuration` intentionally exposes `process_by_name` without
|
|
104
|
+
matching `server_by_name` or `service_by_name` helpers. Do not flag missing
|
|
105
|
+
server/service configuration lookup helpers as a feature gap unless downstream
|
|
106
|
+
usage evidence exists or the task is explicitly about changing the
|
|
107
|
+
pre-start configuration lookup API.
|
|
82
108
|
|
|
83
109
|
## Runtime Model
|
|
84
110
|
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -11,7 +11,7 @@ It helps you:
|
|
|
11
11
|
- start **OS processes** (e.g. your Go/Java/Rust service binary),
|
|
12
12
|
- start **in-process Ruby servers** (e.g. small HTTP/TCP/gRPC fakes for dependencies),
|
|
13
13
|
- optionally start **service proxies** for fault-injection in front of externally managed dependencies,
|
|
14
|
-
- wait for readiness/shutdown using **TCP port checks
|
|
14
|
+
- wait for readiness/shutdown using **TCP port checks**, optional process HTTP/gRPC checks, and optional service TCP checks.
|
|
15
15
|
|
|
16
16
|
Once started, you can test however you like (TCP, HTTP, gRPC, etc).
|
|
17
17
|
|
|
@@ -79,15 +79,20 @@ Process/server fields:
|
|
|
79
79
|
- `log`: per-runner log file used by process output redirection or server implementations.
|
|
80
80
|
|
|
81
81
|
Process-only fields:
|
|
82
|
-
- `readiness`: optional
|
|
82
|
+
- `readiness`: optional list of startup readiness checks. Supported kinds are `http` and `grpc`.
|
|
83
|
+
HTTP checks require explicit `port` and path-only `path`. gRPC checks require explicit `port` and
|
|
84
|
+
health `service`.
|
|
83
85
|
|
|
84
86
|
Service fields:
|
|
85
|
-
- `port`: client-facing service port.
|
|
87
|
+
- `port`: client-facing service port.
|
|
88
|
+
- `timeout`: max time (seconds) for opt-in service readiness checks. Defaults to `1.0`.
|
|
89
|
+
- `readiness`: optional list of startup readiness checks. Supported kind is `tcp`, which requires
|
|
90
|
+
explicit `host` and `port`.
|
|
86
91
|
|
|
87
|
-
Nonnative readiness and shutdown checks are TCP port checks by default. Configure process/server ports that are dedicated to the test run; if another process is already listening on the same endpoint, results are undefined. Processes can also opt into
|
|
92
|
+
Nonnative readiness and shutdown checks are TCP port checks by default. Configure process/server ports that are dedicated to the test run; if another process is already listening on the same endpoint, results are undefined. Processes can also opt into HTTP and gRPC readiness checks that run after TCP readiness succeeds. Services do not get automatic TCP readiness/shutdown checks, but can opt into TCP startup readiness for externally managed dependencies. HTTP readiness paths must be path-only values, such as `/test/readyz`; absolute URLs and scheme-relative URLs are rejected.
|
|
88
93
|
|
|
89
94
|
> [!WARNING]
|
|
90
|
-
> TCP readiness and shutdown checks only prove that a TCP port opened or closed. HTTP readiness
|
|
95
|
+
> TCP readiness and shutdown checks only prove that a TCP port opened or closed. HTTP and gRPC readiness are process-only. Service readiness is TCP-only and should target the dependency endpoint that must be reachable before managed servers/processes start.
|
|
91
96
|
|
|
92
97
|
Start and stop Nonnative around the test scope that should own the configured runners:
|
|
93
98
|
|
|
@@ -162,6 +167,19 @@ response = Nonnative.observability.health(
|
|
|
162
167
|
expect(response.code).to eq(200)
|
|
163
168
|
```
|
|
164
169
|
|
|
170
|
+
`Nonnative.grpc_health` is a helper for the standard gRPC health checking protocol:
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
health = Nonnative.grpc_health(
|
|
174
|
+
host: '127.0.0.1',
|
|
175
|
+
port: 12_322,
|
|
176
|
+
service: 'example.v1.ExampleService',
|
|
177
|
+
timeout: 2
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
expect(health.serving?).to eq(true)
|
|
181
|
+
```
|
|
182
|
+
|
|
165
183
|
### 🔁 Lifecycle strategies (Cucumber integration)
|
|
166
184
|
|
|
167
185
|
Nonnative ships Cucumber hooks (when loaded) that support these tags/strategies:
|
|
@@ -210,7 +228,10 @@ Nonnative.configure do |config|
|
|
|
210
228
|
p.ports = [12_321]
|
|
211
229
|
p.log = '12_321.log'
|
|
212
230
|
p.signal = 'INT' # Possible values are described in Signal.list.keys.
|
|
213
|
-
p.readiness =
|
|
231
|
+
p.readiness = [
|
|
232
|
+
{ kind: 'http', port: 12_321, path: '/test/readyz' },
|
|
233
|
+
{ kind: 'grpc', port: 12_322, service: 'example.v1.ExampleService' }
|
|
234
|
+
]
|
|
214
235
|
p.environment = { # Pass environment variables to process.
|
|
215
236
|
'TEST' => 'true'
|
|
216
237
|
}
|
|
@@ -247,8 +268,12 @@ processes:
|
|
|
247
268
|
log: 12_321.log
|
|
248
269
|
signal: INT # Possible values are described in Signal.list.keys.
|
|
249
270
|
readiness:
|
|
250
|
-
|
|
251
|
-
|
|
271
|
+
- kind: http
|
|
272
|
+
port: 12321
|
|
273
|
+
path: /test/readyz
|
|
274
|
+
- kind: grpc
|
|
275
|
+
port: 12322
|
|
276
|
+
service: example.v1.ExampleService
|
|
252
277
|
environment: # Pass environment variables to process.
|
|
253
278
|
TEST: true
|
|
254
279
|
-
|
|
@@ -593,7 +618,7 @@ end
|
|
|
593
618
|
|
|
594
619
|
A service is an external dependency to your system that you **do not** want Nonnative to start (no OS process, no Ruby thread).
|
|
595
620
|
|
|
596
|
-
Services do not get process lifecycle management or TCP readiness/shutdown checks from Nonnative. They provide a named endpoint for a dependency that another tool already manages.
|
|
621
|
+
Services do not get process lifecycle management or automatic TCP readiness/shutdown checks from Nonnative. They provide a named endpoint for a dependency that another tool already manages, and can opt into TCP startup readiness when the dependency must be reachable before managed servers/processes start.
|
|
597
622
|
|
|
598
623
|
Set it up programmatically:
|
|
599
624
|
|
|
@@ -610,6 +635,8 @@ Nonnative.configure do |config|
|
|
|
610
635
|
s.name = 'postgres'
|
|
611
636
|
s.host = '127.0.0.1'
|
|
612
637
|
s.port = 5432
|
|
638
|
+
s.timeout = 5
|
|
639
|
+
s.readiness = [{ kind: 'tcp', host: '127.0.0.1', port: 5432 }]
|
|
613
640
|
end
|
|
614
641
|
|
|
615
642
|
config.service do |s|
|
|
@@ -632,6 +659,11 @@ services:
|
|
|
632
659
|
name: postgres
|
|
633
660
|
host: 127.0.0.1
|
|
634
661
|
port: 5432
|
|
662
|
+
timeout: 5
|
|
663
|
+
readiness:
|
|
664
|
+
- kind: tcp
|
|
665
|
+
host: 127.0.0.1
|
|
666
|
+
port: 5432
|
|
635
667
|
-
|
|
636
668
|
name: redis
|
|
637
669
|
host: 127.0.0.1
|
|
@@ -665,6 +697,7 @@ Nonnative.proxies['custom'] = CustomProxy
|
|
|
665
697
|
```
|
|
666
698
|
|
|
667
699
|
Only services support proxies. For `fault_injection`, keep the service `host`/`port` as the client-facing proxy endpoint and use nested `proxy.host`/`proxy.port` for the upstream target behind the proxy.
|
|
700
|
+
When service readiness is configured for a proxied dependency, set the readiness `host`/`port` to the upstream dependency, not the client-facing proxy listener.
|
|
668
701
|
|
|
669
702
|
##### 🧩 Service Proxies
|
|
670
703
|
|
|
@@ -688,6 +721,8 @@ config.service do |s|
|
|
|
688
721
|
delay: 5
|
|
689
722
|
}
|
|
690
723
|
}
|
|
724
|
+
|
|
725
|
+
s.readiness = [{ kind: 'tcp', host: '127.0.0.1', port: 6379 }]
|
|
691
726
|
end
|
|
692
727
|
```
|
|
693
728
|
|
|
@@ -701,6 +736,10 @@ services:
|
|
|
701
736
|
name: redis
|
|
702
737
|
host: 127.0.0.1
|
|
703
738
|
port: 16379
|
|
739
|
+
readiness:
|
|
740
|
+
- kind: tcp
|
|
741
|
+
host: 127.0.0.1
|
|
742
|
+
port: 6379
|
|
704
743
|
proxy:
|
|
705
744
|
kind: fault_injection
|
|
706
745
|
host: 127.0.0.1
|
|
@@ -719,6 +758,7 @@ Clients connect to the service `host`/`port`, while the proxy forwards traffic t
|
|
|
719
758
|
|
|
720
759
|
- `close_all` - Closes the socket as soon as it connects.
|
|
721
760
|
- `delay` - Delays traffic on the connection. Defaults to 2 seconds and can be configured through options.
|
|
761
|
+
- `timeout` - Accepts the connection and stalls traffic until reset or stop closes the connection, so clients exercise their own read timeout behavior.
|
|
722
762
|
- `invalid_data` - Forwards client requests unchanged, then corrupts upstream responses before they reach the client.
|
|
723
763
|
|
|
724
764
|
###### 🧩 Fault Injection Services
|
|
@@ -730,6 +770,7 @@ name = 'name of service in configuration'
|
|
|
730
770
|
service = Nonnative.pool.service_by_name(name)
|
|
731
771
|
|
|
732
772
|
service.proxy.close_all # To use close_all.
|
|
773
|
+
service.proxy.timeout # To stall traffic until reset or stop.
|
|
733
774
|
service.proxy.reset # To reset it back to a good state.
|
|
734
775
|
```
|
|
735
776
|
|
|
@@ -737,6 +778,7 @@ Use the Cucumber proxy steps:
|
|
|
737
778
|
|
|
738
779
|
```cucumber
|
|
739
780
|
Given I set the proxy for service 'service_1' to 'close_all'
|
|
781
|
+
Given I set the proxy for service 'service_1' to 'timeout'
|
|
740
782
|
Then I should reset the proxy for service 'service_1'
|
|
741
783
|
```
|
|
742
784
|
|
|
@@ -22,7 +22,7 @@ module Nonnative
|
|
|
22
22
|
# p.ports = [8080, 9090]
|
|
23
23
|
# p.timeout = 10
|
|
24
24
|
# p.log = 'api.log'
|
|
25
|
-
# p.readiness = { port: 8080, path: '/example/readyz' }
|
|
25
|
+
# p.readiness = [{ kind: 'http', port: 8080, path: '/example/readyz' }]
|
|
26
26
|
# end
|
|
27
27
|
# end
|
|
28
28
|
#
|
|
@@ -177,11 +177,11 @@ module Nonnative
|
|
|
177
177
|
def add_services(cfg)
|
|
178
178
|
services = cfg.services || []
|
|
179
179
|
services.each do |loaded_service|
|
|
180
|
-
reject_readiness(loaded_service, 'services')
|
|
181
|
-
|
|
182
180
|
service do |service_config|
|
|
183
181
|
service_config.name = loaded_service.name
|
|
184
182
|
service_config.host = loaded_service.host if loaded_service.host
|
|
183
|
+
service_config.timeout = loaded_service.timeout if loaded_service.timeout
|
|
184
|
+
service_config.readiness = loaded_service.readiness if loaded_service.readiness
|
|
185
185
|
assign_service_port(service_config, loaded_service)
|
|
186
186
|
|
|
187
187
|
assign_proxy(service_config, loaded_service.proxy)
|
|
@@ -28,7 +28,7 @@ module Nonnative
|
|
|
28
28
|
# @return [Hash, nil] environment variables to pass to the spawned process
|
|
29
29
|
attr_accessor :environment
|
|
30
30
|
|
|
31
|
-
# @return [Nonnative::ConfigurationReadiness
|
|
31
|
+
# @return [Array<Nonnative::ConfigurationReadiness>] optional readiness checks
|
|
32
32
|
attr_reader :readiness
|
|
33
33
|
|
|
34
34
|
# Creates a process configuration with bounded lifecycle defaults.
|
|
@@ -41,14 +41,23 @@ module Nonnative
|
|
|
41
41
|
super
|
|
42
42
|
|
|
43
43
|
self.timeout = DEFAULT_TIMEOUT
|
|
44
|
+
self.readiness = []
|
|
44
45
|
end
|
|
45
46
|
|
|
46
|
-
# Sets optional
|
|
47
|
+
# Sets optional process readiness checks.
|
|
47
48
|
#
|
|
48
|
-
# @param value [Hash, #to_h
|
|
49
|
+
# @param value [Array<Hash, #to_h>, nil] readiness checks with required `kind` and `port`
|
|
49
50
|
# @return [void]
|
|
50
51
|
def readiness=(value)
|
|
51
|
-
@readiness = value.nil? ?
|
|
52
|
+
@readiness = value.nil? ? [] : build_readiness(value)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def build_readiness(value)
|
|
58
|
+
raise ArgumentError, 'Process readiness must be a list of checks' unless value.is_a?(Array)
|
|
59
|
+
|
|
60
|
+
value.map { |check| Nonnative::ConfigurationReadiness.new(check) }
|
|
52
61
|
end
|
|
53
62
|
end
|
|
54
63
|
end
|
|
@@ -4,8 +4,8 @@ module Nonnative
|
|
|
4
4
|
# Proxy configuration attached to a service configuration.
|
|
5
5
|
#
|
|
6
6
|
# A proxy allows you to interpose behavior between a client and a real service. For example,
|
|
7
|
-
# the built-in `"fault_injection"` proxy can close connections, introduce delays,
|
|
8
|
-
# for resilience testing.
|
|
7
|
+
# the built-in `"fault_injection"` proxy can close connections, introduce delays, stall traffic,
|
|
8
|
+
# or corrupt data for resilience testing.
|
|
9
9
|
#
|
|
10
10
|
# This object is created automatically for each service via {Nonnative::ConfigurationService}.
|
|
11
11
|
# When `kind` is set to `"none"`, no proxy is started and the service will use its configured
|
|
@@ -1,26 +1,44 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Nonnative
|
|
4
|
-
#
|
|
4
|
+
# Readiness check configuration for a managed process.
|
|
5
5
|
#
|
|
6
|
-
# Readiness is optional. When present,
|
|
7
|
-
#
|
|
6
|
+
# Readiness is optional. When present, each check declares a `kind` and explicit endpoint details
|
|
7
|
+
# to poll after TCP readiness succeeds.
|
|
8
8
|
class ConfigurationReadiness
|
|
9
|
-
|
|
9
|
+
KINDS = %w[http grpc].freeze
|
|
10
|
+
|
|
11
|
+
# @return [String] readiness check kind
|
|
12
|
+
attr_accessor :kind
|
|
13
|
+
|
|
14
|
+
# @return [Integer] process readiness port
|
|
10
15
|
attr_accessor :port
|
|
11
16
|
|
|
12
17
|
# @return [String] path-only HTTP readiness path
|
|
13
18
|
attr_accessor :path
|
|
14
19
|
|
|
15
|
-
# @
|
|
20
|
+
# @return [String] gRPC health service name
|
|
21
|
+
attr_accessor :service
|
|
22
|
+
|
|
23
|
+
# @param value [Hash, #to_h] readiness check attributes
|
|
16
24
|
def initialize(value)
|
|
17
25
|
attributes = value.respond_to?(:to_h) ? value.to_h : value
|
|
26
|
+
self.kind = attribute(attributes, :kind)&.to_s
|
|
18
27
|
self.port = attribute(attributes, :port)
|
|
19
28
|
self.path = attribute(attributes, :path)
|
|
29
|
+
self.service = attribute(attributes, :service)
|
|
20
30
|
|
|
21
31
|
validate!
|
|
22
32
|
end
|
|
23
33
|
|
|
34
|
+
def http?
|
|
35
|
+
kind == 'http'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def grpc?
|
|
39
|
+
kind == 'grpc'
|
|
40
|
+
end
|
|
41
|
+
|
|
24
42
|
private
|
|
25
43
|
|
|
26
44
|
def attribute(attributes, name)
|
|
@@ -28,11 +46,23 @@ module Nonnative
|
|
|
28
46
|
end
|
|
29
47
|
|
|
30
48
|
def validate!
|
|
49
|
+
raise ArgumentError, "Process readiness requires 'kind'" if kind.nil?
|
|
50
|
+
raise ArgumentError, "Process readiness kind must be one of: #{KINDS.join(', ')}" unless KINDS.include?(kind)
|
|
31
51
|
raise ArgumentError, "Process readiness requires 'port'" if port.nil?
|
|
52
|
+
|
|
53
|
+
validate_http! if http?
|
|
54
|
+
validate_grpc! if grpc?
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def validate_http!
|
|
32
58
|
raise ArgumentError, "Process readiness requires 'path'" if path.nil?
|
|
33
59
|
raise ArgumentError, 'Process readiness path must be path-only' unless path_only?
|
|
34
60
|
end
|
|
35
61
|
|
|
62
|
+
def validate_grpc!
|
|
63
|
+
raise ArgumentError, "Process readiness requires 'service'" if service.nil?
|
|
64
|
+
end
|
|
65
|
+
|
|
36
66
|
def path_only?
|
|
37
67
|
uri = URI.parse(path.to_s)
|
|
38
68
|
|
|
@@ -14,11 +14,17 @@ module Nonnative
|
|
|
14
14
|
# @return [Integer] client-facing port used by the service proxy
|
|
15
15
|
attr_accessor :port
|
|
16
16
|
|
|
17
|
+
# @return [Numeric] readiness timeout (seconds) used when waiting for service readiness
|
|
18
|
+
attr_accessor :timeout
|
|
19
|
+
|
|
17
20
|
# Proxy configuration for this service.
|
|
18
21
|
#
|
|
19
22
|
# @return [Nonnative::ConfigurationProxy]
|
|
20
23
|
attr_reader :proxy
|
|
21
24
|
|
|
25
|
+
# @return [Array<Nonnative::ConfigurationServiceReadiness>] optional service readiness checks
|
|
26
|
+
attr_reader :readiness
|
|
27
|
+
|
|
22
28
|
# Creates a service configuration with defaults.
|
|
23
29
|
#
|
|
24
30
|
# @return [void]
|
|
@@ -26,9 +32,19 @@ module Nonnative
|
|
|
26
32
|
super
|
|
27
33
|
|
|
28
34
|
self.port = 0
|
|
35
|
+
self.timeout = DEFAULT_TIMEOUT
|
|
36
|
+
@readiness = []
|
|
29
37
|
@proxy = Nonnative::ConfigurationProxy.new
|
|
30
38
|
end
|
|
31
39
|
|
|
40
|
+
# Sets optional service readiness checks.
|
|
41
|
+
#
|
|
42
|
+
# @param value [Array<Hash, #to_h>, nil] readiness checks with required `kind`, `host`, and `port`
|
|
43
|
+
# @return [void]
|
|
44
|
+
def readiness=(value)
|
|
45
|
+
@readiness = value.nil? ? [] : build_readiness(value)
|
|
46
|
+
end
|
|
47
|
+
|
|
32
48
|
# Sets proxy configuration using a hash-like value.
|
|
33
49
|
#
|
|
34
50
|
# This is primarily used when loading YAML configuration files, where proxy attributes are
|
|
@@ -67,5 +83,13 @@ module Nonnative
|
|
|
67
83
|
def ports=(_value)
|
|
68
84
|
raise ArgumentError, "Use 'port' instead of 'ports' for service '#{name}'"
|
|
69
85
|
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def build_readiness(value)
|
|
90
|
+
raise ArgumentError, 'Service readiness must be a list of checks' unless value.is_a?(Array)
|
|
91
|
+
|
|
92
|
+
value.map { |check| Nonnative::ConfigurationServiceReadiness.new(check) }
|
|
93
|
+
end
|
|
70
94
|
end
|
|
71
95
|
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nonnative
|
|
4
|
+
# Service readiness check configuration.
|
|
5
|
+
#
|
|
6
|
+
# Service readiness is for externally managed dependencies. The TCP target should be the dependency
|
|
7
|
+
# endpoint that must be reachable before managed servers and processes start.
|
|
8
|
+
class ConfigurationServiceReadiness
|
|
9
|
+
KINDS = %w[tcp].freeze
|
|
10
|
+
|
|
11
|
+
# @return [String] readiness check kind
|
|
12
|
+
attr_reader :kind
|
|
13
|
+
|
|
14
|
+
# @return [String] readiness target host
|
|
15
|
+
attr_reader :host
|
|
16
|
+
|
|
17
|
+
# @return [Integer] readiness target port
|
|
18
|
+
attr_reader :port
|
|
19
|
+
|
|
20
|
+
# @param value [Hash, #to_h] readiness check attributes
|
|
21
|
+
# @return [void]
|
|
22
|
+
def initialize(value)
|
|
23
|
+
attributes = value.to_h.transform_keys(&:to_sym)
|
|
24
|
+
|
|
25
|
+
@kind = attributes[:kind]&.to_s
|
|
26
|
+
@host = attributes[:host]
|
|
27
|
+
@port = attributes[:port]
|
|
28
|
+
|
|
29
|
+
validate!
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Returns whether this is a TCP readiness check.
|
|
33
|
+
#
|
|
34
|
+
# @return [Boolean]
|
|
35
|
+
def tcp?
|
|
36
|
+
kind == 'tcp'
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def validate!
|
|
42
|
+
raise ArgumentError, "Service readiness requires 'kind'" if kind.nil?
|
|
43
|
+
raise ArgumentError, "Service readiness kind must be one of: #{KINDS.join(', ')}" unless KINDS.include?(kind)
|
|
44
|
+
raise ArgumentError, "Service readiness requires 'host'" if host.nil?
|
|
45
|
+
raise ArgumentError, "Service readiness requires 'port'" if port.nil?
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/nonnative/cucumber.rb
CHANGED
|
@@ -11,6 +11,7 @@ module Nonnative
|
|
|
11
11
|
#
|
|
12
12
|
# - {#close_all}: close connections immediately on accept
|
|
13
13
|
# - {#delay}: delay reads by a configured duration (default: 2 seconds)
|
|
14
|
+
# - {#timeout}: accept connections and keep them silent until clients time out
|
|
14
15
|
# - {#invalid_data}: forward requests unchanged and mutate upstream responses before they reach clients
|
|
15
16
|
# - {#reset}: return to healthy pass-through behavior
|
|
16
17
|
#
|
|
@@ -107,6 +108,17 @@ module Nonnative
|
|
|
107
108
|
apply_state :delay
|
|
108
109
|
end
|
|
109
110
|
|
|
111
|
+
# Accepts connections and stalls without forwarding bytes.
|
|
112
|
+
#
|
|
113
|
+
# This simulates a dependency that accepts a TCP connection but leaves clients waiting until
|
|
114
|
+
# their own read timeout fires. The proxy keeps the connection silent until reset or stop closes
|
|
115
|
+
# active connections.
|
|
116
|
+
#
|
|
117
|
+
# @return [void]
|
|
118
|
+
def timeout
|
|
119
|
+
apply_state :timeout
|
|
120
|
+
end
|
|
121
|
+
|
|
110
122
|
# Mutates upstream responses while forwarding client requests unchanged.
|
|
111
123
|
#
|
|
112
124
|
# @return [void]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nonnative
|
|
4
|
+
# Client helper for the standard gRPC health checking protocol.
|
|
5
|
+
class GRPCHealth
|
|
6
|
+
NETWORK_ERRORS = [
|
|
7
|
+
GRPC::BadStatus,
|
|
8
|
+
GRPC::Core::CallError
|
|
9
|
+
].freeze
|
|
10
|
+
|
|
11
|
+
# @param host [String] gRPC server host
|
|
12
|
+
# @param port [Integer] gRPC server port
|
|
13
|
+
# @param service [String] gRPC health service name
|
|
14
|
+
# @param timeout [Numeric] default call timeout in seconds
|
|
15
|
+
def initialize(host:, port:, service:, timeout: 1)
|
|
16
|
+
@host = host
|
|
17
|
+
@port = port
|
|
18
|
+
@service = service.to_s
|
|
19
|
+
@timeout = timeout
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Calls the gRPC health check endpoint.
|
|
23
|
+
#
|
|
24
|
+
# @param deadline [Time] gRPC deadline
|
|
25
|
+
# @return [Grpc::Health::V1::HealthCheckResponse]
|
|
26
|
+
def check(deadline: Time.now + timeout)
|
|
27
|
+
stub.check(request, deadline: deadline)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns true when the gRPC health endpoint reports SERVING.
|
|
31
|
+
#
|
|
32
|
+
# @param deadline [Time] gRPC deadline
|
|
33
|
+
# @return [Boolean]
|
|
34
|
+
def serving?(deadline: Time.now + timeout)
|
|
35
|
+
check(deadline: deadline).status == :SERVING
|
|
36
|
+
rescue *NETWORK_ERRORS
|
|
37
|
+
false
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Returns the checked gRPC health endpoint for lifecycle diagnostics.
|
|
41
|
+
#
|
|
42
|
+
# @return [String]
|
|
43
|
+
def endpoint
|
|
44
|
+
service.empty? ? "grpc://#{host}:#{port}" : "grpc://#{host}:#{port}/#{service}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
attr_reader :host, :port, :service, :timeout
|
|
50
|
+
|
|
51
|
+
def request
|
|
52
|
+
Grpc::Health::V1::HealthCheckRequest.new(service: service)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def stub
|
|
56
|
+
@stub ||= Grpc::Health::V1::Health::Stub.new("#{host}:#{port}", :this_channel_is_insecure)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nonnative
|
|
4
|
+
# Probes a managed process gRPC health endpoint.
|
|
5
|
+
class GRPCProbe
|
|
6
|
+
# @param process [Nonnative::ConfigurationProcess] process configuration
|
|
7
|
+
# @param readiness [Nonnative::ConfigurationReadiness] gRPC readiness attributes
|
|
8
|
+
def initialize(process, readiness)
|
|
9
|
+
@health = Nonnative::GRPCHealth.new(
|
|
10
|
+
host: process.host,
|
|
11
|
+
port: readiness.port,
|
|
12
|
+
service: readiness.service,
|
|
13
|
+
timeout: process.timeout
|
|
14
|
+
)
|
|
15
|
+
@timeout = Nonnative::Timeout.new(process.timeout)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Returns whether the configured gRPC health endpoint reports SERVING before timeout.
|
|
19
|
+
#
|
|
20
|
+
# @return [Boolean]
|
|
21
|
+
def ready?
|
|
22
|
+
Nonnative.logger.info "checking if readiness '#{endpoint}' is ready"
|
|
23
|
+
|
|
24
|
+
timeout.perform do
|
|
25
|
+
raise Nonnative::Error unless health.serving?
|
|
26
|
+
|
|
27
|
+
true
|
|
28
|
+
rescue Nonnative::Error
|
|
29
|
+
sleep_interval
|
|
30
|
+
retry
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Returns the gRPC health endpoint for lifecycle diagnostics.
|
|
35
|
+
#
|
|
36
|
+
# @return [String]
|
|
37
|
+
def endpoint
|
|
38
|
+
health.endpoint
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
attr_reader :health, :timeout
|
|
44
|
+
|
|
45
|
+
def sleep_interval
|
|
46
|
+
sleep 0.01
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
data/lib/nonnative/http_probe.rb
CHANGED
|
@@ -12,9 +12,10 @@ module Nonnative
|
|
|
12
12
|
RestClient::ServerBrokeConnection
|
|
13
13
|
].freeze
|
|
14
14
|
|
|
15
|
-
# @param process [Nonnative::ConfigurationProcess] process configuration
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
# @param process [Nonnative::ConfigurationProcess] process configuration
|
|
16
|
+
# @param readiness [Nonnative::ConfigurationReadiness] HTTP readiness attributes
|
|
17
|
+
def initialize(process, readiness)
|
|
18
|
+
@readiness = readiness
|
|
18
19
|
@base_url = "http://#{process.host}:#{readiness.port}"
|
|
19
20
|
@timeout = Nonnative::Timeout.new(process.timeout)
|
|
20
21
|
|
data/lib/nonnative/pool.rb
CHANGED
|
@@ -25,7 +25,8 @@ module Nonnative
|
|
|
25
25
|
|
|
26
26
|
# Starts all configured runners and yields results for each process/server.
|
|
27
27
|
#
|
|
28
|
-
# Services are started first (proxy-only), then servers and processes are
|
|
28
|
+
# Services are started first (proxy-only) and checked for opt-in readiness, then servers and processes are
|
|
29
|
+
# started and checked for readiness.
|
|
29
30
|
#
|
|
30
31
|
# @yieldparam name [String, nil] runner name
|
|
31
32
|
# @yieldparam values [Object] runner-specific return value from `start` (e.g. `[pid, running]` for processes)
|
|
@@ -36,6 +37,10 @@ module Nonnative
|
|
|
36
37
|
errors = []
|
|
37
38
|
|
|
38
39
|
errors.concat(service_lifecycle(services, :start, :start))
|
|
40
|
+
service_readiness_errors = check_service_readiness(services)
|
|
41
|
+
errors.concat(service_readiness_errors)
|
|
42
|
+
return errors if service_readiness_errors.any?
|
|
43
|
+
|
|
39
44
|
[servers, processes].each { |runners| errors.concat(run_lifecycle_checks(runners, :start, :open?, :start, &)) }
|
|
40
45
|
|
|
41
46
|
errors
|
|
@@ -180,6 +185,15 @@ module Nonnative
|
|
|
180
185
|
end
|
|
181
186
|
end
|
|
182
187
|
|
|
188
|
+
def check_service_readiness(all)
|
|
189
|
+
all.each_with_object([]) do |service, errors|
|
|
190
|
+
next unless service.respond_to?(:readiness)
|
|
191
|
+
|
|
192
|
+
checks = service.readiness.map { |readiness| Nonnative::TCPProbe.new(readiness, timeout: service.timeout) }
|
|
193
|
+
errors << service_readiness_error(service, checks) unless checks.all?(&:ready?)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
183
197
|
def run_lifecycle_checks(runners, lifecycle_method, port_method, action, &)
|
|
184
198
|
checks = []
|
|
185
199
|
errors = []
|
|
@@ -220,6 +234,10 @@ module Nonnative
|
|
|
220
234
|
"#{check.capitalize} check failed for #{runner_name(type)}: #{error.class} - #{error.message}"
|
|
221
235
|
end
|
|
222
236
|
|
|
237
|
+
def service_readiness_error(service, checks)
|
|
238
|
+
"Started #{runner_name(service)}, though did not respond in time for readiness: #{checks.map(&:endpoint).join(', ')}"
|
|
239
|
+
end
|
|
240
|
+
|
|
223
241
|
def runner_name(type)
|
|
224
242
|
name = type.name
|
|
225
243
|
return "runner '#{name}'" if name
|
data/lib/nonnative/ports.rb
CHANGED
|
@@ -12,14 +12,14 @@ module Nonnative
|
|
|
12
12
|
def initialize(runner)
|
|
13
13
|
@runner = runner
|
|
14
14
|
@ports = runner.ports.map { |port| Nonnative::Port.new(runner, port) }
|
|
15
|
-
@readiness =
|
|
15
|
+
@readiness = readiness_probes
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
# Returns whether all configured ports become connectable before their timeouts elapse.
|
|
19
19
|
#
|
|
20
20
|
# @return [Boolean]
|
|
21
21
|
def open?
|
|
22
|
-
ports.all?(&:open?) &&
|
|
22
|
+
ports.all?(&:open?) && readiness.all?(&:ready?)
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
# Returns whether all configured ports become non-connectable before their timeouts elapse.
|
|
@@ -41,7 +41,7 @@ module Nonnative
|
|
|
41
41
|
# @return [String]
|
|
42
42
|
def description
|
|
43
43
|
details = []
|
|
44
|
-
details << "readiness: #{readiness.endpoint}" if readiness
|
|
44
|
+
details << "readiness: #{readiness.map(&:endpoint).join(', ')}" if readiness.any?
|
|
45
45
|
log = runner.log if runner.respond_to?(:log)
|
|
46
46
|
details << "log: #{log}" if log
|
|
47
47
|
|
|
@@ -51,5 +51,20 @@ module Nonnative
|
|
|
51
51
|
private
|
|
52
52
|
|
|
53
53
|
attr_reader :ports, :readiness, :runner
|
|
54
|
+
|
|
55
|
+
def readiness_probes
|
|
56
|
+
return [] unless runner.respond_to?(:readiness)
|
|
57
|
+
|
|
58
|
+
runner.readiness.map { |check| readiness_probe(check) }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def readiness_probe(check)
|
|
62
|
+
case check.kind
|
|
63
|
+
when 'http'
|
|
64
|
+
Nonnative::HTTPProbe.new(runner, check)
|
|
65
|
+
when 'grpc'
|
|
66
|
+
Nonnative::GRPCProbe.new(runner, check)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
54
69
|
end
|
|
55
70
|
end
|
data/lib/nonnative/service.rb
CHANGED
|
@@ -24,6 +24,20 @@ module Nonnative
|
|
|
24
24
|
@proxy = Nonnative::ProxyFactory.create(service)
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
# Returns configured service readiness checks.
|
|
28
|
+
#
|
|
29
|
+
# @return [Array<Nonnative::ConfigurationServiceReadiness>]
|
|
30
|
+
def readiness
|
|
31
|
+
service.readiness
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Returns the configured service readiness timeout.
|
|
35
|
+
#
|
|
36
|
+
# @return [Numeric]
|
|
37
|
+
def timeout
|
|
38
|
+
service.timeout
|
|
39
|
+
end
|
|
40
|
+
|
|
27
41
|
# Starts the configured proxy (if any).
|
|
28
42
|
#
|
|
29
43
|
# @return [void]
|
|
@@ -16,6 +16,7 @@ module Nonnative
|
|
|
16
16
|
# @see Nonnative::SocketPairFactory
|
|
17
17
|
# @see Nonnative::CloseAllSocketPair
|
|
18
18
|
# @see Nonnative::DelaySocketPair
|
|
19
|
+
# @see Nonnative::TimeoutSocketPair
|
|
19
20
|
# @see Nonnative::InvalidDataSocketPair
|
|
20
21
|
class SocketPair
|
|
21
22
|
# @param proxy [#host, #port, #options] proxy configuration used to connect upstream
|
|
@@ -10,18 +10,20 @@ module Nonnative
|
|
|
10
10
|
# - `:none` (or any unknown value) -> {Nonnative::SocketPair} (pass-through)
|
|
11
11
|
# - `:close_all` -> {Nonnative::CloseAllSocketPair}
|
|
12
12
|
# - `:delay` -> {Nonnative::DelaySocketPair}
|
|
13
|
+
# - `:timeout` -> {Nonnative::TimeoutSocketPair}
|
|
13
14
|
# - `:invalid_data` -> {Nonnative::InvalidDataSocketPair}
|
|
14
15
|
#
|
|
15
16
|
# @see Nonnative::FaultInjectionProxy
|
|
16
17
|
# @see Nonnative::SocketPair
|
|
17
18
|
# @see Nonnative::CloseAllSocketPair
|
|
18
19
|
# @see Nonnative::DelaySocketPair
|
|
20
|
+
# @see Nonnative::TimeoutSocketPair
|
|
19
21
|
# @see Nonnative::InvalidDataSocketPair
|
|
20
22
|
class SocketPairFactory
|
|
21
23
|
class << self
|
|
22
24
|
# Creates a socket-pair instance for the given proxy state.
|
|
23
25
|
#
|
|
24
|
-
# @param kind [Symbol] proxy state (e.g. `:none`, `:close_all`, `:delay`, `:invalid_data`)
|
|
26
|
+
# @param kind [Symbol] proxy state (e.g. `:none`, `:close_all`, `:delay`, `:timeout`, `:invalid_data`)
|
|
25
27
|
# @param proxy [Nonnative::ConfigurationProxy] proxy configuration (host/port/options)
|
|
26
28
|
# @return [Nonnative::SocketPair] a socket-pair implementation instance
|
|
27
29
|
def create(kind, proxy)
|
|
@@ -30,6 +32,8 @@ module Nonnative
|
|
|
30
32
|
CloseAllSocketPair
|
|
31
33
|
when :delay
|
|
32
34
|
DelaySocketPair
|
|
35
|
+
when :timeout
|
|
36
|
+
TimeoutSocketPair
|
|
33
37
|
when :invalid_data
|
|
34
38
|
InvalidDataSocketPair
|
|
35
39
|
else
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nonnative
|
|
4
|
+
# Probes a TCP readiness endpoint.
|
|
5
|
+
class TCPProbe
|
|
6
|
+
Target = Struct.new(:host, :timeout, keyword_init: true)
|
|
7
|
+
|
|
8
|
+
# @param readiness [#host, #port] TCP readiness attributes
|
|
9
|
+
# @param timeout [Numeric] maximum time to wait for the TCP endpoint
|
|
10
|
+
def initialize(readiness, timeout:)
|
|
11
|
+
@port = Nonnative::Port.new(Target.new(host: readiness.host, timeout:), readiness.port)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Returns whether the TCP endpoint becomes connectable before the timeout elapses.
|
|
15
|
+
#
|
|
16
|
+
# @return [Boolean]
|
|
17
|
+
def ready?
|
|
18
|
+
port.open?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Returns the checked endpoint for lifecycle diagnostics.
|
|
22
|
+
#
|
|
23
|
+
# @return [String]
|
|
24
|
+
def endpoint
|
|
25
|
+
port.endpoint
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
attr_reader :port
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nonnative
|
|
4
|
+
# Socket-pair variant used by the fault-injection proxy to simulate read timeouts.
|
|
5
|
+
#
|
|
6
|
+
# When active, the proxy accepts the client connection and keeps it open without forwarding bytes.
|
|
7
|
+
# Clients with read deadlines should observe their own timeout behavior.
|
|
8
|
+
#
|
|
9
|
+
# This behavior is enabled by calling {Nonnative::FaultInjectionProxy#timeout}.
|
|
10
|
+
#
|
|
11
|
+
# @see Nonnative::FaultInjectionProxy
|
|
12
|
+
# @see Nonnative::SocketPairFactory
|
|
13
|
+
# @see Nonnative::SocketPair
|
|
14
|
+
class TimeoutSocketPair < SocketPair
|
|
15
|
+
# Keeps the accepted socket silent until reset or stop closes active proxy connections.
|
|
16
|
+
#
|
|
17
|
+
# @param local_socket [TCPSocket] the accepted client socket
|
|
18
|
+
# @return [void]
|
|
19
|
+
def connect(local_socket)
|
|
20
|
+
@local_socket = local_socket
|
|
21
|
+
|
|
22
|
+
Nonnative.logger.info "stalling socket '#{local_socket.inspect}' for 'timeout' pair"
|
|
23
|
+
|
|
24
|
+
# Do not call super: the base implementation opens the upstream socket and forwards traffic.
|
|
25
|
+
sleep 0.1 until local_socket.closed?
|
|
26
|
+
ensure
|
|
27
|
+
close
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/nonnative/version.rb
CHANGED
data/lib/nonnative.rb
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# It can:
|
|
7
7
|
#
|
|
8
8
|
# - start external processes and in-process servers
|
|
9
|
-
# - wait for readiness via port checks
|
|
9
|
+
# - wait for readiness via port checks, optional process HTTP/gRPC readiness, and optional service TCP readiness
|
|
10
10
|
# - optionally run fault-injection proxies in front of services
|
|
11
11
|
#
|
|
12
12
|
# The public entry points are exposed as module-level methods on {Nonnative}.
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
# p.ports = [8080, 9090]
|
|
28
28
|
# p.timeout = 10
|
|
29
29
|
# p.log = 'api.log'
|
|
30
|
-
# p.readiness = { port: 8080, path: '/example/readyz' }
|
|
30
|
+
# p.readiness = [{ kind: 'http', port: 8080, path: '/example/readyz' }]
|
|
31
31
|
# end
|
|
32
32
|
# end
|
|
33
33
|
#
|
|
@@ -51,6 +51,7 @@ require 'shellwords'
|
|
|
51
51
|
require 'uri'
|
|
52
52
|
|
|
53
53
|
require 'grpc'
|
|
54
|
+
require 'grpc/health/v1/health_services_pb'
|
|
54
55
|
require 'sinatra'
|
|
55
56
|
require 'rest-client'
|
|
56
57
|
require 'retriable'
|
|
@@ -75,6 +76,7 @@ require 'nonnative/configuration_file'
|
|
|
75
76
|
require 'nonnative/configuration'
|
|
76
77
|
require 'nonnative/configuration_runner'
|
|
77
78
|
require 'nonnative/configuration_readiness'
|
|
79
|
+
require 'nonnative/configuration_service_readiness'
|
|
78
80
|
require 'nonnative/configuration_process'
|
|
79
81
|
require 'nonnative/configuration_server'
|
|
80
82
|
require 'nonnative/configuration_service'
|
|
@@ -85,7 +87,10 @@ require 'nonnative/server'
|
|
|
85
87
|
require 'nonnative/service'
|
|
86
88
|
require 'nonnative/pool'
|
|
87
89
|
require 'nonnative/http_client'
|
|
90
|
+
require 'nonnative/tcp_probe'
|
|
88
91
|
require 'nonnative/http_probe'
|
|
92
|
+
require 'nonnative/grpc_health'
|
|
93
|
+
require 'nonnative/grpc_probe'
|
|
89
94
|
require 'nonnative/http_server'
|
|
90
95
|
require 'nonnative/http_proxy_server'
|
|
91
96
|
require 'nonnative/grpc_server'
|
|
@@ -97,6 +102,7 @@ require 'nonnative/fault_injection_proxy'
|
|
|
97
102
|
require 'nonnative/socket_pair'
|
|
98
103
|
require 'nonnative/close_all_socket_pair'
|
|
99
104
|
require 'nonnative/delay_socket_pair'
|
|
105
|
+
require 'nonnative/timeout_socket_pair'
|
|
100
106
|
require 'nonnative/invalid_data_socket_pair'
|
|
101
107
|
require 'nonnative/socket_pair_factory'
|
|
102
108
|
require 'nonnative/go_executable'
|
|
@@ -190,9 +196,16 @@ module Nonnative
|
|
|
190
196
|
# Returns an HTTP client for common health/readiness endpoints.
|
|
191
197
|
#
|
|
192
198
|
# @return [Nonnative::Observability]
|
|
193
|
-
def observability
|
|
194
|
-
|
|
195
|
-
|
|
199
|
+
def observability = (@observability ||= Nonnative::Observability.new(configuration.url))
|
|
200
|
+
|
|
201
|
+
# Returns a client helper for the standard gRPC health checking protocol.
|
|
202
|
+
#
|
|
203
|
+
# @param host [String] gRPC server host
|
|
204
|
+
# @param port [Integer] gRPC server port
|
|
205
|
+
# @param service [String] gRPC health service name
|
|
206
|
+
# @param timeout [Numeric] default call timeout in seconds
|
|
207
|
+
# @return [Nonnative::GRPCHealth]
|
|
208
|
+
def grpc_health(host:, port:, service:, timeout: 1) = Nonnative::GRPCHealth.new(host: host, port: port, service: service, timeout: timeout)
|
|
196
209
|
|
|
197
210
|
# Returns the configured proxy kinds mapped to proxy classes.
|
|
198
211
|
#
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: nonnative
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.14.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alejandro Falkowski
|
|
@@ -296,11 +296,14 @@ files:
|
|
|
296
296
|
- lib/nonnative/configuration_runner.rb
|
|
297
297
|
- lib/nonnative/configuration_server.rb
|
|
298
298
|
- lib/nonnative/configuration_service.rb
|
|
299
|
+
- lib/nonnative/configuration_service_readiness.rb
|
|
299
300
|
- lib/nonnative/cucumber.rb
|
|
300
301
|
- lib/nonnative/delay_socket_pair.rb
|
|
301
302
|
- lib/nonnative/error.rb
|
|
302
303
|
- lib/nonnative/fault_injection_proxy.rb
|
|
303
304
|
- lib/nonnative/go_executable.rb
|
|
305
|
+
- lib/nonnative/grpc_health.rb
|
|
306
|
+
- lib/nonnative/grpc_probe.rb
|
|
304
307
|
- lib/nonnative/grpc_server.rb
|
|
305
308
|
- lib/nonnative/header.rb
|
|
306
309
|
- lib/nonnative/http_client.rb
|
|
@@ -325,7 +328,9 @@ files:
|
|
|
325
328
|
- lib/nonnative/start_error.rb
|
|
326
329
|
- lib/nonnative/startup.rb
|
|
327
330
|
- lib/nonnative/stop_error.rb
|
|
331
|
+
- lib/nonnative/tcp_probe.rb
|
|
328
332
|
- lib/nonnative/timeout.rb
|
|
333
|
+
- lib/nonnative/timeout_socket_pair.rb
|
|
329
334
|
- lib/nonnative/version.rb
|
|
330
335
|
- nonnative.gemspec
|
|
331
336
|
homepage: https://github.com/alexfalkowski/nonnative
|