Merge pull request #5571 from agile6v/dev
feat: support the combination of Nginx variables for annotation upstream-hash-by.
This commit is contained in:
commit
d061375afa
6 changed files with 95 additions and 22 deletions
|
@ -213,7 +213,7 @@ There is a special mode of upstream hashing called subset. In this mode, upstrea
|
|||
|
||||
To enable consistent hashing for a backend:
|
||||
|
||||
`nginx.ingress.kubernetes.io/upstream-hash-by`: the nginx variable, text value or any combination thereof to use for consistent hashing. For example `nginx.ingress.kubernetes.io/upstream-hash-by: "$request_uri"` to consistently hash upstream requests by the current request URI.
|
||||
`nginx.ingress.kubernetes.io/upstream-hash-by`: the nginx variable, text value or any combination thereof to use for consistent hashing. For example: `nginx.ingress.kubernetes.io/upstream-hash-by: "$request_uri"` or `nginx.ingress.kubernetes.io/upstream-hash-by: "$request_uri$host"` or `nginx.ingress.kubernetes.io/upstream-hash-by: "${request_uri}-text-value"` to consistently hash upstream requests by the current request URI.
|
||||
|
||||
"subset" hashing can be enabled setting `nginx.ingress.kubernetes.io/upstream-hash-by-subset`: "true". This maps requests to subset of nodes instead of a single one. `upstream-hash-by-subset-size` determines the size of each subset (default 3).
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ func TestParse(t *testing.T) {
|
|||
expected string
|
||||
}{
|
||||
{map[string]string{annotation: "$request_uri"}, "$request_uri"},
|
||||
{map[string]string{annotation: "$request_uri$scheme"}, "$request_uri$scheme"},
|
||||
{map[string]string{annotation: "false"}, "false"},
|
||||
{map[string]string{}, ""},
|
||||
{nil, ""},
|
||||
|
|
|
@ -1,14 +1,21 @@
|
|||
local balancer_resty = require("balancer.resty")
|
||||
local resty_chash = require("resty.chash")
|
||||
local util = require("util")
|
||||
local ngx_log = ngx.log
|
||||
local ngx_ERR = ngx.ERR
|
||||
|
||||
local _M = balancer_resty:new({ factory = resty_chash, name = "chash" })
|
||||
|
||||
function _M.new(self, backend)
|
||||
local nodes = util.get_nodes(backend.endpoints)
|
||||
local complex_val, err = util.parse_complex_value(backend["upstreamHashByConfig"]["upstream-hash-by"])
|
||||
if err ~= nil then
|
||||
ngx_log(ngx_ERR, "could not parse the value of the upstream-hash-by: ", err)
|
||||
end
|
||||
|
||||
local o = {
|
||||
instance = self.factory:new(nodes),
|
||||
hash_by = backend["upstreamHashByConfig"]["upstream-hash-by"],
|
||||
hash_by = complex_val,
|
||||
traffic_shaping_policy = backend.trafficShapingPolicy,
|
||||
alternative_backends = backend.alternativeBackends,
|
||||
}
|
||||
|
@ -18,7 +25,7 @@ function _M.new(self, backend)
|
|||
end
|
||||
|
||||
function _M.balance(self)
|
||||
local key = util.lua_ngx_var(self.hash_by)
|
||||
local key = util.generate_var_value(self.hash_by)
|
||||
return self.instance:find(key)
|
||||
end
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
local resty_chash = require("resty.chash")
|
||||
local util = require("util")
|
||||
local ngx_log = ngx.log
|
||||
local ngx_ERR = ngx.ERR
|
||||
|
||||
local _M = { name = "chashsubset" }
|
||||
|
||||
|
@ -44,10 +46,14 @@ end
|
|||
|
||||
function _M.new(self, backend)
|
||||
local subset_map, subsets = build_subset_map(backend)
|
||||
local complex_val, err = util.parse_complex_value(backend["upstreamHashByConfig"]["upstream-hash-by"])
|
||||
if err ~= nil then
|
||||
ngx_log(ngx_ERR, "could not parse the value of the upstream-hash-by: ", err)
|
||||
end
|
||||
|
||||
local o = {
|
||||
instance = resty_chash:new(subset_map),
|
||||
hash_by = backend["upstreamHashByConfig"]["upstream-hash-by"],
|
||||
hash_by = complex_val,
|
||||
subsets = subsets,
|
||||
current_endpoints = backend.endpoints
|
||||
}
|
||||
|
@ -57,7 +63,7 @@ function _M.new(self, backend)
|
|||
end
|
||||
|
||||
function _M.balance(self)
|
||||
local key = util.lua_ngx_var(self.hash_by)
|
||||
local key = util.generate_var_value(self.hash_by)
|
||||
local subset_id = self.instance:find(key)
|
||||
local endpoints = self.subsets[subset_id]
|
||||
local endpoint = endpoints[math.random(#endpoints)]
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
local original_ngx = ngx
|
||||
local util = require("util")
|
||||
|
||||
local function reset_ngx()
|
||||
_G.ngx = original_ngx
|
||||
end
|
||||
|
@ -9,28 +11,43 @@ local function mock_ngx(mock)
|
|||
_G.ngx = _ngx
|
||||
end
|
||||
|
||||
describe("lua_ngx_var", function()
|
||||
local util = require("util")
|
||||
describe("utility", function()
|
||||
|
||||
after_each(function()
|
||||
reset_ngx()
|
||||
end)
|
||||
|
||||
describe("lua_ngx_var", function()
|
||||
describe("ngx_complex_value", function()
|
||||
before_each(function()
|
||||
mock_ngx({ var = { remote_addr = "192.168.1.1", [1] = "nginx/regexp/1/group/capturing" } })
|
||||
end)
|
||||
|
||||
local ngx_complex_value = function(data)
|
||||
local ret, err = util.parse_complex_value(data)
|
||||
if err ~= nil then
|
||||
return ""
|
||||
end
|
||||
return util.generate_var_value(ret)
|
||||
end
|
||||
|
||||
it("returns value of nginx var by key", function()
|
||||
assert.equal("192.168.1.1", util.lua_ngx_var("$remote_addr"))
|
||||
assert.equal("192.168.1.1", ngx_complex_value("$remote_addr"))
|
||||
end)
|
||||
|
||||
|
||||
it("returns value of nginx var when key is number", function()
|
||||
assert.equal("nginx/regexp/1/group/capturing", util.lua_ngx_var("$1"))
|
||||
assert.equal("nginx/regexp/1/group/capturing", ngx_complex_value("$1"))
|
||||
end)
|
||||
|
||||
it("returns nil when variable is not defined", function()
|
||||
assert.equal(nil, util.lua_ngx_var("$foo_bar"))
|
||||
it("returns value of nginx var by multiple variables", function()
|
||||
assert.equal("192.168.1.1nginx/regexp/1/group/capturing", ngx_complex_value("$remote_addr$1"))
|
||||
end)
|
||||
|
||||
it("returns value by the combination of variable and text value", function()
|
||||
assert.equal("192.168.1.1-text-value", ngx_complex_value("${remote_addr}-text-value"))
|
||||
end)
|
||||
|
||||
it("returns empty when variable is not defined", function()
|
||||
assert.equal("", ngx_complex_value("$foo_bar"))
|
||||
end)
|
||||
end)
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
local string = string
|
||||
local string_len = string.len
|
||||
local string_sub = string.sub
|
||||
local string_format = string.format
|
||||
local pairs = pairs
|
||||
local ipairs = ipairs
|
||||
local tonumber = tonumber
|
||||
local getmetatable = getmetatable
|
||||
local type = type
|
||||
local next = next
|
||||
local table = table
|
||||
|
||||
local re_gmatch = ngx.re.gmatch
|
||||
|
||||
local _M = {}
|
||||
|
||||
|
@ -24,15 +24,57 @@ function _M.get_nodes(endpoints)
|
|||
return nodes
|
||||
end
|
||||
|
||||
-- given an Nginx variable i.e $request_uri
|
||||
-- it returns value of ngx.var[request_uri]
|
||||
function _M.lua_ngx_var(ngx_var)
|
||||
local var_name = string_sub(ngx_var, 2)
|
||||
if var_name:match("^%d+$") then
|
||||
var_name = tonumber(var_name)
|
||||
-- parse the compound variables, then call generate_var_value function
|
||||
-- to parse into a string value.
|
||||
function _M.parse_complex_value(complex_value)
|
||||
local reg = [[ (\\\$[0-9a-zA-Z_]+) | ]] -- \$var
|
||||
.. [[ \$\{([0-9a-zA-Z_]+)\} | ]] -- ${var}
|
||||
.. [[ \$([0-9a-zA-Z_]+) | ]] -- $var
|
||||
.. [[ (\$|[^$\\]+) ]] -- $ or text value
|
||||
local iterator, err = re_gmatch(complex_value, reg, "jiox")
|
||||
if not iterator then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
local v
|
||||
local t = {}
|
||||
while true do
|
||||
v, err = iterator()
|
||||
if err then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
if not v then
|
||||
break
|
||||
end
|
||||
|
||||
table.insert(t, v)
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
-- Parse the return value of function parse_complex_value
|
||||
-- into a string value
|
||||
function _M.generate_var_value(data)
|
||||
if data == nil then
|
||||
return ""
|
||||
end
|
||||
|
||||
return ngx.var[var_name]
|
||||
local t = {}
|
||||
for _, value in ipairs(data) do
|
||||
local var_name = value[2] or value[3]
|
||||
if var_name then
|
||||
if var_name:match("^%d+$") then
|
||||
var_name = tonumber(var_name)
|
||||
end
|
||||
table.insert(t, ngx.var[var_name])
|
||||
else
|
||||
table.insert(t, value[1] or value[4])
|
||||
end
|
||||
end
|
||||
|
||||
return table.concat(t, "")
|
||||
end
|
||||
|
||||
-- normalize_endpoints takes endpoints as an array of endpoint objects
|
||||
|
|
Loading…
Reference in a new issue