nonnative 2.13.0 → 2.15.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 +4 -4
- data/AGENTS.md +7 -0
- data/Gemfile.lock +2 -2
- data/README.md +2 -0
- data/lib/nonnative/fault_injection_proxy.rb +63 -20
- data/lib/nonnative/grpc_server.rb +2 -2
- data/lib/nonnative/http_server.rb +2 -2
- data/lib/nonnative/socket_pair.rb +23 -7
- data/lib/nonnative/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f556739cd561141b772bdeaa9f7cdc8f9934d734bf7517d4e17ef16f2f809f15
|
|
4
|
+
data.tar.gz: fbba3f9c3f5902d2c385a8e509c27f681f6e44876d02ee7d63b00a66edb19a1d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 65e74315f3bb6e81b1c2797fce743c2f0741a52e2edd40ba608a30e4d582e08b1408008249dd76e81972a438054810390b709f785e6ef455b719ef89658a572c
|
|
7
|
+
data.tar.gz: 0e43f244c7867e12a512a71aea9f70f2fa0323c62f97f5c417512e131450a7fc714419cb2af0798a692848d5fe95111417ea263b80c1ce5a6f853663a0c03fce
|
data/.circleci/config.yml
CHANGED
|
@@ -3,7 +3,7 @@ version: 2.1
|
|
|
3
3
|
jobs:
|
|
4
4
|
build:
|
|
5
5
|
docker:
|
|
6
|
-
- image: alexfalkowski/ruby:2.
|
|
6
|
+
- image: alexfalkowski/ruby:2.12
|
|
7
7
|
working_directory: ~/nonnative
|
|
8
8
|
steps:
|
|
9
9
|
- checkout:
|
|
@@ -35,7 +35,7 @@ jobs:
|
|
|
35
35
|
resource_class: arm.large
|
|
36
36
|
sync:
|
|
37
37
|
docker:
|
|
38
|
-
- image: alexfalkowski/release:7.
|
|
38
|
+
- image: alexfalkowski/release:7.13
|
|
39
39
|
working_directory: ~/nonnative
|
|
40
40
|
steps:
|
|
41
41
|
- checkout:
|
|
@@ -46,7 +46,7 @@ jobs:
|
|
|
46
46
|
resource_class: arm.large
|
|
47
47
|
version:
|
|
48
48
|
docker:
|
|
49
|
-
- image: alexfalkowski/release:7.
|
|
49
|
+
- image: alexfalkowski/release:7.13
|
|
50
50
|
working_directory: ~/nonnative
|
|
51
51
|
steps:
|
|
52
52
|
- checkout:
|
|
@@ -58,7 +58,7 @@ jobs:
|
|
|
58
58
|
resource_class: arm.large
|
|
59
59
|
wait-all:
|
|
60
60
|
docker:
|
|
61
|
-
- image: alexfalkowski/ruby:2.
|
|
61
|
+
- image: alexfalkowski/ruby:2.12
|
|
62
62
|
steps:
|
|
63
63
|
- run: echo "all applicable jobs finished"
|
|
64
64
|
resource_class: arm.large
|
data/AGENTS.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
This repo is the `nonnative` Ruby gem: a Ruby-first harness for end-to-end testing of systems implemented in other languages by starting processes/servers/services, waiting on TCP port readiness, and optionally putting fault-injection proxies in front of them.
|
|
4
4
|
|
|
5
|
+
## Shared skill
|
|
6
|
+
|
|
7
|
+
Use the shared `coding-standards` skill from `./bin/skills/coding-standards`
|
|
8
|
+
for cross-repository coding, review, testing, documentation, and PR
|
|
9
|
+
conventions. Treat this `AGENTS.md` as the repo-specific companion to that
|
|
10
|
+
skill.
|
|
11
|
+
|
|
5
12
|
## Quick map
|
|
6
13
|
|
|
7
14
|
- Library code: `lib/nonnative/**/*.rb`
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -681,6 +681,8 @@ services:
|
|
|
681
681
|
|
|
682
682
|
The `fault_injection` proxy allows you to simulate failures by injecting them. We currently support the following:
|
|
683
683
|
|
|
684
|
+
Clients connect to the runner `host`/`port`, while the proxy forwards traffic to nested `proxy.host`/`proxy.port`.
|
|
685
|
+
|
|
684
686
|
- `close_all` - Closes the socket as soon as it connects.
|
|
685
687
|
- `delay` - This delays the communication between the connection. Default is 2 secs can be configured through options.
|
|
686
688
|
- `invalid_data` - This takes the input and rearranges it to produce invalid data.
|
|
@@ -18,15 +18,15 @@ module Nonnative
|
|
|
18
18
|
#
|
|
19
19
|
# ## Wiring
|
|
20
20
|
#
|
|
21
|
-
# When enabled, your test/client should
|
|
22
|
-
# and the proxy will
|
|
21
|
+
# When enabled, your test/client should connect to the runner `host` / `port` (the proxy endpoint),
|
|
22
|
+
# and the proxy will forward traffic to the upstream target exposed by {#host}:{#port}.
|
|
23
23
|
#
|
|
24
24
|
# ## Configuration
|
|
25
25
|
#
|
|
26
26
|
# The proxy is configured via the runner’s `proxy` hash:
|
|
27
27
|
#
|
|
28
28
|
# - `kind`: `"fault_injection"`
|
|
29
|
-
# - `host` / `port`:
|
|
29
|
+
# - `host` / `port`: upstream target behind the proxy (exposed via {#host}/{#port})
|
|
30
30
|
# - `log`: file path used by this proxy’s internal logger
|
|
31
31
|
# - `wait`: sleep interval (seconds) applied after state changes
|
|
32
32
|
# - `options`:
|
|
@@ -35,6 +35,21 @@ module Nonnative
|
|
|
35
35
|
# @see Nonnative::Proxy
|
|
36
36
|
# @see Nonnative::SocketPairFactory
|
|
37
37
|
class FaultInjectionProxy < Nonnative::Proxy
|
|
38
|
+
class Connection
|
|
39
|
+
attr_accessor :pair, :thread
|
|
40
|
+
attr_reader :socket
|
|
41
|
+
|
|
42
|
+
def initialize(socket)
|
|
43
|
+
@socket = socket
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def close
|
|
47
|
+
pair&.close
|
|
48
|
+
socket.close unless socket.closed?
|
|
49
|
+
thread&.terminate
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
38
53
|
# @param service [Nonnative::ConfigurationRunner] runner configuration with proxy settings
|
|
39
54
|
def initialize(service)
|
|
40
55
|
@connections = Concurrent::Hash.new
|
|
@@ -48,7 +63,7 @@ module Nonnative
|
|
|
48
63
|
# Starts the proxy accept loop in a background thread.
|
|
49
64
|
#
|
|
50
65
|
# This binds a TCP server on the underlying runner’s `service.host` / `service.port`.
|
|
51
|
-
# Clients
|
|
66
|
+
# Clients connect to that runner endpoint, while upstream traffic is forwarded to {#host}:{#port}.
|
|
52
67
|
#
|
|
53
68
|
# @return [void]
|
|
54
69
|
def start
|
|
@@ -58,12 +73,21 @@ module Nonnative
|
|
|
58
73
|
Nonnative.logger.info "started with host '#{service.host}' and port '#{service.port}' for proxy 'fault_injection'"
|
|
59
74
|
end
|
|
60
75
|
|
|
61
|
-
# Stops the proxy and closes its listening socket.
|
|
76
|
+
# Stops the proxy, closes active connections, and closes its listening socket.
|
|
62
77
|
#
|
|
63
78
|
# @return [void]
|
|
64
79
|
def stop
|
|
65
|
-
|
|
66
|
-
|
|
80
|
+
mutex.synchronize do
|
|
81
|
+
close_connections
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
server = @tcp_server
|
|
85
|
+
@tcp_server = nil
|
|
86
|
+
server&.close
|
|
87
|
+
|
|
88
|
+
listener_thread = @thread
|
|
89
|
+
@thread = nil
|
|
90
|
+
listener_thread&.join
|
|
67
91
|
|
|
68
92
|
Nonnative.logger.info "stopped with host '#{service.host}' and port '#{service.port}' for proxy 'fault_injection'"
|
|
69
93
|
end
|
|
@@ -98,14 +122,14 @@ module Nonnative
|
|
|
98
122
|
apply_state :none
|
|
99
123
|
end
|
|
100
124
|
|
|
101
|
-
# Returns the host
|
|
125
|
+
# Returns the upstream host behind this proxy.
|
|
102
126
|
#
|
|
103
127
|
# @return [String]
|
|
104
128
|
def host
|
|
105
129
|
service.proxy.host
|
|
106
130
|
end
|
|
107
131
|
|
|
108
|
-
# Returns the port
|
|
132
|
+
# Returns the upstream port behind this proxy.
|
|
109
133
|
#
|
|
110
134
|
# @return [Integer]
|
|
111
135
|
def port
|
|
@@ -118,14 +142,16 @@ module Nonnative
|
|
|
118
142
|
|
|
119
143
|
def perform_start
|
|
120
144
|
loop do
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
145
|
+
local_socket = tcp_server.accept
|
|
146
|
+
id = local_socket.object_id
|
|
147
|
+
register_connection(id, local_socket)
|
|
148
|
+
connection_thread = Thread.start(local_socket) do |accepted_socket|
|
|
149
|
+
accept_connection id, accepted_socket
|
|
125
150
|
end
|
|
126
|
-
|
|
127
|
-
connections[thread.object_id] = thread
|
|
151
|
+
attach_connection_thread(id, connection_thread)
|
|
128
152
|
end
|
|
153
|
+
rescue IOError, Errno::EBADF
|
|
154
|
+
nil
|
|
129
155
|
end
|
|
130
156
|
|
|
131
157
|
def accept_connection(id, socket)
|
|
@@ -144,6 +170,7 @@ module Nonnative
|
|
|
144
170
|
Nonnative.logger.info "connecting for '#{id}' with socket '#{socket.inspect}' and state '#{state}' for proxy 'fault_injection'"
|
|
145
171
|
|
|
146
172
|
pair = SocketPairFactory.create(state, service.proxy)
|
|
173
|
+
attach_connection_pair(id, pair)
|
|
147
174
|
pair.connect(socket)
|
|
148
175
|
rescue StandardError => e
|
|
149
176
|
socket.close
|
|
@@ -152,12 +179,10 @@ module Nonnative
|
|
|
152
179
|
end
|
|
153
180
|
|
|
154
181
|
def close_connections
|
|
155
|
-
connections.each do |id,
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
thread.terminate
|
|
182
|
+
connections.each do |id, connection|
|
|
183
|
+
close_connection(id, connection)
|
|
159
184
|
end
|
|
160
|
-
|
|
185
|
+
ensure
|
|
161
186
|
connections.clear
|
|
162
187
|
end
|
|
163
188
|
|
|
@@ -177,5 +202,23 @@ module Nonnative
|
|
|
177
202
|
def read_state
|
|
178
203
|
mutex.synchronize { state }
|
|
179
204
|
end
|
|
205
|
+
|
|
206
|
+
def register_connection(id, socket)
|
|
207
|
+
connections[id] = Connection.new(socket)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def attach_connection_thread(id, thread)
|
|
211
|
+
connections[id]&.thread = thread
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def attach_connection_pair(id, pair)
|
|
215
|
+
connections[id]&.pair = pair
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def close_connection(id, connection)
|
|
219
|
+
Nonnative.logger.info "closing connection for '#{id}' for proxy 'fault_injection'"
|
|
220
|
+
|
|
221
|
+
connection.close
|
|
222
|
+
end
|
|
180
223
|
end
|
|
181
224
|
end
|
|
@@ -33,8 +33,8 @@ module Nonnative
|
|
|
33
33
|
|
|
34
34
|
# Binds the gRPC server and begins serving requests.
|
|
35
35
|
#
|
|
36
|
-
# The server binds to the proxy host/port so
|
|
37
|
-
#
|
|
36
|
+
# The server binds to the upstream proxy host/port so the fault-injection proxy can expose the
|
|
37
|
+
# runner host/port as the client-facing endpoint used by readiness checks.
|
|
38
38
|
#
|
|
39
39
|
# @return [void]
|
|
40
40
|
def perform_start
|
|
@@ -48,8 +48,8 @@ module Nonnative
|
|
|
48
48
|
|
|
49
49
|
# Binds the Puma server and begins serving.
|
|
50
50
|
#
|
|
51
|
-
# The listener binds to the proxy host/port so
|
|
52
|
-
#
|
|
51
|
+
# The listener binds to the upstream proxy host/port so the fault-injection proxy can expose the
|
|
52
|
+
# runner host/port as the client-facing endpoint used by readiness checks.
|
|
53
53
|
#
|
|
54
54
|
# @return [void]
|
|
55
55
|
def perform_start
|
|
@@ -28,19 +28,27 @@ module Nonnative
|
|
|
28
28
|
# @param local_socket [TCPSocket] the accepted client socket
|
|
29
29
|
# @return [void]
|
|
30
30
|
def connect(local_socket)
|
|
31
|
-
|
|
31
|
+
@local_socket = local_socket
|
|
32
|
+
@remote_socket = create_remote_socket
|
|
32
33
|
|
|
33
34
|
loop do
|
|
34
|
-
ready = select([local_socket, remote_socket], nil, nil)
|
|
35
|
+
ready = select([@local_socket, @remote_socket], nil, nil)
|
|
35
36
|
|
|
36
|
-
break if pipe?(ready, local_socket, remote_socket)
|
|
37
|
-
break if pipe?(ready, remote_socket, local_socket)
|
|
37
|
+
break if pipe?(ready, @local_socket, @remote_socket)
|
|
38
|
+
break if pipe?(ready, @remote_socket, @local_socket)
|
|
38
39
|
end
|
|
39
40
|
ensure
|
|
40
|
-
Nonnative.logger.info "finished connect for local socket '#{local_socket.inspect}' and '#{remote_socket&.inspect}' for 'socket_pair'"
|
|
41
|
+
Nonnative.logger.info "finished connect for local socket '#{@local_socket.inspect}' and '#{@remote_socket&.inspect}' for 'socket_pair'"
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
close
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Closes any open sockets managed by this pair.
|
|
47
|
+
#
|
|
48
|
+
# @return [void]
|
|
49
|
+
def close
|
|
50
|
+
close_socket @local_socket
|
|
51
|
+
close_socket @remote_socket
|
|
44
52
|
end
|
|
45
53
|
|
|
46
54
|
protected
|
|
@@ -94,5 +102,13 @@ module Nonnative
|
|
|
94
102
|
def write(socket, data)
|
|
95
103
|
socket.write(data)
|
|
96
104
|
end
|
|
105
|
+
|
|
106
|
+
def close_socket(socket)
|
|
107
|
+
return if socket.nil? || socket.closed?
|
|
108
|
+
|
|
109
|
+
socket.close
|
|
110
|
+
rescue IOError
|
|
111
|
+
nil
|
|
112
|
+
end
|
|
97
113
|
end
|
|
98
114
|
end
|
data/lib/nonnative/version.rb
CHANGED
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: 2.
|
|
4
|
+
version: 2.15.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alejandro Falkowski
|
|
@@ -345,7 +345,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
345
345
|
- !ruby/object:Gem::Version
|
|
346
346
|
version: '0'
|
|
347
347
|
requirements: []
|
|
348
|
-
rubygems_version: 4.0.
|
|
348
|
+
rubygems_version: 4.0.10
|
|
349
349
|
specification_version: 4
|
|
350
350
|
summary: Allows you to keep using the power of ruby to test other systems
|
|
351
351
|
test_files: []
|