Skip to content

Commit 2b774e5

Browse files
authored
fix(consumer): missed consumer update due to wrong version in cache (#12413)
1 parent 138d403 commit 2b774e5

File tree

2 files changed

+182
-12
lines changed

2 files changed

+182
-12
lines changed

apisix/consumer.lua

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ do
9494
count = consumers_count_for_lrucache
9595
})
9696

97-
local function construct_consumer_data(val, plugin_config)
97+
local function construct_consumer_data(val, name, plugin_config)
9898
-- if the val is a Consumer, clone it to the local consumer;
9999
-- if the val is a Credential, to get the Consumer by consumer_name and then clone
100100
-- it to the local consumer.
@@ -103,20 +103,28 @@ local function construct_consumer_data(val, plugin_config)
103103
local consumer_name = get_consumer_name_from_credential_etcd_key(val.key)
104104
local the_consumer = consumers:get(consumer_name)
105105
if the_consumer and the_consumer.value then
106-
consumer = core.table.clone(the_consumer.value)
107-
consumer.modifiedIndex = the_consumer.modifiedIndex
108-
consumer.credential_id = get_credential_id_from_etcd_key(val.key)
106+
consumer = consumers_id_lrucache(val.value.id .. name, val.modifiedIndex..
107+
the_consumer.modifiedIndex,
108+
function (val, the_consumer)
109+
consumer = core.table.clone(the_consumer.value)
110+
consumer.modifiedIndex = the_consumer.modifiedIndex
111+
consumer.credential_id = get_credential_id_from_etcd_key(val.key)
112+
return consumer
113+
end, val, the_consumer)
109114
else
110115
-- Normally wouldn't get here:
111116
-- it should belong to a consumer for any credential.
112-
core.log.error("failed to get the consumer for the credential,",
117+
return nil, "failed to get the consumer for the credential,",
113118
" a wild credential has appeared!",
114-
" credential key: ", val.key, ", consumer name: ", consumer_name)
115-
return nil, "failed to get the consumer for the credential"
119+
" credential key: ", val.key, ", consumer name: ", consumer_name
116120
end
117121
else
118-
consumer = core.table.clone(val.value)
119-
consumer.modifiedIndex = val.modifiedIndex
122+
consumer = consumers_id_lrucache(val.value.id .. name, val.modifiedIndex,
123+
function (val)
124+
consumer = core.table.clone(val.value)
125+
consumer.modifiedIndex = val.modifiedIndex
126+
return consumer
127+
end, val)
120128
end
121129

122130
-- if the consumer has labels, set the field custom_id to it.
@@ -160,9 +168,10 @@ function plugin_consumer()
160168
}
161169
end
162170

163-
local consumer = consumers_id_lrucache(val.value.id .. name,
164-
val.modifiedIndex, construct_consumer_data, val, config)
165-
if consumer == nil then
171+
local consumer, err = construct_consumer_data(val, name, config)
172+
if not consumer then
173+
core.log.error("failed to construct consumer data for plugin ",
174+
name, ": ", err)
166175
goto CONTINUE
167176
end
168177

t/admin/consumer-credentials.t

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
18+
use t::APISIX 'no_plan';
19+
20+
repeat_each(1);
21+
no_long_string();
22+
no_root_location();
23+
no_shuffle();
24+
log_level("info");
25+
26+
run_tests;
27+
28+
__DATA__
29+
30+
=== TEST 1: Verify consumer plugin update takes effect immediately
31+
--- extra_yaml_config
32+
nginx_config:
33+
worker_processes: 1
34+
--- config
35+
location /t {
36+
content_by_lua_block {
37+
local t = require("lib.test_admin").test
38+
local json = require("toolkit.json")
39+
local http = require("resty.http")
40+
41+
-- 1. Create route with key-auth plugin
42+
local code, body = t('/apisix/admin/routes/1',
43+
ngx.HTTP_PUT,
44+
[[{
45+
"uri": "/anything",
46+
"upstream": {
47+
"type": "roundrobin",
48+
"nodes": {
49+
"httpbin.org:80": 1
50+
}
51+
},
52+
"plugins": {
53+
"key-auth": {
54+
"query": "apikey"
55+
}
56+
}
57+
}]]
58+
)
59+
if code >= 300 then
60+
ngx.status = code
61+
ngx.say("Route creation failed: " .. body)
62+
return
63+
end
64+
65+
-- 2. Create consumer jack
66+
code, body = t('/apisix/admin/consumers',
67+
ngx.HTTP_PUT,
68+
[[{
69+
"username": "jack"
70+
}]]
71+
)
72+
if code >= 300 then
73+
ngx.status = code
74+
ngx.say("Consumer creation failed: " .. body)
75+
return
76+
end
77+
78+
-- 3. Create credentials for jack
79+
code, body = t('/apisix/admin/consumers/jack/credentials/auth-one',
80+
ngx.HTTP_PUT,
81+
[[{
82+
"plugins": {
83+
"key-auth": {
84+
"key": "auth-one"
85+
}
86+
}
87+
}]]
88+
)
89+
if code >= 300 then
90+
ngx.status = code
91+
ngx.say("Credential creation failed: " .. body)
92+
return
93+
end
94+
ngx.sleep(0.5) -- wait for etcd to sync
95+
-- 4. Verify valid request succeeds
96+
local httpc = http.new()
97+
local res, err = httpc:request_uri(
98+
"http://127.0.0.1:"..ngx.var.server_port.."/anything?apikey=auth-one",
99+
{ method = "GET" }
100+
)
101+
if not res then
102+
ngx.say("Request failed: ", err)
103+
return
104+
end
105+
106+
if res.status ~= 200 then
107+
ngx.say("Unexpected status: ", res.status)
108+
ngx.say(res.body)
109+
return
110+
end
111+
112+
-- 5. Update consumer with fault-injection plugin
113+
code, body = t('/apisix/admin/consumers/jack',
114+
ngx.HTTP_PUT,
115+
[[{
116+
"username": "jack",
117+
"plugins": {
118+
"fault-injection": {
119+
"abort": {
120+
"http_status": 400,
121+
"body": "abort"
122+
}
123+
}
124+
}
125+
}]]
126+
)
127+
if code >= 300 then
128+
ngx.status = code
129+
ngx.say("Consumer update failed: " .. body)
130+
return
131+
end
132+
133+
-- 6. Verify all requests return 400
134+
for i = 1, 5 do
135+
local res, err = httpc:request_uri(
136+
"http://127.0.0.1:"..ngx.var.server_port.."/anything?apikey=auth-one",
137+
{ method = "GET" }
138+
)
139+
if not res then
140+
ngx.say(i, ": Request failed: ", err)
141+
return
142+
end
143+
144+
if res.status ~= 400 then
145+
ngx.say(i, ": Expected 400 but got ", res.status)
146+
return
147+
end
148+
149+
if res.body ~= "abort" then
150+
ngx.say(i, ": Unexpected response body: ", res.body)
151+
return
152+
end
153+
end
154+
155+
ngx.say("All requests aborted as expected")
156+
}
157+
}
158+
--- request
159+
GET /t
160+
--- response_body
161+
All requests aborted as expected

0 commit comments

Comments
 (0)