nonnative 1.27.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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +38 -0
  3. data/.config/cucumber.yml +1 -0
  4. data/.editorconfig +24 -0
  5. data/.gitignore +11 -0
  6. data/.rubocop.yml +87 -0
  7. data/.ruby-version +1 -0
  8. data/Gemfile +8 -0
  9. data/Gemfile.lock +202 -0
  10. data/LICENSE +24 -0
  11. data/Makefile +27 -0
  12. data/README.md +366 -0
  13. data/Rakefile +4 -0
  14. data/bin/console +15 -0
  15. data/bin/setup +9 -0
  16. data/lib/nonnative.rb +89 -0
  17. data/lib/nonnative/before.rb +11 -0
  18. data/lib/nonnative/close_all_socket_pair.rb +9 -0
  19. data/lib/nonnative/command.rb +54 -0
  20. data/lib/nonnative/configuration.rb +80 -0
  21. data/lib/nonnative/configuration_process.rb +7 -0
  22. data/lib/nonnative/configuration_proxy.rb +13 -0
  23. data/lib/nonnative/configuration_server.rb +19 -0
  24. data/lib/nonnative/delay_socket_pair.rb +12 -0
  25. data/lib/nonnative/error.rb +6 -0
  26. data/lib/nonnative/fault_injection_proxy.rb +78 -0
  27. data/lib/nonnative/grpc_server.rb +44 -0
  28. data/lib/nonnative/http_client.rb +53 -0
  29. data/lib/nonnative/http_server.rb +40 -0
  30. data/lib/nonnative/invalid_data_socket_pair.rb +11 -0
  31. data/lib/nonnative/manual.rb +3 -0
  32. data/lib/nonnative/no_proxy.rb +17 -0
  33. data/lib/nonnative/observability.rb +13 -0
  34. data/lib/nonnative/pool.rb +60 -0
  35. data/lib/nonnative/port.rb +44 -0
  36. data/lib/nonnative/proxy.rb +13 -0
  37. data/lib/nonnative/proxy_factory.rb +18 -0
  38. data/lib/nonnative/server.rb +41 -0
  39. data/lib/nonnative/service.rb +26 -0
  40. data/lib/nonnative/socket_pair.rb +53 -0
  41. data/lib/nonnative/socket_pair_factory.rb +22 -0
  42. data/lib/nonnative/start_error.rb +6 -0
  43. data/lib/nonnative/startup.rb +7 -0
  44. data/lib/nonnative/stop_error.rb +6 -0
  45. data/lib/nonnative/timeout.rb +21 -0
  46. data/lib/nonnative/version.rb +5 -0
  47. data/nonnative.gemspec +42 -0
  48. data/sonar-project.properties +4 -0
  49. metadata +340 -0
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nonnative
4
+ class HTTPClient
5
+ def initialize(host)
6
+ @host = host
7
+ end
8
+
9
+ protected
10
+
11
+ def get(pathname, headers = {}, timeout = 60)
12
+ with_exception do
13
+ uri = URI.join(host, pathname)
14
+ RestClient::Request.execute(method: :get, url: uri.to_s, headers: headers, timeout: timeout)
15
+ end
16
+ end
17
+
18
+ def post(pathname, payload, headers = {}, timeout = 60)
19
+ with_exception do
20
+ uri = URI.join(host, pathname)
21
+ RestClient::Request.execute(method: :post, url: uri.to_s, payload: payload.to_json, headers: headers,
22
+ timeout: timeout)
23
+ end
24
+ end
25
+
26
+ def delete(pathname, headers = {}, timeout = 60)
27
+ with_exception do
28
+ uri = URI.join(host, pathname)
29
+ RestClient::Request.execute(method: :delete, url: uri.to_s, headers: headers, timeout: timeout)
30
+ end
31
+ end
32
+
33
+ def put(pathname, payload, headers = {}, timeout = 60)
34
+ with_exception do
35
+ uri = URI.join(host, pathname)
36
+ RestClient::Request.execute(method: :put, url: uri.to_s, payload: payload.to_json, headers: headers,
37
+ timeout: timeout)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ attr_reader :host
44
+
45
+ def with_exception
46
+ yield
47
+ rescue RestClient::Exceptions::ReadTimeout => e
48
+ raise e
49
+ rescue RestClient::ExceptionWithResponse => e
50
+ e.response
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nonnative
4
+ class HTTPServer < Nonnative::Server
5
+ def initialize(service)
6
+ log = File.open(service.log, 'a')
7
+ events = Puma::Events.new(log, log)
8
+ @server = Puma::Server.new(app, events)
9
+
10
+ super service
11
+ end
12
+
13
+ protected
14
+
15
+ def perform_start
16
+ server.add_tcp_listener '0.0.0.0', proxy.port
17
+ server.run.join
18
+ end
19
+
20
+ def perform_stop
21
+ server.stop(true)
22
+ end
23
+
24
+ def wait_start
25
+ timeout.perform do
26
+ super until server.running
27
+ end
28
+ end
29
+
30
+ def wait_stop
31
+ timeout.perform do
32
+ super while server.running
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :queue, :server
39
+ end
40
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nonnative
4
+ class InvalidDataSocketPair < SocketPair
5
+ def write(socket, data)
6
+ data = data.chars.shuffle.join
7
+
8
+ super socket, data
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Do nothing as it's manual
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nonnative
4
+ class NoProxy < Proxy
5
+ def start
6
+ # Do nothing.
7
+ end
8
+
9
+ def stop
10
+ # Do nothing.
11
+ end
12
+
13
+ def port
14
+ service.port
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nonnative
4
+ class Observability < Nonnative::HTTPClient
5
+ def health
6
+ get('health', { content_type: :json, accept: :json })
7
+ end
8
+
9
+ def metrics
10
+ get('metrics')
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nonnative
4
+ class Pool
5
+ def initialize(configuration)
6
+ @configuration = configuration
7
+ end
8
+
9
+ def start(&block)
10
+ [servers, processes].each { |t| process(t, :start, :open?, &block) }
11
+ end
12
+
13
+ def stop(&block)
14
+ [processes, servers].each { |t| process(t, :stop, :closed?, &block) }
15
+ end
16
+
17
+ def server_by_name(name)
18
+ index = configuration.servers.find_index { |s| s.name == name }
19
+ servers[index].first
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :configuration
25
+
26
+ def processes
27
+ @processes ||= configuration.processes.map do |d|
28
+ [Nonnative::Command.new(d), Nonnative::Port.new(d)]
29
+ end
30
+ end
31
+
32
+ def servers
33
+ @servers ||= configuration.servers.map do |d|
34
+ [d.klass.new(d), Nonnative::Port.new(d)]
35
+ end
36
+ end
37
+
38
+ def process(all, type_method, port_method, &block)
39
+ types = []
40
+ pids = []
41
+ threads = []
42
+
43
+ all.each do |type, port|
44
+ types << type
45
+ pids << type.send(type_method)
46
+ threads << Thread.new { port.send(port_method) }
47
+ end
48
+
49
+ ports = threads.map(&:value)
50
+
51
+ yield_results(types, pids, ports, &block)
52
+ end
53
+
54
+ def yield_results(all, pids, ports)
55
+ all.zip(pids, ports).each do |type, id, result|
56
+ yield type.name, id, result
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nonnative
4
+ class Port
5
+ def initialize(process)
6
+ @process = process
7
+ @timeout = Nonnative::Timeout.new(process.timeout)
8
+ end
9
+
10
+ def open?
11
+ timeout.perform do
12
+ open_socket
13
+ true
14
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
15
+ sleep_interval
16
+ retry
17
+ end
18
+ end
19
+
20
+ def closed?
21
+ timeout.perform do
22
+ open_socket
23
+ raise Nonnative::Error
24
+ rescue Nonnative::Error
25
+ sleep_interval
26
+ retry
27
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ECONNRESET
28
+ true
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :process, :timeout
35
+
36
+ def open_socket
37
+ TCPSocket.new('0.0.0.0', process.port).close
38
+ end
39
+
40
+ def sleep_interval
41
+ sleep 0.01
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nonnative
4
+ class Proxy
5
+ def initialize(service)
6
+ @service = service
7
+ end
8
+
9
+ protected
10
+
11
+ attr_reader :service
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nonnative
4
+ class ProxyFactory
5
+ class << self
6
+ def create(service)
7
+ proxy = case service.proxy.type
8
+ when 'fault_injection'
9
+ FaultInjectionProxy
10
+ else
11
+ NoProxy
12
+ end
13
+
14
+ proxy.new(service)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nonnative
4
+ class Server < Nonnative::Service
5
+ attr_reader :proxy
6
+
7
+ def initialize(service)
8
+ @proxy = Nonnative::ProxyFactory.create(service)
9
+
10
+ super service
11
+ end
12
+
13
+ def start
14
+ unless thread
15
+ proxy.start
16
+ @thread = Thread.new { perform_start }
17
+
18
+ wait_start
19
+ end
20
+
21
+ object_id
22
+ end
23
+
24
+ def stop
25
+ if thread
26
+ perform_stop
27
+ thread.terminate
28
+ proxy.stop
29
+
30
+ @thread = nil
31
+ wait_stop
32
+ end
33
+
34
+ object_id
35
+ end
36
+
37
+ private
38
+
39
+ attr_reader :thread
40
+ end
41
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nonnative
4
+ class Service
5
+ def initialize(service)
6
+ @service = service
7
+ @timeout = Nonnative::Timeout.new(service.timeout)
8
+ end
9
+
10
+ def name
11
+ service.name
12
+ end
13
+
14
+ protected
15
+
16
+ attr_reader :service, :timeout
17
+
18
+ def wait_start
19
+ sleep 0.1
20
+ end
21
+
22
+ def wait_stop
23
+ sleep 0.1
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nonnative
4
+ class SocketPair
5
+ def initialize(proxy, logger)
6
+ @proxy = proxy
7
+ @logger = logger
8
+ end
9
+
10
+ def connect(local_socket)
11
+ remote_socket = create_remote_socket
12
+
13
+ loop do
14
+ ready = select([local_socket, remote_socket], nil, nil)
15
+
16
+ break if pipe(ready, local_socket, remote_socket)
17
+ break if pipe(ready, remote_socket, local_socket)
18
+ end
19
+ rescue StandardError => e
20
+ logger.error e
21
+ ensure
22
+ local_socket.close
23
+ remote_socket&.close
24
+ end
25
+
26
+ protected
27
+
28
+ attr_reader :proxy, :logger
29
+
30
+ def create_remote_socket
31
+ ::TCPSocket.new('0.0.0.0', proxy.port)
32
+ end
33
+
34
+ def pipe(ready, socket1, socket2)
35
+ if ready[0].include?(socket1)
36
+ data = read(socket1)
37
+ return true if data.empty?
38
+
39
+ write socket2, data
40
+ end
41
+
42
+ false
43
+ end
44
+
45
+ def read(socket)
46
+ socket.recv(1024)
47
+ end
48
+
49
+ def write(socket, data)
50
+ socket.write(data)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nonnative
4
+ class SocketPairFactory
5
+ class << self
6
+ def create(type, proxy, logger)
7
+ pair = case type
8
+ when :close_all
9
+ CloseAllSocketPair
10
+ when :delay
11
+ DelaySocketPair
12
+ when :invalid_data
13
+ InvalidDataSocketPair
14
+ else
15
+ SocketPair
16
+ end
17
+
18
+ pair.new(proxy, logger)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nonnative
4
+ class StartError < Nonnative::Error
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ at_exit do
4
+ Nonnative.stop
5
+ end
6
+
7
+ Nonnative.start