Lua /etc/resolv.conf parser and some refactoring

This commit is contained in:
Elvin Efendi 2019-08-13 18:21:22 -04:00
parent 333d9fd48d
commit d46b4148fa
15 changed files with 224 additions and 124 deletions

View file

@ -315,8 +315,7 @@ describe("Balancer", function()
}
}
local dns_helper = require("test/dns_helper")
dns_helper.mock_dns_query({
helpers.mock_resty_dns_query({
{
name = "example.com",
address = "192.168.1.1",

View file

@ -115,15 +115,20 @@ describe("Configuration", function()
end)
it("returns a status of 400", function()
local original_io_open = _G.io.open
_G.io.open = function(filename, extension) return false end
assert.has_no.errors(configuration.call)
assert.equal(ngx.status, ngx.HTTP_BAD_REQUEST)
_G.io.open = original_io_open
end)
it("logs 'dynamic-configuration: unable to read valid request body to stderr'", function()
local original_io_open = _G.io.open
_G.io.open = function(filename, extension) return false end
local s = spy.on(ngx, "log")
assert.has_no.errors(configuration.call)
assert.spy(s).was_called_with(ngx.ERR, "dynamic-configuration: unable to read valid request body")
_G.io.open = original_io_open
end)
end)

View file

@ -1,27 +0,0 @@
local _M = {}
local configuration = require("configuration")
local resolver = require("resty.dns.resolver")
local old_resolver_new = resolver.new
local function reset(nameservers)
configuration.nameservers = nameservers or { "1.1.1.1" }
end
function _M.mock_new(func, nameservers)
reset(nameservers)
resolver.new = func
end
function _M.mock_dns_query(response, err)
reset()
resolver.new = function(self, options)
local r = old_resolver_new(self, options)
r.query = function(self, name, options, tries)
return response, err
end
return r
end
end
return _M

View file

@ -0,0 +1,47 @@
local _M = {}
local resty_dns_resolver = require("resty.dns.resolver")
local original_resty_dns_resolver_new = resty_dns_resolver.new
local original_io_open = io.open
function _M.with_resolv_conf(content, func)
local new_resolv_conf_f = assert(io.tmpfile())
new_resolv_conf_f:write(content)
new_resolv_conf_f:seek("set", 0)
io.open = function(path, mode)
if path ~= "/etc/resolv.conf" then
error("expected '/etc/resolv.conf' as path but got: " .. tostring(path))
end
if mode ~= "r" then
error("expected 'r' as mode but got: " .. tostring(mode))
end
return new_resolv_conf_f, nil
end
func()
io.open = original_io_open
if io.type(new_resolv_conf_f) ~= "closed file" then
error("file was left open")
end
end
function _M.mock_resty_dns_new(func)
resty_dns_resolver.new = func
end
function _M.mock_resty_dns_query(response, err)
resty_dns_resolver.new = function(self, options)
local r = original_resty_dns_resolver_new(self, options)
r.query = function(self, name, options, tries)
return response, err
end
return r
end
end
return _M

View file

@ -11,6 +11,7 @@ do
-- if there's more constants need to be whitelisted for test runs, add here.
local GLOBALS_ALLOWED_IN_TEST = {
_TEST = true,
helpers = true,
}
local newindex = function(table, key, value)
rawset(table, key, value)
@ -33,6 +34,8 @@ do
setmetatable(_G, { __newindex = newindex })
end
_G.helpers = require("test.helpers")
local ffi = require("ffi")
local lua_ingress = require("lua_ingress")

View file

@ -1,41 +1,51 @@
local conf = [===[
nameserver 1.2.3.4
nameserver 4.5.6.7
search ingress-nginx.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
]===]
helpers.with_resolv_conf(conf, function()
require("util.resolv_conf")
end)
describe("resolve", function()
local dns = require("util.dns")
local dns_helper = require("test/dns_helper")
it("sets correct nameservers", function()
dns_helper.mock_new(function(self, options)
helpers.mock_resty_dns_new(function(self, options)
assert.are.same({ nameservers = { "1.2.3.4", "4.5.6.7" }, retrans = 5, timeout = 2000 }, options)
return nil, ""
end, { "1.2.3.4", "4.5.6.7" })
end)
dns.resolve("example.com")
end)
it("returns host when an error happens", function()
local s_ngx_log = spy.on(ngx, "log")
dns_helper.mock_new(function(...) return nil, "an error" end)
helpers.mock_resty_dns_new(function(...) return nil, "an error" end)
assert.are.same({ "example.com" }, dns.resolve("example.com"))
assert.spy(s_ngx_log).was_called_with(ngx.ERR, "failed to instantiate the resolver: an error")
dns_helper.mock_dns_query(nil, "oops!")
helpers.mock_resty_dns_query(nil, "oops!")
assert.are.same({ "example.com" }, dns.resolve("example.com"))
assert.spy(s_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\noops!\noops!")
dns_helper.mock_dns_query({ errcode = 1, errstr = "format error" })
helpers.mock_resty_dns_query({ errcode = 1, errstr = "format error" })
assert.are.same({ "example.com" }, dns.resolve("example.com"))
assert.spy(s_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\nserver returned error code: 1: format error\nserver returned error code: 1: format error")
dns_helper.mock_dns_query({})
helpers.mock_resty_dns_query({})
assert.are.same({ "example.com" }, dns.resolve("example.com"))
assert.spy(s_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\nno record resolved\nno record resolved")
dns_helper.mock_dns_query({ { name = "example.com", cname = "sub.example.com", ttl = 60 } })
helpers.mock_resty_dns_query({ { name = "example.com", cname = "sub.example.com", ttl = 60 } })
assert.are.same({ "example.com" }, dns.resolve("example.com"))
assert.spy(s_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\nno record resolved\nno record resolved")
end)
it("resolves all A records of given host, caches them with minimal ttl and returns from cache next time", function()
dns_helper.mock_dns_query({
helpers.mock_resty_dns_query({
{
name = "example.com",
address = "192.168.1.1",
@ -66,7 +76,7 @@ describe("resolve", function()
assert.are.same({ "192.168.1.1", "1.2.3.4" }, dns.resolve("example.com"))
dns_helper.mock_new(function(...)
helpers.mock_resty_dns_new(function(...)
error("expected to short-circuit and return response from cache")
end)
assert.are.same({ "192.168.1.1", "1.2.3.4" }, dns.resolve("example.com"))

View file

@ -0,0 +1,64 @@
local original_io_open = io.open
describe("resolv_conf", function()
after_each(function()
package.loaded["util.resolv_conf"] = nil
io.open = original_io_open
end)
it("errors when file can not be opened", function()
io.open = function(...)
return nil, "file does not exist"
end
assert.has_error(function() require("util.resolv_conf") end, "could not open /etc/resolv.conf: file does not exist")
end)
it("opens '/etc/resolv.conf' with mode 'r'", function()
io.open = function(path, mode)
assert.are.same("/etc/resolv.conf", path)
assert.are.same("r", mode)
return original_io_open(path, mode)
end
assert.has_no.errors(function() require("util.resolv_conf") end)
end)
it("correctly parses resolv.conf", function()
local conf = [===[
# This is a comment
nameserver 10.96.0.10
nameserver 10.96.0.99
search ingress-nginx.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
]===]
helpers.with_resolv_conf(conf, function()
local resolv_conf = require("util.resolv_conf")
assert.are.same({
nameservers = { "10.96.0.10", "10.96.0.99" },
search = { "ingress-nginx.svc.cluster.local", "svc.cluster.local", "cluster.local" },
ndots = 5,
}, resolv_conf)
end)
end)
it("ignores options that it does not understand", function()
local conf = [===[
nameserver 10.96.0.10
search example.com
options debug
options ndots:3
]===]
helpers.with_resolv_conf(conf, function()
local resolv_conf = require("util.resolv_conf")
assert.are.same({
nameservers = { "10.96.0.10" },
search = { "example.com" },
ndots = 3,
}, resolv_conf)
end)
end)
end)