nonnative 3.29.0 → 3.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +98 -10
- data/lib/nonnative/configuration.rb +7 -6
- data/lib/nonnative/configuration_process.rb +6 -1
- data/lib/nonnative/configuration_readiness.rb +3 -1
- data/lib/nonnative/configuration_runner.rb +1 -1
- data/lib/nonnative/configuration_service.rb +3 -3
- data/lib/nonnative/error.rb +2 -1
- data/lib/nonnative/fault_injection_proxy.rb +2 -0
- data/lib/nonnative/go_executable.rb +2 -1
- data/lib/nonnative/grpc_health.rb +7 -2
- data/lib/nonnative/http_server.rb +3 -2
- data/lib/nonnative/observability.rb +3 -1
- data/lib/nonnative/pool.rb +9 -7
- data/lib/nonnative/ports.rb +5 -4
- data/lib/nonnative/process.rb +4 -1
- data/lib/nonnative/proxy.rb +2 -1
- data/lib/nonnative/runner.rb +1 -1
- data/lib/nonnative/service.rb +3 -3
- data/lib/nonnative/socket_pair.rb +26 -3
- data/lib/nonnative/version.rb +1 -1
- data/lib/nonnative.rb +10 -6
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 954eb6b4c3c122729406d275f3befa59e19b8db405e14cb2ee8fd759354f97ba
|
|
4
|
+
data.tar.gz: 39291c978f15b62b187aeb51b5d51f8b0479d314261f1eaab85e6d9f441b3a54
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f2a6efd54e9af260f1e0261347e0f2d93825392833bbbc8818e073dfd13359eb3739afa8bd535fea01e502fbe3d0664abbae49b41eb17fed1531c1a19019e0f1
|
|
7
|
+
data.tar.gz: 3b6f5e46899a77d275500d7d5edca75a46f739db103d7b006ca8cbe8699a24edc362679226ba7e67221dd2fb1cc200e09c4c366b9b3faa8a42fa17be61b9b246
|
data/README.md
CHANGED
|
@@ -57,7 +57,10 @@ YAML configuration is loaded as data only: ERB is not evaluated and arbitrary Ru
|
|
|
57
57
|
deserialized.
|
|
58
58
|
|
|
59
59
|
> [!CAUTION]
|
|
60
|
-
> Treat YAML configuration as plain data. ERB is not evaluated
|
|
60
|
+
> Treat YAML configuration as plain data. ERB is not evaluated, `${VAR}` values are not expanded,
|
|
61
|
+
> and arbitrary Ruby object tags are rejected. Unknown structural keys may be ignored, although YAML
|
|
62
|
+
> syntax, object safety, and supported value shapes are still validated. Keep values that vary by
|
|
63
|
+
> environment in programmatic Ruby configuration, where Ruby's `ENV` is available.
|
|
61
64
|
|
|
62
65
|
High-level configuration fields:
|
|
63
66
|
- `version`: configuration version (example: `"1.0"`).
|
|
@@ -74,7 +77,8 @@ Common runner fields:
|
|
|
74
77
|
|
|
75
78
|
Process/server fields:
|
|
76
79
|
- `ports`: client-facing ports. These are also used for readiness/shutdown port checks.
|
|
77
|
-
- `timeout`: max time (seconds) for readiness/shutdown
|
|
80
|
+
- `timeout`: max time (seconds) for each readiness/shutdown check. For processes, the same value also
|
|
81
|
+
bounds optional HTTP/gRPC probes and graceful child exit after the stop signal. Defaults to `1.0`.
|
|
78
82
|
- `wait`: small sleep (seconds) between lifecycle steps.
|
|
79
83
|
- `log`: per-runner log file used by process output redirection or server implementations.
|
|
80
84
|
|
|
@@ -89,7 +93,7 @@ Service fields:
|
|
|
89
93
|
- `readiness`: optional list of startup readiness checks. Supported kind is `tcp`, which requires
|
|
90
94
|
explicit `host` and `port`.
|
|
91
95
|
|
|
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.
|
|
96
|
+
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. HTTP readiness sends a plain HTTP `GET` without configurable request headers and is ready only when the final response is 2xx. gRPC readiness uses the standard health `Check` over an insecure channel and is ready only for `SERVING`. Non-ready responses are retried until the process timeout elapses. 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.
|
|
93
97
|
|
|
94
98
|
> [!WARNING]
|
|
95
99
|
> 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.
|
|
@@ -108,12 +112,19 @@ Nonnative.start
|
|
|
108
112
|
Nonnative.stop
|
|
109
113
|
```
|
|
110
114
|
|
|
111
|
-
`Nonnative.start`
|
|
115
|
+
`Nonnative.start` runs ordered tiers: service lifecycle calls and optional readiness checks complete,
|
|
116
|
+
then server lifecycle and readiness checks complete, then process lifecycle and readiness checks run.
|
|
117
|
+
A failed service readiness check prevents later tiers; other collected startup errors trigger rollback
|
|
118
|
+
after the attempted tiers finish. `Nonnative.stop` reverses the tiers: processes, servers, then
|
|
119
|
+
services. Model dependencies in that direction; a managed server can satisfy a process dependency,
|
|
120
|
+
but a server cannot wait on a managed process. Startup failures raise `Nonnative::StartError`, and
|
|
121
|
+
shutdown failures raise `Nonnative::StopError`.
|
|
112
122
|
|
|
113
|
-
> [!
|
|
114
|
-
> `Nonnative.
|
|
115
|
-
>
|
|
116
|
-
> `Nonnative.clear` clears memoized configuration,
|
|
123
|
+
> [!WARNING]
|
|
124
|
+
> `Nonnative.clear` forgets the current pool; it does not stop live processes, server threads, or
|
|
125
|
+
> proxies. To reuse the same Ruby process, call `Nonnative.stop`, then `Nonnative.clear`, configure
|
|
126
|
+
> the next system, and call `Nonnative.start`. `clear` also clears the memoized configuration,
|
|
127
|
+
> logger, and observability client.
|
|
117
128
|
|
|
118
129
|
### 🧩 Test framework setup
|
|
119
130
|
|
|
@@ -167,6 +178,10 @@ response = Nonnative.observability.health(
|
|
|
167
178
|
expect(response.code).to eq(200)
|
|
168
179
|
```
|
|
169
180
|
|
|
181
|
+
HTTP error statuses are returned as response objects so callers can inspect `.code` and `.body`.
|
|
182
|
+
Request timeouts and broken connections raise their RestClient exceptions; observability requests are
|
|
183
|
+
not retried automatically.
|
|
184
|
+
|
|
170
185
|
`Nonnative.grpc_health` is a helper for the standard gRPC health checking protocol:
|
|
171
186
|
|
|
172
187
|
```ruby
|
|
@@ -180,6 +195,10 @@ health = Nonnative.grpc_health(
|
|
|
180
195
|
expect(health.serving?).to eq(true)
|
|
181
196
|
```
|
|
182
197
|
|
|
198
|
+
The helper always uses an insecure plaintext gRPC channel. Pass `service: ''` or `nil` to check the
|
|
199
|
+
overall server. `check` returns the full `HealthCheckResponse` and propagates gRPC failures, while
|
|
200
|
+
`serving?` returns `false` for any non-`SERVING` status or gRPC failure.
|
|
201
|
+
|
|
183
202
|
### 🔑 Tokens
|
|
184
203
|
|
|
185
204
|
`Nonnative.token` builds a signer for authenticating requests against a service under test. You pass the signing parameters directly (parsed from your own configuration); it is not coupled to any service's config format. The generated string is ready for `Nonnative::Header.auth_bearer`.
|
|
@@ -241,6 +260,32 @@ The repo’s own Cucumber suite also uses taxonomy tags to classify coverage:
|
|
|
241
260
|
|
|
242
261
|
Requiring `nonnative` is enough; the Cucumber hooks and step definitions are installed lazily once Cucumber’s Ruby DSL is ready.
|
|
243
262
|
|
|
263
|
+
#### 🥒 Public Cucumber steps
|
|
264
|
+
|
|
265
|
+
The shipped steps are compatibility surface for downstream suites:
|
|
266
|
+
|
|
267
|
+
- `When I start the system` starts immediately. For expected failures, pair
|
|
268
|
+
`When I attempt to start the system` with `Then starting the system should raise an error`, or use
|
|
269
|
+
the equivalent stop steps.
|
|
270
|
+
- `Then I should see {string} as healthy` expects the configured health endpoint to return `200` and
|
|
271
|
+
a body containing neither the supplied service name nor `service unavailable`; `unhealthy` expects
|
|
272
|
+
`503` and a body identifying the service or `service unavailable`.
|
|
273
|
+
- `Then the process {string} should consume less than {string} of memory` accepts values such as
|
|
274
|
+
`25mb` for a started process.
|
|
275
|
+
- The two log steps search either a configured process log or an explicit file path for the requested
|
|
276
|
+
text: `Then I should see a log entry of {string} for process {string}` and
|
|
277
|
+
`Then I should see a log entry of {string} in the file {string}`.
|
|
278
|
+
- `Given I set the proxy for service {string} to {string}` accepts `close_all`, `reset_peer`, `delay`,
|
|
279
|
+
`timeout`, `invalid_data`, `bandwidth`, `limit_data`, or `reset`; the reset action step is
|
|
280
|
+
`Then I should reset the proxy for service {string}`.
|
|
281
|
+
|
|
282
|
+
```cucumber
|
|
283
|
+
@manual
|
|
284
|
+
Scenario: startup is expected to fail
|
|
285
|
+
When I attempt to start the system
|
|
286
|
+
Then starting the system should raise an error
|
|
287
|
+
```
|
|
288
|
+
|
|
244
289
|
### ⚙️ Processes
|
|
245
290
|
|
|
246
291
|
A process is some sort of command that you would run locally.
|
|
@@ -249,6 +294,12 @@ Programmatic `p.command` values must be callables that return a shell string or
|
|
|
249
294
|
> [!TIP]
|
|
250
295
|
> Prefer argv arrays for new process commands. Use shell strings only when you intentionally need shell parsing, expansion, or redirection.
|
|
251
296
|
|
|
297
|
+
Managed processes inherit the Ruby parent's working directory and environment; loading YAML from a
|
|
298
|
+
different directory does not change the child working directory. Relative command, config, log, and
|
|
299
|
+
generated-output paths resolve from that inherited directory. Configured `environment` values are
|
|
300
|
+
stringified and override variables with the same names while preserving the rest of the parent
|
|
301
|
+
environment.
|
|
302
|
+
|
|
252
303
|
Set it up programmatically:
|
|
253
304
|
|
|
254
305
|
```ruby
|
|
@@ -338,6 +389,10 @@ Nonnative.configure do |config|
|
|
|
338
389
|
end
|
|
339
390
|
```
|
|
340
391
|
|
|
392
|
+
On stop, Nonnative sends the configured signal (`INT` by default) and waits up to `timeout` for the
|
|
393
|
+
child to exit. If it remains alive, Nonnative sends `KILL` and reports the stop as unsuccessful, so
|
|
394
|
+
`Nonnative.stop` raises `Nonnative::StopError` even if the configured shutdown ports close.
|
|
395
|
+
|
|
341
396
|
With cucumber you can also verify how much memory is used by the process:
|
|
342
397
|
|
|
343
398
|
```cucumber
|
|
@@ -465,8 +520,9 @@ module Nonnative
|
|
|
465
520
|
end
|
|
466
521
|
```
|
|
467
522
|
|
|
468
|
-
To run multiple Rack services on one managed port, pass a non-empty mount map.
|
|
469
|
-
with
|
|
523
|
+
To run multiple Rack services on one managed port, pass a non-empty `Rack::URLMap` mount map. Keys
|
|
524
|
+
can be path prefixes beginning with `/` or host-qualified URLs; the mounted application receives the
|
|
525
|
+
remaining `PATH_INFO`:
|
|
470
526
|
|
|
471
527
|
```ruby
|
|
472
528
|
module Nonnative
|
|
@@ -541,6 +597,10 @@ end
|
|
|
541
597
|
|
|
542
598
|
The system allows you to define an in-process HTTP forward proxy server for external systems, e.g. `api.github.com`. This is a server implementation, not a fault-injection service proxy.
|
|
543
599
|
|
|
600
|
+
The upstream scheme is always HTTPS. The proxy forwards the request path and query for `GET`, `HEAD`,
|
|
601
|
+
`POST`, `PUT`, `PATCH`, `DELETE`, and `OPTIONS`, while removing proxy credentials plus `Host` and
|
|
602
|
+
`Accept-Encoding` before the upstream request.
|
|
603
|
+
|
|
544
604
|
The proxy preserves the upstream status, body, and safe end-to-end response headers such as `Content-Type`, `ETag`, and application-specific metadata. It removes hop-by-hop, connection-nominated, proxy-authentication, and framing headers; `Set-Cookie`, `Location`, and `Content-Encoding` are not forwarded.
|
|
545
605
|
|
|
546
606
|
Define your server:
|
|
@@ -783,9 +843,28 @@ These proxies can simulate different situations. Available proxy kinds are:
|
|
|
783
843
|
Custom proxy kinds can be registered through `Nonnative.proxies`:
|
|
784
844
|
|
|
785
845
|
```ruby
|
|
846
|
+
class CustomProxy < Nonnative::Proxy
|
|
847
|
+
# Inherit #initialize(service), or call super from a custom initializer.
|
|
848
|
+
def start; end
|
|
849
|
+
def stop; end
|
|
850
|
+
def reset; end
|
|
851
|
+
end
|
|
852
|
+
|
|
786
853
|
Nonnative.proxies['custom'] = CustomProxy
|
|
854
|
+
|
|
855
|
+
Nonnative.configure do |config|
|
|
856
|
+
config.service do |s|
|
|
857
|
+
s.name = 'dependency'
|
|
858
|
+
s.host = '127.0.0.1'
|
|
859
|
+
s.port = 12_345
|
|
860
|
+
s.proxy.kind = 'custom'
|
|
861
|
+
end
|
|
862
|
+
end
|
|
787
863
|
```
|
|
788
864
|
|
|
865
|
+
Custom proxies must accept the service configuration and implement `start`, `stop`, and `reset`;
|
|
866
|
+
Nonnative invokes those methods during service lifecycle and pool reset.
|
|
867
|
+
|
|
789
868
|
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.
|
|
790
869
|
When service readiness is configured for a proxied dependency, set the readiness `host`/`port` to the upstream dependency, not the client-facing proxy listener.
|
|
791
870
|
|
|
@@ -856,6 +935,10 @@ Clients connect to the service `host`/`port`, while the proxy forwards traffic t
|
|
|
856
935
|
|
|
857
936
|
###### 🧩 Fault Injection Services
|
|
858
937
|
|
|
938
|
+
> [!WARNING]
|
|
939
|
+
> Every proxy state change closes active client connections so that new connections observe the new
|
|
940
|
+
> state. Apply the state before connecting, and reconnect after changing or resetting it.
|
|
941
|
+
|
|
859
942
|
Set the proxy state programmatically:
|
|
860
943
|
|
|
861
944
|
```ruby
|
|
@@ -902,6 +985,11 @@ YAML `go:` configuration is for Go test binaries compiled with `go test -c`. It
|
|
|
902
985
|
> [!IMPORTANT]
|
|
903
986
|
> If `tools` is omitted or empty, Nonnative enables all Go tools: `prof`, `trace`, and `cover`. Provide a subset, such as `tools: [cover]`, to limit the generated `-test.*` flags.
|
|
904
987
|
|
|
988
|
+
Create the configured `output` directory before starting Nonnative; the helper does not create it and
|
|
989
|
+
the Go test binary must be able to write there. Artifact names include the executable basename
|
|
990
|
+
without its extension, the command, and a random four-character suffix, for example
|
|
991
|
+
`reports/your_binary-sub_command-Ab12-cpu.prof`.
|
|
992
|
+
|
|
905
993
|
To get this to work you will need to create a `main_test.go` file with these contents:
|
|
906
994
|
|
|
907
995
|
```go
|
|
@@ -60,14 +60,15 @@ module Nonnative
|
|
|
60
60
|
# @return [Array<Nonnative::ConfigurationServer>] configured in-process servers
|
|
61
61
|
attr_accessor :servers
|
|
62
62
|
|
|
63
|
-
# @return [Array<Nonnative::ConfigurationService>] configured
|
|
63
|
+
# @return [Array<Nonnative::ConfigurationService>] configured external dependencies
|
|
64
64
|
attr_accessor :services
|
|
65
65
|
|
|
66
66
|
# Loads a configuration file and appends its runners to this instance.
|
|
67
67
|
#
|
|
68
|
-
# The file is loaded using safe YAML parsing. ERB
|
|
69
|
-
# arbitrary object deserialization is not allowed,
|
|
70
|
-
#
|
|
68
|
+
# The file is loaded using safe YAML parsing. ERB and environment placeholders are not evaluated,
|
|
69
|
+
# arbitrary object deserialization is not allowed, and unknown structural keys may be ignored.
|
|
70
|
+
# Top-level supported attributes are copied onto this object, and runner sections are transformed
|
|
71
|
+
# into configuration runner objects.
|
|
71
72
|
#
|
|
72
73
|
# @param path [String] path to a configuration file (typically YAML)
|
|
73
74
|
# @return [void]
|
|
@@ -108,8 +109,8 @@ module Nonnative
|
|
|
108
109
|
|
|
109
110
|
# Adds a service configuration entry.
|
|
110
111
|
#
|
|
111
|
-
# A "service" does not manage a Ruby thread or OS
|
|
112
|
-
# and
|
|
112
|
+
# A "service" represents an externally managed dependency. It does not manage a Ruby thread or OS
|
|
113
|
+
# process, but it can wait for TCP readiness and optionally control a proxy for the dependency.
|
|
113
114
|
#
|
|
114
115
|
# @yieldparam service [Nonnative::ConfigurationService]
|
|
115
116
|
# @return [void]
|
|
@@ -11,6 +11,8 @@ module Nonnative
|
|
|
11
11
|
# @see Nonnative::Configuration
|
|
12
12
|
# @see Nonnative::Process
|
|
13
13
|
class ConfigurationProcess < ConfigurationRunner
|
|
14
|
+
# The child inherits the parent Ruby process's working directory.
|
|
15
|
+
#
|
|
14
16
|
# @return [Proc] a callable that returns the command to execute
|
|
15
17
|
# as a shell string or argv array
|
|
16
18
|
# (e.g. `-> { "./bin/api" }` or `-> { ["./bin/api", "--port", "8080"] }`)
|
|
@@ -19,12 +21,15 @@ module Nonnative
|
|
|
19
21
|
# @return [String, nil] signal name to use for stopping (defaults to `"INT"` when not set)
|
|
20
22
|
attr_accessor :signal
|
|
21
23
|
|
|
22
|
-
# @return [Numeric]
|
|
24
|
+
# @return [Numeric] timeout (seconds) independently applied to child exit, port checks, and each
|
|
25
|
+
# optional HTTP/gRPC readiness probe (defaults to `1.0`)
|
|
23
26
|
attr_accessor :timeout
|
|
24
27
|
|
|
25
28
|
# @return [String] log file path to append process stdout/stderr to
|
|
26
29
|
attr_accessor :log
|
|
27
30
|
|
|
31
|
+
# Values are stringified and override matching variables in the inherited parent environment.
|
|
32
|
+
#
|
|
28
33
|
# @return [Hash, nil] environment variables to pass to the spawned process
|
|
29
34
|
attr_accessor :environment
|
|
30
35
|
|
|
@@ -4,7 +4,9 @@ module Nonnative
|
|
|
4
4
|
# Readiness check configuration for a managed process.
|
|
5
5
|
#
|
|
6
6
|
# Readiness is optional. When present, each check declares a `kind` and explicit endpoint details
|
|
7
|
-
# to poll after TCP readiness succeeds.
|
|
7
|
+
# to poll after TCP readiness succeeds. HTTP checks issue a plain unauthenticated GET and accept a
|
|
8
|
+
# final 2xx response. gRPC checks use the standard health Check over an insecure channel and accept
|
|
9
|
+
# only SERVING. Non-ready results are retried until the process timeout elapses.
|
|
8
10
|
class ConfigurationReadiness
|
|
9
11
|
KINDS = %w[http grpc].freeze
|
|
10
12
|
|
|
@@ -12,7 +12,7 @@ module Nonnative
|
|
|
12
12
|
# @see Nonnative::ConfigurationServer
|
|
13
13
|
# @see Nonnative::ConfigurationService
|
|
14
14
|
class ConfigurationRunner
|
|
15
|
-
# Default bounded readiness
|
|
15
|
+
# Default bounded lifecycle/readiness timeout for configured runners.
|
|
16
16
|
DEFAULT_TIMEOUT = 1.0
|
|
17
17
|
|
|
18
18
|
# @return [String, nil] runner name used for lookup (for example via `pool.process_by_name`)
|
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
module Nonnative
|
|
4
4
|
# Service-specific configuration.
|
|
5
5
|
#
|
|
6
|
-
# A "service"
|
|
7
|
-
#
|
|
6
|
+
# A "service" represents an externally managed dependency. It does not start a Ruby thread or OS
|
|
7
|
+
# process. It can wait for TCP readiness and can optionally run a proxy in front of the dependency.
|
|
8
8
|
#
|
|
9
9
|
# Instances are usually created through {Nonnative::Configuration#service}.
|
|
10
10
|
#
|
|
11
11
|
# @see Nonnative::Configuration
|
|
12
12
|
# @see Nonnative::Service
|
|
13
13
|
class ConfigurationService < ConfigurationRunner
|
|
14
|
-
# @return [Integer] client-facing port used
|
|
14
|
+
# @return [Integer] client-facing dependency port, used as the listener when a proxy is enabled
|
|
15
15
|
attr_accessor :port
|
|
16
16
|
|
|
17
17
|
# @return [Numeric] readiness timeout (seconds) used when waiting for service readiness
|
data/lib/nonnative/error.rb
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
module Nonnative
|
|
4
4
|
# Parent error for all Nonnative errors.
|
|
5
5
|
#
|
|
6
|
-
#
|
|
6
|
+
# Rescue this type to handle Nonnative-specific errors. Public APIs may also raise standard Ruby,
|
|
7
|
+
# dependency, filesystem, or transport exceptions that are not subclasses of this class.
|
|
7
8
|
#
|
|
8
9
|
# @see Nonnative::StartError
|
|
9
10
|
# @see Nonnative::StopError
|
|
@@ -37,6 +37,8 @@ module Nonnative
|
|
|
37
37
|
# - `delay`: delay duration in seconds used by {#delay}
|
|
38
38
|
# - `jitter`: optional random offset (seconds) added in `-jitter..jitter` to each `delay` (a
|
|
39
39
|
# negative value uses its magnitude), so clients see variable latency instead of a flat value
|
|
40
|
+
# - `rate`: positive throughput limit in KB/s used by {#bandwidth}; absent or non-positive
|
|
41
|
+
# values forward at full speed
|
|
40
42
|
# - `bytes`: positive response byte limit used by {#limit_data}; absent or non-positive values
|
|
41
43
|
# use pass-through behavior
|
|
42
44
|
#
|
|
@@ -20,6 +20,7 @@ module Nonnative
|
|
|
20
20
|
# If `tools` is `nil` or empty, all tools (`prof`, `trace`, `cover`) are enabled.
|
|
21
21
|
#
|
|
22
22
|
# Parameter strings are parsed into argv words using shell-style quoting.
|
|
23
|
+
# The output directory must already exist and be writable; this helper does not create it.
|
|
23
24
|
#
|
|
24
25
|
# @example
|
|
25
26
|
# executable = Nonnative::GoExecutable.new(%w[prof cover], './svc.test', 'reports')
|
|
@@ -31,7 +32,7 @@ module Nonnative
|
|
|
31
32
|
class GoExecutable
|
|
32
33
|
# @param tools [Array<String>, nil] tool names to enable (see class docs)
|
|
33
34
|
# @param exec [String] path to the compiled Go test binary
|
|
34
|
-
# @param output [String] output directory for generated files
|
|
35
|
+
# @param output [String] existing writable output directory for generated files
|
|
35
36
|
def initialize(tools, exec, output)
|
|
36
37
|
@tools = tools.nil? || tools.empty? ? %w[prof trace cover] : tools
|
|
37
38
|
@exec = exec
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Nonnative
|
|
4
|
-
# Client helper for the standard gRPC health checking protocol.
|
|
4
|
+
# Client helper for the standard gRPC health checking protocol over an insecure plaintext channel.
|
|
5
|
+
# An empty or nil service name checks overall server health.
|
|
5
6
|
class GRPCHealth
|
|
6
7
|
NETWORK_ERRORS = [
|
|
7
8
|
GRPC::BadStatus,
|
|
@@ -10,7 +11,7 @@ module Nonnative
|
|
|
10
11
|
|
|
11
12
|
# @param host [String] gRPC server host
|
|
12
13
|
# @param port [Integer] gRPC server port
|
|
13
|
-
# @param service [String] gRPC health service name
|
|
14
|
+
# @param service [String, nil] gRPC health service name, or empty/nil for overall server health
|
|
14
15
|
# @param timeout [Numeric] default call timeout in seconds
|
|
15
16
|
def initialize(host:, port:, service:, timeout: 1)
|
|
16
17
|
@host = host
|
|
@@ -21,6 +22,8 @@ module Nonnative
|
|
|
21
22
|
|
|
22
23
|
# Calls the gRPC health check endpoint.
|
|
23
24
|
#
|
|
25
|
+
# gRPC status and network failures are propagated to the caller.
|
|
26
|
+
#
|
|
24
27
|
# @param deadline [Time] gRPC deadline
|
|
25
28
|
# @return [Grpc::Health::V1::HealthCheckResponse]
|
|
26
29
|
def check(deadline: Time.now + timeout)
|
|
@@ -29,6 +32,8 @@ module Nonnative
|
|
|
29
32
|
|
|
30
33
|
# Returns true when the gRPC health endpoint reports SERVING.
|
|
31
34
|
#
|
|
35
|
+
# Returns `false` for non-SERVING responses and gRPC status/network failures.
|
|
36
|
+
#
|
|
32
37
|
# @param deadline [Time] gRPC deadline
|
|
33
38
|
# @return [Boolean]
|
|
34
39
|
def serving?(deadline: Time.now + timeout)
|
|
@@ -36,9 +36,10 @@ module Nonnative
|
|
|
36
36
|
class HTTPServer < Nonnative::Server
|
|
37
37
|
# Creates a Puma server for the given HTTP service and runner configuration.
|
|
38
38
|
#
|
|
39
|
-
# @param http_service [#call, Hash<String, #call>] an HTTP service instance or
|
|
39
|
+
# @param http_service [#call, Hash<String, #call>] an HTTP service instance or `Rack::URLMap`
|
|
40
|
+
# mount map using path prefixes or host-qualified URLs
|
|
40
41
|
# @param service [Nonnative::ConfigurationServer] server configuration
|
|
41
|
-
# @raise [ArgumentError] if `http_service` is an empty mount map
|
|
42
|
+
# @raise [ArgumentError] if `http_service` is an empty mount map or contains an invalid mount path
|
|
42
43
|
def initialize(http_service, service)
|
|
43
44
|
if http_service.is_a?(Hash)
|
|
44
45
|
raise ArgumentError, 'HTTP server requires at least one service mount' if http_service.empty?
|
|
@@ -13,7 +13,9 @@ module Nonnative
|
|
|
13
13
|
# - `/<name>/metrics`
|
|
14
14
|
#
|
|
15
15
|
# Requests are performed using {Nonnative::HTTPClient}, so callers may pass RestClient options
|
|
16
|
-
# such as `headers`, `open_timeout`, and `read_timeout`.
|
|
16
|
+
# such as `headers`, `open_timeout`, and `read_timeout`. HTTP error statuses are returned as response
|
|
17
|
+
# objects; timeouts and broken connections raise their RestClient exceptions. Requests are not
|
|
18
|
+
# retried automatically.
|
|
17
19
|
#
|
|
18
20
|
# @example
|
|
19
21
|
# Nonnative.configure do |config|
|
data/lib/nonnative/pool.rb
CHANGED
|
@@ -6,10 +6,11 @@ module Nonnative
|
|
|
6
6
|
# A pool is created when {Nonnative.start} is called and is accessible via {Nonnative.pool}.
|
|
7
7
|
#
|
|
8
8
|
# Lifecycle order is important:
|
|
9
|
-
# - On start: services
|
|
10
|
-
# - On stop: processes
|
|
9
|
+
# - On start: services, then servers, then processes; each tier completes readiness before the next
|
|
10
|
+
# - On stop: processes, then servers, then services
|
|
11
11
|
#
|
|
12
|
-
# Readiness
|
|
12
|
+
# Readiness uses TCP port checks plus optional process HTTP/gRPC probes and optional service TCP
|
|
13
|
+
# checks. Shutdown uses configured TCP port checks.
|
|
13
14
|
#
|
|
14
15
|
# @see Nonnative.start
|
|
15
16
|
# @see Nonnative.stop
|
|
@@ -25,9 +26,9 @@ module Nonnative
|
|
|
25
26
|
|
|
26
27
|
# Starts all configured runners and collects lifecycle and readiness errors.
|
|
27
28
|
#
|
|
28
|
-
#
|
|
29
|
-
# started and checked for readiness. Each readiness failure is described
|
|
30
|
-
# that exited early, its termination detail.
|
|
29
|
+
# Externally managed services are handled first and checked for opt-in readiness. Servers are then
|
|
30
|
+
# started and checked for readiness, followed by processes. Each readiness failure is described
|
|
31
|
+
# with the runner and, for a process that exited early, its termination detail.
|
|
31
32
|
#
|
|
32
33
|
# @return [Array<String>] lifecycle and readiness-check errors collected while starting
|
|
33
34
|
def start
|
|
@@ -45,7 +46,8 @@ module Nonnative
|
|
|
45
46
|
|
|
46
47
|
# Stops all configured runners and collects lifecycle and shutdown errors.
|
|
47
48
|
#
|
|
48
|
-
# Processes and servers are stopped first and checked for shutdown, then
|
|
49
|
+
# Processes and servers are stopped first and checked for shutdown, then service proxies are
|
|
50
|
+
# stopped when configured.
|
|
49
51
|
#
|
|
50
52
|
# @return [Array<String>] lifecycle and shutdown-check errors collected while stopping
|
|
51
53
|
def stop
|
data/lib/nonnative/ports.rb
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Nonnative
|
|
4
|
-
# Performs aggregate
|
|
4
|
+
# Performs aggregate readiness and shutdown checks for a configured runner.
|
|
5
5
|
#
|
|
6
|
-
# A runner is considered ready only when every configured port is open
|
|
7
|
-
# configured port is closed.
|
|
6
|
+
# A runner is considered ready only when every configured port is open and every configured
|
|
7
|
+
# HTTP/gRPC readiness probe reports ready, and stopped only when every configured port is closed.
|
|
8
8
|
#
|
|
9
9
|
# @see Nonnative::Port
|
|
10
10
|
class Ports
|
|
@@ -15,7 +15,8 @@ module Nonnative
|
|
|
15
15
|
@readiness = readiness_probes
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
# Returns whether all configured ports become connectable
|
|
18
|
+
# Returns whether all configured ports become connectable and all configured HTTP/gRPC readiness
|
|
19
|
+
# probes report ready before their timeouts elapse.
|
|
19
20
|
#
|
|
20
21
|
# @return [Boolean]
|
|
21
22
|
def open?
|
data/lib/nonnative/process.rb
CHANGED
|
@@ -6,7 +6,8 @@ module Nonnative
|
|
|
6
6
|
# A process runner:
|
|
7
7
|
# - spawns a child process using the configured command and environment,
|
|
8
8
|
# - waits briefly (via the runner `wait`), and
|
|
9
|
-
# - participates in readiness/shutdown
|
|
9
|
+
# - participates in TCP readiness/shutdown checks plus optional HTTP/gRPC readiness probes
|
|
10
|
+
# orchestrated by {Nonnative::Pool}.
|
|
10
11
|
#
|
|
11
12
|
# The underlying configuration is a {Nonnative::ConfigurationProcess}.
|
|
12
13
|
#
|
|
@@ -46,6 +47,8 @@ module Nonnative
|
|
|
46
47
|
# Stops the process if it is running.
|
|
47
48
|
#
|
|
48
49
|
# The process is signalled using the configured signal (defaults to `INT` when not set).
|
|
50
|
+
# If it does not exit before the configured timeout, it is killed and the returned success value
|
|
51
|
+
# is `false`; {Nonnative.stop} reports that outcome as a {Nonnative::StopError}.
|
|
49
52
|
#
|
|
50
53
|
# @return [Array<(Integer, Boolean)>]
|
|
51
54
|
# a tuple of:
|
data/lib/nonnative/proxy.rb
CHANGED
|
@@ -10,7 +10,8 @@ module Nonnative
|
|
|
10
10
|
# implementation-specific; for example {Nonnative::FaultInjectionProxy#host} and
|
|
11
11
|
# {Nonnative::FaultInjectionProxy#port} return the upstream target behind the proxy.
|
|
12
12
|
#
|
|
13
|
-
#
|
|
13
|
+
# Custom proxies must accept the service configuration in `initialize` and implement these public
|
|
14
|
+
# methods, which the service and pool invoke unconditionally:
|
|
14
15
|
# - `start`: begin proxying (bind/listen, start threads, etc)
|
|
15
16
|
# - `stop`: stop proxying and release resources
|
|
16
17
|
# - `reset`: return proxy behavior to a healthy/default state
|
data/lib/nonnative/runner.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Nonnative
|
|
|
8
8
|
#
|
|
9
9
|
# - {Nonnative::Process} for OS-level child processes
|
|
10
10
|
# - {Nonnative::Server} for in-process Ruby servers (threads)
|
|
11
|
-
# - {Nonnative::Service} for
|
|
11
|
+
# - {Nonnative::Service} for externally managed dependencies with optional readiness/proxying
|
|
12
12
|
#
|
|
13
13
|
# @see Nonnative::Process
|
|
14
14
|
# @see Nonnative::Server
|
data/lib/nonnative/service.rb
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
module Nonnative
|
|
4
4
|
# Runtime runner for an external dependency.
|
|
5
5
|
#
|
|
6
|
-
# A service runner does not manage an OS process or Ruby thread. It
|
|
7
|
-
#
|
|
8
|
-
#
|
|
6
|
+
# A service runner does not manage an OS process or Ruby thread. It represents a dependency managed
|
|
7
|
+
# elsewhere (for example a database running in Docker), supports optional TCP readiness, and manages
|
|
8
|
+
# an optional proxy lifecycle (start/stop/reset).
|
|
9
9
|
#
|
|
10
10
|
# The underlying configuration is a {Nonnative::ConfigurationService}.
|
|
11
11
|
#
|
|
@@ -4,7 +4,9 @@ module Nonnative
|
|
|
4
4
|
# Base socket-pair implementation used by TCP proxies.
|
|
5
5
|
#
|
|
6
6
|
# A socket-pair connects an accepted local socket to a remote upstream socket and forwards bytes
|
|
7
|
-
# in both directions
|
|
7
|
+
# in both directions. When one direction reaches EOF (for example a client that half-closes its
|
|
8
|
+
# write side after sending a request), the paired write side is closed and the other direction
|
|
9
|
+
# keeps forwarding until it too ends, so an in-flight response is delivered rather than dropped.
|
|
8
10
|
#
|
|
9
11
|
# This is used by {Nonnative::FaultInjectionProxy} to implement pass-through forwarding, and is
|
|
10
12
|
# subclassed to inject failures (close immediately, delay reads, corrupt writes, etc).
|
|
@@ -31,12 +33,14 @@ module Nonnative
|
|
|
31
33
|
def connect(local_socket)
|
|
32
34
|
@local_socket = local_socket
|
|
33
35
|
@remote_socket = create_remote_socket
|
|
36
|
+
@open = [@local_socket, @remote_socket]
|
|
34
37
|
|
|
35
38
|
loop do
|
|
36
|
-
ready = select(
|
|
39
|
+
ready = select(@open, nil, nil)
|
|
37
40
|
|
|
38
41
|
break if pipe?(ready, @local_socket, @remote_socket)
|
|
39
42
|
break if pipe?(ready, @remote_socket, @local_socket)
|
|
43
|
+
break if @open.empty?
|
|
40
44
|
end
|
|
41
45
|
ensure
|
|
42
46
|
Nonnative.logger.info "finished connect for local socket '#{@local_socket.inspect}' and '#{@remote_socket&.inspect}' for 'socket_pair'"
|
|
@@ -68,6 +72,10 @@ module Nonnative
|
|
|
68
72
|
|
|
69
73
|
# Pipes data from `source_socket` to `destination_socket` when the source is readable.
|
|
70
74
|
#
|
|
75
|
+
# On EOF the source is half-closed rather than terminating the whole connection: the destination
|
|
76
|
+
# write side is closed and the source is removed from the forwarding set, so the opposite
|
|
77
|
+
# direction keeps forwarding until it also ends.
|
|
78
|
+
#
|
|
71
79
|
# @param ready [Array<Array<IO>>] the result from `select`
|
|
72
80
|
# @param source_socket [IO] readable side
|
|
73
81
|
# @param destination_socket [IO] writable side
|
|
@@ -75,7 +83,7 @@ module Nonnative
|
|
|
75
83
|
def pipe?(ready, source_socket, destination_socket)
|
|
76
84
|
if ready[0].include?(source_socket)
|
|
77
85
|
data = read(source_socket)
|
|
78
|
-
return
|
|
86
|
+
return half_close(source_socket, destination_socket) if data.empty?
|
|
79
87
|
|
|
80
88
|
write destination_socket, data
|
|
81
89
|
end
|
|
@@ -104,6 +112,21 @@ module Nonnative
|
|
|
104
112
|
socket.write(data)
|
|
105
113
|
end
|
|
106
114
|
|
|
115
|
+
# Half-closes one direction after its source reaches EOF: closes the destination write side and
|
|
116
|
+
# removes the source from the forwarding set, so the opposite direction keeps forwarding.
|
|
117
|
+
#
|
|
118
|
+
# @param source_socket [IO] the side that reached EOF
|
|
119
|
+
# @param destination_socket [IO] the paired side whose write half is closed
|
|
120
|
+
# @return [false] so the forwarding loop continues until both directions have ended
|
|
121
|
+
def half_close(source_socket, destination_socket)
|
|
122
|
+
@open.delete(source_socket)
|
|
123
|
+
destination_socket.close_write
|
|
124
|
+
|
|
125
|
+
false
|
|
126
|
+
rescue IOError, SystemCallError
|
|
127
|
+
false
|
|
128
|
+
end
|
|
129
|
+
|
|
107
130
|
def close_socket(socket)
|
|
108
131
|
return if socket.nil? || socket.closed?
|
|
109
132
|
|
data/lib/nonnative/version.rb
CHANGED
data/lib/nonnative.rb
CHANGED
|
@@ -188,7 +188,7 @@ module Nonnative
|
|
|
188
188
|
# Use this when passing a command string directly to `spawn`.
|
|
189
189
|
#
|
|
190
190
|
# @param tools [Array<String>] enabled tool names (e.g. `["prof", "trace", "cover"]`)
|
|
191
|
-
# @param output [String] directory where outputs should be written
|
|
191
|
+
# @param output [String] existing writable directory where outputs should be written
|
|
192
192
|
# @param exec [String] the test binary (or wrapper) to execute
|
|
193
193
|
# @param cmd [String] the command argument passed to the test binary
|
|
194
194
|
# @param params [Array<String>] extra parameter strings for the command
|
|
@@ -202,7 +202,7 @@ module Nonnative
|
|
|
202
202
|
# Use this when passing argv entries directly to `spawn`.
|
|
203
203
|
#
|
|
204
204
|
# @param tools [Array<String>] enabled tool names (e.g. `["prof", "trace", "cover"]`)
|
|
205
|
-
# @param output [String] directory where outputs should be written
|
|
205
|
+
# @param output [String] existing writable directory where outputs should be written
|
|
206
206
|
# @param exec [String] the test binary (or wrapper) to execute
|
|
207
207
|
# @param cmd [String] the command argument passed to the test binary
|
|
208
208
|
# @param params [Array<String>] extra parameter strings for the command
|
|
@@ -237,7 +237,7 @@ module Nonnative
|
|
|
237
237
|
#
|
|
238
238
|
# @param host [String] gRPC server host
|
|
239
239
|
# @param port [Integer] gRPC server port
|
|
240
|
-
# @param service [String] gRPC health service name
|
|
240
|
+
# @param service [String, nil] gRPC health service name, or empty/nil for overall server health
|
|
241
241
|
# @param timeout [Numeric] default call timeout in seconds
|
|
242
242
|
# @return [Nonnative::GRPCHealth]
|
|
243
243
|
def grpc_health(host:, port:, service:, timeout: 1) = Nonnative::GRPCHealth.new(host: host, port: port, service: service, timeout: timeout)
|
|
@@ -265,7 +265,8 @@ module Nonnative
|
|
|
265
265
|
|
|
266
266
|
# Starts all configured services, servers, and processes, and waits for readiness.
|
|
267
267
|
#
|
|
268
|
-
# Readiness is determined by
|
|
268
|
+
# Readiness is determined by port checks, plus optional process HTTP/gRPC readiness and optional
|
|
269
|
+
# service TCP readiness.
|
|
269
270
|
#
|
|
270
271
|
# @return [void]
|
|
271
272
|
# @raise [Nonnative::StartError] if one or more runners fail to start or become ready in time
|
|
@@ -325,6 +326,8 @@ module Nonnative
|
|
|
325
326
|
|
|
326
327
|
# Clears the memoized pool instance.
|
|
327
328
|
#
|
|
329
|
+
# This does not stop runners owned by the pool. Call {Nonnative.stop} before clearing a live pool.
|
|
330
|
+
#
|
|
328
331
|
# @return [void]
|
|
329
332
|
def clear_pool
|
|
330
333
|
@pool = nil
|
|
@@ -332,8 +335,9 @@ module Nonnative
|
|
|
332
335
|
|
|
333
336
|
# Clears memoized configuration, logger, observability client, and pool.
|
|
334
337
|
#
|
|
335
|
-
#
|
|
336
|
-
#
|
|
338
|
+
# This does not stop processes, server threads, or proxies. To reconfigure in the same Ruby
|
|
339
|
+
# process, call {Nonnative.stop}, then `clear`, configure the next system, and call
|
|
340
|
+
# {Nonnative.start}.
|
|
337
341
|
#
|
|
338
342
|
# @return [void]
|
|
339
343
|
def clear
|