nonnative 3.10.0 → 3.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/.circleci/config.yml +2 -2
- data/Gemfile.lock +1 -1
- data/README.md +92 -18
- data/lib/nonnative/configuration.rb +3 -3
- data/lib/nonnative/configuration_process.rb +13 -4
- data/lib/nonnative/configuration_readiness.rb +45 -6
- data/lib/nonnative/configuration_service.rb +24 -0
- data/lib/nonnative/configuration_service_readiness.rb +48 -0
- data/lib/nonnative/cucumber.rb +2 -1
- data/lib/nonnative/grpc_health.rb +59 -0
- data/lib/nonnative/grpc_probe.rb +49 -0
- data/lib/nonnative/http_probe.rb +5 -4
- data/lib/nonnative/no_proxy.rb +4 -4
- data/lib/nonnative/pool.rb +19 -1
- data/lib/nonnative/ports.rb +18 -3
- data/lib/nonnative/service.rb +14 -0
- data/lib/nonnative/startup.rb +5 -0
- data/lib/nonnative/tcp_probe.rb +32 -0
- data/lib/nonnative/version.rb +1 -1
- data/lib/nonnative.rb +22 -5
- data/nonnative.gemspec +2 -2
- metadata +8 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 304a9d9d01fafb9f0822e4510c647f87095f78987feecb5a9d2eddc43317a880
|
|
4
|
+
data.tar.gz: 366157abd34f6ffa48cce159dffd49075e4a98653ea5d3f3b11b2494ad47b4a1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6fa7d92393e3a4df9f400a7f35c857998c13dc026ec85be407fa37d6e8ba2189c5992af70e001c1cdfaf9589d1b6d33a2daf11abf8361f84f07ab8bc3a72fed1
|
|
7
|
+
data.tar.gz: bf37673b803ff66338009fbbd458d707c6eed9ef0ba618bb53b97e2f5770788708343f6bd34b67758fd6d1a13e464f63edffced76c26591f8ac4fe4ec2267a3e
|
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.19
|
|
21
21
|
alpine:
|
|
22
22
|
docker:
|
|
23
23
|
- image: alpine:latest
|
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
|
|
|
@@ -38,6 +38,18 @@ Or install it yourself as:
|
|
|
38
38
|
gem install nonnative
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
## 🛠️ Contributor Bootstrap
|
|
42
|
+
|
|
43
|
+
Fresh clones need the shared `bin/` submodule before Make targets can load:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
git submodule sync && git submodule update --init
|
|
47
|
+
make help
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Use `make dep` before local validation when dependencies are missing. The CI-parity checks are
|
|
51
|
+
`make lint`, `make sec`, `make features`, and `make benchmarks`.
|
|
52
|
+
|
|
41
53
|
## 🚀 Usage
|
|
42
54
|
|
|
43
55
|
Nonnative is configured via `Nonnative.configure` (programmatic) or `config.load_file(...)` (YAML).
|
|
@@ -67,15 +79,20 @@ Process/server fields:
|
|
|
67
79
|
- `log`: per-runner log file used by process output redirection or server implementations.
|
|
68
80
|
|
|
69
81
|
Process-only fields:
|
|
70
|
-
- `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`.
|
|
71
85
|
|
|
72
86
|
Service fields:
|
|
73
|
-
- `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`.
|
|
74
91
|
|
|
75
|
-
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.
|
|
76
93
|
|
|
77
94
|
> [!WARNING]
|
|
78
|
-
> 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.
|
|
79
96
|
|
|
80
97
|
Start and stop Nonnative around the test scope that should own the configured runners:
|
|
81
98
|
|
|
@@ -98,6 +115,37 @@ Nonnative.stop
|
|
|
98
115
|
> Call `Nonnative.clear` before reconfiguring Nonnative or starting a new lifecycle in the same Ruby process.
|
|
99
116
|
> `Nonnative.clear` clears memoized configuration, logger, observability client, and pool.
|
|
100
117
|
|
|
118
|
+
### 🧩 Test framework setup
|
|
119
|
+
|
|
120
|
+
For Cucumber, load and configure Nonnative in `features/support/env.rb`, then use lifecycle tags on scenarios:
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
require 'nonnative'
|
|
124
|
+
|
|
125
|
+
Nonnative.configure do |config|
|
|
126
|
+
config.load_file('configuration.yml')
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```cucumber
|
|
131
|
+
@startup
|
|
132
|
+
Scenario: run with Nonnative around this scenario
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
For RSpec or another suite that should start Nonnative once per test run, configure first and then require the startup integration:
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
require 'nonnative'
|
|
139
|
+
|
|
140
|
+
Nonnative.configure do |config|
|
|
141
|
+
config.load_file('configuration.yml')
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
require 'nonnative/startup'
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`nonnative/startup` calls `Nonnative.start` immediately and registers an `at_exit` stop, so load configuration before requiring it.
|
|
148
|
+
|
|
101
149
|
### 📈 Observability
|
|
102
150
|
|
|
103
151
|
`Nonnative.observability` is an HTTP client for common service endpoints under the configured `name` and `url`:
|
|
@@ -119,11 +167,24 @@ response = Nonnative.observability.health(
|
|
|
119
167
|
expect(response.code).to eq(200)
|
|
120
168
|
```
|
|
121
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
|
+
|
|
122
183
|
### 🔁 Lifecycle strategies (Cucumber integration)
|
|
123
184
|
|
|
124
185
|
Nonnative ships Cucumber hooks (when loaded) that support these tags/strategies:
|
|
125
186
|
- `@startup`: start before scenario; stop after scenario
|
|
126
|
-
- `@manual`: stop after scenario
|
|
187
|
+
- `@manual`: stop after scenario; use `When I start the system` to start manually
|
|
127
188
|
- `@clear`: clears memoized configuration, logger, observability client, and pool before scenario
|
|
128
189
|
- `@reset`: resets proxies after scenario
|
|
129
190
|
|
|
@@ -140,14 +201,6 @@ The repo’s own Cucumber suite also uses taxonomy tags to classify coverage:
|
|
|
140
201
|
|
|
141
202
|
Requiring `nonnative` is enough; the Cucumber hooks and step definitions are installed lazily once Cucumber’s Ruby DSL is ready.
|
|
142
203
|
|
|
143
|
-
If you want "start once per test run", require:
|
|
144
|
-
|
|
145
|
-
```ruby
|
|
146
|
-
require 'nonnative/startup'
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
This calls `Nonnative.start` immediately and registers an `at_exit` stop.
|
|
150
|
-
|
|
151
204
|
### ⚙️ Processes
|
|
152
205
|
|
|
153
206
|
A process is some sort of command that you would run locally.
|
|
@@ -175,7 +228,10 @@ Nonnative.configure do |config|
|
|
|
175
228
|
p.ports = [12_321]
|
|
176
229
|
p.log = '12_321.log'
|
|
177
230
|
p.signal = 'INT' # Possible values are described in Signal.list.keys.
|
|
178
|
-
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
|
+
]
|
|
179
235
|
p.environment = { # Pass environment variables to process.
|
|
180
236
|
'TEST' => 'true'
|
|
181
237
|
}
|
|
@@ -212,8 +268,12 @@ processes:
|
|
|
212
268
|
log: 12_321.log
|
|
213
269
|
signal: INT # Possible values are described in Signal.list.keys.
|
|
214
270
|
readiness:
|
|
215
|
-
|
|
216
|
-
|
|
271
|
+
- kind: http
|
|
272
|
+
port: 12321
|
|
273
|
+
path: /test/readyz
|
|
274
|
+
- kind: grpc
|
|
275
|
+
port: 12322
|
|
276
|
+
service: example.v1.ExampleService
|
|
217
277
|
environment: # Pass environment variables to process.
|
|
218
278
|
TEST: true
|
|
219
279
|
-
|
|
@@ -558,7 +618,7 @@ end
|
|
|
558
618
|
|
|
559
619
|
A service is an external dependency to your system that you **do not** want Nonnative to start (no OS process, no Ruby thread).
|
|
560
620
|
|
|
561
|
-
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.
|
|
562
622
|
|
|
563
623
|
Set it up programmatically:
|
|
564
624
|
|
|
@@ -575,6 +635,8 @@ Nonnative.configure do |config|
|
|
|
575
635
|
s.name = 'postgres'
|
|
576
636
|
s.host = '127.0.0.1'
|
|
577
637
|
s.port = 5432
|
|
638
|
+
s.timeout = 5
|
|
639
|
+
s.readiness = [{ kind: 'tcp', host: '127.0.0.1', port: 5432 }]
|
|
578
640
|
end
|
|
579
641
|
|
|
580
642
|
config.service do |s|
|
|
@@ -597,6 +659,11 @@ services:
|
|
|
597
659
|
name: postgres
|
|
598
660
|
host: 127.0.0.1
|
|
599
661
|
port: 5432
|
|
662
|
+
timeout: 5
|
|
663
|
+
readiness:
|
|
664
|
+
- kind: tcp
|
|
665
|
+
host: 127.0.0.1
|
|
666
|
+
port: 5432
|
|
600
667
|
-
|
|
601
668
|
name: redis
|
|
602
669
|
host: 127.0.0.1
|
|
@@ -630,6 +697,7 @@ Nonnative.proxies['custom'] = CustomProxy
|
|
|
630
697
|
```
|
|
631
698
|
|
|
632
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.
|
|
633
701
|
|
|
634
702
|
##### 🧩 Service Proxies
|
|
635
703
|
|
|
@@ -653,6 +721,8 @@ config.service do |s|
|
|
|
653
721
|
delay: 5
|
|
654
722
|
}
|
|
655
723
|
}
|
|
724
|
+
|
|
725
|
+
s.readiness = [{ kind: 'tcp', host: '127.0.0.1', port: 6379 }]
|
|
656
726
|
end
|
|
657
727
|
```
|
|
658
728
|
|
|
@@ -666,6 +736,10 @@ services:
|
|
|
666
736
|
name: redis
|
|
667
737
|
host: 127.0.0.1
|
|
668
738
|
port: 16379
|
|
739
|
+
readiness:
|
|
740
|
+
- kind: tcp
|
|
741
|
+
host: 127.0.0.1
|
|
742
|
+
port: 6379
|
|
669
743
|
proxy:
|
|
670
744
|
kind: fault_injection
|
|
671
745
|
host: 127.0.0.1
|
|
@@ -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
|
|
@@ -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
|
-
# @return [String] HTTP readiness path
|
|
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,8 +46,29 @@ 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?
|
|
59
|
+
raise ArgumentError, 'Process readiness path must be path-only' unless path_only?
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def validate_grpc!
|
|
63
|
+
raise ArgumentError, "Process readiness requires 'service'" if service.nil?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def path_only?
|
|
67
|
+
uri = URI.parse(path.to_s)
|
|
68
|
+
|
|
69
|
+
uri.scheme.nil? && uri.host.nil?
|
|
70
|
+
rescue URI::InvalidURIError
|
|
71
|
+
false
|
|
33
72
|
end
|
|
34
73
|
end
|
|
35
74
|
end
|
|
@@ -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
|
@@ -43,10 +43,11 @@ module Nonnative
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def install_hooks
|
|
46
|
+
# Register @clear before startup hooks so combined tags reset stale state before creating a pool.
|
|
47
|
+
Before('@clear') { Nonnative.clear }
|
|
46
48
|
Before('@startup') { Nonnative.start }
|
|
47
49
|
After('@startup') { Nonnative.stop }
|
|
48
50
|
After('@manual') { Nonnative.stop }
|
|
49
|
-
Before('@clear') { Nonnative.clear }
|
|
50
51
|
After('@reset') { Nonnative.reset }
|
|
51
52
|
end
|
|
52
53
|
end
|
|
@@ -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
|
|
|
@@ -28,7 +29,7 @@ module Nonnative
|
|
|
28
29
|
Nonnative.logger.info "checking if readiness '#{endpoint}' is ready"
|
|
29
30
|
|
|
30
31
|
timeout.perform do
|
|
31
|
-
response = get(
|
|
32
|
+
response = get(path)
|
|
32
33
|
raise Nonnative::Error unless ready_response?(response)
|
|
33
34
|
|
|
34
35
|
true
|
data/lib/nonnative/no_proxy.rb
CHANGED
|
@@ -4,8 +4,8 @@ module Nonnative
|
|
|
4
4
|
# No-op proxy implementation.
|
|
5
5
|
#
|
|
6
6
|
# This is the default proxy when `service.proxy.kind` is `"none"`.
|
|
7
|
-
# It does not bind/listen or alter traffic; it simply exposes the
|
|
8
|
-
# `host` and
|
|
7
|
+
# It does not bind/listen or alter traffic; it simply exposes the service configuration's
|
|
8
|
+
# client-facing `host` and `port`.
|
|
9
9
|
#
|
|
10
10
|
# Services can always call `start`, `stop`, and `reset` safely on this proxy.
|
|
11
11
|
#
|
|
@@ -41,7 +41,7 @@ module Nonnative
|
|
|
41
41
|
|
|
42
42
|
# Returns the host clients should connect to.
|
|
43
43
|
#
|
|
44
|
-
# For {NoProxy}, this is the
|
|
44
|
+
# For {NoProxy}, this is the service configuration's client-facing `host`.
|
|
45
45
|
#
|
|
46
46
|
# @return [String]
|
|
47
47
|
def host
|
|
@@ -50,7 +50,7 @@ module Nonnative
|
|
|
50
50
|
|
|
51
51
|
# Returns the port clients should connect to.
|
|
52
52
|
#
|
|
53
|
-
# For {NoProxy}, this is the
|
|
53
|
+
# For {NoProxy}, this is the service configuration's client-facing `port`.
|
|
54
54
|
#
|
|
55
55
|
# @return [Integer]
|
|
56
56
|
def port
|
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]
|
data/lib/nonnative/startup.rb
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Starts the configured Nonnative pool immediately and registers an `at_exit` stop hook.
|
|
4
|
+
#
|
|
5
|
+
# Configure Nonnative before requiring this file; it is intended for suites that keep one
|
|
6
|
+
# Nonnative lifecycle open for the whole Ruby process.
|
|
7
|
+
|
|
3
8
|
at_exit do
|
|
4
9
|
Nonnative.stop
|
|
5
10
|
end
|
|
@@ -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
|
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
|
#
|
|
@@ -48,8 +48,10 @@ require 'yaml'
|
|
|
48
48
|
require 'open3'
|
|
49
49
|
require 'securerandom'
|
|
50
50
|
require 'shellwords'
|
|
51
|
+
require 'uri'
|
|
51
52
|
|
|
52
53
|
require 'grpc'
|
|
54
|
+
require 'grpc/health/v1/health_services_pb'
|
|
53
55
|
require 'sinatra'
|
|
54
56
|
require 'rest-client'
|
|
55
57
|
require 'retriable'
|
|
@@ -74,6 +76,7 @@ require 'nonnative/configuration_file'
|
|
|
74
76
|
require 'nonnative/configuration'
|
|
75
77
|
require 'nonnative/configuration_runner'
|
|
76
78
|
require 'nonnative/configuration_readiness'
|
|
79
|
+
require 'nonnative/configuration_service_readiness'
|
|
77
80
|
require 'nonnative/configuration_process'
|
|
78
81
|
require 'nonnative/configuration_server'
|
|
79
82
|
require 'nonnative/configuration_service'
|
|
@@ -84,7 +87,10 @@ require 'nonnative/server'
|
|
|
84
87
|
require 'nonnative/service'
|
|
85
88
|
require 'nonnative/pool'
|
|
86
89
|
require 'nonnative/http_client'
|
|
90
|
+
require 'nonnative/tcp_probe'
|
|
87
91
|
require 'nonnative/http_probe'
|
|
92
|
+
require 'nonnative/grpc_health'
|
|
93
|
+
require 'nonnative/grpc_probe'
|
|
88
94
|
require 'nonnative/http_server'
|
|
89
95
|
require 'nonnative/http_proxy_server'
|
|
90
96
|
require 'nonnative/grpc_server'
|
|
@@ -189,9 +195,16 @@ module Nonnative
|
|
|
189
195
|
# Returns an HTTP client for common health/readiness endpoints.
|
|
190
196
|
#
|
|
191
197
|
# @return [Nonnative::Observability]
|
|
192
|
-
def observability
|
|
193
|
-
|
|
194
|
-
|
|
198
|
+
def observability = (@observability ||= Nonnative::Observability.new(configuration.url))
|
|
199
|
+
|
|
200
|
+
# Returns a client helper for the standard gRPC health checking protocol.
|
|
201
|
+
#
|
|
202
|
+
# @param host [String] gRPC server host
|
|
203
|
+
# @param port [Integer] gRPC server port
|
|
204
|
+
# @param service [String] gRPC health service name
|
|
205
|
+
# @param timeout [Numeric] default call timeout in seconds
|
|
206
|
+
# @return [Nonnative::GRPCHealth]
|
|
207
|
+
def grpc_health(host:, port:, service:, timeout: 1) = Nonnative::GRPCHealth.new(host: host, port: port, service: service, timeout: timeout)
|
|
195
208
|
|
|
196
209
|
# Returns the configured proxy kinds mapped to proxy classes.
|
|
197
210
|
#
|
|
@@ -204,8 +217,12 @@ module Nonnative
|
|
|
204
217
|
|
|
205
218
|
# Resolves a proxy implementation for a configured kind.
|
|
206
219
|
#
|
|
220
|
+
# `nil` and `"none"` resolve to {Nonnative::NoProxy}; any other kind must be registered in
|
|
221
|
+
# {Nonnative.proxies}.
|
|
222
|
+
#
|
|
207
223
|
# @param kind [String] proxy kind name (for example `"fault_injection"`)
|
|
208
224
|
# @return [Class] a subclass of {Nonnative::Proxy}
|
|
225
|
+
# @raise [ArgumentError] if the kind is not `"none"` and has not been registered
|
|
209
226
|
def proxy(kind)
|
|
210
227
|
kind.nil? || kind == 'none' ? NoProxy : proxies.fetch(kind) { raise ArgumentError, "Unsupported proxy kind '#{kind}'" }
|
|
211
228
|
end
|
data/nonnative.gemspec
CHANGED
|
@@ -11,8 +11,8 @@ Gem::Specification.new do |spec|
|
|
|
11
11
|
spec.authors = ['Alejandro Falkowski']
|
|
12
12
|
spec.email = ['alexrfalkowski@gmail.com']
|
|
13
13
|
|
|
14
|
-
spec.summary = '
|
|
15
|
-
spec.description =
|
|
14
|
+
spec.summary = 'Ruby-first end-to-end harness for testing systems implemented in other languages'
|
|
15
|
+
spec.description = 'Starts OS processes, in-process Ruby servers, and proxy-only services with TCP readiness checks and fault-injection proxies.'
|
|
16
16
|
spec.homepage = 'https://github.com/alexfalkowski/nonnative'
|
|
17
17
|
spec.license = 'MIT'
|
|
18
18
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
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.13.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alejandro Falkowski
|
|
@@ -263,7 +263,8 @@ dependencies:
|
|
|
263
263
|
- - "<"
|
|
264
264
|
- !ruby/object:Gem::Version
|
|
265
265
|
version: '5'
|
|
266
|
-
description:
|
|
266
|
+
description: Starts OS processes, in-process Ruby servers, and proxy-only services
|
|
267
|
+
with TCP readiness checks and fault-injection proxies.
|
|
267
268
|
email:
|
|
268
269
|
- alexrfalkowski@gmail.com
|
|
269
270
|
executables: []
|
|
@@ -295,11 +296,14 @@ files:
|
|
|
295
296
|
- lib/nonnative/configuration_runner.rb
|
|
296
297
|
- lib/nonnative/configuration_server.rb
|
|
297
298
|
- lib/nonnative/configuration_service.rb
|
|
299
|
+
- lib/nonnative/configuration_service_readiness.rb
|
|
298
300
|
- lib/nonnative/cucumber.rb
|
|
299
301
|
- lib/nonnative/delay_socket_pair.rb
|
|
300
302
|
- lib/nonnative/error.rb
|
|
301
303
|
- lib/nonnative/fault_injection_proxy.rb
|
|
302
304
|
- lib/nonnative/go_executable.rb
|
|
305
|
+
- lib/nonnative/grpc_health.rb
|
|
306
|
+
- lib/nonnative/grpc_probe.rb
|
|
303
307
|
- lib/nonnative/grpc_server.rb
|
|
304
308
|
- lib/nonnative/header.rb
|
|
305
309
|
- lib/nonnative/http_client.rb
|
|
@@ -324,6 +328,7 @@ files:
|
|
|
324
328
|
- lib/nonnative/start_error.rb
|
|
325
329
|
- lib/nonnative/startup.rb
|
|
326
330
|
- lib/nonnative/stop_error.rb
|
|
331
|
+
- lib/nonnative/tcp_probe.rb
|
|
327
332
|
- lib/nonnative/timeout.rb
|
|
328
333
|
- lib/nonnative/version.rb
|
|
329
334
|
- nonnative.gemspec
|
|
@@ -351,5 +356,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
351
356
|
requirements: []
|
|
352
357
|
rubygems_version: 4.0.11
|
|
353
358
|
specification_version: 4
|
|
354
|
-
summary:
|
|
359
|
+
summary: Ruby-first end-to-end harness for testing systems implemented in other languages
|
|
355
360
|
test_files: []
|