1
0
Fork 0
mirror of https://github.com/puma/puma.git synced 2022-11-09 13:48:40 -05:00

Socket activation tests (#2176)

* Socket activation tests

* Refactor
This commit is contained in:
Nate Berkopec 2020-03-11 12:50:44 -06:00 committed by GitHub
parent 5f9e30e6e0
commit 3df357192a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 71 additions and 41 deletions

View file

@ -58,34 +58,32 @@ module Puma
ios.map { |io| io.addr[1] }.uniq
end
def import_from_env(env_hash)
remove = []
remove += create_inherited_fds(env_hash)
env_hash.each do |k,v|
if k == 'LISTEN_FDS' && ENV['LISTEN_PID'].to_i == $$
# systemd socket activation.
# LISTEN_FDS = number of listening sockets. e.g. 2 means accept on 2 sockets w/descriptors 3 and 4.
# LISTEN_PID = PID of the service process, aka us
# see https://www.freedesktop.org/software/systemd/man/systemd-socket-activate.html
def create_inherited_fds(env_hash)
env_hash.select {|k,v| k =~ /PUMA_INHERIT_\d+/}.each do |_k, v|
fd, url = v.split(":", 2)
@inherited_fds[url] = fd.to_i
end.keys # pass keys back for removal
end
number_of_sockets_to_listen_on = v.to_i
number_of_sockets_to_listen_on.times do |index|
fd = index + 3 # 3 is the magic number you add to follow the SA protocol
sock = TCPServer.for_fd(fd) # TODO: change to BasicSocket?
key = begin # Try to parse as a path
[:unix, Socket.unpack_sockaddr_un(sock.getsockname)]
rescue ArgumentError # Try to parse as a port/ip
port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
addr = "[#{addr}]" if addr =~ /\:/
[:tcp, addr, port]
end
@activated_sockets[key] = sock
@events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
end
remove << k << 'LISTEN_PID'
# systemd socket activation.
# LISTEN_FDS = number of listening sockets. e.g. 2 means accept on 2 sockets w/descriptors 3 and 4.
# LISTEN_PID = PID of the service process, aka us
# see https://www.freedesktop.org/software/systemd/man/systemd-socket-activate.html
def create_activated_fds(env_hash)
return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
env_hash['LISTEN_FDS'].to_i.times do |index|
sock = TCPServer.for_fd(socket_activation_fd(index))
key = begin # Try to parse as a path
[:unix, Socket.unpack_sockaddr_un(sock.getsockname)]
rescue ArgumentError # Try to parse as a port/ip
port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
addr = "[#{addr}]" if addr =~ /\:/
[:tcp, addr, port]
end
@activated_sockets[key] = sock
@events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
end
remove
["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
end
def parse(binds, logger, log_msg = 'Listening')
@ -382,14 +380,8 @@ module Puma
end.map { |addrinfo| addrinfo.ip_address }.uniq
end
# def create_activated_sockets(env_hash)
# end
def create_inherited_fds(env_hash)
env_hash.select {|k,v| k =~ /PUMA_INHERIT_\d+/}.each do |_k, v|
fd, url = v.split(":", 2)
@inherited_fds[url] = fd.to_i
end.keys # pass keys back for removal
def socket_activation_fd(int)
int + 3 # 3 is the magic number you add to follow the SA protocol
end
end
end

View file

@ -48,8 +48,8 @@ module Puma
@config = conf
@binder = Binder.new(@events)
env_to_remove = @binder.import_from_env(ENV)
env_to_remove.each { |k| ENV.delete k }
@binder.create_inherited_fds(ENV).each { |k| ENV.delete k }
@binder.create_activated_fds(ENV).each { |k| ENV.delete k }
@environment = conf.environment

View file

@ -250,7 +250,7 @@ class TestBinder < TestBinderBase
def test_import_from_env_listen_inherit
@binder.parse ["tcp://127.0.0.1:0"], @events
removals = @binder.import_from_env(@binder.redirects_for_restart_env)
removals = @binder.create_inherited_fds(@binder.redirects_for_restart_env)
@binder.listeners.each do |url, io|
assert_equal io.to_i, @binder.inherited_fds[url]
@ -258,14 +258,52 @@ class TestBinder < TestBinderBase
assert_includes removals, "PUMA_INHERIT_0"
end
# test socket activation with tcp
# test socket activation with IPv6
# test socket activation with Unix
# test socket activation logs to events
# test socket activation returns the right keys to remove
# Socket activation tests. We have to skip all of these on non-UNIX platforms
# because the check that we do in the code only works if you support UNIX sockets.
# This is OK, because systemd obviously only works on Linux.
def test_socket_activation_tcp
skip UNIX_SKT_MSG unless UNIX_SKT_EXIST
url = "127.0.0.1"
port = UniquePort.call
sock = Addrinfo.tcp(url, port).listen
assert_activates_sockets(url: url, port: port, sock: sock)
end
def test_socket_activation_tcp_ipv6
skip UNIX_SKT_MSG unless UNIX_SKT_EXIST
url = "::"
port = UniquePort.call
sock = Addrinfo.tcp(url, port).listen
assert_activates_sockets(url: url, port: port, sock: sock)
end
def test_socket_activation_unix
skip UNIX_SKT_MSG unless UNIX_SKT_EXIST
path = "test/unixserver.state"
sock = Addrinfo.unix(path).listen
assert_activates_sockets(path: path, sock: sock)
ensure
File.unlink path if path
end
private
def assert_activates_sockets(path: nil, port: nil, url: nil, sock: nil)
hash = { "LISTEN_FDS" => 1, "LISTEN_PID" => $$ }
@events.instance_variable_set(:@debug, true)
@binder.instance_variable_set(:@sock_fd, sock.fileno)
def @binder.socket_activation_fd(int); @sock_fd; end
@result = @binder.create_activated_fds(hash)
url = "[::]" if url == "::"
ary = path ? [:unix, path] : [:tcp, url, port]
assert_kind_of TCPServer, @binder.activated_sockets[ary]
assert_match "Registered #{ary.join(":")} for activation from LISTEN_FDS", @events.stdout.string
assert_equal ["LISTEN_FDS", "LISTEN_PID"], @result
end
def assert_parsing_logs_uri(order = [:unix, :tcp])
skip UNIX_SKT_MSG if order.include?(:unix) && !UNIX_SKT_EXIST