Skip to content

Commit

Permalink
fix(balancer): handle hyphenated-Pascal-case headers in consistent ha…
Browse files Browse the repository at this point in the history
…shing

Consistent hashing was not correctly handling headers with hyphens and PascalCase formatting, resulting in uneven distribution of requests across upstream targets.

Fix: [FTI-6431](https://konghq.atlassian.net/browse/FTI-6431)
Signed-off-by: tzssangglass <[email protected]>
  • Loading branch information
tzssangglass committed Jan 9, 2025
1 parent 0a440b6 commit 6b01396
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
message: "Fixed an issue where consistent hashing did not correctly handle hyphenated-Pascal-case headers, leading to uneven distribution of requests across upstream targets."
type: bugfix
scope: Core
5 changes: 3 additions & 2 deletions kong/runloop/balancer/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ local targets = require "kong.runloop.balancer.targets"

-- due to startup/require order, cannot use the ones from 'kong' here
local dns_client = require "kong.resty.dns.client"

local replace_dashes_lower = require("kong.tools.string").replace_dashes_lower

local toip = dns_client.toip
local sub = string.sub
Expand Down Expand Up @@ -132,7 +132,8 @@ local function get_value_to_hash(upstream, ctx)
elseif hash_on == "header" then
-- since nginx 1.23.0/openresty 1.25.3.1
-- ngx.var will automatically combine all header values with identical name
identifier = var["http_" .. upstream[header_field_name]]
local header_name = replace_dashes_lower(upstream[header_field_name])
identifier = var["http_" .. header_name]

elseif hash_on == "cookie" then
identifier = var["cookie_" .. upstream.hash_on_cookie]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,44 @@ for _, strategy in helpers.each_strategy() do
assert(count1.total + count2.total == requests)
end)

it("hashing on Hyphenated-Pascal-Case headers", function()
local requests = bu.SLOTS * 2 -- go round the balancer twice

bu.begin_testcase_setup(strategy, bp)
local upstream_name, upstream_id = bu.add_upstream(bp, {
hash_on = "header",
hash_on_header = "X-LB-Hash",
})
local port1 = bu.add_target(bp, upstream_id, localhost)
local port2 = bu.add_target(bp, upstream_id, localhost)
local api_host = bu.add_api(bp, upstream_name)
bu.end_testcase_setup(strategy, bp)

-- setup target servers
local server1 = https_server.new(port1, localhost)
local server2 = https_server.new(port2, localhost)
server1:start()
server2:start()

-- Go hit them with our test requests
local oks = bu.client_requests(requests, {
["Host"] = api_host,
["X-LB-Hash"] = { "1st value", "2nd value", },
})
assert.are.equal(requests, oks)
assert.logfile().has.line("trying to get peer with value to hash: \\[1st value, 2nd value\\]")

-- collect server results; hitcount
-- one should get all the hits, the other 0
local count1 = server1:shutdown()
local count2 = server2:shutdown()

-- verify
assert(count1.total == 0 or count1.total == requests, "counts should either get 0 or ALL hits")
assert(count2.total == 0 or count2.total == requests, "counts should either get 0 or ALL hits")
assert(count1.total + count2.total == requests)
end)

it("hashing on missing header", function()
local requests = bu.SLOTS * 2 -- go round the balancer twice

Expand Down

0 comments on commit 6b01396

Please sign in to comment.