117 lines
4.0 KiB
Lua
117 lines
4.0 KiB
Lua
local records_key = KEYS[1]
|
|
local target_health_key = KEYS[2]
|
|
local operation_key = KEYS[3]
|
|
|
|
local observed_at_ms = tonumber(ARGV[1])
|
|
local latency_ns = tonumber(ARGV[2])
|
|
local proxy_id = ARGV[3]
|
|
local profile_key = ARGV[4]
|
|
local success = ARGV[5]
|
|
local max_consecutive_failures = tonumber(ARGV[6])
|
|
local task_id = ARGV[7]
|
|
local observation_digest = ARGV[8]
|
|
local operation_ttl_ms = tonumber(ARGV[9])
|
|
|
|
local committed = redis.call('GET', operation_key)
|
|
if committed then
|
|
return committed
|
|
end
|
|
|
|
local function finish(reply, hard_expiry_ms)
|
|
local encoded = cjson.encode(reply)
|
|
redis.call('SET', operation_key, encoded, 'PX', operation_ttl_ms)
|
|
if hard_expiry_ms then
|
|
local operation_expiry_ms = redis.call('PEXPIRETIME', operation_key)
|
|
if operation_expiry_ms > hard_expiry_ms then
|
|
redis.call('PEXPIREAT', operation_key, hard_expiry_ms)
|
|
end
|
|
end
|
|
return encoded
|
|
end
|
|
|
|
if not observed_at_ms or observed_at_ms <= 0 or not latency_ns or latency_ns < 0 or
|
|
type(proxy_id) ~= 'string' or proxy_id == '' or type(profile_key) ~= 'string' or
|
|
string.len(profile_key) ~= 64 or (success ~= '0' and success ~= '1') or
|
|
not max_consecutive_failures or max_consecutive_failures <= 0 or type(task_id) ~= 'string' or
|
|
task_id == '' or type(observation_digest) ~= 'string' or string.len(observation_digest) ~= 64 or
|
|
not operation_ttl_ms or operation_ttl_ms <= 0 then
|
|
return finish({status = 'invalid'})
|
|
end
|
|
|
|
local raw_proxy = redis.call('HGET', records_key, proxy_id)
|
|
if not raw_proxy then
|
|
return finish({status = 'not_found'})
|
|
end
|
|
local proxy = cjson.decode(raw_proxy)
|
|
if type(proxy.expiresAtMs) ~= 'number' or proxy.expiresAtMs <= observed_at_ms then
|
|
redis.call('DEL', target_health_key)
|
|
return finish({status = 'not_found'})
|
|
end
|
|
|
|
local raw_target = redis.call('HGET', target_health_key, profile_key)
|
|
local target
|
|
if raw_target then
|
|
local decoded, value = pcall(cjson.decode, raw_target)
|
|
if not decoded or type(value) ~= 'table' then
|
|
return finish({status = 'invalid'})
|
|
end
|
|
target = value
|
|
else
|
|
target = {
|
|
version = 1,
|
|
status = 'UNKNOWN',
|
|
consecutiveFailures = 0,
|
|
latencyNs = 0,
|
|
}
|
|
end
|
|
|
|
if target.version ~= 1 or type(target.status) ~= 'string' or
|
|
type(target.consecutiveFailures) ~= 'number' or target.consecutiveFailures < 0 or
|
|
type(target.latencyNs) ~= 'number' or target.latencyNs < 0 then
|
|
return finish({status = 'invalid'})
|
|
end
|
|
|
|
local last_task_id = target.lastHealthTaskId or ''
|
|
local last_digest = target.lastHealthDigest or ''
|
|
local last_observed_at_ms = tonumber(target.lastHealthObservedAtMs or '0')
|
|
if not last_observed_at_ms or last_observed_at_ms < 0 then
|
|
return finish({status = 'invalid'})
|
|
end
|
|
if last_observed_at_ms > 0 then
|
|
if type(last_task_id) ~= 'string' or last_task_id == '' or type(last_digest) ~= 'string' or
|
|
string.len(last_digest) ~= 64 then
|
|
return finish({status = 'invalid'})
|
|
end
|
|
if last_task_id == task_id then
|
|
if last_digest == observation_digest then
|
|
return finish({status = 'ok', target = raw_target}, proxy.expiresAtMs)
|
|
end
|
|
return finish({status = 'conflict'})
|
|
end
|
|
if observed_at_ms <= last_observed_at_ms then
|
|
return finish({status = 'stale'})
|
|
end
|
|
end
|
|
|
|
target.lastHealthTaskId = task_id
|
|
target.lastHealthDigest = observation_digest
|
|
target.lastHealthObservedAtMs = observed_at_ms
|
|
target.latencyNs = latency_ns
|
|
if success == '1' then
|
|
target.status = 'AVAILABLE'
|
|
target.consecutiveFailures = 0
|
|
target.lastSuccessAtMs = observed_at_ms
|
|
else
|
|
target.consecutiveFailures = target.consecutiveFailures + 1
|
|
if target.consecutiveFailures >= max_consecutive_failures then
|
|
target.status = 'UNHEALTHY'
|
|
else
|
|
target.status = 'SUSPECT'
|
|
end
|
|
end
|
|
|
|
local encoded = cjson.encode(target)
|
|
redis.call('HSET', target_health_key, profile_key, encoded)
|
|
redis.call('PEXPIREAT', target_health_key, proxy.expiresAtMs)
|
|
return finish({status = 'ok', target = encoded}, proxy.expiresAtMs)
|