项目文件夹

文件
2026-07-13 12:32:21 +08:00

346 行
9.3 KiB
Lua

local balancer = require "kong.runloop.balancer"
local cjson = require "cjson"
local workspaces = require "kong.workspaces"
local tools_ip = require "kong.tools.ip"
local setmetatable = setmetatable
local tostring = tostring
local ipairs = ipairs
local table = table
local type = type
local min = math.min
local table_merge = require("kong.tools.table").table_merge
local _TARGETS = {}
local DEFAULT_PORT = 8000
local function sort_targets(a, b)
if a.created_at < b.created_at then
return true
end
if a.created_at == b.created_at then
return a.id < b.id
end
return false
end
local function format_target(target)
local p = tools_ip.normalize_ip(target)
if not p then
return false, "Invalid target; not a valid hostname or ip address"
end
return tools_ip.format_host(p, DEFAULT_PORT)
end
function _TARGETS:insert(entity, options)
if entity.target then
local formatted_target, err = format_target(entity.target)
if not formatted_target then
local err_t = self.errors:schema_violation({ target = err })
return nil, tostring(err_t), err_t
end
entity.target = formatted_target
end
return self.super.insert(self, entity, options)
end
function _TARGETS:upsert(pk, entity, options)
entity.id = pk.id
if entity.target then
local formatted_target, err = format_target(entity.target)
if not formatted_target then
local err_t = self.errors:schema_violation({ target = err })
return nil, tostring(err_t), err_t
end
entity.target = formatted_target
end
-- backward compatibility with Kong older than 2.2.0
local workspace = workspaces.get_workspace_id()
local opts = { nulls = true, workspace = workspace }
for existent, err, err_t in self:each_for_upstream(entity.upstream, nil, opts) do
if not existent then
return nil, err, err_t
end
if existent.target == entity.target then
-- if the upserting entity is newer, update
if entity.created_at > existent.created_at then
local ok, err, err_t = self.super.delete(self, existent, opts)
if ok then
return self.super.insert(self, entity, options)
end
return ok, err, err_t
end
-- if upserting entity is older, keep the existent entity
return true
end
end
return self.super.insert(self, entity, options)
end
function _TARGETS:upsert_by_target(unique_key, entity, options)
entity.target = unique_key
return self:insert(entity, options)
end
function _TARGETS:select(pk, options)
local target, err, err_t = self.super.select(self, pk, options)
if err then
return nil, err, err_t
end
if target then
local formatted_target, err = format_target(target.target)
if not formatted_target then
local err_t = self.errors:schema_violation({ target = err })
return nil, tostring(err_t), err_t
end
target.target = formatted_target
end
return target
end
function _TARGETS:delete_by_target(tgt, options)
local target, err, err_t = self:select_by_target(tgt)
if err then
return nil, err, err_t
end
return self.super.delete(self, target, options)
end
-- Paginate through the targets for an upstream, including those with
-- weight=0 (i.e. the "raw" representation of targets in the database)
function _TARGETS:page_for_upstream_raw(upstream_pk, ...)
local page, err, err_t, offset =
self.super.page_for_upstream(self, upstream_pk, ...)
if err then
return nil, tostring(err), err_t
end
for _, target in ipairs(page) do
local formatted_target, err = format_target(target.target)
if not formatted_target then
local err_t = self.errors:schema_violation({ target = err })
return nil, tostring(err_t), err_t
end
target.target = formatted_target
end
return page, nil, nil, offset
end
-- Return the entire list of targets for an upstream, including entries with
-- weight=0 (i.e. the "raw" representation of targets in the database)
function _TARGETS:select_by_upstream_raw(upstream_pk, options)
local targets = {}
-- Note that each_for_upstream is not overridden, so it returns "raw".
for target, err, err_t in self:each_for_upstream(upstream_pk, nil, options) do
if not target then
return nil, err, err_t
end
local formatted_target, err = format_target(target.target)
if not formatted_target then
local err_t = self.errors:schema_violation({ target = err })
return nil, tostring(err_t), err_t
end
target.target = formatted_target
table.insert(targets, target)
end
table.sort(targets, sort_targets)
return targets
end
-- Paginate through targets for an upstream, returning only the active
-- (weight>0) target.
function _TARGETS:page_for_upstream(upstream_pk, size, offset, options)
-- Read all targets
local targets, err, err_t = self:select_by_upstream_raw(upstream_pk, options)
if not targets then
return nil, err, err_t
end
local all_active_targets = {}
local seen = {}
local len = 0
for i = #targets, 1, -1 do
local entry = targets[i]
if not seen[entry.target] then
-- add what we want to send to the client in our array
len = len + 1
all_active_targets[len] = entry
-- track that we found this host:port so we only show
-- the most recent active one
seen[entry.target] = true
end
end
local pagination = self.pagination
if type(options) == "table" and type(options.pagination) == "table" then
pagination = table_merge(pagination, options.pagination)
end
if not size then
size = pagination.page_size
end
size = min(size, pagination.max_page_size)
offset = offset or 0
-- Extract the requested page
local page = setmetatable({}, cjson.array_mt)
for i = 1 + offset, size + offset do
local target = all_active_targets[i]
if not target then
break
end
table.insert(page, target)
end
local next_offset
if all_active_targets[size + offset + 1] then
next_offset = tostring(size + offset)
end
return page, nil, nil, next_offset
end
-- Paginate through targets for an upstream, returning only the
-- latest state of each active (weight>0) target, and include
-- health information to the returned records.
function _TARGETS:page_for_upstream_with_health(upstream_pk, ...)
local targets, err, err_t, next_offset = self:page_for_upstream(upstream_pk, ...)
if not targets then
return nil, err, err_t
end
local health_info
health_info, err = balancer.get_upstream_health(upstream_pk.id)
if err then
ngx.log(ngx.ERR, "failed getting upstream health: ", err)
end
if health_info then
for _, target in ipairs(targets) do
-- In case of DNS errors when registering a target,
-- that error happens inside lua-resty-dns-client
-- and the end-result is that it just doesn't launch the callback,
-- which means kong.runloop.balancer and healthchecks don't get
-- notified about the target at all. We extrapolate the DNS error
-- out of the fact that the target is missing from the balancer.
-- Note that lua-resty-dns-client does retry by itself,
-- meaning that if DNS is down and it eventually resumes working, the
-- library will issue the callback and the target will change state.
if health_info[target.target] ~= nil and
#health_info[target.target].addresses > 0 then
target.health = "HEALTHCHECKS_OFF"
-- If any of the target addresses are healthy, then the target is
-- considered healthy.
for _, address in ipairs(health_info[target.target].addresses) do
if address.health == "HEALTHY" then
target.health = "HEALTHY"
break
elseif address.health == "UNHEALTHY" then
target.health = "UNHEALTHY"
end
end
else
target.health = "DNS_ERROR"
end
target.data = health_info[target.target]
end
end
return targets, nil, nil, next_offset
end
function _TARGETS:select_by_upstream_filter(upstream_pk, filter, options)
local targets, err, err_t = self:select_by_upstream_raw(upstream_pk, options)
if not targets then
return nil, err, err_t
end
for i = #targets, 1, -1 do
local t = targets[i]
if t.id == filter.id or t.target == filter.target then
return t
end
end
end
function _TARGETS:post_health(upstream_pk, target, address, is_healthy)
local upstream = balancer.get_upstream_by_id(upstream_pk.id)
local host_addr = tools_ip.normalize_ip(target.target)
local hostname = tools_ip.format_host(host_addr.host)
local ip
local port
if address ~= nil then
local addr = tools_ip.normalize_ip(address)
ip = addr.host
if addr.port then
port = addr.port
else
port = DEFAULT_PORT
end
else
ip = nil
port = host_addr.port
end
local _, err = balancer.post_health(upstream, hostname, ip, port, is_healthy)
if err then
return nil, err
end
local health = is_healthy and 1 or 0
local packet = ("%s|%s|%d|%d|%s|%s"):format(hostname, ip or "", port, health,
upstream.id,
upstream.name)
kong.cluster_events:broadcast("balancer:post_health", packet)
return true
end
function _TARGETS:get_balancer_health(upstream_pk)
local health_info, err = balancer.get_balancer_health(upstream_pk.id)
if err then
ngx.log(ngx.ERR, "failed getting upstream health: ", err)
end
return health_info
end
return _TARGETS