Merge pull request #5571 from agile6v/dev

feat: support the combination of Nginx variables for annotation upstream-hash-by.
This commit is contained in:
Kubernetes Prow Robot 2020-06-01 15:10:14 -07:00 committed by GitHub
commit d061375afa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 95 additions and 22 deletions

View file

@ -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).

View file

@ -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, ""},

View file

@ -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

View file

@ -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)]

View file

@ -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)

View file

@ -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