Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a "failOnIncompleteCheck" option to if*up Lua functions #15098

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/lua-records/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ Record creation functions
- ``timeout``: Maximum time in seconds that you allow the check to take (default 2)
- ``interval``: Time interval between two checks, in seconds. Defaults to :ref:`setting-lua-health-checks-interval` if not specified.
- ``minimumFailures``: The number of unsuccessful checks in a row required to mark the address as down. Defaults to 1 if not specified, i.e. report as down on the first unsuccessful check.
- ``failOnIncompleteCheck``: if set to "true", return SERVFAIL instead of applying ``backupSelector``, if none of the addresses has completed their background health check yet.


.. function:: ifurlup(url, addresses[, options])
Expand All @@ -93,6 +94,7 @@ Record creation functions
- ``useragent``: Set the HTTP "User-Agent" header in the requests. By default it is set to "PowerDNS Authoritative Server"
- ``byteslimit``: Limit the maximum download size to ``byteslimit`` bytes (default 0 meaning no limit).
- ``minimumFailures``: The number of unsuccessful checks in a row required to mark the address as down. Defaults to 1 if not specified, i.e. report as down on the first unsuccessful check.
- ``failOnIncompleteCheck``: if set to "true", return SERVFAIL instead of applying ``backupSelector``, if none of the addresses has completed their background health check yet.

An example of a list of address sets:

Expand Down
53 changes: 44 additions & 9 deletions pdns/lua-record.cc
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,13 @@ class IsUpOracle
}
};

// The return value of this function can be one of three sets of values:
// - positive integer: the target is up, the return value is its weight.
// (1 if weights are not used)
// - zero: the target is down.
// - negative integer: the check for this target has not completed yet.
// (this value is only reported if the failOnIncompleteCheck option is
// set, otherwise zero will be returned)
//NOLINTNEXTLINE(readability-identifier-length)
int IsUpOracle::isUp(const CheckDesc& cd)
{
Expand Down Expand Up @@ -349,6 +356,14 @@ int IsUpOracle::isUp(const CheckDesc& cd)
(*statuses)[cd] = std::make_unique<CheckState>(now);
}
}
// If explicitly asked to fail on incomplete checks, report this (as
// a negative value).
static const std::string foic{"failOnIncompleteCheck"};
if (cd.opts.count(foic) != 0) {
if (cd.opts.at(foic) == "true") {
return -1;
}
}
return 0;
}

Expand Down Expand Up @@ -882,7 +897,7 @@ static std::string pickConsistentWeightedHashed(const ComboAddress& bestwho, con
return {};
}

