nonnative 2.13.0 → 2.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4bf1427ceb9bd3e1a93d7879934477938eb677a12f1687439e99d477ed32ddfa
4
- data.tar.gz: c8db117445b7de2b9f16eec5fb36839aeeb6b0496f4e8077d9ce70852fe2b303
3
+ metadata.gz: 7f6e8c25babd4ee199097263400e00ebcc5617a82655d8943985f39abed58c86
4
+ data.tar.gz: 8717d16d7759563f38255e0134292c8e16838cdde145db2623555b8551d84fee
5
5
  SHA512:
6
- metadata.gz: a0c388775687f4f0832200cddf41def76228097216a80485554ed1874be54a66564946c3bdde65b3c78ce651c6664794c83d8b59e66c24e1719fb0c482295f08
7
- data.tar.gz: 150f9db8f08b7ef7ea0697be96e1f4a4c779b3674d66283a7461b0f3037fc448baa3886d42f8984ddee47190ed3269d9c659badb7b333da859ce31e685c82a47
6
+ metadata.gz: 50a983a80efad1e7a79ee2c00e6115d3d972da2301facacc8b77f982bc888c477d07920569eb486d51e424cb58e05c0939135ebd337728c00fa214a3c3d1d10e
7
+ data.tar.gz: 1ad10d708a5fc5996e9e8597c936f3efcb0365316d75bb74de6585de82c56464d7d191a484440914968093bd6eb58564f2007469ff87443e79eb3a9dd496afca
data/.circleci/config.yml CHANGED
@@ -35,7 +35,7 @@ jobs:
35
35
  resource_class: arm.large
36
36
  sync:
37
37
  docker:
38
- - image: alexfalkowski/release:7.6
38
+ - image: alexfalkowski/release:7.7
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.6
49
+ - image: alexfalkowski/release:7.7
50
50
  working_directory: ~/nonnative
51
51
  steps:
52
52
  - checkout:
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- nonnative (2.13.0)
4
+ nonnative (2.13.1)
5
5
  concurrent-ruby (>= 1, < 2)
6
6
  config (>= 5, < 6)
7
7
  cucumber (>= 7, < 12)
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 typically connect to {#host}:{#port} (the proxy endpoint),
22
- # and the proxy will connect onward to the underlying service.
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`: where the proxy should be reached by clients (exposed via {#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 should connect to {#host}:{#port}.
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
- thread&.terminate
66
- tcp_server&.close
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 clients should connect to when using this proxy.
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 clients should connect to when using this proxy.
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
- thread = Thread.start(tcp_server.accept) do |local_socket|
122
- id = Thread.current.object_id
123
-
124
- accept_connection id, local_socket
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, thread|
156
- Nonnative.logger.info "closing connection for '#{id}' for proxy 'fault_injection'"
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 that enabling a proxy results in traffic and readiness
37
- # checks consistently targeting the proxy endpoint.
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 that enabling a proxy results in traffic and
52
- # readiness checks consistently targeting the proxy endpoint.
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
- remote_socket = create_remote_socket
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
- local_socket.close
43
- remote_socket&.close
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
@@ -4,5 +4,5 @@ module Nonnative
4
4
  # The current gem version.
5
5
  #
6
6
  # @return [String]
7
- VERSION = '2.13.0'
7
+ VERSION = '2.13.1'
8
8
  end
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.13.0
4
+ version: 2.13.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alejandro Falkowski