static vector<string> genericIfUp(const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options, const std::function<bool(const ComboAddress&, const opts_t&)>& upcheckf, uint16_t port = 0)
static vector<string> genericIfUp(const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options, const std::function<int(const ComboAddress&, const opts_t&)>& upcheckf, uint16_t port = 0)
{
vector<vector<ComboAddress> > candidates;
opts_t opts;
Expand All @@ -891,20 +906,30 @@ static vector<string> genericIfUp(const boost::variant<iplist_t, ipunitlist_t>&

candidates = convMultiComboAddressList(ips, port);

bool incompleteCheck{true};
for(const auto& unit : candidates) {
vector<ComboAddress> available;
for(const auto& c : unit) {
if (upcheckf(c, opts)) {
int status = upcheckf(c, opts);
if (status > 0) {
available.push_back(c);
}
if (status >= 0) {
incompleteCheck = false;
}
}
if(!available.empty()) {
vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
return convComboAddressListToString(res);
}
}

// All units down, apply backupSelector on all candidates
// All units down or have not completed their checks yet.
if (incompleteCheck) {
throw std::runtime_error("if{url,port}up health check has not completed yet");
}

// Apply backupSelector on all candidates
vector<ComboAddress> ret{};
for(const auto& unit : candidates) {
ret.insert(ret.end(), unit.begin(), unit.end());
Expand Down Expand Up @@ -1194,7 +1219,7 @@ static void setupLuaRecords(LuaContext& lua) // NOLINT(readability-function-cogn
port = std::max(port, 0);
port = std::min(port, static_cast<int>(std::numeric_limits<uint16_t>::max()));

auto checker = [](const ComboAddress& addr, const opts_t& opts) {
auto checker = [](const ComboAddress& addr, const opts_t& opts) -> int {
return g_up.isUp(addr, opts);
};
return genericIfUp(ips, options, checker, port);
Expand All @@ -1210,6 +1235,7 @@ static void setupLuaRecords(LuaContext& lua) // NOLINT(readability-function-cogn
ca_unspec.sin4.sin_family=AF_UNSPEC;

// ipurls: { { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" } }
bool incompleteCheck{true};
for (const auto& [count, unitmap] : ipurls) {
// unitmap: 1 = { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" }
vector<ComboAddress> available;
Expand All @@ -1218,17 +1244,26 @@ static void setupLuaRecords(LuaContext& lua) // NOLINT(readability-function-cogn
// unit: ["192.0.2.1"] = "https://example.com"
ComboAddress ip(ipStr);
candidates.push_back(ip);
if (g_up.isUp(ca_unspec, url, opts)) {
int status = g_up.isUp(ca_unspec, url, opts);
if (status > 0) {
available.push_back(ip);
}
if (status >= 0) {
incompleteCheck = false;
}
}
if(!available.empty()) {
vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
return convComboAddressListToString(res);
}
}

// All units down, apply backupSelector on all candidates
// All units down or have not completed their checks yet.
if (incompleteCheck) {
throw std::runtime_error("ifexturlup health check has not completed yet");
}

// Apply backupSelector on all candidates
vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, candidates);
return convComboAddressListToString(res);
});
Expand All @@ -1237,7 +1272,7 @@ static void setupLuaRecords(LuaContext& lua) // NOLINT(readability-function-cogn
const boost::variant<iplist_t, ipunitlist_t>& ips,
boost::optional<opts_t> options) {

auto checker = [&url](const ComboAddress& addr, const opts_t& opts) {
auto checker = [&url](const ComboAddress& addr, const opts_t& opts) -> int {
return g_up.isUp(addr, url, opts);
};
return genericIfUp(ips, options, checker);
Expand Down Expand Up @@ -1287,7 +1322,7 @@ static void setupLuaRecords(LuaContext& lua) // NOLINT(readability-function-cogn

lua.writeFunction("pickrandomsample", [](int n, const iplist_t& ips) {
vector<string> items = convStringList(ips);
return pickRandomSample<string>(n, items);
return pickRandomSample<string>(n, items);
});

lua.writeFunction("pickhashed", [](const iplist_t& ips) {
Expand Down Expand Up @@ -1461,7 +1496,7 @@ static void setupLuaRecords(LuaContext& lua) // NOLINT(readability-function-cogn

lua.writeFunction("all", [](const vector< pair<int,string> >& ips) {
vector<string> result;
result.reserve(ips.size());
result.reserve(ips.size());

for(const auto& ip : ips) {
result.emplace_back(ip.second);
Expand Down
29 changes: 29 additions & 0 deletions regression-tests.auth-py/test_LuaRecords.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ class BaseLuaTest(AuthTest):
"return ifurlup('http://www.lua.org:8080/', "
"USAips, settings) ")

usa-failincomplete IN LUA A ( ";settings={{stringmatch='Programming in Lua', failOnIncompleteCheck='true'}} "
"USAips={{'192.168.42.105'}}"
"return ifurlup('http://www.lua.org:8080/', "
"USAips, settings) ")

mix.ifurlup IN LUA A ("ifurlup('http://www.other.org:8080/ping.json', "
"{{ '192.168.42.101', '{prefix}.101' }}, "
"{{ stringmatch='pong' }}) ")
Expand Down Expand Up @@ -1338,6 +1343,30 @@ def testIfurlupInterval(self):
self.assertAnyRRsetInAnswer(res, reachable_rrs)
self.assertNoneRRsetInAnswer(res, unreachable_rrs)

def testIfurlupFailOnIncompleteCheck(self):
"""
Simple ifurlup() test with failOnIncompleteCheck option set.
"""
ips = ['192.168.42.105']
all_rrs = []
for ip in ips:
rr = dns.rrset.from_text('usa-failincomplete.example.org.', 0, dns.rdataclass.IN, 'A', ip)
all_rrs.append(rr)

query = dns.message.make_query('usa-failincomplete.example.org', 'A')
res = self.sendUDPQuery(query)
self.assertRcodeEqual(res, dns.rcode.SERVFAIL)

# The above request being sent at time T, the following events occur:
# T+00: SERVFAIL returned as no data available yet
# T+00: checker thread starts
# T+02: 192.168.42.105 found down and marked as such

time.sleep(3)
res = self.sendUDPQuery(query)
self.assertRcodeEqual(res, dns.rcode.NOERROR)
self.assertAnyRRsetInAnswer(res, all_rrs)

if __name__ == '__main__':
unittest.main()
exit(0)
Loading