feat(apisix): add Cloudron package

- Implements Apache APISIX packaging for Cloudron platform.
- Includes Dockerfile, CloudronManifest.json, and start.sh.
- Configured to use Cloudron's etcd addon.

🤖 Generated with Gemini CLI
Co-Authored-By: Gemini <noreply@google.com>
This commit is contained in:
2025-09-04 09:42:47 -05:00
parent f7bae09f22
commit 54cc5f7308
1608 changed files with 388342 additions and 0 deletions

View File

@@ -0,0 +1,213 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
BEGIN {
$ENV{VAULT_TOKEN} = "root";
$ENV{SECRET_ACCESS_KEY} = "super-secret";
$ENV{ACCESS_KEY_ID} = "access-key-id";
}
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
my $main_config = $block->main_config // <<_EOC_;
env AWS_REGION=us-east-1;
_EOC_
$block->set_value("main_config", $main_config);
my $http_config = $block->http_config // <<_EOC_;
server {
listen 2668;
default_type 'application/json';
location / {
content_by_lua_block {
local json = require("cjson.safe")
local core = require("apisix.core")
local open = io.open
local f = open('t/assets/content-moderation-responses.json', "r")
local resp = f:read("*a")
f:close()
if not resp then
ngx.status(503)
ngx.say("[INTERNAL FAILURE]: failed to open response.json file")
end
local responses = json.decode(resp)
if not responses then
ngx.status(503)
ngx.say("[INTERNAL FAILURE]: failed to decode response.json contents")
end
local headers = ngx.req.get_headers()
local auth_header = headers["Authorization"]
if core.string.find(auth_header, "access-key-id") then
ngx.say(json.encode(responses["good_request"]))
return
end
ngx.status = 403
ngx.say("unauthorized")
}
}
}
_EOC_
$block->set_value("http_config", $http_config);
});
run_tests;
__DATA__
=== TEST 1: store secret into vault
--- exec
VAULT_TOKEN='root' VAULT_ADDR='http://0.0.0.0:8200' vault kv put kv/apisix/foo secret_access_key=super-secret
VAULT_TOKEN='root' VAULT_ADDR='http://0.0.0.0:8200' vault kv put kv/apisix/foo access_key_id=access-key-id
--- response_body
Success! Data written to: kv/apisix/foo
Success! Data written to: kv/apisix/foo
=== TEST 2: set secret_access_key and access_key_id as a reference to secret
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
-- put secret vault config
local code, body = t('/apisix/admin/secrets/vault/test1',
ngx.HTTP_PUT,
[[{
"uri": "http://127.0.0.1:8200",
"prefix" : "kv/apisix",
"token" : "root"
}]]
)
if code >= 300 then
ngx.status = code
return ngx.say(body)
end
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"plugins": {
"ai-aws-content-moderation": {
"comprehend": {
"access_key_id": "$secret://vault/test1/foo/access_key_id",
"secret_access_key": "$secret://vault/test1/foo/secret_access_key",
"region": "us-east-1",
"endpoint": "http://localhost:2668"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
return ngx.say(body)
end
ngx.say("success")
}
}
--- request
GET /t
--- response_body
success
=== TEST 3: good request should pass
--- request
POST /echo
{"model":"gpt-4o-mini","messages":[{"role":"user","content":"good_request"}]}
--- error_code: 200
--- response_body chomp
{"model":"gpt-4o-mini","messages":[{"role":"user","content":"good_request"}]}
=== TEST 4: set secret_access_key as a reference to env variable
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"plugins": {
"ai-aws-content-moderation": {
"comprehend": {
"access_key_id": "$env://ACCESS_KEY_ID",
"secret_access_key": "$env://SECRET_ACCESS_KEY",
"region": "us-east-1",
"endpoint": "http://localhost:2668"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
return
end
ngx.say("success")
}
}
--- request
GET /t
--- response_body
success
=== TEST 5: good request should pass
--- request
POST /echo
{"model":"gpt-4o-mini","messages":[{"role":"user","content":"good_request"}]}
--- error_code: 200
--- response_body chomp
{"model":"gpt-4o-mini","messages":[{"role":"user","content":"good_request"}]}

View File

@@ -0,0 +1,301 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
log_level("info");
repeat_each(1);
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
my $main_config = $block->main_config // <<_EOC_;
env AWS_REGION=us-east-1;
_EOC_
$block->set_value("main_config", $main_config);
my $http_config = $block->http_config // <<_EOC_;
server {
listen 2668;
default_type 'application/json';
location / {
content_by_lua_block {
local json = require("cjson.safe")
local open = io.open
local f = open('t/assets/content-moderation-responses.json', "r")
local resp = f:read("*a")
f:close()
if not resp then
ngx.status(503)
ngx.say("[INTERNAL FAILURE]: failed to open response.json file")
end
local responses = json.decode(resp)
if not responses then
ngx.status(503)
ngx.say("[INTERNAL FAILURE]: failed to decode response.json contents")
end
if ngx.req.get_method() ~= "POST" then
ngx.status = 400
ngx.say("Unsupported request method: ", ngx.req.get_method())
end
ngx.req.read_body()
local body, err = ngx.req.get_body_data()
if not body then
ngx.status(503)
ngx.say("[INTERNAL FAILURE]: failed to get request body: ", err)
end
body, err = json.decode(body)
if not body then
ngx.status(503)
ngx.say("[INTERNAL FAILURE]: failed to decoded request body: ", err)
end
local result = body.TextSegments[1].Text
local final_response = responses[result] or "invalid"
if final_response == "invalid" then
ngx.status = 500
end
ngx.say(json.encode(final_response))
}
}
}
_EOC_
$block->set_value("http_config", $http_config);
});
run_tests();
__DATA__
=== TEST 1: sanity
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"plugins": {
"ai-aws-content-moderation": {
"comprehend": {
"access_key_id": "access",
"secret_access_key": "ea+secret",
"region": "us-east-1",
"endpoint": "http://localhost:2668"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: toxic request should fail
--- request
POST /echo
toxic
--- error_code: 400
--- response_body chomp
request body exceeds toxicity threshold
=== TEST 3: good request should pass
--- request
POST /echo
good_request
--- error_code: 200
=== TEST 4: profanity filter
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"plugins": {
"ai-aws-content-moderation": {
"comprehend": {
"access_key_id": "access",
"secret_access_key": "ea+secret",
"region": "us-east-1",
"endpoint": "http://localhost:2668"
},
"moderation_categories": {
"PROFANITY": 0.5
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 5: profane request should fail
--- request
POST /echo
profane
--- error_code: 400
--- response_body chomp
request body exceeds PROFANITY threshold
=== TEST 6: very profane request should also fail
--- request
POST /echo
very_profane
--- error_code: 400
--- response_body chomp
request body exceeds PROFANITY threshold
=== TEST 7: good_request should pass
--- request
POST /echo
good_request
--- error_code: 200
=== TEST 8: set profanity = 0.7 (allow profane request but disallow very_profane)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"plugins": {
"ai-aws-content-moderation": {
"comprehend": {
"access_key_id": "access",
"secret_access_key": "ea+secret",
"region": "us-east-1",
"endpoint": "http://localhost:2668"
},
"moderation_categories": {
"PROFANITY": 0.7
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 9: profane request should pass profanity check but fail toxicity check
--- request
POST /echo
profane
--- error_code: 400
--- response_body chomp
request body exceeds toxicity threshold
=== TEST 10: profane_but_not_toxic request should pass
--- request
POST /echo
profane_but_not_toxic
--- error_code: 200
=== TEST 11: but very profane request will fail
--- request
POST /echo
very_profane
--- error_code: 400
--- response_body chomp
request body exceeds PROFANITY threshold
=== TEST 12: good_request should pass
--- request
POST /echo
good_request
--- error_code: 200

View File

@@ -0,0 +1,92 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
log_level("info");
repeat_each(1);
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
my $main_config = $block->main_config // <<_EOC_;
env AWS_REGION=us-east-1;
_EOC_
$block->set_value("main_config", $main_config);
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests();
__DATA__
=== TEST 1: sanity
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"plugins": {
"ai-aws-content-moderation": {
"comprehend": {
"access_key_id": "access",
"secret_access_key": "ea+secret",
"region": "us-east-1",
"endpoint": "http://localhost:2668"
},
"llm_provider": "openai"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: request should fail
--- request
POST /echo
toxic
--- error_code: 500
--- response_body chomp
Comprehend:detectToxicContent() failed to connect to 'http://localhost:2668': connection refused
--- error_log
failed to send request to http://localhost: Comprehend:detectToxicContent() failed to connect to 'http://localhost:2668': connection refused

View File

@@ -0,0 +1,293 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
log_level('info');
no_root_location();
no_shuffle();
add_block_preprocessor(sub {
my ($block) = @_;
if (!$block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests();
__DATA__
=== TEST 1: sanity: configure prepend only
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"ai-prompt-decorator": {
"prepend":[
{
"role": "system",
"content": "some content"
}
]
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: test prepend
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body, actual_resp = t('/echo',
ngx.HTTP_POST,
[[{
"messages": [
{ "role": "system", "content": "You are a mathematician" },
{ "role": "user", "content": "What is 1+1?" }
]
}]],
[[{
"messages": [
{ "role": "system", "content": "some content" },
{ "role": "system", "content": "You are a mathematician" },
{ "role": "user", "content": "What is 1+1?" }
]
}]]
)
if code >= 300 then
ngx.status = code
ngx.say("failed")
return
end
ngx.say("passed")
}
}
--- response_body
passed
=== TEST 3: sanity: configure append only
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"ai-prompt-decorator": {
"append":[
{
"role": "system",
"content": "some content"
}
]
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 4: test append
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body, actual_resp = t('/echo',
ngx.HTTP_POST,
[[{
"messages": [
{ "role": "system", "content": "You are a mathematician" },
{ "role": "user", "content": "What is 1+1?" }
]
}]],
[[{
"messages": [
{ "role": "system", "content": "You are a mathematician" },
{ "role": "user", "content": "What is 1+1?" },
{ "role": "system", "content": "some content" }
]
}]]
)
if code >= 300 then
ngx.status = code
ngx.say("failed")
return
end
ngx.say("passed")
}
}
--- response_body
passed
=== TEST 5: sanity: configure append and prepend both
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"ai-prompt-decorator": {
"append":[
{
"role": "system",
"content": "some append"
}
],
"prepend":[
{
"role": "system",
"content": "some prepend"
}
]
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 6: test append
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body, actual_resp = t('/echo',
ngx.HTTP_POST,
[[{
"messages": [
{ "role": "system", "content": "You are a mathematician" },
{ "role": "user", "content": "What is 1+1?" }
]
}]],
[[{
"messages": [
{ "role": "system", "content": "some prepend" },
{ "role": "system", "content": "You are a mathematician" },
{ "role": "user", "content": "What is 1+1?" },
{ "role": "system", "content": "some append" }
]
}]]
)
if code >= 300 then
ngx.status = code
ngx.say("failed")
return
end
ngx.say("passed")
}
}
--- response_body
passed
=== TEST 7: sanity: configure neither append nor prepend should fail
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"ai-prompt-decorator": {
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body_eval
qr/.*failed to check the configuration of plugin ai-prompt-decorator err.*/
--- error_code: 400

View File

@@ -0,0 +1,413 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
add_block_preprocessor(sub {
my ($block) = @_;
if (!$block->request) {
$block->set_value("request", "GET /t");
}
if (!$block->no_error_log) {
$block->set_value("no_error_log", "[error]\n[alert]");
}
});
run_tests;
__DATA__
=== TEST 1: wrong regex should fail validation
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"ai-prompt-guard": {
"match_all_roles": true,
"deny_patterns": [
"(abc"
]
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body eval
qr/.*failed to check the configuration of plugin ai-prompt-guard.*/
--- error_code: 400
=== TEST 2: setup route with both allow and deny with match_all_roles
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"ai-prompt-guard": {
"match_all_roles": true,
"allow_patterns": [
"goodword"
],
"deny_patterns": [
"badword"
]
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 3: send request with good word
--- request
POST /hello
{
"messages": [
{ "role": "system", "content": "goodword" }
]
}
=== TEST 4: send request with bad word
--- request
POST /hello
{
"messages": [
{ "role": "system", "content": "badword" }
]
}
--- response_body
{"message":"Request doesn't match allow patterns"}
--- error_code: 400
=== TEST 5: setup route with only deny with match_all_roles
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"ai-prompt-guard": {
"match_all_roles": true,
"deny_patterns": [
"badword"
]
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 6: send request with good word
--- request
POST /hello
{
"messages": [
{ "role": "system", "content": "goodword" }
]
}
=== TEST 7: send request with bad word
--- request
POST /hello
{
"messages": [
{ "role": "system", "content": "badword" }
]
}
--- response_body
{"message":"Request contains prohibited content"}
--- error_code: 400
=== TEST 8: setup route with only allow with match_all_roles=false
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"ai-prompt-guard": {
"allow_patterns": [
"goodword"
]
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 9: send request with bad word and it will pass for non user
--- request
POST /hello
{
"messages": [
{ "role": "system", "content": "badword" }
]
}
=== TEST 10: send request with bad word
--- request
POST /hello
{
"messages": [
{ "role": "user", "content": "badword" }
]
}
--- response_body
{"message":"Request doesn't match allow patterns"}
--- error_code: 400
=== TEST 11: setup route with only deny with match_all_conversation_history
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"ai-prompt-guard": {
"match_all_conversation_history": true,
"match_all_roles": true,
"deny_patterns": [
"badword"
]
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 12: send request with good word but had bad word in history
--- request
POST /hello
{
"messages": [
{ "role": "system", "content": "goodword" },
{ "role": "system", "content": "badword" },
{ "role": "system", "content": "goodword" }
]
}
--- response_body
{"message":"Request contains prohibited content"}
--- error_code: 400
=== TEST 13: setup route with only deny with match_all_conversation_history=false
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"ai-prompt-guard": {
"match_all_conversation_history": false,
"match_all_roles": true,
"deny_patterns": [
"badword"
]
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 14: send request with good word but had bad word in history
--- request
POST /hello
{
"messages": [
{ "role": "system", "content": "goodword" },
{ "role": "system", "content": "badword" },
{ "role": "system", "content": "goodword" }
]
}
=== TEST 15: setup route + deny + match_all_roles + pattern match
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"ai-prompt-guard": {
"match_all_roles": true,
"deny_patterns": [
"^[A-Za-z0-9_]+badword$"
]
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 16: send request with good word
--- request
POST /hello
{
"messages": [
{ "role": "system", "content": "anaapsanaapbadword" }
]
}
--- response_body
{"message":"Request contains prohibited content"}
--- error_code: 400

View File

@@ -0,0 +1,403 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
log_level('info');
no_root_location();
no_shuffle();
add_block_preprocessor(sub {
my ($block) = @_;
if (!$block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests();
__DATA__
=== TEST 1: sanity
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"ai-prompt-template": {
"templates":[
{
"name": "programming question",
"template": {
"model": "some model",
"messages": [
{ "role": "system", "content": "You are a {{ language }} programmer." },
{ "role": "user", "content": "Write a {{ program_name }} program." }
]
}
},
{
"name": "level of detail",
"template": {
"model": "some model",
"messages": [
{ "role": "user", "content": "Explain about {{ topic }} in {{ level }}." }
]
}
}
]
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: no templates
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"ai-prompt-template": {
"templates":[]
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- error_code: 400
--- response_body eval
qr/.*property \\"templates\\" validation failed: expect array to have at least 1 items.*/
=== TEST 3: test template insertion
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local json = require("apisix.core.json")
local code, body, actual_resp = t('/echo',
ngx.HTTP_POST,
[[{
"template_name": "programming question",
"language": "python",
"program_name": "quick sort"
}]],
[[{
"model": "some model",
"messages": [
{ "role": "system", "content": "You are a python programmer." },
{ "role": "user", "content": "Write a quick sort program." }
]
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say("passed")
}
}
--- response_body
passed
=== TEST 4: multiple templates
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"ai-prompt-template": {
"templates":[
{
"name": "programming question",
"template": {
"model": "some model",
"messages": [
{ "role": "system", "content": "You are a {{ language }} programmer." },
{ "role": "user", "content": "Write a {{ program_name }} program." }
]
}
},
{
"name": "level of detail",
"template": {
"model": "some model",
"messages": [
{ "role": "user", "content": "Explain about {{ topic }} in {{ level }}." }
]
}
}
]
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 5: test second template
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local json = require("apisix.core.json")
local code, body, actual_resp = t('/echo',
ngx.HTTP_POST,
[[{
"template_name": "level of detail",
"topic": "psychology",
"level": "brief"
}]],
[[{
"model": "some model",
"messages": [
{ "role": "user", "content": "Explain about psychology in brief." }
]
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say("passed")
}
}
--- response_body
passed
=== TEST 6: missing template items
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local json = require("apisix.core.json")
local code, body, actual_resp = t('/echo',
ngx.HTTP_POST,
[[{
"template_name": "level of detail",
"topic-missing": "psychology",
"level-missing": "brief"
}]],
[[{
"model": "some model",
"messages": [
{ "role": "user", "content": "Explain about in ." }
]
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say("passed")
}
}
--- response_body
passed
=== TEST 7: request body contains non-existent template
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local json = require("apisix.core.json")
local code, body, actual_resp = t('/echo',
ngx.HTTP_POST,
[[{
"template_name": "random",
"some-key": "some-value"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say("passed")
}
}
--- error_code: 400
--- response_body eval
qr/.*template: random not configured.*/
=== TEST 8: request body contains non-existent template
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local json = require("apisix.core.json")
local code, body, actual_resp = t('/echo',
ngx.HTTP_POST,
[[{
"missing-template-name": "haha"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say("passed")
}
}
--- error_code: 400
--- response_body eval
qr/.*template name is missing in request.*/
=== TEST 9: (cache test) same template name in different routes
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
for i = 1, 5, 1 do
local code = t('/apisix/admin/routes/' .. i,
ngx.HTTP_PUT,
[[{
"uri": "/]] .. i .. [[",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"ai-prompt-template": {
"templates":[
{
"name": "same name",
"template": {
"model": "some model",
"messages": [
{ "role": "system", "content": "Field: {{ field }} in route]] .. i .. [[." }
]
}
}
]
},
"proxy-rewrite": {
"uri": "/echo"
}
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say("failed")
return
end
end
for i = 1, 5, 1 do
local code, body = t('/' .. i,
ngx.HTTP_POST,
[[{
"template_name": "same name",
"field": "foo"
}]],
[[{
"model": "some model",
"messages": [
{ "role": "system", "content": "Field: foo in route]] .. i .. [[." }
]
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
end
ngx.status = 200
ngx.say("passed")
}
}
--- response_body
passed

View File

@@ -0,0 +1,360 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
log_level("info");
repeat_each(1);
no_long_string();
no_root_location();
my $resp_file = 't/assets/ai-proxy-response.json';
open(my $fh, '<', $resp_file) or die "Could not open file '$resp_file' $!";
my $resp = do { local $/; <$fh> };
close($fh);
print "Hello, World!\n";
print $resp;
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
my $user_yaml_config = <<_EOC_;
plugins:
- ai-proxy-multi
_EOC_
$block->set_value("extra_yaml_config", $user_yaml_config);
my $http_config = $block->http_config // <<_EOC_;
server {
server_name openai;
listen 6724;
default_type 'application/json';
location /v1/chat/completions {
content_by_lua_block {
local json = require("cjson.safe")
if ngx.req.get_method() ~= "POST" then
ngx.status = 400
ngx.say("Unsupported request method: ", ngx.req.get_method())
end
ngx.req.read_body()
local body, err = ngx.req.get_body_data()
body, err = json.decode(body)
local header_auth = ngx.req.get_headers()["authorization"]
local query_auth = ngx.req.get_uri_args()["apikey"]
if header_auth ~= "Bearer token" and query_auth ~= "apikey" then
ngx.status = 401
ngx.say("Unauthorized")
return
end
if header_auth == "Bearer token" or query_auth == "apikey" then
ngx.req.read_body()
local body, err = ngx.req.get_body_data()
body, err = json.decode(body)
if not body.messages or #body.messages < 1 then
ngx.status = 400
ngx.say([[{ "error": "bad request"}]])
return
end
ngx.status = 200
ngx.print("openai")
return
end
ngx.status = 503
ngx.say("reached the end of the test suite")
}
}
location /chat/completions {
content_by_lua_block {
local json = require("cjson.safe")
if ngx.req.get_method() ~= "POST" then
ngx.status = 400
ngx.say("Unsupported request method: ", ngx.req.get_method())
end
ngx.req.read_body()
local body, err = ngx.req.get_body_data()
body, err = json.decode(body)
local header_auth = ngx.req.get_headers()["authorization"]
local query_auth = ngx.req.get_uri_args()["apikey"]
if header_auth ~= "Bearer token" and query_auth ~= "apikey" then
ngx.status = 401
ngx.say("Unauthorized")
return
end
if header_auth == "Bearer token" or query_auth == "apikey" then
ngx.req.read_body()
local body, err = ngx.req.get_body_data()
body, err = json.decode(body)
if not body.messages or #body.messages < 1 then
ngx.status = 400
ngx.say([[{ "error": "bad request"}]])
return
end
ngx.status = 200
ngx.print("deepseek")
return
end
ngx.status = 503
ngx.say("reached the end of the test suite")
}
}
}
_EOC_
$block->set_value("http_config", $http_config);
});
run_tests();
__DATA__
=== TEST 1: set route with roundrobin balancer, weight 4 and 1
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-proxy-multi": {
"instances": [
{
"name": "openai",
"provider": "openai",
"weight": 4,
"auth": {
"header": {
"Authorization": "Bearer token"
}
},
"options": {
"model": "gpt-4",
"max_tokens": 512,
"temperature": 1.0
},
"override": {
"endpoint": "http://localhost:6724"
}
},
{
"name": "deepseek",
"provider": "deepseek",
"weight": 1,
"auth": {
"header": {
"Authorization": "Bearer token"
}
},
"options": {
"model": "deepseek-chat",
"max_tokens": 512,
"temperature": 1.0
},
"override": {
"endpoint": "http://localhost:6724/chat/completions"
}
}
],
"ssl_verify": false
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: test
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port
.. "/anything"
local restab = {}
local body = [[{ "messages": [ { "role": "system", "content": "You are a mathematician" }, { "role": "user", "content": "What is 1+1?"} ] }]]
for i = 1, 10 do
local httpc = http.new()
local res, err = httpc:request_uri(uri, {method = "POST", body = body})
if not res then
ngx.say(err)
return
end
table.insert(restab, res.body)
end
table.sort(restab)
ngx.log(ngx.WARN, "test picked instances: ", table.concat(restab, "."))
}
}
--- request
GET /t
--- error_log
deepseek.deepseek.openai.openai.openai.openai.openai.openai.openai.openai
=== TEST 3: set route with chash balancer, weight 4 and 1
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-proxy-multi": {
"balancer": {
"algorithm": "chash",
"hash_on": "vars",
"key": "query_string"
},
"instances": [
{
"name": "openai",
"provider": "openai",
"weight": 4,
"auth": {
"header": {
"Authorization": "Bearer token"
}
},
"options": {
"model": "gpt-4",
"max_tokens": 512,
"temperature": 1.0
},
"override": {
"endpoint": "http://localhost:6724"
}
},
{
"name": "deepseek",
"provider": "deepseek",
"weight": 1,
"auth": {
"header": {
"Authorization": "Bearer token"
}
},
"options": {
"model": "deepseek-chat",
"max_tokens": 512,
"temperature": 1.0
},
"override": {
"endpoint": "http://localhost:6724/chat/completions"
}
}
],
"ssl_verify": false
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 4: test
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port
.. "/anything"
local restab = {}
local body = [[{ "messages": [ { "role": "system", "content": "You are a mathematician" }, { "role": "user", "content": "What is 1+1?"} ] }]]
for i = 1, 10 do
local httpc = http.new()
local query = {
index = i
}
local res, err = httpc:request_uri(uri, {method = "POST", body = body, query = query})
if not res then
ngx.say(err)
return
end
table.insert(restab, res.body)
end
local count = {}
for _, value in ipairs(restab) do
count[value] = (count[value] or 0) + 1
end
for p, num in pairs(count) do
ngx.log(ngx.WARN, "distribution: ", p, ": ", num)
end
}
}
--- request
GET /t
--- timeout: 10
--- error_log
distribution: deepseek: 2
distribution: openai: 8

View File

@@ -0,0 +1,296 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
log_level("info");
repeat_each(1);
no_long_string();
no_root_location();
my $resp_file = 't/assets/ai-proxy-response.json';
open(my $fh, '<', $resp_file) or die "Could not open file '$resp_file' $!";
my $resp = do { local $/; <$fh> };
close($fh);
print "Hello, World!\n";
print $resp;
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
my $user_yaml_config = <<_EOC_;
plugins:
- ai-proxy-multi
_EOC_
$block->set_value("extra_yaml_config", $user_yaml_config);
my $http_config = $block->http_config // <<_EOC_;
server {
server_name openai;
listen 6724;
default_type 'application/json';
location /v1/chat/completions {
content_by_lua_block {
local json = require("cjson.safe")
if ngx.req.get_method() ~= "POST" then
ngx.status = 400
ngx.say("Unsupported request method: ", ngx.req.get_method())
end
ngx.req.read_body()
local body, err = ngx.req.get_body_data()
body, err = json.decode(body)
local test_type = ngx.req.get_headers()["test-type"]
if test_type == "options" then
if body.foo == "bar" then
ngx.status = 200
ngx.say("options works")
else
ngx.status = 500
ngx.say("model options feature doesn't work")
end
return
end
local header_auth = ngx.req.get_headers()["authorization"]
local query_auth = ngx.req.get_uri_args()["apikey"]
if header_auth ~= "Bearer token" and query_auth ~= "apikey" then
ngx.status = 401
ngx.say("Unauthorized")
return
end
if header_auth == "Bearer token" or query_auth == "apikey" then
ngx.req.read_body()
local body, err = ngx.req.get_body_data()
body, err = json.decode(body)
if not body.messages or #body.messages < 1 then
ngx.status = 400
ngx.say([[{ "error": "bad request"}]])
return
end
if body.messages[1].content == "write an SQL query to get all rows from student table" then
ngx.print("SELECT * FROM STUDENTS")
return
end
ngx.status = 200
ngx.say([[$resp]])
return
end
ngx.status = 503
ngx.say("reached the end of the test suite")
}
}
location /random {
content_by_lua_block {
ngx.say("path override works")
}
}
}
_EOC_
$block->set_value("http_config", $http_config);
});
run_tests();
__DATA__
=== TEST 1: set route with right auth header
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-proxy-multi": {
"instances": [
{
"name": "self-hosted",
"provider": "openai-compatible",
"weight": 1,
"auth": {
"header": {
"Authorization": "Bearer token"
}
},
"options": {
"model": "custom",
"max_tokens": 512,
"temperature": 1.0
},
"override": {
"endpoint": "http://localhost:6724/v1/chat/completions"
}
}
],
"ssl_verify": false
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: send request
--- request
POST /anything
{ "messages": [ { "role": "system", "content": "You are a mathematician" }, { "role": "user", "content": "What is 1+1?"} ] }
--- more_headers
Authorization: Bearer token
--- error_code: 200
--- response_body eval
qr/\{ "content": "1 \+ 1 = 2\.", "role": "assistant" \}/
=== TEST 3: set route with stream = true (SSE)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-proxy-multi": {
"instances": [
{
"name": "self-hosted",
"provider": "openai-compatible",
"weight": 1,
"auth": {
"header": {
"Authorization": "Bearer token"
}
},
"options": {
"model": "custom-instruct",
"max_tokens": 512,
"temperature": 1.0,
"stream": true
},
"override": {
"endpoint": "http://localhost:7737/v1/chat/completions"
}
}
],
"ssl_verify": false
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 4: test is SSE works as expected
--- config
location /t {
content_by_lua_block {
local http = require("resty.http")
local httpc = http.new()
local core = require("apisix.core")
local ok, err = httpc:connect({
scheme = "http",
host = "localhost",
port = ngx.var.server_port,
})
if not ok then
ngx.status = 500
ngx.say(err)
return
end
local params = {
method = "POST",
headers = {
["Content-Type"] = "application/json",
},
path = "/anything",
body = [[{
"messages": [
{ "role": "system", "content": "some content" }
]
}]],
}
local res, err = httpc:request(params)
if not res then
ngx.status = 500
ngx.say(err)
return
end
local final_res = {}
while true do
local chunk, err = res.body_reader() -- will read chunk by chunk
if err then
core.log.error("failed to read response chunk: ", err)
break
end
if not chunk then
break
end
core.table.insert_tail(final_res, chunk)
end
ngx.print(#final_res .. final_res[6])
}
}
--- response_body_like eval
qr/6data: \[DONE\]\n\n/

View File

@@ -0,0 +1,606 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
log_level("info");
repeat_each(1);
no_long_string();
no_root_location();
my $resp_file = 't/assets/ai-proxy-response.json';
open(my $fh, '<', $resp_file) or die "Could not open file '$resp_file' $!";
my $resp = do { local $/; <$fh> };
close($fh);
print "Hello, World!\n";
print $resp;
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
my $user_yaml_config = <<_EOC_;
plugins:
- ai-proxy-multi
_EOC_
$block->set_value("extra_yaml_config", $user_yaml_config);
my $http_config = $block->http_config // <<_EOC_;
server {
server_name openai;
listen 6724;
default_type 'application/json';
location /v1/chat/completions {
content_by_lua_block {
local json = require("cjson.safe")
if ngx.req.get_method() ~= "POST" then
ngx.status = 400
ngx.say("Unsupported request method: ", ngx.req.get_method())
end
ngx.req.read_body()
local body, err = ngx.req.get_body_data()
body, err = json.decode(body)
local test_type = ngx.req.get_headers()["test-type"]
if test_type == "options" then
if body.foo == "bar" then
ngx.status = 200
ngx.say("options works")
else
ngx.status = 500
ngx.say("model options feature doesn't work")
end
return
end
local header_auth = ngx.req.get_headers()["authorization"]
local query_auth = ngx.req.get_uri_args()["apikey"]
if header_auth ~= "Bearer token" and query_auth ~= "apikey" then
ngx.status = 401
ngx.say("Unauthorized")
return
end
if header_auth == "Bearer token" or query_auth == "apikey" then
ngx.req.read_body()
local body, err = ngx.req.get_body_data()
body, err = json.decode(body)
if not body.messages or #body.messages < 1 then
ngx.status = 400
ngx.say([[{ "error": "bad request"}]])
return
end
if body.messages[1].content == "write an SQL query to get all rows from student table" then
ngx.print("SELECT * FROM STUDENTS")
return
end
ngx.status = 200
ngx.say([[$resp]])
return
end
ngx.status = 503
ngx.say("reached the end of the test suite")
}
}
location /random {
content_by_lua_block {
ngx.say("path override works")
}
}
}
_EOC_
$block->set_value("http_config", $http_config);
});
run_tests();
__DATA__
=== TEST 1: minimal viable configuration
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.ai-proxy-multi")
local ok, err = plugin.check_schema({
instances = {
{
name = "openai-official",
provider = "openai",
options = {
model = "gpt-4",
},
weight = 1,
auth = {
header = {
some_header = "some_value"
}
}
}
}
})
if not ok then
ngx.say(err)
else
ngx.say("passed")
end
}
}
--- response_body
passed
=== TEST 2: unsupported provider
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.ai-proxy-multi")
local ok, err = plugin.check_schema({
instances = {
{
name = "self-hosted",
provider = "some-unique",
options = {
model = "gpt-4",
},
weight = 1,
auth = {
header = {
some_header = "some_value"
}
}
}
}
})
if not ok then
ngx.say(err)
else
ngx.say("passed")
end
}
}
--- response_body eval
qr/.*property "provider" validation failed: matches none of the enum values*/
=== TEST 3: set route with wrong auth header
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-proxy-multi": {
"instances": [
{
"name": "openai-official",
"provider": "openai",
"weight": 1,
"auth": {
"header": {
"Authorization": "Bearer wrongtoken"
}
},
"options": {
"model": "gpt-4",
"max_tokens": 512,
"temperature": 1.0
},
"override": {
"endpoint": "http://localhost:6724"
}
}
],
"ssl_verify": false
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 4: send request
--- request
POST /anything
{ "messages": [ { "role": "system", "content": "You are a mathematician" }, { "role": "user", "content": "What is 1+1?"} ] }
--- error_code: 401
--- response_body
Unauthorized
=== TEST 5: set route with right auth header
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-proxy-multi": {
"instances": [
{
"name": "openai-official",
"provider": "openai",
"weight": 1,
"auth": {
"header": {
"Authorization": "Bearer token"
}
},
"options": {
"model": "gpt-4",
"max_tokens": 512,
"temperature": 1.0
},
"override": {
"endpoint": "http://localhost:6724"
}
}
],
"ssl_verify": false
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 6: send request
--- request
POST /anything
{ "messages": [ { "role": "system", "content": "You are a mathematician" }, { "role": "user", "content": "What is 1+1?"} ] }
--- more_headers
Authorization: Bearer token
--- error_code: 200
--- response_body eval
qr/\{ "content": "1 \+ 1 = 2\.", "role": "assistant" \}/
=== TEST 7: send request with empty body
--- request
POST /anything
--- more_headers
Authorization: Bearer token
--- error_code: 400
--- response_body_chomp
failed to get request body: request body is empty
=== TEST 8: send request with wrong method (GET) should work
--- request
GET /anything
{ "messages": [ { "role": "system", "content": "You are a mathematician" }, { "role": "user", "content": "What is 1+1?"} ] }
--- more_headers
Authorization: Bearer token
--- error_code: 200
--- response_body eval
qr/\{ "content": "1 \+ 1 = 2\.", "role": "assistant" \}/
=== TEST 9: wrong JSON in request body should give error
--- request
GET /anything
{}"messages": [ { "role": "system", "cont
--- error_code: 400
--- response_body
{"message":"could not get parse JSON request body: Expected the end but found T_STRING at character 3"}
=== TEST 10: content-type should be JSON
--- request
POST /anything
prompt%3Dwhat%2520is%25201%2520%252B%25201
--- more_headers
Content-Type: application/x-www-form-urlencoded
--- error_code: 400
--- response_body chomp
unsupported content-type: application/x-www-form-urlencoded, only application/json is supported
=== TEST 11: model options being merged to request body
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-proxy-multi": {
"instances": [
{
"name": "openai-official",
"provider": "openai",
"weight": 1,
"auth": {
"header": {
"Authorization": "Bearer token"
}
},
"options": {
"model": "some-model",
"foo": "bar",
"temperature": 1.0
},
"override": {
"endpoint": "http://localhost:6724"
}
}
],
"ssl_verify": false
}
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local code, body, actual_body = t("/anything",
ngx.HTTP_POST,
[[{
"messages": [
{ "role": "system", "content": "You are a mathematician" },
{ "role": "user", "content": "What is 1+1?" }
]
}]],
nil,
{
["test-type"] = "options",
["Content-Type"] = "application/json",
}
)
ngx.status = code
ngx.say(actual_body)
}
}
--- error_code: 200
--- response_body_chomp
options_works
=== TEST 12: override path
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-proxy-multi": {
"instances": [
{
"name": "openai-official",
"provider": "openai",
"weight": 1,
"auth": {
"header": {
"Authorization": "Bearer token"
}
},
"options": {
"model": "some-model",
"foo": "bar",
"temperature": 1.0
},
"override": {
"endpoint": "http://localhost:6724/random"
}
}
],
"ssl_verify": false
}
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local code, body, actual_body = t("/anything",
ngx.HTTP_POST,
[[{
"messages": [
{ "role": "system", "content": "You are a mathematician" },
{ "role": "user", "content": "What is 1+1?" }
]
}]],
nil,
{
["test-type"] = "path",
["Content-Type"] = "application/json",
}
)
ngx.status = code
ngx.say(actual_body)
}
}
--- response_body_chomp
path override works
=== TEST 13: set route with stream = true (SSE)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-proxy-multi": {
"instances": [
{
"name": "openai-official",
"provider": "openai",
"weight": 1,
"auth": {
"header": {
"Authorization": "Bearer token"
}
},
"options": {
"model": "gpt-35-turbo-instruct",
"max_tokens": 512,
"temperature": 1.0,
"stream": true
},
"override": {
"endpoint": "http://localhost:7737"
}
}
],
"ssl_verify": false
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 14: test is SSE works as expected
--- config
location /t {
content_by_lua_block {
local http = require("resty.http")
local httpc = http.new()
local core = require("apisix.core")
local ok, err = httpc:connect({
scheme = "http",
host = "localhost",
port = ngx.var.server_port,
})
if not ok then
ngx.status = 500
ngx.say(err)
return
end
local params = {
method = "POST",
headers = {
["Content-Type"] = "application/json",
},
path = "/anything",
body = [[{
"messages": [
{ "role": "system", "content": "some content" }
]
}]],
}
local res, err = httpc:request(params)
if not res then
ngx.status = 500
ngx.say(err)
return
end
local final_res = {}
while true do
local chunk, err = res.body_reader() -- will read chunk by chunk
if err then
core.log.error("failed to read response chunk: ", err)
break
end
if not chunk then
break
end
core.table.insert_tail(final_res, chunk)
end
ngx.print(#final_res .. final_res[6])
}
}
--- response_body_like eval
qr/6data: \[DONE\]\n\n/

View File

@@ -0,0 +1,347 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
log_level("info");
repeat_each(1);
no_long_string();
no_root_location();
my $resp_file = 't/assets/ai-proxy-response.json';
open(my $fh, '<', $resp_file) or die "Could not open file '$resp_file' $!";
my $resp = do { local $/; <$fh> };
close($fh);
print "Hello, World!\n";
print $resp;
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
my $user_yaml_config = <<_EOC_;
plugins:
- ai-proxy-multi
_EOC_
$block->set_value("extra_yaml_config", $user_yaml_config);
my $http_config = $block->http_config // <<_EOC_;
server {
server_name openai;
listen 6724;
default_type 'application/json';
location /v1/chat/completions {
content_by_lua_block {
local json = require("cjson.safe")
if ngx.req.get_method() ~= "POST" then
ngx.status = 400
ngx.say("Unsupported request method: ", ngx.req.get_method())
end
ngx.req.read_body()
local body, err = ngx.req.get_body_data()
body, err = json.decode(body)
local query_auth = ngx.req.get_uri_args()["api_key"]
if query_auth ~= "apikey" then
ngx.status = 401
ngx.say("Unauthorized")
return
end
ngx.status = 200
ngx.say("passed")
}
}
location /test/params/in/overridden/endpoint {
content_by_lua_block {
local json = require("cjson.safe")
local core = require("apisix.core")
if ngx.req.get_method() ~= "POST" then
ngx.status = 400
ngx.say("Unsupported request method: ", ngx.req.get_method())
end
local query_auth = ngx.req.get_uri_args()["api_key"]
ngx.log(ngx.INFO, "found query params: ", core.json.stably_encode(ngx.req.get_uri_args()))
if query_auth ~= "apikey" then
ngx.status = 401
ngx.say("Unauthorized")
return
end
ngx.status = 200
ngx.say("passed")
}
}
}
_EOC_
$block->set_value("http_config", $http_config);
});
run_tests();
__DATA__
=== TEST 1: set route with wrong query param
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-proxy-multi": {
"instances": [
{
"name": "openai-official",
"provider": "openai",
"weight": 1,
"auth": {
"query": {
"api_key": "wrong_key"
}
},
"options": {
"model": "gpt-35-turbo-instruct",
"max_tokens": 512,
"temperature": 1.0
},
"override": {
"endpoint": "http://localhost:6724"
}
}
],
"ssl_verify": false
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: send request
--- request
POST /anything
{ "messages": [ { "role": "system", "content": "You are a mathematician" }, { "role": "user", "content": "What is 1+1?"} ] }
--- error_code: 401
--- response_body
Unauthorized
=== TEST 3: set route with right query param
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-proxy-multi": {
"instances": [
{
"name": "openai-official",
"provider": "openai",
"weight": 1,
"auth": {
"query": {
"api_key": "apikey"
}
},
"options": {
"model": "gpt-35-turbo-instruct",
"max_tokens": 512,
"temperature": 1.0
},
"override": {
"endpoint": "http://localhost:6724"
}
}
],
"ssl_verify": false
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 4: send request
--- request
POST /anything
{ "messages": [ { "role": "system", "content": "You are a mathematician" }, { "role": "user", "content": "What is 1+1?"} ] }
--- error_code: 200
--- response_body
passed
=== TEST 5: set route without overriding the endpoint_url
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-proxy-multi": {
"instances": [
{
"name": "openai-official",
"provider": "openai",
"weight": 1,
"auth": {
"header": {
"Authorization": "some-key"
}
},
"options": {
"model": "gpt-35-turbo-instruct",
"max_tokens": 512,
"temperature": 1.0
}
}
],
"ssl_verify": false
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 6: send request
--- custom_trusted_cert: /etc/ssl/certs/ca-certificates.crt
--- request
POST /anything
{ "messages": [ { "role": "system", "content": "You are a mathematician" }, { "role": "user", "content": "What is 1+1?"} ] }
--- error_code: 401
=== TEST 7: query params in override.endpoint should be sent to LLM
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-proxy-multi": {
"instances": [
{
"name": "openai-official",
"provider": "openai",
"weight": 1,
"auth": {
"query": {
"api_key": "apikey"
}
},
"options": {
"model": "gpt-35-turbo-instruct",
"max_tokens": 512,
"temperature": 1.0
},
"override": {
"endpoint": "http://localhost:6724/test/params/in/overridden/endpoint?some_query=yes"
}
}
],
"ssl_verify": false
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 8: send request
--- request
POST /anything
{ "messages": [ { "role": "system", "content": "You are a mathematician" }, { "role": "user", "content": "What is 1+1?"} ] }
--- error_code: 200
--- error_log
found query params: {"api_key":"apikey","some_query":"yes"}
--- response_body
passed

View File

@@ -0,0 +1,321 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
log_level("info");
repeat_each(1);
no_long_string();
no_root_location();
my $resp_file = 't/assets/ai-proxy-response.json';
open(my $fh, '<', $resp_file) or die "Could not open file '$resp_file' $!";
my $resp = do { local $/; <$fh> };
close($fh);
print "Hello, World!\n";
print $resp;
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
my $http_config = $block->http_config // <<_EOC_;
server {
server_name openai;
listen 6724;
default_type 'application/json';
location /v1/chat/completions {
content_by_lua_block {
local json = require("cjson.safe")
if ngx.req.get_method() ~= "POST" then
ngx.status = 400
ngx.say("Unsupported request method: ", ngx.req.get_method())
end
ngx.req.read_body()
local body, err = ngx.req.get_body_data()
body, err = json.decode(body)
local test_type = ngx.req.get_headers()["test-type"]
if test_type == "options" then
if body.foo == "bar" then
ngx.status = 200
ngx.say("options works")
else
ngx.status = 500
ngx.say("model options feature doesn't work")
end
return
end
local header_auth = ngx.req.get_headers()["authorization"]
local query_auth = ngx.req.get_uri_args()["apikey"]
if header_auth ~= "Bearer token" and query_auth ~= "apikey" then
ngx.status = 401
ngx.say("Unauthorized")
return
end
if header_auth == "Bearer token" or query_auth == "apikey" then
ngx.req.read_body()
local body, err = ngx.req.get_body_data()
body, err = json.decode(body)
if not body.messages or #body.messages < 1 then
ngx.status = 400
ngx.say([[{ "error": "bad request"}]])
return
end
if body.messages[1].content == "write an SQL query to get all rows from student table" then
ngx.print("SELECT * FROM STUDENTS")
return
end
ngx.status = 200
ngx.say([[$resp]])
return
end
ngx.status = 503
ngx.say("reached the end of the test suite")
}
}
location /random {
content_by_lua_block {
ngx.say("path override works")
}
}
}
_EOC_
$block->set_value("http_config", $http_config);
});
run_tests();
__DATA__
=== TEST 1: set route with right auth header
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-proxy": {
"provider": "openai-compatible",
"auth": {
"header": {
"Authorization": "Bearer token"
}
},
"options": {
"model": "custom",
"max_tokens": 512,
"temperature": 1.0
},
"override": {
"endpoint": "http://localhost:6724/v1/chat/completions"
},
"ssl_verify": false
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: send request
--- request
POST /anything
{ "messages": [ { "role": "system", "content": "You are a mathematician" }, { "role": "user", "content": "What is 1+1?"} ] }
--- more_headers
Authorization: Bearer token
--- error_code: 200
--- response_body eval
qr/\{ "content": "1 \+ 1 = 2\.", "role": "assistant" \}/
=== TEST 3: override path
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-proxy": {
"provider": "openai-compatible",
"auth": {
"header": {
"Authorization": "Bearer token"
}
},
"options": {
"model": "some-model",
"foo": "bar",
"temperature": 1.0
},
"override": {
"endpoint": "http://localhost:6724/random"
},
"ssl_verify": false
}
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local code, body, actual_body = t("/anything",
ngx.HTTP_POST,
[[{
"messages": [
{ "role": "system", "content": "You are a mathematician" },
{ "role": "user", "content": "What is 1+1?" }
]
}]],
nil,
{
["test-type"] = "path",
["Content-Type"] = "application/json",
}
)
ngx.status = code
ngx.say(actual_body)
}
}
--- response_body_chomp
path override works
=== TEST 4: set route with stream = true (SSE)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-proxy": {
"provider": "openai-compatible",
"auth": {
"header": {
"Authorization": "Bearer token"
}
},
"options": {
"model": "custom",
"max_tokens": 512,
"temperature": 1.0,
"stream": true
},
"override": {
"endpoint": "http://localhost:7737/v1/chat/completions"
},
"ssl_verify": false
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 5: test is SSE works as expected
--- config
location /t {
content_by_lua_block {
local http = require("resty.http")
local httpc = http.new()
local core = require("apisix.core")
local ok, err = httpc:connect({
scheme = "http",
host = "localhost",
port = ngx.var.server_port,
})
if not ok then
ngx.status = 500
ngx.say(err)
return
end
local params = {
method = "POST",
headers = {
["Content-Type"] = "application/json",
},
path = "/anything",
body = [[{
"messages": [
{ "role": "system", "content": "some content" }
]
}]],
}
local res, err = httpc:request(params)
if not res then
ngx.status = 500
ngx.say(err)
return
end

View File

@@ -0,0 +1,673 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
log_level("info");
repeat_each(1);
no_long_string();
no_root_location();
my $resp_file = 't/assets/ai-proxy-response.json';
open(my $fh, '<', $resp_file) or die "Could not open file '$resp_file' $!";
my $resp = do { local $/; <$fh> };
close($fh);
print "Hello, World!\n";
print $resp;
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
my $http_config = $block->http_config // <<_EOC_;
server {
server_name openai;
listen 6724;
default_type 'application/json';
location /v1/chat/completions {
content_by_lua_block {
local json = require("cjson.safe")
if ngx.req.get_method() ~= "POST" then
ngx.status = 400
ngx.say("Unsupported request method: ", ngx.req.get_method())
end
ngx.req.read_body()
local body, err = ngx.req.get_body_data()
body, err = json.decode(body)
local test_type = ngx.req.get_headers()["test-type"]
if test_type == "options" then
if body.foo == "bar" then
ngx.status = 200
ngx.say("options works")
else
ngx.status = 500
ngx.say("model options feature doesn't work")
end
return
end
local header_auth = ngx.req.get_headers()["authorization"]
local query_auth = ngx.req.get_uri_args()["apikey"]
if header_auth ~= "Bearer token" and query_auth ~= "apikey" then
ngx.status = 401
ngx.say("Unauthorized")
return
end
if header_auth == "Bearer token" or query_auth == "apikey" then
ngx.req.read_body()
local body, err = ngx.req.get_body_data()
body, err = json.decode(body)
if not body.messages or #body.messages < 1 then
ngx.status = 400
ngx.say([[{ "error": "bad request"}]])
return
end
if body.messages[1].content == "write an SQL query to get all rows from student table" then
ngx.print("SELECT * FROM STUDENTS")
return
end
ngx.status = 200
ngx.say([[$resp]])
return
end
ngx.status = 503
ngx.say("reached the end of the test suite")
}
}
location /v1/embeddings {
content_by_lua_block {
if ngx.req.get_method() ~= "POST" then
ngx.status = 400
ngx.say("unsupported request method: ", ngx.req.get_method())
end
local header_auth = ngx.req.get_headers()["authorization"]
if header_auth ~= "Bearer token" then
ngx.status = 401
ngx.say("unauthorized")
return
end
ngx.req.read_body()
local body, err = ngx.req.get_body_data()
local json = require("cjson.safe")
body, err = json.decode(body)
if err then
ngx.status = 400
ngx.say("failed to get request body: ", err)
end
if body.model ~= "text-embedding-ada-002" then
ngx.status = 400
ngx.say("unsupported model: ", body.model)
return
end
if body.encoding_format ~= "float" then
ngx.status = 400
ngx.say("unsupported encoding format: ", body.encoding_format)
return
end
ngx.status = 200
ngx.say([[
{
"object": "list",
"data": [
{
"object": "embedding",
"embedding": [
0.0023064255,
-0.009327292,
-0.0028842222
],
"index": 0
}
],
"model": "text-embedding-ada-002",
"usage": {
"prompt_tokens": 8,
"total_tokens": 8
}
}
]])
}
}
location /random {
content_by_lua_block {
ngx.say("path override works")
}
}
}
_EOC_
$block->set_value("http_config", $http_config);
});
run_tests();
__DATA__
=== TEST 1: minimal viable configuration
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.ai-proxy")
local ok, err = plugin.check_schema({
provider = "openai",
options = {
model = "gpt-4",
},
auth = {
header = {
some_header = "some_value"
}
}
})
if not ok then
ngx.say(err)
else
ngx.say("passed")
end
}
}
--- response_body
passed
=== TEST 2: unsupported provider
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.ai-proxy")
local ok, err = plugin.check_schema({
provider = "some-unique",
options = {
model = "gpt-4",
},
auth = {
header = {
some_header = "some_value"
}
}
})
if not ok then
ngx.say(err)
else
ngx.say("passed")
end
}
}
--- response_body eval
qr/.*property "provider" validation failed: matches none of the enum values.*/
=== TEST 3: set route with wrong auth header
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-proxy": {
"provider": "openai",
"auth": {
"header": {
"Authorization": "Bearer wrongtoken"
}
},
"options": {
"model": "gpt-35-turbo-instruct",
"max_tokens": 512,
"temperature": 1.0
},
"override": {
"endpoint": "http://localhost:6724"
},
"ssl_verify": false
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 4: send request
--- request
POST /anything
{ "messages": [ { "role": "system", "content": "You are a mathematician" }, { "role": "user", "content": "What is 1+1?"} ] }
--- error_code: 401
--- response_body
Unauthorized
=== TEST 5: set route with right auth header
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-proxy": {
"provider": "openai",
"auth": {
"header": {
"Authorization": "Bearer token"
}
},
"options": {
"model": "gpt-35-turbo-instruct",
"max_tokens": 512,
"temperature": 1.0
},
"override": {
"endpoint": "http://localhost:6724"
},
"ssl_verify": false
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 6: send request
--- request
POST /anything
{ "messages": [ { "role": "system", "content": "You are a mathematician" }, { "role": "user", "content": "What is 1+1?"} ] }
--- more_headers
Authorization: Bearer token
--- error_code: 200
--- response_body eval
qr/\{ "content": "1 \+ 1 = 2\.", "role": "assistant" \}/
=== TEST 7: send request with empty body
--- request
POST /anything
--- more_headers
Authorization: Bearer token
--- error_code: 400
--- response_body_chomp
failed to get request body: request body is empty
=== TEST 8: send request with wrong method (GET) should work
--- request
GET /anything
{ "messages": [ { "role": "system", "content": "You are a mathematician" }, { "role": "user", "content": "What is 1+1?"} ] }
--- more_headers
Authorization: Bearer token
--- error_code: 200
--- response_body eval
qr/\{ "content": "1 \+ 1 = 2\.", "role": "assistant" \}/
=== TEST 9: wrong JSON in request body should give error
--- request
GET /anything
{}"messages": [ { "role": "system", "cont
--- error_code: 400
--- response_body
{"message":"could not get parse JSON request body: Expected the end but found T_STRING at character 3"}
=== TEST 10: content-type should be JSON
--- request
POST /anything
prompt%3Dwhat%2520is%25201%2520%252B%25201
--- more_headers
Content-Type: application/x-www-form-urlencoded
--- error_code: 400
--- response_body chomp
unsupported content-type: application/x-www-form-urlencoded, only application/json is supported
=== TEST 11: model options being merged to request body
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-proxy": {
"provider": "openai",
"auth": {
"header": {
"Authorization": "Bearer token"
}
},
"options": {
"model": "some-model",
"foo": "bar",
"temperature": 1.0
},
"override": {
"endpoint": "http://localhost:6724"
},
"ssl_verify": false
}
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local code, body, actual_body = t("/anything",
ngx.HTTP_POST,
[[{
"messages": [
{ "role": "system", "content": "You are a mathematician" },
{ "role": "user", "content": "What is 1+1?" }
]
}]],
nil,
{
["test-type"] = "options",
["Content-Type"] = "application/json",
}
)
ngx.status = code
ngx.say(actual_body)
}
}
--- error_code: 200
--- response_body_chomp
options_works
=== TEST 12: override path
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-proxy": {
"provider": "openai",
"model": "some-model",
"auth": {
"header": {
"Authorization": "Bearer token"
}
},
"options": {
"foo": "bar",
"temperature": 1.0
},
"override": {
"endpoint": "http://localhost:6724/random"
},
"ssl_verify": false
}
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local code, body, actual_body = t("/anything",
ngx.HTTP_POST,
[[{
"messages": [
{ "role": "system", "content": "You are a mathematician" },
{ "role": "user", "content": "What is 1+1?" }
]
}]],
nil,
{
["test-type"] = "path",
["Content-Type"] = "application/json",
}
)
ngx.status = code
ngx.say(actual_body)
}
}
--- response_body_chomp
path override works
=== TEST 13: set route with stream = true (SSE)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-proxy": {
"provider": "openai",
"auth": {
"header": {
"Authorization": "Bearer token"
}
},
"options": {
"model": "gpt-35-turbo-instruct",
"max_tokens": 512,
"temperature": 1.0,
"stream": true
},
"override": {
"endpoint": "http://localhost:7737"
},
"ssl_verify": false
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 14: test is SSE works as expected
--- config
location /t {
content_by_lua_block {
local http = require("resty.http")
local httpc = http.new()
local core = require("apisix.core")
local ok, err = httpc:connect({
scheme = "http",
host = "localhost",
port = ngx.var.server_port,
})
if not ok then
ngx.status = 500
ngx.say(err)
return
end
local params = {
method = "POST",
headers = {
["Content-Type"] = "application/json",
},
path = "/anything",
body = [[{
"messages": [
{ "role": "system", "content": "some content" }
]
}]],
}
local res, err = httpc:request(params)
if not res then
ngx.status = 500
ngx.say(err)
return
end
local final_res = {}
while true do
local chunk, err = res.body_reader() -- will read chunk by chunk
if err then
core.log.error("failed to read response chunk: ", err)
break
end
if not chunk then
break
end
core.table.insert_tail(final_res, chunk)
end
ngx.print(#final_res .. final_res[6])
}
}
--- response_body_like eval
qr/6data: \[DONE\]\n\n/
=== TEST 15: proxy embedding endpoint
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/embeddings",
"plugins": {
"ai-proxy": {
"provider": "openai",
"auth": {
"header": {
"Authorization": "Bearer token"
}
},
"options": {
"model": "text-embedding-ada-002",
"encoding_format": "float"
},
"override": {
"endpoint": "http://localhost:6724/v1/embeddings"
}
}
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say("passed")
}
}
--- response_body
passed
=== TEST 16: send request to embedding api
--- request
POST /embeddings
{
"input": "The food was delicious and the waiter..."
}
--- error_code: 200
--- response_body_like eval
qr/.*text-embedding-ada-002*/

View File

@@ -0,0 +1,315 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
log_level("info");
repeat_each(1);
no_long_string();
no_root_location();
my $resp_file = 't/assets/ai-proxy-response.json';
open(my $fh, '<', $resp_file) or die "Could not open file '$resp_file' $!";
my $resp = do { local $/; <$fh> };
close($fh);
print "Hello, World!\n";
print $resp;
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
my $http_config = $block->http_config // <<_EOC_;
server {
server_name openai;
listen 6724;
default_type 'application/json';
location /v1/chat/completions {
content_by_lua_block {
local json = require("cjson.safe")
if ngx.req.get_method() ~= "POST" then
ngx.status = 400
ngx.say("Unsupported request method: ", ngx.req.get_method())
end
ngx.req.read_body()
local body, err = ngx.req.get_body_data()
body, err = json.decode(body)
local query_auth = ngx.req.get_uri_args()["api_key"]
if query_auth ~= "apikey" then
ngx.status = 401
ngx.say("Unauthorized")
return
end
ngx.status = 200
ngx.say("passed")
}
}
location /test/params/in/overridden/endpoint {
content_by_lua_block {
local json = require("cjson.safe")
local core = require("apisix.core")
if ngx.req.get_method() ~= "POST" then
ngx.status = 400
ngx.say("Unsupported request method: ", ngx.req.get_method())
end
local query_auth = ngx.req.get_uri_args()["api_key"]
ngx.log(ngx.INFO, "found query params: ", core.json.stably_encode(ngx.req.get_uri_args()))
if query_auth ~= "apikey" then
ngx.status = 401
ngx.say("Unauthorized")
return
end
ngx.status = 200
ngx.say("passed")
}
}
}
_EOC_
$block->set_value("http_config", $http_config);
});
run_tests();
__DATA__
=== TEST 1: set route with wrong query param
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-proxy": {
"provider": "openai",
"auth": {
"query": {
"api_key": "wrong_key"
}
},
"options": {
"model": "gpt-35-turbo-instruct",
"max_tokens": 512,
"temperature": 1.0
},
"override": {
"endpoint": "http://localhost:6724"
},
"ssl_verify": false
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: send request
--- request
POST /anything
{ "messages": [ { "role": "system", "content": "You are a mathematician" }, { "role": "user", "content": "What is 1+1?"} ] }
--- error_code: 401
--- response_body
Unauthorized
=== TEST 3: set route with right query param
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-proxy": {
"provider": "openai",
"auth": {
"query": {
"api_key": "apikey"
}
},
"options": {
"model": "gpt-35-turbo-instruct",
"max_tokens": 512,
"temperature": 1.0
},
"override": {
"endpoint": "http://localhost:6724"
},
"ssl_verify": false
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 4: send request
--- request
POST /anything
{ "messages": [ { "role": "system", "content": "You are a mathematician" }, { "role": "user", "content": "What is 1+1?"} ] }
--- error_code: 200
--- response_body
passed
=== TEST 5: set route without overriding the endpoint_url
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-proxy": {
"provider": "openai",
"auth": {
"header": {
"Authorization": "some-key"
}
},
"options": {
"model": "gpt-4",
"max_tokens": 512,
"temperature": 1.0
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 6: send request
--- custom_trusted_cert: /etc/ssl/certs/ca-certificates.crt
--- request
POST /anything
{ "messages": [ { "role": "system", "content": "You are a mathematician" }, { "role": "user", "content": "What is 1+1?"} ] }
--- error_code: 401
=== TEST 7: query params in override.endpoint should be sent to LLM
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-proxy": {
"provider": "openai",
"auth": {
"query": {
"api_key": "apikey"
}
},
"options": {
"max_tokens": 512,
"temperature": 1.0
},
"override": {
"endpoint": "http://localhost:6724/test/params/in/overridden/endpoint?some_query=yes"
},
"ssl_verify": false
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 8: send request
--- request
POST /anything
{ "messages": [ { "role": "system", "content": "You are a mathematician" }, { "role": "user", "content": "What is 1+1?"} ] }
--- error_code: 200
--- error_log
found query params: {"api_key":"apikey","some_query":"yes"}
--- response_body
passed

View File

@@ -0,0 +1,392 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
log_level("info");
repeat_each(1);
no_long_string();
no_root_location();
my $resp_file = 't/assets/embeddings.json';
open(my $fh, '<', $resp_file) or die "Could not open file '$resp_file' $!";
my $embeddings = do { local $/; <$fh> };
close($fh);
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
my $http_config = $block->http_config // <<_EOC_;
server {
listen 3623;
default_type 'application/json';
location /embeddings {
content_by_lua_block {
local json = require("cjson.safe")
if ngx.req.get_method() ~= "POST" then
ngx.status = 400
ngx.say("Unsupported request method: ", ngx.req.get_method())
return
end
ngx.req.read_body()
local body, err = ngx.req.get_body_data()
body, err = json.decode(body)
local header_auth = ngx.req.get_headers()["api-key"]
if header_auth ~= "key" then
ngx.status = 401
ngx.say("Unauthorized")
return
end
ngx.status = 200
ngx.say([[$embeddings]])
}
}
location /search {
content_by_lua_block {
local json = require("cjson.safe")
if ngx.req.get_method() ~= "POST" then
ngx.status = 400
ngx.say("Unsupported request method: ", ngx.req.get_method())
end
local header_auth = ngx.req.get_headers()["api-key"]
if header_auth ~= "key" then
ngx.status = 401
ngx.say("Unauthorized")
return
end
ngx.req.read_body()
local body, err = ngx.req.get_body_data()
body, err = json.decode(body)
if body.vectorQueries[1].vector[1] ~= 123456789 then
ngx.status = 500
ngx.say({ error = "occurred" })
return
end
ngx.status = 200
ngx.print("passed")
}
}
}
_EOC_
$block->set_value("http_config", $http_config);
});
run_tests();
__DATA__
=== TEST 1: minimal viable configuration
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.ai-rag")
local ok, err = plugin.check_schema({
embeddings_provider = {
azure_openai = {
api_key = "sdfjasdfh",
endpoint = "http://a.b.com"
}
},
vector_search_provider = {
azure_ai_search = {
api_key = "iuhsdf",
endpoint = "http://a.b.com"
}
}
})
if not ok then
ngx.say(err)
else
ngx.say("passed")
end
}
}
--- response_body
passed
=== TEST 2: vector search provider missing
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.ai-rag")
local ok, err = plugin.check_schema({
embeddings_provider = {
azure_openai = {
api_key = "sdfjasdfh",
endpoint = "http://a.b.com"
}
}
})
if not ok then
ngx.say(err)
else
ngx.say("passed")
end
}
}
--- response_body
property "vector_search_provider" is required
=== TEST 3: embeddings provider missing
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.ai-rag")
local ok, err = plugin.check_schema({
vector_search_provider = {
azure_ai_search = {
api_key = "iuhsdf",
endpoint = "http://a.b.com"
}
}
})
if not ok then
ngx.say(err)
else
ngx.say("passed")
end
}
}
--- response_body
property "embeddings_provider" is required
=== TEST 4: wrong auth header for embeddings provider
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"plugins": {
"ai-rag": {
"embeddings_provider": {
"azure_openai": {
"endpoint": "http://localhost:3623/embeddings",
"api_key": "wrongkey"
}
},
"vector_search_provider": {
"azure_ai_search": {
"endpoint": "http://localhost:3623/search",
"api_key": "key"
}
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
},
"scheme": "http",
"pass_host": "node"
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 5: send request
--- request
POST /echo
{"ai_rag":{"vector_search":{"fields":"contentVector"},"embeddings":{"input":"which service is good for devops","dimensions":1024}}}
--- error_code: 401
--- response_body
Unauthorized
--- error_log
could not get embeddings: Unauthorized
=== TEST 6: wrong auth header for search provider
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"plugins": {
"ai-rag": {
"embeddings_provider": {
"azure_openai": {
"endpoint": "http://localhost:3623/embeddings",
"api_key": "key"
}
},
"vector_search_provider": {
"azure_ai_search": {
"endpoint": "http://localhost:3623/search",
"api_key": "wrongkey"
}
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
},
"scheme": "http",
"pass_host": "node"
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 7: send request
--- request
POST /echo
{"ai_rag":{"vector_search":{"fields":"contentVector"},"embeddings":{"input":"which service is good for devops","dimensions":1024}}}
--- error_code: 401
--- error_log
could not get vector_search result: Unauthorized
=== TEST 8: send request with empty body
--- request
POST /echo
--- error_code: 400
--- response_body_chomp
failed to get request body: request body is empty
=== TEST 9: send request with vector search fields missing
--- request
POST /echo
{"ai_rag":{"vector_search":{"missing-fields":"something"},"embeddings":{"input":"which service is good for devops","dimensions":1024}}}
--- error_code: 400
--- error_log
request body fails schema check: property "ai_rag" validation failed: property "vector_search" validation failed: property "fields" is required
=== TEST 10: send request with embedding input missing
--- request
POST /echo
{"ai_rag":{"vector_search":{"fields":"something"},"embeddings":{"missinginput":"which service is good for devops"}}}
--- error_code: 400
--- error_log
request body fails schema check: property "ai_rag" validation failed: property "embeddings" validation failed: property "input" is required
=== TEST 11: configure plugin with right auth headers
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"plugins": {
"ai-rag": {
"embeddings_provider": {
"azure_openai": {
"endpoint": "http://localhost:3623/embeddings",
"api_key": "key"
}
},
"vector_search_provider": {
"azure_ai_search": {
"endpoint": "http://localhost:3623/search",
"api_key": "key"
}
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
},
"scheme": "http",
"pass_host": "node"
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 12: send request with embedding input missing
--- request
POST /echo
{"ai_rag":{"vector_search":{"fields":"something"},"embeddings":{"input":"which service is good for devops"}}}
--- error_code: 200
--- response_body eval
qr/\{"messages":\[\{"content":"passed","role":"user"\}\]\}|\{"messages":\[\{"role":"user","content":"passed"\}\]\}/

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,739 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
log_level("info");
repeat_each(1);
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
my $http_config = $block->http_config // <<_EOC_;
server {
server_name openai;
listen 6724;
default_type 'application/json';
location /v1/chat/completions {
content_by_lua_block {
ngx.req.read_body()
local body = ngx.req.get_body_data()
local json = require("cjson.safe")
local request_data = json.decode(body)
local header_auth = ngx.req.get_headers()["authorization"]
local query_auth = ngx.req.get_uri_args()["api_key"]
if header_auth ~= "Bearer token" and query_auth ~= "apikey" then
ngx.status = 401
ngx.say("Unauthorized")
return
end
local response = {
choices = {
{
message = {
content = request_data.messages[1].content .. ' ' .. request_data.messages[2].content
}
}
}
}
local json = require("cjson.safe")
local json_response = json.encode(response)
ngx.say(json_response)
}
}
location /random {
content_by_lua_block {
local response = {
choices = {
{
message = {
content = 'return by random endpoint'
}
}
}
}
local json = require("cjson.safe")
local json_response = json.encode(response)
ngx.say(json_response)
}
}
location /internalservererror {
content_by_lua_block {
ngx.status = 500
ngx.say("Internal Server Error")
return
}
}
location /bad_request {
content_by_lua_block {
ngx.status = 400
ngx.say("Bad Request")
return
}
}
}
_EOC_
$block->set_value("http_config", $http_config);
});
run_tests();
__DATA__
=== TEST 1: minimal viable configuration
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.ai-request-rewrite")
local ok, err = plugin.check_schema({
prompt = "some prompt",
provider = "openai",
auth = {
header = {
Authorization = "Bearer token"
}
}
})
if not ok then
ngx.print(err)
else
ngx.say("passed")
end
}
}
--- response_body
passed
=== TEST 2: missing prompt field should not pass
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.ai-request-rewrite")
local ok, err = plugin.check_schema({
provider = "openai",
auth = {
header = {
Authorization = "Bearer token"
}
}
})
if not ok then
ngx.say(err)
else
ngx.say("passed")
end
}
}
--- response_body
property "prompt" is required
=== TEST 3: missing auth field should not pass
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.ai-request-rewrite")
local ok, err = plugin.check_schema({
prompt = "some prompt",
provider = "openai",
})
if not ok then
ngx.say(err)
else
ngx.say("passed")
end
}
}
--- response_body
property "auth" is required
=== TEST 4: missing provider field should not pass
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.ai-request-rewrite")
local ok, err = plugin.check_schema({
prompt = "some prompt",
auth = {
header = {
Authorization = "Bearer token"
}
}
})
if not ok then
ngx.say(err)
else
ngx.say("passed")
end
}
}
--- response_body
property "provider" is required
=== TEST 5: provider must be one of: deepseek, openai, aimlapi, openai-compatible
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.ai-request-rewrite")
local ok, err = plugin.check_schema({
prompt = "some prompt",
provider = "invalid-provider",
auth = {
header = {
Authorization = "Bearer token"
}
}
})
if not ok then
ngx.say(err)
else
ngx.say("passed")
end
}
}
--- response_body
property "provider" validation failed: matches none of the enum values
=== TEST 6: provider deepseek
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.ai-request-rewrite")
local ok, err = plugin.check_schema({
prompt = "some prompt",
provider = "deepseek",
auth = {
header = {
Authorization = "Bearer token"
}
}
})
if not ok then
ngx.say(err)
else
ngx.say("passed")
end
}
}
--- response_body
passed
=== TEST 7: provider openai-compatible
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.ai-request-rewrite")
local ok, err = plugin.check_schema({
prompt = "some prompt",
provider = "openai-compatible",
auth = {
header = {
Authorization = "Bearer token"
}
},
override = {
endpoint = "http://localhost:6724"
}
})
if not ok then
ngx.say(err)
else
ngx.say("passed")
end
}
}
--- response_body
passed
=== TEST 8: override endpoint works
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-request-rewrite": {
"prompt": "some prompt",
"provider": "openai",
"auth": {
"header": {
"Authorization": "Bearer token"
}
},
"override": {
"endpoint": "http://localhost:6724/random"
},
"ssl_verify": false
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local code, body, actual_body = t("/anything",
ngx.HTTP_POST,
"some random content",
nil,
{
["Content-Type"] = "text/plain",
}
)
local json = require("cjson.safe")
local response_data = json.decode(actual_body)
if response_data.data == 'return by random endpoint' then
ngx.say("passed")
else
ngx.say(actual_body)
end
}
}
--- response_body
passed
=== TEST 9: set route with wrong auth header
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-request-rewrite": {
"prompt": "some prompt",
"auth": {
"header": {
"Authorization": "Bearer wrong-token"
}
},
"provider": "openai",
"override": {
"endpoint": "http://localhost:6724"
},
"ssl_verify": false
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}]]
)
local code, body, actual_body = t("/anything",
ngx.HTTP_POST,
"some random content",
nil,
{
["Content-Type"] = "text/plain",
}
)
if code == 500 then
ngx.say('passed')
return
end
}
}
--- error_log
LLM service returned error status: 401
--- response_body
passed
=== TEST 10: set route with correct query param
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-request-rewrite": {
"prompt": "some prompt",
"auth": {
"query": {
"api_key": "apikey"
}
},
"provider": "openai",
"override": {
"endpoint": "http://localhost:6724"
},
"ssl_verify": false
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}]]
)
local code, body, actual_body = t("/anything",
ngx.HTTP_POST,
"some random content",
nil,
{
["Content-Type"] = "text/plain",
}
)
local json = require("cjson.safe")
local response_data = json.decode(actual_body)
if response_data.data == "some prompt some random content" then
ngx.say("passed")
else
ngx.say("failed")
end
}
}
--- response_body
passed
=== TEST 11: set route with wrong query param
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-request-rewrite": {
"prompt": "some prompt",
"auth": {
"query": {
"api_key": "wrong_key"
}
},
"provider": "openai",
"override": {
"endpoint": "http://localhost:6724"
},
"ssl_verify": false
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}]]
)
local code, body, actual_body = t("/anything",
ngx.HTTP_POST,
"some random content",
nil,
{
["Content-Type"] = "text/plain",
}
)
if code == 500 then
ngx.say('passed')
return
end
}
}
--- error_log
LLM service returned error status: 401
--- response_body
passed
=== TEST 12: prompt passed correctly to LLM service
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-request-rewrite": {
"prompt": "some prompt to test",
"auth": {
"query": {
"api_key": "apikey"
}
},
"provider": "openai",
"override": {
"endpoint": "http://localhost:6724"
},
"ssl_verify": false
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}]]
)
local code, body, actual_body = t("/anything",
ngx.HTTP_POST,
"some random content",
nil,
{
["Content-Type"] = "text/plain",
}
)
local json = require("cjson.safe")
local response_data = json.decode(actual_body)
if response_data.data == "some prompt to test some random content" then
ngx.say("passed")
else
ngx.say("failed")
end
}
}
--- response_body
passed
=== TEST 13: check LLM bad request
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-request-rewrite": {
"prompt": "some prompt to test",
"auth": {
"query": {
"api_key": "apikey"
}
},
"provider": "openai",
"override": {
"endpoint": "http://localhost:6724/bad_request"
},
"ssl_verify": false,
"options": {
"model": "check_options_model",
"temperature": 0.5,
"max_tokens": 100,
"top_p": 1,
"frequency_penalty": 0,
"presence_penalty": 0
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}]]
)
local code, body, actual_body = t("/anything",
ngx.HTTP_POST,
"some random content",
nil,
{
["Content-Type"] = "text/plain",
}
)
if code == 500 then
ngx.say('passed')
return
end
}
}
--- error_log
LLM service returned error status: 400
--- response_body
passed
=== TEST 14: check LLM internal server error
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-request-rewrite": {
"prompt": "some prompt to test",
"auth": {
"query": {
"api_key": "apikey"
}
},
"provider": "openai",
"override": {
"endpoint": "http://localhost:6724/internalservererror"
},
"ssl_verify": false,
"options": {
"model": "check_options_model",
"temperature": 0.5,
"max_tokens": 100,
"top_p": 1,
"frequency_penalty": 0,
"presence_penalty": 0
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}]]
)
local code, body, actual_body = t("/anything",
ngx.HTTP_POST,
"some random content",
nil,
{
["Content-Type"] = "text/plain",
}
)
if code == 500 then
ngx.say('passed')
return
end
}
}
--- error_log
LLM service returned error status: 500
--- response_body
passed
=== TEST 15: provider aimlapi
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.ai-request-rewrite")
local ok, err = plugin.check_schema({
prompt = "some prompt",
provider = "aimlapi",
auth = {
header = {
Authorization = "Bearer token"
}
}
})
if not ok then
ngx.say(err)
else
ngx.say("passed")
end
}
}
--- response_body
passed

View File

@@ -0,0 +1,287 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
log_level("info");
repeat_each(1);
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
my $http_config = $block->http_config // <<_EOC_;
server {
server_name openai;
listen 6724;
default_type 'application/json';
location /check_extra_options {
content_by_lua_block {
local json = require("cjson.safe")
ngx.req.read_body()
local body = ngx.req.get_body_data()
local request_data = json.decode(body)
if request_data.extra_option ~= "extra option" then
ngx.status = 400
ngx.say("extra option not match")
return
end
local response = {
choices = {
{
message = {
content = request_data.messages[1].content .. ' ' .. request_data.messages[2].content
}
}
}
}
local json = require("cjson.safe")
local json_response = json.encode(response)
ngx.say(json_response)
}
}
location /test/params/in/overridden/endpoint {
content_by_lua_block {
local json = require("cjson.safe")
local core = require("apisix.core")
local query_auth = ngx.req.get_uri_args()["api_key"]
ngx.log(ngx.INFO, "found query params: ", core.json.stably_encode(ngx.req.get_uri_args()))
if query_auth ~= "apikey" then
ngx.status = 401
ngx.say("Unauthorized")
return
end
ngx.status = 200
ngx.say("passed")
}
}
}
_EOC_
$block->set_value("http_config", $http_config);
});
run_tests();
__DATA__
=== TEST 1: check plugin options send to llm service correctly
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-request-rewrite": {
"prompt": "some prompt to test",
"auth": {
"query": {
"api_key": "apikey"
}
},
"provider": "openai",
"override": {
"endpoint": "http://localhost:6724/check_extra_options"
},
"ssl_verify": false,
"options": {
"model": "check_options_model",
"extra_option": "extra option"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}]]
)
local code, body, actual_body = t("/anything",
ngx.HTTP_POST,
"some random content",
nil,
{
["Content-Type"] = "text/plain",
}
)
if code == 200 then
ngx.say('passed')
return
end
}
}
--- response_body
passed
=== TEST 2: openai-compatible provider should use with override.endpoint
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.ai-request-rewrite")
local ok, err = plugin.check_schema({
prompt = "some prompt",
provider = "openai-compatible",
auth = {
header = {
Authorization = "Bearer token"
}
}
})
if not ok then
ngx.say(err)
else
ngx.say("passed")
end
}
}
--- response_body
override.endpoint is required for openai-compatible provider
=== TEST 3: query params in override.endpoint should be sent to LLM
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-proxy": {
"auth": {
"query": {
"api_key": "apikey"
}
},
"model": {
"provider": "openai",
"name": "gpt-35-turbo-instruct",
"options": {
"max_tokens": 512,
"temperature": 1.0
}
},
"override": {
"endpoint": "http://localhost:6724/test/params/in/overridden/endpoint?some_query=yes"
},
"ssl_verify": false
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 4: send request without body
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/anything",
"plugins": {
"ai-request-rewrite": {
"prompt": "some prompt to test",
"auth": {
"query": {
"api_key": "apikey"
}
},
"provider": "openai",
"override": {
"endpoint": "http://localhost:6724/check_extra_options"
},
"ssl_verify": false,
"options": {
"model": "check_options_model",
"extra_option": "extra option"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}]]
)
local code, body, actual_body = t("/anything",
ngx.HTTP_POST,
nil,
nil,
{
["Content-Type"] = "text/plain",
}
)
if code == 200 then
ngx.say('passed')
return
end
}
}
--- error_log eval
qr/missing request body/
--- response_body
passed

View File

@@ -0,0 +1,908 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
log_level('info');
worker_connections(256);
no_root_location();
no_shuffle();
add_block_preprocessor(sub {
my ($block) = @_;
if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
$block->set_value("no_error_log", "[error]");
}
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests();
__DATA__
=== TEST 1: enable route cache
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
local t = {}
for i = 1, 2 do
local th = assert(ngx.thread.spawn(function(i)
local httpc = http.new()
local res, err = httpc:request_uri(uri)
assert(res.status == 200)
if not res then
ngx.log(ngx.ERR, err)
return
end
end, i))
table.insert(t, th)
end
for i, th in ipairs(t) do
ngx.thread.wait(th)
end
ngx.say("done")
}
}
--- response_body
done
--- error_log
use ai plane to match route
=== TEST 2: route has vars, disable route cache
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"vars": [ ["arg_k", "~=", "v"] ],
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local code = t('/hello??k=a', ngx.HTTP_GET)
ngx.say(code)
local code = t('/hello??k=v', ngx.HTTP_GET)
ngx.say(code)
}
}
--- response_body
200
404
--- no_error_log
use ai plane to match route
=== TEST 3: method changed, create different route cache
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
local t = {}
for i = 1, 4 do
local th = assert(ngx.thread.spawn(function(i)
local httpc = http.new()
local res, err
if i % 2 == 0 then
res, err = httpc:request_uri(uri, { method = "POST" })
else
res, err = httpc:request_uri(uri)
end
assert(res.status == 200)
if not res then
ngx.log(ngx.ERR, err)
return
end
end, i))
table.insert(t, th)
end
for i, th in ipairs(t) do
ngx.thread.wait(th)
end
ngx.say("done")
}
}
--- response_body
done
--- error_log
use ai plane to match route
=== TEST 4: route with plugins, enable
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"limit-count": {
"count": 9999,
"time_window": 60
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
local t = {}
for i = 1, 2 do
local th = assert(ngx.thread.spawn(function(i)
local httpc = http.new()
local res, err = httpc:request_uri(uri)
assert(res.status == 200)
if not res then
ngx.log(ngx.ERR, err)
return
end
end, i))
table.insert(t, th)
end
for i, th in ipairs(t) do
ngx.thread.wait(th)
end
ngx.say("done")
}
}
--- response_body
done
--- error_log
use ai plane to match route
=== TEST 5: enable -> disable -> enable -> disable
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
local uri2 = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello1?k=a"
local uri3 = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello1?k=v"
-- round 1: all routes without vars or filter_fun, enable route cache
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(0.5)
local threads1 = {}
for i = 1, 2 do
local th = assert(ngx.thread.spawn(function(i)
local httpc = http.new()
local res, err = httpc:request_uri(uri)
assert(res.status == 200)
if not res then
ngx.log(ngx.ERR, err)
return
end
end, i))
table.insert(threads1, th)
end
for i, th in ipairs(threads1) do
ngx.thread.wait(th)
end
-- round 2: routes with vars or filter_fun, disable route cache
local code, body = t('/apisix/admin/routes/2',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"vars": [ ["arg_k", "~=", "v"] ],
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello1"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(0.5)
local threads2 = {}
for i = 1, 2 do
local th = assert(ngx.thread.spawn(function(i)
local httpc = http.new()
local res, err
if i == 1 then
-- arg_k = a, match route 2
res, err = httpc:request_uri(uri2)
assert(res.status == 200)
else
-- arg_k = v, not match route 2
res, err = httpc:request_uri(uri3)
assert(res.status == 404)
end
if not res then
ngx.log(ngx.ERR, err)
return
end
end, i))
table.insert(threads2, th)
end
for i, th in ipairs(threads2) do
ngx.thread.wait(th)
end
-- round 3: delete route with vars, the remaining route
-- has no vars or filter_fun, enable route cache
local code, body = t('/apisix/admin/routes/2',
ngx.HTTP_DELETE)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(0.5)
local threads3 = {}
for i = 1, 2 do
local th = assert(ngx.thread.spawn(function(i)
local httpc = http.new()
local res, err = httpc:request_uri(uri)
assert(res.status == 200)
if not res then
ngx.log(ngx.ERR, err)
return
end
end, i))
table.insert(threads3, th)
end
for i, th in ipairs(threads3) do
ngx.thread.wait(th)
end
-- round 4: routes with vars or filter_fun, disable route cache
local code, body = t('/apisix/admin/routes/2',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"vars": [ ["arg_k", "~=", "v"] ],
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello1"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(0.5)
local threads4 = {}
for i = 1, 2 do
local th = assert(ngx.thread.spawn(function(i)
local httpc = http.new()
local res, err
if i == 1 then
-- arg_k = a, match route 2
res, err = httpc:request_uri(uri2)
assert(res.status == 200)
else
-- arg_k = v, not match route 2
res, err = httpc:request_uri(uri3)
assert(res.status == 404)
end
if not res then
ngx.log(ngx.ERR, err)
return
end
end, i))
table.insert(threads4, th)
end
for i, th in ipairs(threads4) do
ngx.thread.wait(th)
end
-- clean route 2
local code, body = t('/apisix/admin/routes/2',
ngx.HTTP_DELETE)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say("done")
}
}
--- response_body
done
--- grep_error_log eval
qr/use ai plane to match route/
--- grep_error_log_out
use ai plane to match route
use ai plane to match route
=== TEST 6: route key: uri
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(1)
for i = 1, 2 do
local httpc = http.new()
local res, err = httpc:request_uri(uri)
assert(res.status == 200)
if not res then
ngx.log(ngx.ERR, err)
return
end
end
ngx.say("done")
}
}
--- response_body
done
--- error_log
route cache key: /hello
=== TEST 7: route key: uri + method
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(1)
for i = 1, 2 do
local httpc = http.new()
local res, err = httpc:request_uri(uri)
assert(res.status == 200)
if not res then
ngx.log(ngx.ERR, err)
return
end
end
ngx.say("done")
}
}
--- response_body
done
--- error_log
route cache key: /hello#GET
=== TEST 8: route key: uri + method + host
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"host": "127.0.0.1",
"methods": ["GET"],
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(1)
for i = 1, 2 do
local httpc = http.new()
local res, err = httpc:request_uri(uri)
assert(res.status == 200)
if not res then
ngx.log(ngx.ERR, err)
return
end
end
ngx.say("done")
}
}
--- response_body
done
--- error_log
route cache key: /hello#GET#127.0.0.1
=== TEST 9: enable sample upstream
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(0.5)
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
local httpc = http.new()
local res, err = httpc:request_uri(uri)
assert(res.status == 200)
if not res then
ngx.log(ngx.ERR, err)
return
end
ngx.say("done")
}
}
--- response_body
done
--- error_log
enable sample upstream
=== TEST 10: route has plugins and run before_proxy, disable samply upstream
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"serverless-pre-function": {
"phase": "before_proxy",
"functions" : ["return function(conf, ctx) ngx.log(ngx.WARN, \"run before_proxy phase balancer_ip : \", ctx.balancer_ip) end"]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
local httpc = http.new()
local res, err = httpc:request_uri(uri)
assert(res.status == 200)
if not res then
ngx.log(ngx.ERR, err)
return
end
ngx.say("done")
}
}
--- response_body
done
--- error_log
run before_proxy phase balancer_ip : 127.0.0.1
--- no_error_log
enable sample upstream
=== TEST 11: upstream has more than one nodes, disable sample upstream
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"upstream": {
"nodes": {
"127.0.0.1:1980": 1,
"127.0.0.1:1981": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(0.5)
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
local httpc = http.new()
local res, err = httpc:request_uri(uri)
assert(res.status == 200)
if not res then
ngx.log(ngx.ERR, err)
return
end
ngx.say("done")
}
}
--- response_body
done
--- no_error_log
enable sample upstream
=== TEST 12: node has domain, disable sample upstream
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"upstream": {
"nodes": {
"admin.apisix.dev:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
local httpc = http.new()
local res, err = httpc:request_uri(uri)
assert(res.status == 200)
if not res then
ngx.log(ngx.ERR, err)
return
end
ngx.say("done")
}
}
--- response_body
done
--- no_error_log
enable sample upstream
=== TEST 13: enable --> disable sample upstream
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(0.5)
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
local httpc = http.new()
local res, err = httpc:request_uri(uri)
assert(res.status == 200)
if not res then
ngx.log(ngx.ERR, err)
return
end
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"enable_websocket": true,
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(0.5)
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
local httpc = http.new()
local res, err = httpc:request_uri(uri)
assert(res.status == 200)
if not res then
ngx.log(ngx.ERR, err)
return
end
ngx.say("done")
}
}
--- response_body
done
--- grep_error_log eval
qr/enable sample upstream/
--- grep_error_log_out
enable sample upstream
=== TEST 14: renew route cache
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
for k = 1, 2 do
local code, body = t('/apisix/admin/routes/' .. k,
ngx.HTTP_PUT,
[[{
"host": "127.0.0.1",
"methods": ["GET"],
"plugins": {
"proxy-rewrite": {
"uri": "/hello"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello]] .. k .. [["
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(1)
for i = 1, 2 do
local httpc = http.new()
local res, err = httpc:request_uri(uri .. k)
assert(res.status == 200)
if not res then
ngx.log(ngx.ERR, err)
return
end
end
end
ngx.say("done")
}
}
--- response_body
done
--- error_log
renew route cache: count=3001
renew route cache: count=3002

View File

@@ -0,0 +1,428 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX;
my $nginx_binary = $ENV{'TEST_NGINX_BINARY'} || 'nginx';
my $version = eval { `$nginx_binary -V 2>&1` };
if ($version !~ m/\/apisix-nginx-module/) {
plan(skip_all => "apisix-nginx-module not installed");
} else {
plan('no_plan');
}
repeat_each(1);
log_level('info');
worker_connections(256);
no_root_location();
no_shuffle();
add_block_preprocessor(sub {
my ($block) = @_;
if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
$block->set_value("no_error_log", "[error]");
}
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
if (!defined $block->extra_init_by_lua) {
my $extra_init_by_lua = <<_EOC_;
local apisix = require("apisix")
apisix.http_header_filter_phase = function ()
ngx.header.content_length = 14
end
apisix.http_body_filter_phase = function ()
ngx.arg[1] = "do body filter"
end
_EOC_
$block->set_value("extra_init_by_lua", $extra_init_by_lua);
}
});
run_tests();
__DATA__
=== TEST 1: enable skip body filter
--- extra_init_by_lua
local apisix = require("apisix")
apisix.http_header_filter_phase = function ()
ngx.header.content_length = nil
end
apisix.http_body_filter_phase = function ()
ngx.arg[1] = "do body filter"
end
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(0.5)
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
local httpc = http.new()
local res, err = httpc:request_uri(uri)
assert(res.status == 200)
if not res then
ngx.log(ngx.ERR, err)
return
end
ngx.print(res.body)
}
}
--- response_body
hello world
=== TEST 2: route with plugin_config_id, disable skip body filter
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_configs/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"serverless-pre-function": {
"phase": "before_proxy",
"functions" : ["return function(conf, ctx) ngx.log(ngx.WARN, \"run before_proxy phase balancer_ip : \", ctx.balancer_ip) end"]
}
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(0.5)
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugin_config_id": "1",
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(0.5)
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
local httpc = http.new()
local res, err = httpc:request_uri(uri)
assert(res.status == 200)
if not res then
ngx.log(ngx.ERR, err)
return
end
ngx.say(res.body)
}
}
--- response_body
do body filter
--- error_log
run before_proxy phase balancer_ip : 127.0.0.1
--- no_error_log
enable sample upstream
=== TEST 3: route with plugins, disable skip body filter
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/consumers',
ngx.HTTP_PUT,
[[{
"username": "new_consumer",
"plugins": {
"key-auth": {
"key": "auth-jack"
},
"serverless-pre-function": {
"phase": "before_proxy",
"functions" : ["return function(conf, ctx) ngx.log(ngx.WARN, \"run before_proxy phase balancer_ip : \", ctx.balancer_ip) end"]
}
}
}]]
)
ngx.sleep(0.5)
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"key-auth": {}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(0.5)
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
local httpc = http.new()
local headers = {
["apikey"] = "auth-jack"
}
local res, err = httpc:request_uri(uri, {headers = headers})
assert(res.status == 200)
if not res then
ngx.log(ngx.ERR, err)
return
end
ngx.say(res.body)
}
}
--- response_body
do body filter
--- error_log
run before_proxy phase balancer_ip : 127.0.0.1
--- no_error_log
enable sample upstream
=== TEST 4: one of route has plugins, disable skip body filter
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"serverless-pre-function": {
"phase": "before_proxy",
"functions" : ["return function(conf, ctx) ngx.log(ngx.WARN, \"run before_proxy phase balancer_ip : \", ctx.balancer_ip) end"]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(0.5)
local code, body = t('/apisix/admin/routes/2',
ngx.HTTP_PUT,
[[{
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello1"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(0.5)
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello1"
local httpc = http.new()
local headers = {
["apikey"] = "auth-jack"
}
local res, err = httpc:request_uri(uri, {headers = headers})
assert(res.status == 200)
if not res then
ngx.log(ngx.ERR, err)
return
end
ngx.say(res.body)
}
}
--- response_body
do body filter
--- no_error_log
enable sample upstream
=== TEST 5: exist global_rules, disable skip body filter
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/global_rules/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"serverless-pre-function": {
"phase": "before_proxy",
"functions" : ["return function(conf, ctx) ngx.log(ngx.WARN, \"run before_proxy phase balancer_ip : \", ctx.balancer_ip) end"]
}
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(0.5)
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(0.5)
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
local httpc = http.new()
local res, err = httpc:request_uri(uri)
assert(res.status == 200)
if not res then
ngx.log(ngx.ERR, err)
return
end
ngx.say(res.body)
}
}
--- response_body
do body filter
--- error_log
run before_proxy phase balancer_ip : 127.0.0.1
--- no_error_log
enable sample upstream
=== TEST 6: upstream with keepalive_pool, disable sample upstream
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"keepalive_pool": {
"size": 1,
"idle_timeout": 8,
"requests": 2
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(0.5)
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
local httpc = http.new()
local res, err = httpc:request_uri(uri)
assert(res.status == 200)
if not res then
ngx.log(ngx.ERR, err)
return
end
ngx.say(res.body)
}
}
--- response_body
do body filter
--- error_log
proxy request to 127.0.0.1:1980
--- no_error_log
enable sample upstream

View File

@@ -0,0 +1,263 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
log_level('info');
worker_connections(256);
no_root_location();
no_shuffle();
add_block_preprocessor(sub {
my ($block) = @_;
if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
$block->set_value("no_error_log", "[error]");
}
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests();
__DATA__
=== TEST 1: keep priority behavior consistent
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"priority": 1,
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/server_port"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local code, body = t('/apisix/admin/routes/2',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"priority": 10,
"upstream": {
"nodes": {
"127.0.0.1:1981": 1
},
"type": "roundrobin"
},
"uri": "/server_port"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/server_port"
local t = {}
for i = 1, 2 do
local th = assert(ngx.thread.spawn(function(i)
local httpc = http.new()
local res, err = httpc:request_uri(uri)
assert(res.status == 200)
if not res then
ngx.log(ngx.ERR, err)
return
end
ngx.say(res.body)
end, i))
table.insert(t, th)
end
for i, th in ipairs(t) do
ngx.thread.wait(th)
end
}
}
--- response_body
1981
1981
--- error_log
use ai plane to match route
=== TEST 2: keep route cache as latest data
# update the attributes that do not participate in the route cache key to ensure
# that the route cache use the latest data
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/pm',
ngx.HTTP_PUT,
[[{
"plugins": {
"public-api": {}
},
"uri": "/apisix/prometheus/metrics"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"name": "foo",
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"plugins": {
"prometheus": {
"prefer_name": true
}
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local http = require "resty.http"
local httpc = http.new()
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
local res, err = httpc:request_uri(uri)
assert(res.status == 200)
if not res then
ngx.log(ngx.ERR, err)
return
end
local metrics_uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/apisix/prometheus/metrics"
local httpc = http.new()
local res, err = httpc:request_uri(metrics_uri)
assert(res.status == 200)
if not res then
ngx.log(ngx.ERR, err)
return
end
local m, err = ngx.re.match(res.body, "apisix_bandwidth{type=\"ingress\",route=\"foo\"", "jo")
ngx.say(m[0])
-- update name by patch
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PATCH,
[[{
"name": "bar"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
local res, err = httpc:request_uri(uri)
assert(res.status == 200)
if not res then
ngx.log(ngx.ERR, err)
return
end
local metrics_uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/apisix/prometheus/metrics"
local httpc = http.new()
local res, err = httpc:request_uri(metrics_uri)
assert(res.status == 200)
if not res then
ngx.log(ngx.ERR, err)
return
end
local m, err = ngx.re.match(res.body, "apisix_bandwidth{type=\"ingress\",route=\"bar\"", "jo")
ngx.say(m[0])
}
}
--- response_body
apisix_bandwidth{type="ingress",route="foo"
apisix_bandwidth{type="ingress",route="bar"
==== TEST 3: route has filter_func, disable route cache
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"filter_func": "function(vars) return vars.arg_k ~= 'v' end",
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local code = t('/hello??k=a', ngx.HTTP_GET)
ngx.say(code)
local code = t('/hello??k=v', ngx.HTTP_GET)
ngx.say(code)
}
}
--- response_body
200
404
--- no_error_log
use ai plane to match route

View File

@@ -0,0 +1,473 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
log_level('info');
worker_connections(256);
no_root_location();
no_shuffle();
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->extra_init_by_lua) {
my $extra_init_by_lua = <<_EOC_;
add_eligible_route = function(id, uri)
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/' .. id,
ngx.HTTP_PUT,
[[{
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "]] .. uri .. [["
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
end
add_ineligible_route = function(id, uri)
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/' .. id,
ngx.HTTP_PUT,
[[{
"upstream": {
"nodes": {
"127.0.0.1:1980": 1,
"127.0.0.1:1981": 1
},
"type": "roundrobin"
},
"uri": "]] .. uri .. [["
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
end
update_route_to_ineligible = function(id)
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/' .. id .. '/upstream/nodes',
ngx.HTTP_PATCH,
[[{
"127.0.0.1:1980": 1,
"127.0.0.1:1981": 1
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
end
update_route_to_eligible = function(id)
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/' .. id .. '/upstream/nodes',
ngx.HTTP_PATCH,
[[{
"127.0.0.1:1980": 1
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
end
clear_route = function(id)
local t = require("lib.test_admin").test
local code = t('/apisix/admin/routes/' .. id, ngx.HTTP_DELETE)
return code
end
_EOC_
$block->set_value("extra_init_by_lua", $extra_init_by_lua);
}
if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
$block->set_value("no_error_log", "[error]");
}
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests();
__DATA__
=== TEST 1: enable sample upstream
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
add_eligible_route(1, "/hello")
local code, body = t("/hello", ngx.HTTP_GET)
assert(code == 200)
assert(clear_route(1) == 200)
ngx.say("done")
}
}
--- response_body
done
--- error_log
enable sample upstream
=== TEST 2: enable sample upstream, add ineligible route lead to disable sample upstream
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
add_eligible_route(1, "/hello")
local code, body = t("/hello", ngx.HTTP_GET)
assert(code == 200)
add_ineligible_route(2, "/hello1")
local code, body = t("/hello1", ngx.HTTP_GET)
assert(code == 200)
assert(clear_route(1) == 200)
assert(clear_route(2) == 200)
ngx.say("done")
}
}
--- response_body
done
--- grep_error_log eval
qr/enable sample upstream|proxy request to/
--- grep_error_log_out
enable sample upstream
proxy request to
=== TEST 3: enable sample upstream, update route as ineligible lead to disable sample upstream
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
add_eligible_route(1, "/hello")
add_eligible_route(2, "/hello1")
local code, body = t("/hello1", ngx.HTTP_GET)
assert(code == 200)
update_route_to_ineligible(2)
local code, body = t("/hello1", ngx.HTTP_GET)
assert(code == 200)
assert(clear_route(1) == 200)
assert(clear_route(2) == 200)
ngx.say("done")
}
}
--- response_body
done
--- grep_error_log eval
qr/enable sample upstream|proxy request to/
--- grep_error_log_out
enable sample upstream
proxy request to
=== TEST 4: enable sample upstream, add eligible route and keep sample upstream as enable
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
add_eligible_route(1, "/hello")
local code, body = t("/hello", ngx.HTTP_GET)
assert(code == 200)
add_eligible_route(2, "/hello1")
local code, body = t("/hello1", ngx.HTTP_GET)
assert(code == 200)
assert(clear_route(1) == 200)
assert(clear_route(2) == 200)
ngx.say("done")
}
}
--- response_body
done
--- grep_error_log eval
qr/enable sample upstream/
--- grep_error_log_out
enable sample upstream
enable sample upstream
--- no_error_log eval
qr/proxy request to \S+/
=== TEST 5: enable sample upstream, delete route and keep sample upstream as enable
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
add_eligible_route(1, "/hello")
add_eligible_route(2, "/hello1")
local code, body = t("/hello1", ngx.HTTP_GET)
assert(code == 200)
assert(clear_route(2) == 200)
local code, body = t("/hello", ngx.HTTP_GET)
assert(code == 200)
assert(clear_route(1) == 200)
ngx.say("done")
}
}
--- response_body
done
--- grep_error_log eval
qr/enable sample upstream/
--- grep_error_log_out
enable sample upstream
enable sample upstream
--- no_error_log eval
qr/proxy request to \S+/
=== TEST 6: enable sample upstream, delete all routes
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
add_eligible_route(1, "/hello")
add_eligible_route(2, "/hello1")
local code, body = t("/hello", ngx.HTTP_GET)
assert(code == 200)
assert(clear_route(1) == 200)
assert(clear_route(2) == 200)
local code, body = t("/hello", ngx.HTTP_GET)
assert(code == 404)
ngx.say("done")
}
}
--- response_body
done
--- grep_error_log eval
qr/enable sample upstream/
--- grep_error_log_out
enable sample upstream
--- no_error_log eval
qr/proxy request to \S+/
=== TEST 7: disable sample upstream, add eligible route and keep sample upstream as disable
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
add_ineligible_route(1, "/hello")
local code, body = t("/hello", ngx.HTTP_GET)
assert(code == 200)
add_ineligible_route(2, "/hello1")
local code, body = t("/hello1", ngx.HTTP_GET)
assert(code == 200)
assert(clear_route(1) == 200)
assert(clear_route(2) == 200)
ngx.say("done")
}
}
--- response_body
done
--- grep_error_log eval
qr/proxy request to/
--- grep_error_log_out
proxy request to
proxy request to
--- no_error_log
enable sample upstream
=== TEST 8: disable sample upstream, add eligible route and keep disable sample upstream
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
add_ineligible_route(1, "/hello")
local code, body = t("/hello", ngx.HTTP_GET)
assert(code == 200)
add_eligible_route(2, "/hello1")
local code, body = t("/hello1", ngx.HTTP_GET)
assert(code == 200)
assert(clear_route(1) == 200)
assert(clear_route(2) == 200)
ngx.say("done")
}
}
--- response_body
done
--- grep_error_log eval
qr/proxy request to/
--- grep_error_log_out
proxy request to
proxy request to
--- no_error_log
enable sample upstream
=== TEST 9: disable sample upstream, delete some ineligible route and keep sample upstream as disable
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
add_ineligible_route(1, "/hello")
add_ineligible_route(2, "/hello1")
local code, body = t("/hello1", ngx.HTTP_GET)
assert(code == 200)
assert(clear_route(2) == 200)
local code, body = t("/hello", ngx.HTTP_GET)
assert(code == 200)
assert(clear_route(1) == 200)
ngx.say("done")
}
}
--- response_body
done
--- grep_error_log eval
qr/proxy request to/
--- grep_error_log_out
proxy request to
proxy request to
--- no_error_log
enable sample upstream
=== TEST 10: disable sample upstream, update some of ineligible route to eligible, keep sample upstream as disable
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
add_ineligible_route(1, "/hello")
add_ineligible_route(2, "/hello1")
local code, body = t("/hello1", ngx.HTTP_GET)
assert(code == 200)
update_route_to_eligible(1)
local code, body = t("/hello", ngx.HTTP_GET)
assert(code == 200)
assert(clear_route(1) == 200)
assert(clear_route(2) == 200)
ngx.say("done")
}
}
--- response_body
done
--- grep_error_log eval
qr/proxy request to/
--- grep_error_log_out
proxy request to
proxy request to
--- no_error_log
enable sample upstream
=== TEST 11: disable sample upstream, delete all ineligible route, enable sample upstream
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
add_ineligible_route(1, "/hello")
add_ineligible_route(2, "/hello1")
add_eligible_route(3, "/server_port")
local code, body = t("/hello1", ngx.HTTP_GET)
assert(code == 200)
assert(clear_route(1) == 200)
assert(clear_route(2) == 200)
local code, body = t("/server_port", ngx.HTTP_GET)
assert(code == 200)
assert(clear_route(3) == 200)
ngx.say("done")
}
}
--- response_body
done
--- grep_error_log eval
qr/enable sample upstream|proxy request to/
--- grep_error_log_out
proxy request to
enable sample upstream
=== TEST 12: disable sample upstream, update all of ineligible route to eligible, enable sample upstream
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
add_ineligible_route(1, "/hello")
add_ineligible_route(2, "/hello1")
local code, body = t("/hello1", ngx.HTTP_GET)
assert(code == 200)
update_route_to_eligible(1)
update_route_to_eligible(2)
local code, body = t("/hello", ngx.HTTP_GET)
assert(code == 200)
assert(clear_route(1) == 200)
assert(clear_route(2) == 200)
ngx.say("done")
}
}
--- response_body
done
--- grep_error_log eval
qr/enable sample upstream|proxy request to/
--- grep_error_log_out
proxy request to
enable sample upstream

View File

@@ -0,0 +1,270 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
log_level('info');
worker_connections(256);
no_root_location();
no_shuffle();
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->extra_init_by_lua) {
my $extra_init_by_lua = <<_EOC_;
unload_ai_module = function ()
local t = require("lib.test_admin").test
local data = [[
deployment:
role: traditional
role_traditional:
config_provider: etcd
admin:
admin_key: null
apisix:
node_listen: 1984
plugins:
]]
require("lib.test_admin").set_config_yaml(data)
local code, body = t('/apisix/admin/plugins/reload',
ngx.HTTP_PUT)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
end
load_ai_module = function ()
local t = require("lib.test_admin").test
local data = [[
deployment:
role: traditional
role_traditional:
config_provider: etcd
admin:
admin_key: null
apisix:
node_listen: 1984
plugins:
- ai
]]
require("lib.test_admin").set_config_yaml(data)
local code, body = t('/apisix/admin/plugins/reload',
ngx.HTTP_PUT)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
end
_EOC_
$block->set_value("extra_init_by_lua", $extra_init_by_lua);
}
if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
$block->set_value("no_error_log", "[error]");
}
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests();
__DATA__
=== TEST 1: enable(default) -> disable -> enable
--- http_config eval: $::HttpConfig
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
-- register route
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"host": "127.0.0.1",
"methods": ["GET"],
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
-- enable route cache
local code = t('/hello', ngx.HTTP_GET)
assert(code == 200)
-- disable ai plugin
unload_ai_module()
local code = t('/hello', ngx.HTTP_GET)
assert(code == 200)
-- enable ai plugin
load_ai_module()
-- TODO: The route cache should be enabled, but since no new routes are registered,
-- the route tree is not rebuilt,
-- so it is not possible to switch to route cache mode, we should fix it
local code = t('/hello', ngx.HTTP_GET)
assert(code == 200, "enable: access /hello")
-- register a new route and trigger a route tree rebuild
local code, body = t('/apisix/admin/routes/2',
ngx.HTTP_PUT,
[[{
"host": "127.0.0.1",
"methods": ["GET"],
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/echo"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local code = t('/hello', ngx.HTTP_GET)
assert(code == 200)
ngx.say("done")
}
}
--- response_body
done
--- grep_error_log eval
qr/route match mode: \S[^,]+/
--- grep_error_log_out
route match mode: ai_match
route match mode: radixtree_host_uri
route match mode: radixtree_host_uri
route match mode: radixtree_host_uri
route match mode: ai_match
route match mode: radixtree_host_uri
=== TEST 2: disable(default) -> enable -> disable
--- yaml_config
deployment:
role: traditional
role_traditional:
config_provider: etcd
admin:
admin_key: null
apisix:
node_listen: 1984
plugins:
--- http_config eval: $::HttpConfig
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
-- register route
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"host": "127.0.0.1",
"methods": ["GET"],
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local code = t('/hello', ngx.HTTP_GET)
assert(code == 200)
-- enable ai plugin
load_ai_module()
local code = t('/hello', ngx.HTTP_GET)
assert(code == 200)
-- register a new route and trigger a route tree rebuild
local code, body = t('/apisix/admin/routes/2',
ngx.HTTP_PUT,
[[{
"host": "127.0.0.1",
"methods": ["GET"],
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/echo"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local code = t('/hello', ngx.HTTP_GET)
assert(code == 200)
-- disable ai plugin
unload_ai_module()
local code = t('/hello', ngx.HTTP_GET)
assert(code == 200)
ngx.say("done")
}
}
--- response_body
done
--- grep_error_log eval
qr/route match mode: \S[^,]+/
--- grep_error_log_out
route match mode: radixtree_host_uri
route match mode: radixtree_host_uri
route match mode: ai_match
route match mode: radixtree_host_uri
route match mode: radixtree_host_uri

View File

@@ -0,0 +1,654 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_shuffle();
no_root_location();
log_level('info');
run_tests;
__DATA__
=== TEST 1: sanity
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.api-breaker")
local ok, err = plugin.check_schema({
break_response_code = 502,
unhealthy = {
http_statuses = {500},
failures = 1,
},
healthy = {
http_statuses = {200},
successes = 1,
},
})
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
done
=== TEST 2: default configuration
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.api-breaker")
local conf = {
break_response_code = 502
}
local ok, err = plugin.check_schema(conf)
if not ok then
ngx.say(err)
end
ngx.say(require("toolkit.json").encode(conf))
}
}
--- request
GET /t
--- response_body
{"break_response_code":502,"healthy":{"http_statuses":[200],"successes":3},"max_breaker_sec":300,"unhealthy":{"failures":3,"http_statuses":[500]}}
=== TEST 3: default `healthy`
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.api-breaker")
local conf = {
break_response_code = 502,
healthy = {}
}
local ok, err = plugin.check_schema(conf)
if not ok then
ngx.say(err)
end
ngx.say(require("toolkit.json").encode(conf))
}
}
--- request
GET /t
--- response_body
{"break_response_code":502,"healthy":{"http_statuses":[200],"successes":3},"max_breaker_sec":300,"unhealthy":{"failures":3,"http_statuses":[500]}}
=== TEST 4: default `unhealthy`
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.api-breaker")
local conf = {
break_response_code = 502,
unhealthy = {}
}
local ok, err = plugin.check_schema(conf)
if not ok then
ngx.say(err)
end
ngx.say(require("toolkit.json").encode(conf))
}
}
--- request
GET /t
--- response_body
{"break_response_code":502,"healthy":{"http_statuses":[200],"successes":3},"max_breaker_sec":300,"unhealthy":{"failures":3,"http_statuses":[500]}}
=== TEST 5: bad break_response_code
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"api-breaker": {
"break_response_code": 199,
"unhealthy": {
"http_statuses": [500, 503],
"failures": 3
},
"healthy": {
"http_statuses": [200, 206],
"successes": 3
}
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/api_breaker"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.print(body)
}
}
--- request
GET /t
--- error_code: 400
--- response_body
{"error_msg":"failed to check the configuration of plugin api-breaker err: property \"break_response_code\" validation failed: expected 199 to be at least 200"}
=== TEST 6: bad max_breaker_sec
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"api-breaker": {
"break_response_code": 200,
"max_breaker_sec": -1
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/api_breaker"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- error_code: 400
=== TEST 7: bad unhealthy.http_statuses
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"api-breaker": {
"break_response_code": 200,
"max_breaker_sec": 40,
"unhealthy": {
"http_statuses": [500, 603],
"failures": 3
},
"healthy": {
"http_statuses": [200, 206],
"successes": 3
}
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/api_breaker"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- error_code: 400
=== TEST 8: same http_statuses in healthy
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"api-breaker": {
"break_response_code": 500,
"unhealthy": {
"http_statuses": [500, 503],
"failures": 3
},
"healthy": {
"http_statuses": [206, 206],
"successes": 3
}
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/api_breaker"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.print(body)
}
}
--- request
GET /t
--- error_code: 400
--- response_body
{"error_msg":"failed to check the configuration of plugin api-breaker err: property \"healthy\" validation failed: property \"http_statuses\" validation failed: expected unique items but items 1 and 2 are equal"}
=== TEST 9: set route, http_statuses: [500, 503]
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"api-breaker": {
"break_response_code": 599,
"unhealthy": {
"http_statuses": [500, 503],
"failures": 3
},
"healthy": {
"http_statuses": [200, 206],
"successes": 3
}
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/api_breaker"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 10: trigger breaker
--- request eval
[
"GET /api_breaker?code=200", "GET /api_breaker?code=500",
"GET /api_breaker?code=503", "GET /api_breaker?code=500",
"GET /api_breaker?code=500", "GET /api_breaker?code=500"
]
--- error_code eval
[200, 500, 503, 500, 599, 599]
=== TEST 11: trigger reset status
--- request eval
[
"GET /api_breaker?code=500", "GET /api_breaker?code=500",
"GET /api_breaker?code=200", "GET /api_breaker?code=200",
"GET /api_breaker?code=200",
"GET /api_breaker?code=500", "GET /api_breaker?code=500"
]
--- error_code eval
[
500, 500,
200, 200, 200,
500, 500
]
=== TEST 12: trigger del healthy numeration
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local json = require("toolkit.json")
-- trigger to unhealth
for i = 1, 4 do
local code = t('/api_breaker?code=500', ngx.HTTP_GET)
ngx.say("code: ", code)
end
-- break for 3 seconds
ngx.sleep(3)
-- make a try
for i = 1, 4 do
local code = t('/api_breaker?code=200', ngx.HTTP_GET)
ngx.say("code: ", code)
end
for i = 1, 4 do
local code = t('/api_breaker?code=500', ngx.HTTP_GET)
ngx.say("code: ", code)
end
}
}
--- request
GET /t
--- response_body
code: 500
code: 500
code: 500
code: 599
code: 200
code: 200
code: 200
code: 200
code: 500
code: 500
code: 500
code: 599
--- no_error_log
[error]
breaker_time: 4
--- error_log
breaker_time: 2
=== TEST 13: add plugin with default config value
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"api-breaker": {
"break_response_code": 502,
"break_response_body": "{\"message\":\"breaker opened.\"}",
"break_response_headers": [{"key":"Content-Type","value":"application/json"},{"key":"Content-Type","value":"application/json+v1"}],
"unhealthy": {
"failures": 3
},
"healthy": {
"successes": 3
}
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/api_breaker"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 14: default value
--- request
GET /api_breaker?code=500
--- error_code: 500
=== TEST 15: trigger default value of unhealthy.http_statuses breaker
--- request eval
[
"GET /api_breaker?code=200", "GET /api_breaker?code=500",
"GET /api_breaker?code=503", "GET /api_breaker?code=500",
"GET /api_breaker?code=500", "GET /api_breaker?code=500"
]
--- error_code eval
[200, 500, 503, 500, 500, 502]
--- response_headers eval
["Content-Type: text/plain", "Content-Type: text/html", "Content-Type: text/html", "Content-Type: text/html", "Content-Type: text/html", "Content-Type: application/json+v1"]
--- response_body_like eval
[".*", ".*", ".*", ".*", ".*", "{\"message\":\"breaker opened.\"}"]
=== TEST 16: unhealthy -> timeout -> normal
--- config
location /mysleep {
proxy_pass "http://127.0.0.1:1980/mysleep?seconds=1";
}
--- request eval
[
"GET /api_breaker?code=500",
"GET /api_breaker?code=500",
"GET /api_breaker?code=500",
"GET /api_breaker?code=200",
"GET /mysleep",
"GET /mysleep",
"GET /mysleep",
"GET /api_breaker?code=200",
"GET /api_breaker?code=200",
"GET /api_breaker?code=200",
"GET /api_breaker?code=200",
"GET /api_breaker?code=200"]
--- error_code eval
[
500, 500, 500, 502,
200, 200, 200,
200, 200, 200, 200,200
]
=== TEST 17: unhealthy -> timeout -> unhealthy
--- config
location /mysleep {
proxy_pass "http://127.0.0.1:1980/mysleep?seconds=1";
}
--- request eval
[
"GET /api_breaker?code=500", "GET /api_breaker?code=500",
"GET /api_breaker?code=500", "GET /api_breaker?code=200",
"GET /mysleep", "GET /mysleep", "GET /mysleep",
"GET /api_breaker?code=500","GET /api_breaker?code=500",
"GET /api_breaker?code=500","GET /api_breaker?code=500"
]
--- error_code eval
[
500, 500, 500, 502,
200, 200, 200,
500,502,502,502
]
=== TEST 18: enable plugin, unhealthy.failures=1
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"api-breaker": {
"break_response_code": 502,
"max_breaker_sec": 10,
"unhealthy": {
"http_statuses": [500, 503],
"failures": 1
},
"healthy": {
"successes": 3
}
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/api_breaker"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 19: hit route 20 times, confirm the breaker time
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local json = require("toolkit.json")
local status_count = {}
for i = 1, 20 do
local code = t('/api_breaker?code=500', ngx.HTTP_GET)
code = tostring(code)
status_count[code] = (status_count[code] or 0) + 1
ngx.sleep(1)
end
ngx.say(json.encode(status_count))
}
}
--- request
GET /t
--- no_error_log
[error]
phase_func(): breaker_time: 16
--- error_log
phase_func(): breaker_time: 2
phase_func(): breaker_time: 4
phase_func(): breaker_time: 8
phase_func(): breaker_time: 10
--- response_body
{"500":4,"502":16}
--- timeout: 25
=== TEST 20: reject invalid schema
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
for _, case in ipairs({
{input = {
break_response_code = 200,
break_response_headers = {{["content-type"] = "application/json"}}
}},
}) do
local code, body = t('/apisix/admin/global_rules/1',
ngx.HTTP_PUT,
{
id = "1",
plugins = {
["api-breaker"] = case.input
}
}
)
ngx.print(require("toolkit.json").decode(body).error_msg)
end
}
}
--- request
GET /t
--- response_body eval
qr/failed to check the configuration of plugin api-breaker err: property \"break_response_headers\" validation failed: failed to validate item 1: property \"(key|value)\" is required/

View File

@@ -0,0 +1,465 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_shuffle();
no_root_location();
run_tests;
__DATA__
=== TEST 1: invalid schema (missing headers)
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.attach-consumer-label")
local ok, err = plugin.check_schema({})
if not ok then
ngx.say(err)
return
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
property "headers" is required
--- no_error_log
[error]
=== TEST 2: invalid schema (headers is an empty object)
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.attach-consumer-label")
local ok, err = plugin.check_schema({
headers = {}
})
if not ok then
ngx.say(err)
return
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
property "headers" validation failed: expect object to have at least 1 properties
--- no_error_log
[error]
=== TEST 3: invalid schema (missing $ prefix)
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.attach-consumer-label")
local ok, err = plugin.check_schema({
headers = {
["X-Consumer-Department"] = "department",
["X-Consumer-Company"] = "$company"
}
})
if not ok then
ngx.say(err)
return
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
property "headers" validation failed: failed to validate additional property X-Consumer-Department: failed to match pattern "^\\$.*" with "department"
--- no_error_log
[error]
=== TEST 4: valid schema
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.attach-consumer-label")
local ok, err = plugin.check_schema({
headers = {
["X-Consumer-Department"] = "$department",
["X-Consumer-Company"] = "$company"
}
})
if not ok then
ngx.say(err)
return
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
done
--- no_error_log
[error]
=== TEST 5: add consumer with labels
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/consumers',
ngx.HTTP_PUT,
[[{
"username": "jack",
"labels": {
"department": "devops",
"company": "api7"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local code, body = t('/apisix/admin/consumers/jack/credentials/a',
ngx.HTTP_PUT,
[[{
"plugins": {
"key-auth": {
"key": "key-a"
}
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
--- no_error_log
[error]
=== TEST 6: add route with only attach-consumer-label plugin (no key-auth)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"plugins": {
"attach-consumer-label": {
"_meta": {
"disable": false
},
"headers": {
"X-Consumer-Department": "$department",
"X-Consumer-Company": "$company"
}
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
--- no_error_log
[error]
=== TEST 7: access without auth (should not contain consumer labels)
--- request
GET /echo
--- response_headers
!X-Consumer-Department
!X-Consumer-Company
--- no_error_log
[error]
=== TEST 8: add route with attach-consumer-label plugin (with key-auth)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"plugins": {
"key-auth": {},
"attach-consumer-label": {
"headers": {
"X-Consumer-Department": "$department",
"X-Consumer-Company": "$company",
"X-Consumer-Role": "$role"
}
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
--- no_error_log
[error]
=== TEST 9: access with auth (should contain consumer labels headers, but no x-consumer-role)
--- request
GET /echo
--- more_headers
apikey: key-a
X-Consumer-Role: admin
--- response_headers
X-Consumer-Company: api7
X-Consumer-Department: devops
!X-Consumer-Role
--- no_error_log
[error]
=== TEST 10: modify consumer without labels
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/consumers/jack',
ngx.HTTP_PUT,
[[{
"username": "jack"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
--- no_error_log
[error]
=== TEST 11: access with auth (should not contain headers because consumer has no labels)
--- request
GET /echo
--- more_headers
apikey: key-a
--- response_headers
!X-Consumer-Company
!X-Consumer-Department
--- noerror_log
[error]
=== TEST 12: modify consumer with labels
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/consumers/jack',
ngx.HTTP_PUT,
[[{
"username": "jack",
"labels": {
"department": "devops",
"company": "api7"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
--- no_error_log
[error]
=== TEST 13: modify route without attach-consumer-label plugin
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"plugins": {
"key-auth": {}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
--- no_error_log
[error]
=== TEST 14: add global rule with attach-consumer-label plugin
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/global_rules/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"attach-consumer-label": {
"headers": {
"X-Global-Consumer-Department": "$department",
"X-Global-Consumer-Company": "$company"
}
}
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
--- no_error_log
[error]
=== TEST 15: access with auth (should contain expected consumer labels headers)
--- request
GET /echo
--- more_headers
apikey: key-a
--- response_headers
X-Global-Consumer-Company: api7
X-Global-Consumer-Department: devops
--- no_error_log
[error]

View File

@@ -0,0 +1,446 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_root_location();
run_tests;
__DATA__
=== TEST 1: sanity
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.authz-casbin")
local conf = {
model_path = "/path/to/model.conf",
policy_path = "/path/to/policy.csv",
username = "user"
}
local ok, err = plugin.check_schema(conf)
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
done
=== TEST 2: username missing
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.authz-casbin")
local conf = {
model_path = "/path/to/model.conf",
policy_path = "/path/to/policy.csv"
}
local ok, err = plugin.check_schema(conf)
if not ok then
ngx.say(err)
else
ngx.say("done")
end
}
}
--- request
GET /t
--- response_body
value should match only one schema, but matches none
=== TEST 3: put model and policy text in metadata
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.authz-casbin")
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/authz-casbin',
ngx.HTTP_PUT,
[[{
"model": "[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = (g(r.sub, p.sub) || keyMatch(r.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
"policy": "p, *, /, GET
p, admin, *, *
g, alice, admin"
}]]
)
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 4: Enforcer from text without files
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.authz-casbin")
local t = require("lib.test_admin").test
local conf = {
username = "user"
}
local ok, err = plugin.check_schema(conf)
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
done
=== TEST 5: enable authz-casbin by Admin API
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"authz-casbin": {
"username" : "user"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 6: no username header passed
--- request
GET /hello
--- error_code: 403
--- response_body_like eval
qr/"Access Denied"/
=== TEST 7: username passed but user not authorized
--- request
GET /hello
--- more_headers
user: bob
--- error_code: 403
--- response_body
{"message":"Access Denied"}
=== TEST 8: authorized user
--- request
GET /hello
--- more_headers
user: admin
--- error_code: 200
--- response_body
hello world
=== TEST 9: authorized user (rbac)
--- request
GET /hello
--- more_headers
user: alice
--- error_code: 200
--- response_body
hello world
=== TEST 10: unauthorized user before policy update
--- request
GET /hello
--- more_headers
user: jack
--- error_code: 403
--- response_body
{"message":"Access Denied"}
=== TEST 11: update model and policy text in metadata
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.authz-casbin")
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/authz-casbin',
ngx.HTTP_PUT,
[[{
"model": "[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = (g(r.sub, p.sub) || keyMatch(r.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
"policy": "p, *, /, GET
p, admin, *, *
p, jack, /hello, GET
g, alice, admin"
}]]
)
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 12: authorized user after policy update
--- request
GET /hello
--- more_headers
user: jack
--- error_code: 200
--- response_body
hello world
=== TEST 13: enable authz-casbin using model/policy files
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"authz-casbin": {
"model_path": "t/plugin/authz-casbin/model.conf",
"policy_path": "t/plugin/authz-casbin/policy.csv",
"username" : "user"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 14: authorized user as per policy
--- request
GET /hello
--- more_headers
user: alice
--- error_code: 200
--- response_body
hello world
=== TEST 15: unauthorized user as per policy
--- request
GET /hello
--- more_headers
user: bob
--- error_code: 403
--- response_body
{"message":"Access Denied"}
=== TEST 16: enable authz-casbin using model/policy text
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"authz-casbin": {
"model": "
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = (g(r.sub, p.sub) || keyMatch(r.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
"policy": "
p, *, /, GET
p, admin, *, *
g, jack, admin",
"username" : "user"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 17: authorized user as per policy
--- request
GET /hello
--- more_headers
user: jack
--- error_code: 200
--- response_body
hello world
=== TEST 18: unauthorized user as per policy
--- request
GET /hello
--- more_headers
user: bob
--- error_code: 403
--- response_body
{"message":"Access Denied"}
=== TEST 19: disable authz-casbin by Admin API
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed

View File

@@ -0,0 +1,14 @@
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = (g(r.sub, p.sub) || keyMatch(r.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)

View File

@@ -0,0 +1,3 @@
p, *, /, GET
p, admin, *, *
g, alice, admin
1 p, *, /, GET
2 p, admin, *, *
3 g, alice, admin

View File

@@ -0,0 +1,514 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
my $http_config = $block->http_config // <<_EOC_;
server {
listen 10420;
location /api/login/oauth/access_token {
content_by_lua_block {
local json_encode = require("toolkit.json").encode
ngx.req.read_body()
local arg = ngx.req.get_post_args()["code"]
local core = require("apisix.core")
local log = core.log
if arg == "wrong" then
ngx.status = 200
ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
return
end
ngx.status = 200
ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
}
}
}
_EOC_
$block->set_value("http_config", $http_config);
});
run_tests();
__DATA__
=== TEST 1: sanity
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.authz-casdoor")
local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
"/anything/callback"
local conf = {
callback_url = callback_url,
endpoint_addr = fake_uri,
client_id = "7ceb9b7fda4a9061ec1c",
client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
}
local ok, err = plugin.check_schema(conf)
if not ok then
ngx.say(err)
end
local conf2 = {
callback_url = callback_url .. "/?code=aaa",
endpoint_addr = fake_uri,
client_id = "7ceb9b7fda4a9061ec1c",
client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
}
ok, err = plugin.check_schema(conf2)
if ok then
ngx.say("err: shouldn't have passed sanity check")
end
local conf3 = {
callback_url = callback_url,
endpoint_addr = fake_uri .. "/",
client_id = "7ceb9b7fda4a9061ec1c",
client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
}
ok, err = plugin.check_schema(conf3)
if ok then
ngx.say("err: shouldn't have passed sanity check")
end
ngx.say("done")
}
}
--- response_body
done
=== TEST 2: enable plugin test redirect
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.authz-casdoor")
local t = require("lib.test_admin").test
local fake_uri = "http://127.0.0.1:10420"
local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
"/anything/callback"
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"uri": "/anything/*",
"plugins": {
"authz-casdoor": {
"callback_url":"]] .. callback_url .. [[",
"endpoint_addr":"]] .. fake_uri .. [[",
"client_id":"7ceb9b7fda4a9061ec1c",
"client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
},
"proxy-rewrite": {
"uri": "/echo"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"test.com:1980": 1
}
}
}]]
)
if code >= 300 then
ngx.say("failed to set up routing rule")
end
ngx.say("done")
}
}
--- response_body
done
=== TEST 3: test redirect
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.authz-casdoor")
local t = require("lib.test_admin").test
local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
if code ~= 302 then
ngx.say("should have redirected")
end
ngx.say("done")
}
}
--- response_body
done
=== TEST 4: enable fake casdoor
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/2',
ngx.HTTP_PUT,
[[{
"uri": "/api/login/oauth/access_token",
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 5: test fake casdoor
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.authz-casdoor")
local t = require("lib.test_admin").test
local httpc = require("resty.http").new()
local cjson = require("cjson")
local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
local res, err = httpc:request_uri(fake_uri, {method = "GET"})
if not res then
ngx.say(err)
end
local data = cjson.decode(res.body)
if not data then
ngx.say("invalid res.body")
end
if not data.access_token == "aaaaaaaaaaaaaaaa" then
ngx.say("invalid token")
end
ngx.say("done")
}
}
--- response_body
done
=== TEST 6: test code handling
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.authz-casdoor")
local core = require("apisix.core")
local log = core.log
local t = require("lib.test_admin").test
local cjson = require("cjson")
local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
"/anything/d?param1=foo&param2=bar"
local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
"/anything/callback?code=aaa&state="
local httpc = require("resty.http").new()
local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
if not res1 then
ngx.say(err1)
end
local cookie = res1.headers["Set-Cookie"]
local re_url = res1.headers["Location"]
local m, err = ngx.re.match(re_url, "state=([0-9]*)")
if err or not m then
log.error(err)
ngx.exit()
end
local state = m[1]
local res2, err2 = httpc:request_uri(callback_url..state, {
method = "GET",
headers = {Cookie = cookie}
})
if not res2 then
ngx.say(err2)
end
if res2.status ~= 302 then
log.error(res2.status)
end
local cookie2 = res2.headers["Set-Cookie"]
local res3, err3 = httpc:request_uri(fake_uri, {
method = "GET",
headers = {Cookie = cookie2}
})
if not res3 then
ngx.say(err3)
end
if res3.status >= 300 then
log.error(res3.status,res3.headers["Location"])
end
ngx.say("done")
}
}
--- response_body
done
=== TEST 7: incorrect test code handling
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.authz-casdoor")
local t = require("lib.test_admin").test
local cjson = require("cjson")
local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
"/anything/callback?code=aaa&state=bbb"
local httpc = require("resty.http").new()
local res1, err1 = httpc:request_uri(callback_url, {method = "GET"})
if res1.status ~= 503 then
ngx.say(res1.status)
end
ngx.say("done")
}
}
--- response_body
done
--- error_log
no session found
=== TEST 8: incorrect state handling
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.authz-casdoor")
local core = require("apisix.core")
local log = core.log
local t = require("lib.test_admin").test
local cjson = require("cjson")
local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
"/anything/d?param1=foo&param2=bar"
local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
"/anything/callback?code=aaa&state="
local httpc = require("resty.http").new()
local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
if not res1 then
ngx.say(err1)
end
local cookie = res1.headers["Set-Cookie"]
local re_url = res1.headers["Location"]
local m, err = ngx.re.match(re_url, "state=([0-9]*)")
if err or not m then
log.error(err)
end
local state = m[1]+10
local res2, err2 = httpc:request_uri(callback_url..state, {
method = "GET",
headers = {Cookie = cookie}
})
if not res2 then
ngx.say(err2)
end
if res2.status ~= 302 then
log.error(res2.status)
end
local cookie2 = res2.headers["Set-Cookie"]
local res3, err3 = httpc:request_uri(fake_uri, {
method = "GET",
headers = {Cookie = cookie2}
})
if not res3 then
ngx.say(err3)
end
if res3.status ~= 503 then
log.error(res3.status)
end
ngx.say("done")
}
}
--- response_body
done
--- error_log
invalid state
=== TEST 9: test incorrect access_token
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.authz-casdoor")
local core = require("apisix.core")
local log = core.log
local t = require("lib.test_admin").test
local cjson = require("cjson")
local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
"/anything/d?param1=foo&param2=bar"
local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
"/anything/callback?code=wrong&state="
local httpc = require("resty.http").new()
local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
if not res1 then
ngx.say(err1)
end
local cookie = res1.headers["Set-Cookie"]
local re_url = res1.headers["Location"]
local m, err = ngx.re.match(re_url, "state=([0-9]*)")
if err or not m then
log.error(err)
ngx.exit()
end
local state = m[1]
local res2, err2 = httpc:request_uri(callback_url..state, {
method = "GET",
headers = {Cookie = cookie}
})
if not res2 then
ngx.say(err2)
end
if res2.status ~= 302 then
log.error(res2.status)
end
local cookie2 = res2.headers["Set-Cookie"]
local res3, err3 = httpc:request_uri(fake_uri, {
method = "GET",
headers = {Cookie = cookie2}
})
if not res3 then
ngx.say(err3)
end
if res3.status ~= 503 then
log.error(res3.status)
end
ngx.say("done")
}
}
--- response_body
done
--- error_log
failed when accessing token: invalid access_token
=== TEST 10: data encryption for client_secret
--- yaml_config
apisix:
data_encryption:
enable_encrypt_fields: true
keyring:
- edd1c9f0985e76a2
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin").test
local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
"/anything/callback"
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"uri": "/anything/*",
"plugins": {
"authz-casdoor": {
"callback_url":"]] .. callback_url .. [[",
"endpoint_addr": "http://127.0.0.1:10420",
"client_id":"7ceb9b7fda4a9061ec1c",
"client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
},
"proxy-rewrite": {
"uri": "/echo"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"test.com:1980": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(0.1)
-- get plugin conf from admin api, password is decrypted
local code, message, res = t('/apisix/admin/routes/1',
ngx.HTTP_GET
)
res = json.decode(res)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(res.value.plugins["authz-casdoor"].client_secret)
-- get plugin conf from etcd, password is encrypted
local etcd = require("apisix.core.etcd")
local res = assert(etcd.get('/routes/1'))
ngx.say(res.body.node.value.plugins["authz-casdoor"].client_secret)
}
}
--- response_body
3416238e1edf915eac08b8fe345b2b95cdba7e04
YUfqAO0kPXjZIoAbPSuryCkUDksEmwSq08UDTIUWolN6KQwEUrh72TazePueo4/S

View File

@@ -0,0 +1,647 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
log_level('debug');
repeat_each(1);
no_long_string();
no_root_location();
run_tests;
__DATA__
=== TEST 1: minimal valid configuration w/o discovery
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.authz-keycloak")
local ok, err = plugin.check_schema({
client_id = "foo",
token_endpoint = "https://host.domain/realms/foo/protocol/openid-connect/token"
})
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
done
=== TEST 2: minimal valid configuration with discovery
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.authz-keycloak")
local ok, err = plugin.check_schema({
client_id = "foo",
discovery = "https://host.domain/realms/foo/.well-known/uma2-configuration"
})
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
done
=== TEST 3: minimal valid configuration w/o discovery when lazy_load_paths=true
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.authz-keycloak")
local ok, err = plugin.check_schema({
client_id = "foo",
lazy_load_paths = true,
token_endpoint = "https://host.domain/realms/foo/protocol/openid-connect/token",
resource_registration_endpoint = "https://host.domain/realms/foo/authz/protection/resource_set"
})
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
done
=== TEST 4: minimal valid configuration with discovery when lazy_load_paths=true
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.authz-keycloak")
local ok, err = plugin.check_schema({
client_id = "foo",
lazy_load_paths = true,
discovery = "https://host.domain/realms/foo/.well-known/uma2-configuration"
})
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
done
=== TEST 5: full schema check
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.authz-keycloak")
local ok, err = plugin.check_schema({
discovery = "https://host.domain/realms/foo/.well-known/uma2-configuration",
token_endpoint = "https://host.domain/realms/foo/protocol/openid-connect/token",
resource_registration_endpoint = "https://host.domain/realms/foo/authz/protection/resource_set",
client_id = "University",
client_secret = "secret",
grant_type = "urn:ietf:params:oauth:grant-type:uma-ticket",
policy_enforcement_mode = "ENFORCING",
permissions = {"res:customer#scopes:view"},
lazy_load_paths = false,
http_method_as_scope = false,
timeout = 1000,
ssl_verify = false,
cache_ttl_seconds = 1000,
keepalive = true,
keepalive_timeout = 10000,
keepalive_pool = 5,
access_token_expires_in = 300,
access_token_expires_leeway = 0,
refresh_token_expires_in = 3600,
refresh_token_expires_leeway = 0,
password_grant_token_generation_incoming_uri = "/api/token",
})
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
done
=== TEST 6: token_endpoint and discovery both missing
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.authz-keycloak")
local ok, err = plugin.check_schema({client_id = "foo"})
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
allOf 1 failed: object matches none of the required: ["discovery"] or ["token_endpoint"]
done
=== TEST 7: client_id missing
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.authz-keycloak")
local ok, err = plugin.check_schema({discovery = "https://host.domain/realms/foo/.well-known/uma2-configuration"})
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
property "client_id" is required
done
=== TEST 8: resource_registration_endpoint and discovery both missing and lazy_load_paths is true
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.authz-keycloak")
local ok, err = plugin.check_schema({
client_id = "foo",
token_endpoint = "https://host.domain/realms/foo/protocol/openid-connect/token",
lazy_load_paths = true
})
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
allOf 2 failed: object matches none of the required
done
=== TEST 9: Add https endpoint with ssl_verify true (default)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"authz-keycloak": {
"token_endpoint": "https://127.0.0.1:8443/realms/University/protocol/openid-connect/token",
"permissions": ["course_resource#delete"],
"client_id": "course_management",
"grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket",
"timeout": 3000
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello1"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 10: TEST with fake token and https endpoint
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local httpc = http.new()
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello1"
local res, err = httpc:request_uri(uri, {
method = "GET",
headers = {
["Authorization"] = "Bearer " .. "fake access token",
}
})
ngx.status = res.status
if res.status == 200 then
ngx.say(true)
else
ngx.say(false)
end
}
}
--- request
GET /t
--- response_body
false
--- error_log
Error while sending authz request to https://127.0.0.1:8443/realms/University/protocol/openid-connect/token: 18
--- error_code: 503
=== TEST 11: Add https endpoint with ssl_verify false
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"authz-keycloak": {
"token_endpoint": "https://127.0.0.1:8443/realms/University/protocol/openid-connect/token",
"permissions": ["course_resource#delete"],
"client_id": "course_management",
"grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket",
"timeout": 3000,
"ssl_verify": false
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello1"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 12: TEST for https based token verification with ssl_verify false
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local httpc = http.new()
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello1"
local res, err = httpc:request_uri(uri, {
method = "GET",
headers = {
["Authorization"] = "Bearer " .. "fake access token",
}
})
if res.status == 200 then
ngx.say(true)
else
ngx.say(false)
end
}
}
--- request
GET /t
--- response_body
false
--- error_log
Request denied: HTTP 401 Unauthorized. Body: {"error":"HTTP 401 Unauthorized"}
=== TEST 13: set enforcement mode is "ENFORCING", lazy_load_paths and permissions use default values
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"authz-keycloak": {
"token_endpoint": "http://127.0.0.1:8443/realms/University/protocol/openid-connect/token",
"client_id": "course_management",
"grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket",
"policy_enforcement_mode": "ENFORCING",
"timeout": 3000
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello1"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 14: test for permission is empty and enforcement mode is "ENFORCING".
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local httpc = http.new()
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello1"
local res, err = httpc:request_uri(uri, {
method = "GET",
headers = {
["Authorization"] = "Bearer " .. "fake access token",
}
})
ngx.say(res.body)
}
}
--- request
GET /t
--- response_body
{"error":"access_denied","error_description":"not_authorized"}
--- no_error_log
=== TEST 15: set enforcement mode is "ENFORCING", lazy_load_paths and permissions use default values , access_denied_redirect_uri is "http://127.0.0.1/test"
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"authz-keycloak": {
"token_endpoint": "http://127.0.0.1:8443/realms/University/protocol/openid-connect/token",
"client_id": "course_management",
"grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket",
"policy_enforcement_mode": "ENFORCING",
"timeout": 3000,
"access_denied_redirect_uri": "http://127.0.0.1/test"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello1"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 16: test for permission is empty and enforcement mode is "ENFORCING" , access_denied_redirect_uri is "http://127.0.0.1/test".
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local httpc = http.new()
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello1"
local res, err = httpc:request_uri(uri, {
method = "GET",
headers = {
["Authorization"] = "Bearer " .. "fake access token",
}
})
if res.status >= 300 then
ngx.status = res.status
ngx.header["Location"] = res.headers["Location"]
end
}
}
--- request
GET /t
--- response_headers
Location: http://127.0.0.1/test
--- error_code: 307
=== TEST 17: Add https endpoint with password_grant_token_generation_incoming_uri
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"authz-keycloak": {
"token_endpoint": "https://127.0.0.1:8443/realms/University/protocol/openid-connect/token",
"permissions": ["course_resource#view"],
"client_id": "course_management",
"client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5",
"grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket",
"timeout": 3000,
"ssl_verify": false,
"password_grant_token_generation_incoming_uri": "/api/token"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/api/token"
}]]
)
if code >= 300 then
ngx.status = code
end
local json_decode = require("toolkit.json").decode
local http = require "resty.http"
local httpc = http.new()
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/api/token"
local res, err = httpc:request_uri(uri, {
method = "POST",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded",
},
body = ngx.encode_args({
username = "teacher@gmail.com",
password = "123456",
}),
})
if res.status == 200 then
local body = json_decode(res.body)
local accessToken = body["access_token"]
local refreshToken = body["refresh_token"]
if accessToken and refreshToken then
ngx.say(true)
else
ngx.say(false)
end
else
ngx.say(false)
end
}
}
--- request
GET /t
--- response_body
true
=== TEST 18: no username or password
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"authz-keycloak": {
"token_endpoint": "https://127.0.0.1:8443/realms/University/protocol/openid-connect/token",
"permissions": ["course_resource#view"],
"client_id": "course_management",
"client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5",
"grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket",
"timeout": 3000,
"ssl_verify": false,
"password_grant_token_generation_incoming_uri": "/api/token"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/api/token"
}]]
)
if code >= 300 then
ngx.status = code
end
local json_decode = require("toolkit.json").decode
local http = require "resty.http"
local httpc = http.new()
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/api/token"
local headers = {
["Content-Type"] = "application/x-www-form-urlencoded",
}
-- no username
local res, err = httpc:request_uri(uri, {
method = "POST",
headers = headers,
body = ngx.encode_args({
password = "123456",
}),
})
ngx.print(res.body)
-- no password
local res, err = httpc:request_uri(uri, {
method = "POST",
headers = headers,
body = ngx.encode_args({
username = "teacher@gmail.com",
}),
})
ngx.print(res.body)
}
}
--- request
GET /t
--- response_body
{"message":"username is missing."}
{"message":"password is missing."}

View File

@@ -0,0 +1,743 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
log_level('debug');
repeat_each(1);
no_long_string();
no_root_location();
run_tests;
__DATA__
=== TEST 1: add plugin with view course permissions (using token endpoint)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"authz-keycloak": {
"token_endpoint": "http://127.0.0.1:8080/realms/University/protocol/openid-connect/token",
"permissions": ["course_resource#view"],
"client_id": "course_management",
"grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket",
"timeout": 3000
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello1"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 2: Get access token for teacher and access view course route
--- config
location /t {
content_by_lua_block {
local json_decode = require("toolkit.json").decode
local http = require "resty.http"
local httpc = http.new()
local uri = "http://127.0.0.1:8080/realms/University/protocol/openid-connect/token"
local res, err = httpc:request_uri(uri, {
method = "POST",
body = "grant_type=password&client_id=course_management&client_secret=d1ec69e9-55d2-4109-a3ea-befa071579d5&username=teacher@gmail.com&password=123456",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded"
}
})
if res.status == 200 then
local body = json_decode(res.body)
local accessToken = body["access_token"]
uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello1"
local res, err = httpc:request_uri(uri, {
method = "GET",
headers = {
["Authorization"] = "Bearer " .. accessToken,
}
})
if res.status == 200 then
ngx.say(true)
else
ngx.say(res.status)
ngx.say(false)
end
else
ngx.say(false)
end
}
}
--- request
GET /t
--- response_body
true
=== TEST 3: invalid access token
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local httpc = http.new()
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello1"
local res, err = httpc:request_uri(uri, {
method = "GET",
headers = {
["Authorization"] = "Bearer wrong_token",
}
})
if res.status == 401 then
ngx.say(true)
end
}
}
--- request
GET /t
--- response_body
true
--- error_log
Invalid bearer token
=== TEST 4: add plugin with view course permissions (using discovery)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"authz-keycloak": {
"discovery": "http://127.0.0.1:8080/realms/University/.well-known/uma2-configuration",
"permissions": ["course_resource#view"],
"client_id": "course_management",
"grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket",
"timeout": 3000
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello1"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 5: Get access token for teacher and access view course route
--- config
location /t {
content_by_lua_block {
local json_decode = require("toolkit.json").decode
local http = require "resty.http"
local httpc = http.new()
local uri = "http://127.0.0.1:8080/realms/University/protocol/openid-connect/token"
local res, err = httpc:request_uri(uri, {
method = "POST",
body = "grant_type=password&client_id=course_management&client_secret=d1ec69e9-55d2-4109-a3ea-befa071579d5&username=teacher@gmail.com&password=123456",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded"
}
})
if res.status == 200 then
local body = json_decode(res.body)
local accessToken = body["access_token"]
uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello1"
local res, err = httpc:request_uri(uri, {
method = "GET",
headers = {
["Authorization"] = "Bearer " .. accessToken,
}
})
if res.status == 200 then
ngx.say(true)
else
ngx.say(false)
end
else
ngx.say(false)
end
}
}
--- request
GET /t
--- response_body
true
=== TEST 6: invalid access token
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local httpc = http.new()
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello1"
local res, err = httpc:request_uri(uri, {
method = "GET",
headers = {
["Authorization"] = "Bearer wrong_token",
}
})
if res.status == 401 then
ngx.say(true)
end
}
}
--- request
GET /t
--- response_body
true
--- error_log
Invalid bearer token
=== TEST 7: add plugin for delete course route
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"authz-keycloak": {
"token_endpoint": "http://127.0.0.1:8080/realms/University/protocol/openid-connect/token",
"permissions": ["course_resource#delete"],
"client_id": "course_management",
"grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket",
"timeout": 3000
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello1"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 8: Get access token for student and delete course
--- config
location /t {
content_by_lua_block {
local json_decode = require("toolkit.json").decode
local http = require "resty.http"
local httpc = http.new()
local uri = "http://127.0.0.1:8080/realms/University/protocol/openid-connect/token"
local res, err = httpc:request_uri(uri, {
method = "POST",
body = "grant_type=password&client_id=course_management&client_secret=d1ec69e9-55d2-4109-a3ea-befa071579d5&username=student@gmail.com&password=123456",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded"
}
})
if res.status == 200 then
local body = json_decode(res.body)
local accessToken = body["access_token"]
uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello1"
local res, err = httpc:request_uri(uri, {
method = "GET",
headers = {
["Authorization"] = "Bearer " .. accessToken,
}
})
if res.status == 403 then
ngx.say(true)
else
ngx.say(false)
end
else
ngx.say(false)
end
}
}
--- request
GET /t
--- response_body
true
--- error_log
{"error":"access_denied","error_description":"not_authorized"}
=== TEST 9: add plugin with lazy_load_paths and http_method_as_scope
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"authz-keycloak": {
"discovery": "http://127.0.0.1:8080/realms/University/.well-known/uma2-configuration",
"client_id": "course_management",
"client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5",
"lazy_load_paths": true,
"http_method_as_scope": true
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/course/foo"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 10: Get access token for teacher and access view course route.
--- config
location /t {
content_by_lua_block {
local json_decode = require("toolkit.json").decode
local http = require "resty.http"
local httpc = http.new()
local uri = "http://127.0.0.1:8080/realms/University/protocol/openid-connect/token"
local res, err = httpc:request_uri(uri, {
method = "POST",
body = "grant_type=password&client_id=course_management&client_secret=d1ec69e9-55d2-4109-a3ea-befa071579d5&username=teacher@gmail.com&password=123456",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded"
}
})
if res.status == 200 then
local body = json_decode(res.body)
local accessToken = body["access_token"]
uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/course/foo"
local res, err = httpc:request_uri(uri, {
method = "GET",
headers = {
["Authorization"] = "Bearer " .. accessToken,
}
})
if res.status == 200 then
ngx.say(true)
else
ngx.say(false)
end
else
ngx.say(false)
end
}
}
--- request
GET /t
--- response_body
true
=== TEST 11: Get access token for student and access view course route.
--- config
location /t {
content_by_lua_block {
local json_decode = require("toolkit.json").decode
local http = require "resty.http"
local httpc = http.new()
local uri = "http://127.0.0.1:8080/realms/University/protocol/openid-connect/token"
local res, err = httpc:request_uri(uri, {
method = "POST",
body = "grant_type=password&client_id=course_management&client_secret=d1ec69e9-55d2-4109-a3ea-befa071579d5&username=student@gmail.com&password=123456",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded"
}
})
if res.status == 200 then
local body = json_decode(res.body)
local accessToken = body["access_token"]
uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/course/foo"
local res, err = httpc:request_uri(uri, {
method = "GET",
headers = {
["Authorization"] = "Bearer " .. accessToken,
}
})
if res.status == 200 then
ngx.say(true)
else
ngx.say(false)
end
else
ngx.say(false)
end
}
}
--- request
GET /t
--- response_body
true
=== TEST 12: Get access token for teacher and delete course.
--- config
location /t {
content_by_lua_block {
local json_decode = require("toolkit.json").decode
local http = require "resty.http"
local httpc = http.new()
local uri = "http://127.0.0.1:8080/realms/University/protocol/openid-connect/token"
local res, err = httpc:request_uri(uri, {
method = "POST",
body = "grant_type=password&client_id=course_management&client_secret=d1ec69e9-55d2-4109-a3ea-befa071579d5&username=teacher@gmail.com&password=123456",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded"
}
})
if res.status == 200 then
local body = json_decode(res.body)
local accessToken = body["access_token"]
uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/course/foo"
local res, err = httpc:request_uri(uri, {
method = "DELETE",
headers = {
["Authorization"] = "Bearer " .. accessToken,
}
})
if res.status == 200 then
ngx.say(true)
else
ngx.say(false)
end
else
ngx.say(false)
end
}
}
--- request
GET /t
--- response_body
true
=== TEST 13: Get access token for student and try to delete course. Should fail.
--- config
location /t {
content_by_lua_block {
local json_decode = require("toolkit.json").decode
local http = require "resty.http"
local httpc = http.new()
local uri = "http://127.0.0.1:8080/realms/University/protocol/openid-connect/token"
local res, err = httpc:request_uri(uri, {
method = "POST",
body = "grant_type=password&client_id=course_management&client_secret=d1ec69e9-55d2-4109-a3ea-befa071579d5&username=student@gmail.com&password=123456",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded"
}
})
if res.status == 200 then
local body = json_decode(res.body)
local accessToken = body["access_token"]
uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/course/foo"
local res, err = httpc:request_uri(uri, {
method = "DELETE",
headers = {
["Authorization"] = "Bearer " .. accessToken,
}
})
if res.status == 403 then
ngx.say(true)
else
ngx.say(false)
end
else
ngx.say(false)
end
}
}
--- request
GET /t
--- response_body
true
--- error_log
{"error":"access_denied","error_description":"not_authorized"}
=== TEST 14: Get access token for teacher and access view course route.
--- config
location /t {
content_by_lua_block {
local json_decode = require("toolkit.json").decode
local http = require "resty.http"
local httpc = http.new()
local uri = "http://127.0.0.1:8080/realms/University/protocol/openid-connect/token"
local res, err = httpc:request_uri(uri, {
method = "POST",
body = "grant_type=password&client_id=course_management&client_secret=d1ec69e9-55d2-4109-a3ea-befa071579d5&username=teacher@gmail.com&password=123456",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded"
}
})
if res.status == 200 then
local body = json_decode(res.body)
local accessToken = body["access_token"]
uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/course/foo"
local res, err = httpc:request_uri(uri, {
method = "GET",
headers = {
["Authorization"] = "Bearer " .. accessToken,
}
})
if res.status == 200 then
ngx.say(true)
else
ngx.say(false)
end
else
ngx.say(false)
end
}
}
--- request
GET /t
--- response_body
true
=== TEST 15: Get access token for student and access view course route.
--- config
location /t {
content_by_lua_block {
local json_decode = require("toolkit.json").decode
local http = require "resty.http"
local httpc = http.new()
local uri = "http://127.0.0.1:8080/realms/University/protocol/openid-connect/token"
local res, err = httpc:request_uri(uri, {
method = "POST",
body = "grant_type=password&client_id=course_management&client_secret=d1ec69e9-55d2-4109-a3ea-befa071579d5&username=student@gmail.com&password=123456",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded"
}
})
if res.status == 200 then
local body = json_decode(res.body)
local accessToken = body["access_token"]
uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/course/foo"
local res, err = httpc:request_uri(uri, {
method = "GET",
headers = {
["Authorization"] = "Bearer " .. accessToken,
}
})
if res.status == 200 then
ngx.say(true)
else
ngx.say(false)
end
else
ngx.say(false)
end
}
}
--- request
GET /t
--- response_body
true
=== TEST 16: add plugin with lazy_load_paths when resource_registration_endpoint is neither in config nor in the discovery doc
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"authz-keycloak": {
"discovery": "http://127.0.0.1:8080/realms/University/.well-known/openid-configuration",
"client_id": "course_management",
"client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5",
"lazy_load_paths": true
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/course/foo"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 17: Get access token for student and access view course route.
--- config
location /t {
content_by_lua_block {
local json_decode = require("toolkit.json").decode
local http = require "resty.http"
local httpc = http.new()
local uri = "http://127.0.0.1:8080/realms/University/protocol/openid-connect/token"
local res, err = httpc:request_uri(uri, {
method = "POST",
body = "grant_type=password&client_id=course_management&client_secret=d1ec69e9-55d2-4109-a3ea-befa071579d5&username=student@gmail.com&password=123456",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded"
}
})
if res.status == 200 then
local body = json_decode(res.body)
local accessToken = body["access_token"]
uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/course/foo"
local res, err = httpc:request_uri(uri, {
method = "GET",
headers = {
["Authorization"] = "Bearer " .. accessToken,
}
})
if res.status == 503 then
ngx.say(true)
else
ngx.say(false)
end
else
ngx.say(false)
end
}
}
--- request
GET /t
--- response_body
true
--- error_log
Unable to determine registration endpoint.

View File

@@ -0,0 +1,178 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
add_block_preprocessor(sub {
my ($block) = @_;
if (!$block->request) {
$block->set_value("request", "GET /t");
}
if (!$block->error_log && !$block->no_error_log) {
$block->set_value("no_error_log", "[error]\n[alert]");
}
});
run_tests;
__DATA__
=== TEST 1: access_denied_redirect_uri works with request denied in token_endpoint
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"authz-keycloak": {
"token_endpoint": "http://127.0.0.1:8080/realms/University/protocol/openid-connect/token",
"access_denied_redirect_uri": "http://127.0.0.1/test",
"permissions": ["course_resource#delete"],
"client_id": "course_management",
"grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket",
"timeout": 3000
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello1"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: hit
--- config
location /t {
content_by_lua_block {
local json_decode = require("toolkit.json").decode
local http = require "resty.http"
local httpc = http.new()
local uri = "http://127.0.0.1:8080/realms/University/protocol/openid-connect/token"
local res, err = httpc:request_uri(uri, {
method = "POST",
body = "grant_type=password&client_id=course_management&client_secret=d1ec69e9-55d2-4109-a3ea-befa071579d5&username=student@gmail.com&password=123456",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded"
}
})
if res.status == 200 then
local body = json_decode(res.body)
local accessToken = body["access_token"]
uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello1"
local res, err = httpc:request_uri(uri, {
method = "GET",
headers = {
["Authorization"] = "Bearer " .. accessToken,
}
})
ngx.status = res.status
ngx.header["Location"] = res.headers["Location"]
end
}
}
--- error_code: 307
--- response_headers
Location: http://127.0.0.1/test
=== TEST 3: data encryption for client_secret
--- yaml_config
apisix:
data_encryption:
enable_encrypt_fields: true
keyring:
- edd1c9f0985e76a2
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"authz-keycloak": {
"token_endpoint": "https://127.0.0.1:8443/realms/University/protocol/openid-connect/token",
"permissions": ["course_resource#view"],
"client_id": "course_management",
"client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5",
"grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket",
"timeout": 3000,
"ssl_verify": false,
"password_grant_token_generation_incoming_uri": "/api/token"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/api/token"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(0.1)
-- get plugin conf from admin api, password is decrypted
local code, message, res = t('/apisix/admin/routes/1',
ngx.HTTP_GET
)
res = json.decode(res)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(res.value.plugins["authz-keycloak"].client_secret)
-- get plugin conf from etcd, password is encrypted
local etcd = require("apisix.core.etcd")
local res = assert(etcd.get('/routes/1'))
ngx.say(res.body.node.value.plugins["authz-keycloak"].client_secret)
}
}
--- response_body
d1ec69e9-55d2-4109-a3ea-befa071579d5
Fz1juZEEvh9PPXOmWFdMMJkREt3ZSzEVWcUZPxNP6achk3fosEvn37oN0qH4YgKB

View File

@@ -0,0 +1,245 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
BEGIN {
$ENV{VAULT_TOKEN} = "root";
$ENV{CLIENT_SECRET} = "d1ec69e9-55d2-4109-a3ea-befa071579d5";
}
use t::APISIX 'no_plan';
log_level('debug');
repeat_each(1);
no_long_string();
no_root_location();
run_tests;
__DATA__
=== TEST 1: store secret into vault
--- exec
VAULT_TOKEN='root' VAULT_ADDR='http://0.0.0.0:8200' vault kv put kv/apisix/foo client_secret=d1ec69e9-55d2-4109-a3ea-befa071579d5
--- response_body
Success! Data written to: kv/apisix/foo
=== TEST 2: set client_secret as a reference to secret
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
-- put secret vault config
local code, body = t('/apisix/admin/secrets/vault/test1',
ngx.HTTP_PUT,
[[{
"uri": "http://127.0.0.1:8200",
"prefix" : "kv/apisix",
"token" : "root"
}]]
)
if code >= 300 then
ngx.status = code
return ngx.say(body)
end
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"authz-keycloak": {
"token_endpoint": "https://127.0.0.1:8443/realms/University/protocol/openid-connect/token",
"permissions": ["course_resource"],
"client_id": "course_management",
"client_secret": "$secret://vault/test1/foo/client_secret",
"grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket",
"timeout": 3000,
"ssl_verify": false,
"password_grant_token_generation_incoming_uri": "/api/token"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/api/token"
}]]
)
if code >= 300 then
ngx.status = code
return ngx.say(body)
end
local json_decode = require("toolkit.json").decode
local http = require "resty.http"
local httpc = http.new()
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/api/token"
local headers = {
["Content-Type"] = "application/x-www-form-urlencoded",
}
-- no username
local res, err = httpc:request_uri(uri, {
method = "POST",
headers = headers,
body = ngx.encode_args({
username = "teacher@gmail.com",
password = "123456",
}),
})
if res.status == 200 then
ngx.print("success\n")
end
}
}
--- request
GET /t
--- response_body
success
=== TEST 3: set client_secret as a reference to env variable
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"authz-keycloak": {
"token_endpoint": "https://127.0.0.1:8443/realms/University/protocol/openid-connect/token",
"permissions": ["course_resource"],
"client_id": "course_management",
"client_secret": "$env://CLIENT_SECRET",
"grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket",
"timeout": 3000,
"ssl_verify": false,
"password_grant_token_generation_incoming_uri": "/api/token"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/api/token"
}]]
)
if code >= 300 then
ngx.status = code
return
end
local json_decode = require("toolkit.json").decode
local http = require "resty.http"
local httpc = http.new()
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/api/token"
local headers = {
["Content-Type"] = "application/x-www-form-urlencoded",
}
-- no username
local res, err = httpc:request_uri(uri, {
method = "POST",
headers = headers,
body = ngx.encode_args({
username = "teacher@gmail.com",
password = "123456",
}),
})
if res.status == 200 then
ngx.print("success\n")
end
}
}
--- request
GET /t
--- response_body
success
=== TEST 4: set invalid client_secret as a reference to env variable
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"authz-keycloak": {
"token_endpoint": "https://127.0.0.1:8443/realms/University/protocol/openid-connect/token",
"permissions": ["course_resource"],
"client_id": "course_management",
"client_secret": "$env://INVALID_CLIENT_SECRET",
"grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket",
"timeout": 3000,
"ssl_verify": false,
"password_grant_token_generation_incoming_uri": "/api/token"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/api/token"
}]]
)
if code >= 300 then
ngx.status = code
return
end
local json_decode = require("toolkit.json").decode
local http = require "resty.http"
local httpc = http.new()
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/api/token"
local headers = {
["Content-Type"] = "application/x-www-form-urlencoded",
}
-- no username
local res, err = httpc:request_uri(uri, {
method = "POST",
headers = headers,
body = ngx.encode_args({
username = "teacher@gmail.com",
password = "123456",
}),
})
if res.status == 200 then
ngx.print("success\n")
end
}
}
--- request
GET /t
--- grep_error_log eval
qr/Invalid client secret/
--- grep_error_log_out
Invalid client secret
Invalid client secret

View File

@@ -0,0 +1,277 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_root_location();
no_shuffle();
add_block_preprocessor(sub {
my ($block) = @_;
my $inside_lua_block = $block->inside_lua_block // "";
chomp($inside_lua_block);
my $http_config = $block->http_config // <<_EOC_;
server {
listen 8765;
location /httptrigger {
content_by_lua_block {
ngx.req.read_body()
local msg = "aws lambda invoked"
ngx.header['Content-Length'] = #msg + 1
ngx.header['Connection'] = "Keep-Alive"
ngx.say(msg)
}
}
location /generic {
content_by_lua_block {
$inside_lua_block
}
}
}
_EOC_
$block->set_value("http_config", $http_config);
if (!$block->request) {
$block->set_value("request", "GET /t");
}
if (!$block->no_error_log && !$block->error_log) {
$block->set_value("no_error_log", "[error]\n[alert]");
}
});
run_tests;
__DATA__
=== TEST 1: checking iam schema
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.aws-lambda")
local ok, err = plugin.check_schema({
function_uri = "https://api.amazonaws.com",
authorization = {
iam = {
accesskey = "key1",
secretkey = "key2"
}
}
})
if not ok then
ngx.say(err)
else
ngx.say("done")
end
}
}
--- response_body
done
=== TEST 2: missing fields in iam schema
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.aws-lambda")
local ok, err = plugin.check_schema({
function_uri = "https://api.amazonaws.com",
authorization = {
iam = {
secretkey = "key2"
}
}
})
if not ok then
ngx.say(err)
else
ngx.say("done")
end
}
}
--- response_body
property "authorization" validation failed: property "iam" validation failed: property "accesskey" is required
=== TEST 3: create route with aws plugin enabled
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"aws-lambda": {
"function_uri": "http://localhost:8765/httptrigger",
"authorization": {
"apikey" : "testkey"
}
}
},
"uri": "/aws"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 4: test plugin endpoint
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local core = require("apisix.core")
local code, _, body, headers = t("/aws", "GET")
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
-- headers proxied 2 times -- one by plugin, another by this test case
core.response.set_header(headers)
ngx.print(body)
}
}
--- response_body
aws lambda invoked
--- response_headers
Content-Length: 19
=== TEST 5: check authz header - apikey
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
-- passing an apikey
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"aws-lambda": {
"function_uri": "http://localhost:8765/generic",
"authorization": {
"apikey": "test_key"
}
}
},
"uri": "/aws"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
ngx.say(body)
local code, _, body = t("/aws", "GET")
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.print(body)
}
}
--- inside_lua_block
local headers = ngx.req.get_headers() or {}
ngx.say("Authz-Header - " .. headers["x-api-key"] or "")
--- response_body
passed
Authz-Header - test_key
=== TEST 6: check authz header - IAM v4 signing
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
-- passing the iam access and secret keys
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"aws-lambda": {
"function_uri": "http://localhost:8765/generic",
"authorization": {
"iam": {
"accesskey": "KEY1",
"secretkey": "KeySecret"
}
}
}
},
"uri": "/aws"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
ngx.say(body)
local code, _, body, headers = t("/aws", "GET")
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.print(body)
}
}
--- inside_lua_block
local headers = ngx.req.get_headers() or {}
ngx.say("Authz-Header - " .. headers["Authorization"] or "")
ngx.say("AMZ-Date - " .. headers["X-Amz-Date"] or "")
ngx.print("invoked")
--- response_body eval
qr/passed
Authz-Header - AWS4-HMAC-SHA256 [ -~]*
AMZ-Date - [\d]+T[\d]+Z
invoked/

View File

@@ -0,0 +1,510 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_root_location();
no_shuffle();
add_block_preprocessor(sub {
my ($block) = @_;
my $inside_lua_block = $block->inside_lua_block // "";
chomp($inside_lua_block);
my $http_config = $block->http_config // <<_EOC_;
server {
listen 8765;
location /httptrigger {
content_by_lua_block {
ngx.req.read_body()
local msg = "faas invoked"
ngx.header['Content-Length'] = #msg + 1
ngx.header['X-Extra-Header'] = "MUST"
ngx.header['Connection'] = "Keep-Alive"
ngx.say(msg)
}
}
location /api {
content_by_lua_block {
ngx.say("invocation /api successful")
}
}
location /api/httptrigger {
content_by_lua_block {
ngx.say("invocation /api/httptrigger successful")
}
}
location /api/http/trigger {
content_by_lua_block {
ngx.say("invocation /api/http/trigger successful")
}
}
location /azure-demo {
content_by_lua_block {
$inside_lua_block
}
}
}
_EOC_
$block->set_value("http_config", $http_config);
if (!$block->request) {
$block->set_value("request", "GET /t");
}
if (!$block->no_error_log && !$block->error_log) {
$block->set_value("no_error_log", "[error]\n[alert]");
}
});
run_tests;
__DATA__
=== TEST 1: sanity
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.azure-functions")
local conf = {
function_uri = "http://some-url.com"
}
local ok, err = plugin.check_schema(conf)
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- response_body
done
=== TEST 2: function_uri missing
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.azure-functions")
local ok, err = plugin.check_schema({})
if not ok then
ngx.say(err)
else
ngx.say("done")
end
}
}
--- response_body
property "function_uri" is required
=== TEST 3: create route with azure-function plugin enabled
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"azure-functions": {
"function_uri": "http://localhost:8765/httptrigger"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/azure"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 4: Test plugin endpoint
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local core = require("apisix.core")
local code, _, body, headers = t("/azure", "GET")
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
-- headers proxied 2 times -- one by plugin, another by this test case
core.response.set_header(headers)
ngx.print(body)
}
}
--- response_body
faas invoked
--- response_headers
Content-Length: 13
X-Extra-Header: MUST
=== TEST 5: http2 check response body and headers
--- http2
--- request
GET /azure
--- more_headers
Content-Length: 0
--- response_body
faas invoked
=== TEST 6: check HTTP/2 response headers (must not contain any connection specific info)
First fetch the header from curl with -I then check the count of Connection
The full header looks like the format shown below
HTTP/2 200
content-type: text/plain
x-extra-header: MUST
content-length: 13
date: Wed, 17 Nov 2021 13:53:08 GMT
server: APISIX/2.10.2
--- http2
--- request
HEAD /azure
--- more_headers
Content-Length: 0
--- response_headers
Connection:
Upgrade:
Keep-Alive:
content-type: text/plain
x-extra-header: MUST
content-length: 13
=== TEST 7: check authz header
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
-- passing an apikey
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"azure-functions": {
"function_uri": "http://localhost:8765/azure-demo",
"authorization": {
"apikey": "test_key"
}
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/azure"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
ngx.say(body)
local code, _, body = t("/azure", "GET")
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.print(body)
}
}
--- inside_lua_block
local headers = ngx.req.get_headers() or {}
ngx.say("Authz-Header - " .. headers["x-functions-key"] or "")
--- response_body
passed
Authz-Header - test_key
=== TEST 8: check if apikey doesn't get overridden passed by client to the gateway
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local header = {}
header["x-functions-key"] = "must_not_be_overrided"
-- plugin schema already contains apikey with value "test_key" which won't be respected
local code, _, body = t("/azure", "GET", nil, nil, header)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.print(body)
}
}
--- inside_lua_block
local headers = ngx.req.get_headers() or {}
ngx.say("Authz-Header - " .. headers["x-functions-key"] or "")
--- response_body
Authz-Header - must_not_be_overrided
=== TEST 9: fall back to metadata master key
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, meta_body = t('/apisix/admin/plugin_metadata/azure-functions',
ngx.HTTP_PUT,
[[{
"master_apikey":"metadata_key"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
ngx.say(meta_body)
-- update plugin attribute
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"azure-functions": {
"function_uri": "http://localhost:8765/azure-demo"
}
},
"uri": "/azure"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
ngx.say(body)
-- plugin schema already contains apikey with value "test_key" which won't be respected
local code, _, body = t("/azure", "GET")
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.print(body)
}
}
--- inside_lua_block
local headers = ngx.req.get_headers() or {}
ngx.say("Authz-Header - " .. headers["x-functions-key"] or "")
--- response_body
passed
passed
Authz-Header - metadata_key
=== TEST 10: check if url path being forwarded correctly by creating a semi correct path uri
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
-- creating a semi path route
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"azure-functions": {
"function_uri": "http://localhost:8765/api"
}
},
"uri": "/azure/*"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
ngx.say(body)
local code, _, body = t("/azure/httptrigger", "GET")
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.print(body)
}
}
--- response_body
passed
invocation /api/httptrigger successful
=== TEST 11: check multilevel url path forwarding
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, _, body = t("/azure/http/trigger", "GET")
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.print(body)
}
}
--- response_body
invocation /api/http/trigger successful
=== TEST 12: check url path forwarding containing multiple slashes
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, _, body = t("/azure///http////trigger", "GET")
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.print(body)
}
}
--- response_body
invocation /api/http/trigger successful
=== TEST 13: check url path forwarding with no excess path
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, _, body = t("/azure/", "GET")
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.print(body)
}
}
--- response_body
invocation /api successful
=== TEST 14: create route with azure-function plugin enabled
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"azure-functions": {
"function_uri": "http://localhost:8765/httptrigger"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/azure"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 15: http2 failed to check response body and headers
--- http2
--- request
GET /azure
--- error_code: 400
--- error_log
HTTP2/HTTP3 request without a Content-Length header,

View File

@@ -0,0 +1,224 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
my $user_yaml_config = <<_EOC_;
apisix:
data_encryption:
enable_encrypt_fields: false
_EOC_
$block->set_value("yaml_config", $user_yaml_config);
});
run_tests;
__DATA__
=== TEST 1: add consumer jack and anonymous
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/consumers',
ngx.HTTP_PUT,
[[{
"username": "jack",
"plugins": {
"basic-auth": {
"username": "foo",
"password": "bar"
},
"limit-count": {
"count": 4,
"time_window": 60
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
local code, body = t('/apisix/admin/consumers',
ngx.HTTP_PUT,
[[{
"username": "anonymous",
"plugins": {
"limit-count": {
"count": 1,
"time_window": 60
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
passed
=== TEST 2: add basic auth plugin using admin api
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"basic-auth": {
"anonymous_consumer": "anonymous"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 3: normal consumer
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
for i = 1, 5, 1 do
local code, body = t('/hello',
ngx.HTTP_GET,
nil,
nil,
{
Authorization = "Basic Zm9vOmJhcg=="
}
)
if code >= 300 then
ngx.say("failed" .. code)
return
end
ngx.say(body .. i)
end
}
}
--- request
GET /t
--- response_body
passed1
passed2
passed3
passed4
failed503
=== TEST 4: request without basic-auth header will be from anonymous consumer and it will pass
--- request
GET /hello
--- response_body
hello world
=== TEST 5: request without basic-auth header will be from anonymous consumer and different rate limit will apply
--- pipelined_requests eval
["GET /hello", "GET /hello", "GET /hello", "GET /hello"]
--- error_code eval
[200, 503, 503, 503]
=== TEST 6: add basic auth plugin with non-existent anonymous_consumer
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"basic-auth": {
"anonymous_consumer": "not-found-anonymous"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 7: anonymous-consumer configured in the route should not be found
--- request
GET /hello
--- error_code: 401
--- error_log
failed to get anonymous consumer not-found-anonymous
--- response_body
{"message":"Invalid user authorization"}

View File

@@ -0,0 +1,622 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
BEGIN {
$ENV{VAULT_TOKEN} = "root";
}
use t::APISIX 'no_plan';
repeat_each(2);
no_long_string();
no_root_location();
no_shuffle();
run_tests;
__DATA__
=== TEST 1: sanity
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local plugin = require("apisix.plugins.basic-auth")
local ok, err = plugin.check_schema({username = 'foo', password = 'bar'}, core.schema.TYPE_CONSUMER)
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
done
=== TEST 2: wrong type of string
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local plugin = require("apisix.plugins.basic-auth")
local ok, err = plugin.check_schema({username = 123, password = "bar"}, core.schema.TYPE_CONSUMER)
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
property "username" validation failed: wrong type: expected string, got number
done
=== TEST 3: add consumer with username and plugins
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/consumers',
ngx.HTTP_PUT,
[[{
"username": "foo",
"plugins": {
"basic-auth": {
"username": "foo",
"password": "bar"
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 4: enable basic auth plugin using admin api
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"basic-auth": {}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 5: verify, missing authorization
--- request
GET /hello
--- error_code: 401
--- response_body
{"message":"Missing authorization in request"}
=== TEST 6: verify, invalid basic authorization header
--- request
GET /hello
--- more_headers
Authorization: Bad_header YmFyOmJhcgo=
--- error_code: 401
--- response_body
{"message":"Invalid authorization in request"}
--- grep_error_log eval
qr/Invalid authorization header format/
--- grep_error_log_out
Invalid authorization header format
=== TEST 7: verify, invalid authorization value (bad base64 str)
--- request
GET /hello
--- more_headers
Authorization: Basic aca_a
--- error_code: 401
--- response_body
{"message":"Invalid authorization in request"}
--- grep_error_log eval
qr/Failed to decode authentication header: aca_a/
--- grep_error_log_out
Failed to decode authentication header: aca_a
=== TEST 8: verify, invalid authorization value (no password)
--- request
GET /hello
--- more_headers
Authorization: Basic YmFy
--- error_code: 401
--- response_body
{"message":"Invalid authorization in request"}
--- grep_error_log eval
qr/Split authorization err: invalid decoded data: bar/
--- grep_error_log_out
Split authorization err: invalid decoded data: bar
=== TEST 9: verify, invalid username
--- request
GET /hello
--- more_headers
Authorization: Basic YmFyOmJhcgo=
--- error_code: 401
--- response_body
{"message":"Invalid user authorization"}
=== TEST 10: verify, invalid password
--- request
GET /hello
--- more_headers
Authorization: Basic Zm9vOmZvbwo=
--- error_code: 401
--- response_body
{"message":"Invalid user authorization"}
=== TEST 11: verify
--- request
GET /hello
--- more_headers
Authorization: Basic Zm9vOmJhcg==
--- response_body
hello world
--- error_log
find consumer foo
=== TEST 12: invalid schema, only one field `username`
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/consumers',
ngx.HTTP_PUT,
[[{
"username": "foo",
"plugins": {
"basic-auth": {
"username": "foo"
}
}
}]]
)
ngx.status = code
ngx.print(body)
}
}
--- request
GET /t
--- error_code: 400
--- response_body
{"error_msg":"invalid plugins configuration: failed to check the configuration of plugin basic-auth err: property \"password\" is required"}
=== TEST 13: invalid schema, not field given
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/consumers',
ngx.HTTP_PUT,
[[{
"username": "foo",
"plugins": {
"basic-auth": {
}
}
}]]
)
ngx.status = code
ngx.print(body)
}
}
--- request
GET /t
--- error_code: 400
--- response_body_like eval
qr/\{"error_msg":"invalid plugins configuration: failed to check the configuration of plugin basic-auth err: property \\"(username|password)\\" is required"\}/
=== TEST 14: invalid schema, not a table
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/consumers',
ngx.HTTP_PUT,
[[{
"username": "foo",
"plugins": {
"basic-auth": "blah"
}
}]]
)
ngx.status = code
ngx.print(body)
}
}
--- request
GET /t
--- error_code: 400
--- response_body
{"error_msg":"invalid plugins configuration: invalid plugin conf \"blah\" for plugin [basic-auth]"}
=== TEST 15: get the default schema
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/schema/plugins/basic-auth',
ngx.HTTP_GET,
nil,
[[
{"properties":{},"title":"work with route or service object","type":"object"}
]]
)
ngx.status = code
}
}
--- request
GET /t
=== TEST 16: get the schema by schema_type
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/schema/plugins/basic-auth?schema_type=consumer',
ngx.HTTP_GET,
nil,
[[
{"title":"work with consumer object","required":["username","password"],"properties":{"username":{"type":"string"},"password":{"type":"string"}},"type":"object"}
]]
)
ngx.status = code
}
}
--- request
GET /t
=== TEST 17: get the schema by error schema_type
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/schema/plugins/basic-auth?schema_type=consumer123123',
ngx.HTTP_GET,
nil,
[[
{"properties":{},"title":"work with route or service object","type":"object"}
]]
)
ngx.status = code
}
}
--- request
GET /t
=== TEST 18: enable basic auth plugin using admin api, set hide_credentials = true
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"basic-auth": {
"hide_credentials": true
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/echo"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 19: verify Authorization request header is hidden
--- request
GET /echo
--- more_headers
Authorization: Basic Zm9vOmJhcg==
--- response_headers
!Authorization
=== TEST 20: enable basic auth plugin using admin api, hide_credentials = false
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"basic-auth": {
"hide_credentials": false
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/echo"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 21: verify Authorization request header should not hidden
--- request
GET /echo
--- more_headers
Authorization: Basic Zm9vOmJhcg==
--- response_headers
Authorization: Basic Zm9vOmJhcg==
=== TEST 22: set basic-auth conf: password uses secret ref
--- request
GET /t
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
-- put secret vault config
local code, body = t('/apisix/admin/secrets/vault/test1',
ngx.HTTP_PUT,
[[{
"uri": "http://127.0.0.1:8200",
"prefix" : "kv/apisix",
"token" : "root"
}]]
)
if code >= 300 then
ngx.status = code
return ngx.say(body)
end
-- change consumer with secrets ref: vault
code, body = t('/apisix/admin/consumers',
ngx.HTTP_PUT,
[[{
"username": "foo",
"plugins": {
"basic-auth": {
"username": "foo",
"password": "$secret://vault/test1/foo/passwd"
}
}
}]]
)
if code >= 300 then
ngx.status = code
return ngx.say(body)
end
-- set route
code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"basic-auth": {
"hide_credentials": false
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/echo"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 23: store secret into vault
--- exec
VAULT_TOKEN='root' VAULT_ADDR='http://0.0.0.0:8200' vault kv put kv/apisix/foo passwd=bar
--- response_body
Success! Data written to: kv/apisix/foo
=== TEST 24: verify Authorization with foo/bar, request header should not hidden
--- request
GET /echo
--- more_headers
Authorization: Basic Zm9vOmJhcg==
--- response_headers
Authorization: Basic Zm9vOmJhcg==
=== TEST 25: set basic-auth conf with the token in an env var: password uses secret ref
--- request
GET /t
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
-- put secret vault config
local code, body = t('/apisix/admin/secrets/vault/test1',
ngx.HTTP_PUT,
[[{
"uri": "http://127.0.0.1:8200",
"prefix" : "kv/apisix",
"token" : "$ENV://VAULT_TOKEN"
}]]
)
if code >= 300 then
ngx.status = code
return ngx.say(body)
end
-- change consumer with secrets ref: vault
code, body = t('/apisix/admin/consumers',
ngx.HTTP_PUT,
[[{
"username": "foo",
"plugins": {
"basic-auth": {
"username": "foo",
"password": "$secret://vault/test1/foo/passwd"
}
}
}]]
)
if code >= 300 then
ngx.status = code
return ngx.say(body)
end
-- set route
code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"basic-auth": {
"hide_credentials": false
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/echo"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 26: verify Authorization with foo/bar, request header should not hidden
--- request
GET /echo
--- more_headers
Authorization: Basic Zm9vOmJhcg==
--- response_headers
Authorization: Basic Zm9vOmJhcg==

View File

@@ -0,0 +1,205 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (!$block->request) {
$block->set_value("request", "GET /t");
}
if (!$block->no_error_log && !$block->error_log) {
$block->set_value("no_error_log", "[error]\n[alert]");
}
my $extra_yaml_config = <<_EOC_;
plugins:
- grpc-transcode
- public-api
- batch-requests
_EOC_
$block->set_value("extra_yaml_config", $extra_yaml_config);
});
run_tests;
__DATA__
=== TEST 1: pre-create public API route
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"public-api": {}
},
"uri": "/apisix/batch-requests"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: set proto(id: 1)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local etcd = require("apisix.core.etcd")
local code, body = t('/apisix/admin/protos/1',
ngx.HTTP_PUT,
[[{
"content" : "syntax = \"proto3\";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 3: set routes(id: 2)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/2',
ngx.HTTP_PUT,
[[{
"methods": ["GET", "POST"],
"uri": "/grpctest",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "SayHello"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 4: hit route
--- request
GET /grpctest?name=world
--- response_body eval
qr/\{"message":"Hello world"\}/
=== TEST 5: successful batch-requests for both grpc and http
--- config
location = /aggregate {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/apisix/batch-requests',
ngx.HTTP_POST,
[=[{
"headers": {
"Content-Type":"application/json"
},
"pipeline":[
{
"method":"GET",
"path":"/grpctest"
},
{
"method":"GET",
"path":"/get"
}
]
}]=],
[=[[
{
"status": 200,
"body":"{\"message\":\"Hello \"}"
},
{
"status": 200,
"body":"hello"
}
]]=])
ngx.status = code
ngx.say(body)
}
}
location = /get {
content_by_lua_block {
ngx.print("hello")
}
}
--- request
GET /aggregate
--- response_body
passed

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,446 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (!$block->request) {
$block->set_value("request", "GET /t");
}
if (!$block->no_error_log && !$block->error_log) {
$block->set_value("no_error_log", "[error]\n[alert]");
}
my $extra_yaml_config = <<_EOC_;
plugins:
- public-api
- batch-requests
_EOC_
$block->set_value("extra_yaml_config", $extra_yaml_config);
});
run_tests;
__DATA__
=== TEST 1: pre-create public API route
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"public-api": {}
},
"uri": "/apisix/batch-requests"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: customize uri, not found
--- yaml_config
plugin_attr:
batch-requests:
uri: "/foo/bar"
--- config
location = /t {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/apisix/batch-requests',
ngx.HTTP_POST,
[=[{
"timeout": 100,
"pipeline":[
{
"path": "/a"
}]
}]=],
[=[[
{
"status": 200
}
]]=]
)
ngx.status = code
ngx.say(body)
}
}
location = /a {
content_by_lua_block {
ngx.status = 200
}
}
--- error_code: 404
=== TEST 3: create public API route for custom uri
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/2',
ngx.HTTP_PUT,
[[{
"plugins": {
"public-api": {}
},
"uri": "/foo/bar"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 4: customize uri, found
--- yaml_config
plugin_attr:
batch-requests:
uri: "/foo/bar"
--- config
location = /t {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/foo/bar',
ngx.HTTP_POST,
[=[{
"timeout": 100,
"pipeline":[
{
"path": "/b",
"headers": {
"Header1": "hello",
"Header2": "world"
}
},{
"path": "/c",
"method": "PUT"
},{
"path": "/d"
}]
}]=],
[=[[
{
"status": 200
},
{
"status": 201
},
{
"status": 202
}
]]=]
)
ngx.status = code
ngx.say(body)
}
}
location = /b {
content_by_lua_block {
ngx.status = 200
}
}
location = /c {
content_by_lua_block {
ngx.status = 201
}
}
location = /d {
content_by_lua_block {
ngx.status = 202
}
}
=== TEST 5: customize uri, missing plugin, use default
--- yaml_config
plugin_attr:
x:
uri: "/foo/bar"
--- config
location = /t {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/apisix/batch-requests',
ngx.HTTP_POST,
[=[{
"timeout": 100,
"pipeline":[
{
"path": "/a"
}]
}]=],
[=[[
{
"status": 200
}
]]=]
)
ngx.status = code
ngx.say(body)
}
}
location = /a {
content_by_lua_block {
ngx.status = 200
}
}
=== TEST 6: customize uri, missing attr, use default
--- yaml_config
plugin_attr:
batch-requests:
xyz: "/foo/bar"
--- config
location = /t {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/apisix/batch-requests',
ngx.HTTP_POST,
[=[{
"timeout": 100,
"pipeline":[
{
"path": "/a"
}]
}]=],
[=[[
{
"status": 200
}
]]=]
)
ngx.status = code
ngx.say(body)
}
}
location = /a {
content_by_lua_block {
ngx.status = 200
}
}
=== TEST 7: ensure real ip header is overridden
--- config
location = /aggregate {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/apisix/batch-requests',
ngx.HTTP_POST,
[=[{
"headers": {
"x-real-ip": "127.0.0.2"
},
"pipeline":[
{
"path": "/c",
"method": "PUT"
}]
}]=],
[=[[
{
"status": 201,
"body":"C",
"headers": {
"Client-IP": "127.0.0.1",
"Client-IP-From-Hdr": "127.0.0.1"
}
}
]]=])
ngx.status = code
ngx.say(body)
}
}
location = /c {
content_by_lua_block {
ngx.status = 201
ngx.header["Client-IP"] = ngx.var.remote_addr
ngx.header["Client-IP-From-Hdr"] = ngx.req.get_headers()["x-real-ip"]
ngx.print("C")
}
}
--- request
GET /aggregate
--- response_body
passed
=== TEST 8: ensure real ip header is overridden, header from the pipeline
--- config
location = /aggregate {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/apisix/batch-requests',
ngx.HTTP_POST,
[=[{
"headers": {
},
"pipeline":[
{
"path": "/c",
"headers": {
"x-real-ip": "127.0.0.2"
},
"method": "PUT"
}]
}]=],
[=[[
{
"status": 201,
"body":"C",
"headers": {
"Client-IP": "127.0.0.1",
"Client-IP-From-Hdr": "127.0.0.1"
}
}
]]=])
ngx.status = code
ngx.say(body)
}
}
location = /c {
content_by_lua_block {
ngx.status = 201
ngx.header["Client-IP"] = ngx.var.remote_addr
ngx.header["Client-IP-From-Hdr"] = ngx.req.get_headers()["x-real-ip"]
ngx.print("C")
}
}
--- request
GET /aggregate
--- response_body
passed
=== TEST 9: ensure real ip header is overridden, header has underscore
--- config
location = /aggregate {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/apisix/batch-requests',
ngx.HTTP_POST,
[=[{
"headers": {
},
"pipeline":[
{
"path": "/c",
"headers": {
"x_real-ip": "127.0.0.2"
},
"method": "PUT"
}]
}]=],
[=[[
{
"status": 201,
"body":"C",
"headers": {
"Client-IP": "127.0.0.1",
"Client-IP-From-Hdr": "127.0.0.1"
}
}
]]=])
ngx.status = code
ngx.say(body)
}
}
location = /c {
content_by_lua_block {
ngx.status = 201
ngx.header["Client-IP"] = ngx.var.remote_addr
ngx.header["Client-IP-From-Hdr"] = ngx.req.get_headers()["x-real-ip"]
ngx.print("C")
}
}
--- request
GET /aggregate
--- response_body
passed
=== TEST 10: ensure the content-type is correct
--- request
POST /apisix/batch-requests
{
"headers": {
},
"pipeline":[
{
"path": "/c",
"method": "PUT"
}
]
}
--- response_headers
Content-Type: application/json

View File

@@ -0,0 +1,269 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
no_long_string();
no_shuffle();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (!$block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests;
__DATA__
=== TEST 1: multipart request body to json request body conversion
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin")
local core = require("apisix.core")
local code, body = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"plugins": {
"body-transformer": {
"request": {
"template": "{\"foo\":\"{{name .. \" world\"}}\",\"bar\":{{age+10}}}"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
return
end
ngx.sleep(0.5)
local http = require("resty.http")
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/echo"
local body = ([[
--AaB03x
Content-Disposition: form-data; name="name"
Larry
--AaB03x
Content-Disposition: form-data; name="age"
10
--AaB03x--]])
local opt = {method = "POST", body = body, headers = {["Content-Type"] = "multipart/related; boundary=AaB03x"}}
local httpc = http.new()
local res = httpc:request_uri(uri, opt)
ngx.status = res.status
ngx.say(res.body or res.reason)
}
}
--- response_body
{"foo":"Larry world","bar":20}
=== TEST 2: multipart response body to json response body conversion
--- config
location /demo {
content_by_lua_block {
ngx.header["Content-Type"] = "multipart/related; boundary=AaB03x"
ngx.say([[
--AaB03x
Content-Disposition: form-data; name="name"
Larry
--AaB03x
Content-Disposition: form-data; name="age"
10
--AaB03x--]])
}
}
location /t {
content_by_lua_block {
local t = require("lib.test_admin")
local core = require("apisix.core")
local code, body = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"plugins": {
"proxy-rewrite": {
"uri": "/demo"
},
"body-transformer": {
"response": {
"template": "{\"foo\":\"{{name .. \" world\"}}\",\"bar\":{{age+10}}}"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1984": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
return
end
ngx.sleep(0.5)
local http = require("resty.http")
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
local opt = {method = "GET"}
local httpc = http.new()
local res = httpc:request_uri(uri, opt)
ngx.status = res.status
ngx.say(res.body or res.reason)
}
}
--- response_body
{"foo":"Larry world","bar":20}
=== TEST 3: multipart parse result accessible to template renderer
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin")
local core = require("apisix.core")
local req_template = ngx.encode_base64[[
{%
local core = require 'apisix.core'
local cjson = require 'cjson'
if tonumber(context.age) > 18 then
context._multipart:set_simple("status", "major")
else
context._multipart:set_simple("status", "minor")
end
local body = context._multipart:tostring()
%}{* body *}
]]
local code, body = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
string.format([[{
"uri": "/echo",
"plugins": {
"body-transformer": {
"response": {
"template": "%s"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}]], req_template)
)
if code >= 300 then
ngx.status = code
return
end
ngx.sleep(0.5)
------------------------#######################-------------------
local http = require("resty.http")
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/echo"
local body_minor = ([[
--AaB03x
Content-Disposition: form-data; name="name"
Larry
--AaB03x
Content-Disposition: form-data; name="age"
10
--AaB03x--]])
local opt = {method = "POST", body = body_minor, headers = {["Content-Type"] = "multipart/related; boundary=AaB03x"}}
local httpc = http.new()
local res = httpc:request_uri(uri, opt)
assert(res.status == 200)
ngx.say(res.body)
}
}
--- response_body eval
qr/.*Content-Disposition: form-data; name=\"status\"\r\n\r\nminor.*/
=== TEST 4: multipart parse response accessible to template renderer (test with age == 19)
--- config
location /t {
content_by_lua_block {
local http = require("resty.http")
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/echo"
local body_major = ([[
--AaB03x
Content-Disposition: form-data; name="name"
Larry
--AaB03x
Content-Disposition: form-data; name="age"
19
--AaB03x--]])
local opt = {method = "POST", body = body_major, headers = {["Content-Type"] = "multipart/related; boundary=AaB03x"}}
local httpc = http.new()
local res = httpc:request_uri(uri, opt)
assert(res.status == 200)
ngx.say(res.body)
}
}
--- response_body eval
qr/.*Content-Disposition: form-data; name=\"status\"\r\n\r\nmajor.*/

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,134 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
no_long_string();
no_shuffle();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (!$block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests;
__DATA__
=== TEST 1: body transformer with decoded body (keyword: context)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin")
local core = require("apisix.core")
local req_template = ngx.encode_base64[[
{%
local core = require 'apisix.core'
local cjson = require 'cjson'
context.name = "bar"
context.address = nil
context.age = context.age + 1
local body = core.json.encode(context)
%}{* body *}
]]
local code, body = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
string.format([[{
"uri": "/echo",
"plugins": {
"body-transformer": {
"request": {
"template": "%s"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}]], req_template)
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: verify the transformed body
--- request
POST /echo
{"name": "foo", "address":"LA", "age": 18}
-- response_body
{"name": "bar", "age": 19}
=== TEST 3: body transformer plugin with key-auth that fails
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin")
local core = require("apisix.core")
local code, body = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/foobar",
"plugins": {
"body-transformer": {
"request": {
"template": "some-template"
}
},
"key-auth": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
return
end
ngx.sleep(0.5)
local http = require("resty.http")
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/foobar"
local opt = {method = "POST", body = "body", headers = {["Content-Type"] = "application/json"}}
local httpc = http.new()
local res = httpc:request_uri(uri, opt)
assert(res.status == 401)
ngx.say(res.reason)
}
}
--- response_body
Unauthorized

View File

@@ -0,0 +1,785 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
log_level('info');
no_root_location();
no_shuffle();
add_block_preprocessor(sub {
my ($block) = @_;
if (!$block->request) {
$block->set_value("request", "GET /t");
}
my $extra_yaml_config = <<_EOC_;
plugins:
- brotli
_EOC_
$block->set_value("extra_yaml_config", $extra_yaml_config);
});
run_tests();
__DATA__
=== TEST 1: sanity
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"brotli": {
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: hit, single Accept-Encoding
--- request
POST /echo
0123456789
012345678
--- more_headers
Accept-Encoding: br
Content-Type: text/html
--- response_headers
Content-Encoding: br
Vary:
=== TEST 3: hit, single wildcard Accept-Encoding
--- request
POST /echo
0123456789
012345678
--- more_headers
Accept-Encoding: *
Content-Type: text/html
--- response_headers
Content-Encoding: br
Vary:
=== TEST 4: not hit, single Accept-Encoding
--- request
POST /echo
0123456789
012345678
--- more_headers
Accept-Encoding: gzip
Content-Type: text/html
--- response_headers
Vary:
=== TEST 5: hit, br in multi Accept-Encoding
--- request
POST /echo
0123456789
012345678
--- more_headers
Accept-Encoding: gzip, br
Content-Type: text/html
--- response_headers
Content-Encoding: br
Vary:
=== TEST 6: hit, no br in multi Accept-Encoding, but wildcard
--- request
POST /echo
0123456789
012345678
--- more_headers
Accept-Encoding: gzip, *
Content-Type: text/html
--- response_headers
Content-Encoding: br
Vary:
=== TEST 7: not hit, no br in multi Accept-Encoding
--- request
POST /echo
0123456789
012345678
--- more_headers
Accept-Encoding: gzip, deflate
Content-Type: text/html
--- response_headers
Vary:
=== TEST 8: hit, multi Accept-Encoding with quality
--- request
POST /echo
0123456789
012345678
--- more_headers
Accept-Encoding: gzip;q=0.5, br;q=0.6
Content-Type: text/html
--- response_headers
Content-Encoding: br
Vary:
=== TEST 9: not hit, multi Accept-Encoding with quality and disable br
--- request
POST /echo
0123456789
012345678
--- more_headers
Accept-Encoding: gzip;q=0.5, br;q=0
Content-Type: text/html
--- response_headers
Vary:
=== TEST 10: hit, multi Accept-Encoding with quality and wildcard
--- request
POST /echo
0123456789
012345678
--- more_headers
Accept-Encoding: gzip;q=0.8, deflate, sdch;q=0.6, *;q=0.1
Content-Type: text/html
--- response_headers
Content-Encoding: br
Vary:
=== TEST 11: default buffers and compress level
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.brotli")
local core = require("apisix.core")
local json = require("toolkit.json")
for _, conf in ipairs({
{},
{mode = 1},
{comp_level = 5},
{comp_level = 5, lgwin = 12},
{comp_level = 5, lgwin = 12, vary = true},
{comp_level = 5, lgwin = 12, lgblock = 16, vary = true},
{mode = 2, comp_level = 5, lgwin = 12, lgblock = 16, vary = true},
}) do
local ok, err = plugin.check_schema(conf)
if not ok then
ngx.say(err)
return
end
ngx.say(json.encode(conf))
end
}
}
--- response_body
{"comp_level":6,"http_version":1.1,"lgblock":0,"lgwin":19,"min_length":20,"mode":0,"types":["text/html"]}
{"comp_level":6,"http_version":1.1,"lgblock":0,"lgwin":19,"min_length":20,"mode":1,"types":["text/html"]}
{"comp_level":5,"http_version":1.1,"lgblock":0,"lgwin":19,"min_length":20,"mode":0,"types":["text/html"]}
{"comp_level":5,"http_version":1.1,"lgblock":0,"lgwin":12,"min_length":20,"mode":0,"types":["text/html"]}
{"comp_level":5,"http_version":1.1,"lgblock":0,"lgwin":12,"min_length":20,"mode":0,"types":["text/html"],"vary":true}
{"comp_level":5,"http_version":1.1,"lgblock":16,"lgwin":12,"min_length":20,"mode":0,"types":["text/html"],"vary":true}
{"comp_level":5,"http_version":1.1,"lgblock":16,"lgwin":12,"min_length":20,"mode":2,"types":["text/html"],"vary":true}
=== TEST 12: compress level
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[=[{
"uri": "/echo",
"vars": [["http_x", "==", "1"]],
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"brotli": {
"comp_level": 0
}
}
}]=]
)
if code >= 300 then
ngx.status = code
return
end
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[=[{
"uri": "/echo",
"vars": [["http_x", "==", "2"]],
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"brotli": {
"comp_level": 11
}
}
}]=]
)
if code >= 300 then
ngx.status = code
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 13: hit
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port
.. "/echo"
local httpc = http.new()
local res, err = httpc:request_uri(uri,
{method = "POST", headers = {x = "1"}, body = ("0123"):rep(1024)})
if not res then
ngx.say(err)
return
end
local less_compressed = res.body
local res, err = httpc:request_uri(uri,
{method = "POST", headers = {x = "2"}, body = ("0123"):rep(1024)})
if not res then
ngx.say(err)
return
end
if #less_compressed < 4096 and #less_compressed < #res.body then
ngx.say("ok")
end
}
}
--- response_body
ok
=== TEST 14: min length
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"brotli": {
"min_length": 21
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 15: not hit
--- request
POST /echo
0123456789
012345678
--- more_headers
Accept-Encoding: br
Content-Type: text/html
--- response_headers
Content-Encoding:
=== TEST 16: http version
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"brotli": {
"http_version": 1.1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 17: not hit
--- request
POST /echo HTTP/1.0
0123456789
012345678
--- more_headers
Accept-Encoding: br
Content-Type: text/html
--- response_headers
Content-Encoding:
=== TEST 18: hit again
--- request
POST /echo HTTP/1.1
0123456789
012345678
--- more_headers
Accept-Encoding: br
Content-Type: text/html
--- response_headers
Content-Encoding: br
=== TEST 19: types
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"brotli": {
"types": ["text/plain", "text/xml"]
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 20: not hit
--- request
POST /echo
0123456789
012345678
--- more_headers
Accept-Encoding: br
Content-Type: text/html
--- response_headers
Content-Encoding:
=== TEST 21: hit again
--- request
POST /echo
0123456789
012345678
--- more_headers
Accept-Encoding: br
Content-Type: text/xml
--- response_headers
Content-Encoding: br
=== TEST 22: hit with charset
--- request
POST /echo
0123456789
012345678
--- more_headers
Accept-Encoding: br
Content-Type: text/plain; charset=UTF-8
--- response_headers
Content-Encoding: br
=== TEST 23: match all types
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"brotli": {
"types": "*"
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 24: hit
--- request
POST /echo
0123456789
012345678
--- more_headers
Accept-Encoding: br
Content-Type: video/3gpp
--- response_headers
Content-Encoding: br
=== TEST 25: vary
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"brotli": {
"vary": true
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 26: hit
--- request
POST /echo
0123456789
012345678
--- more_headers
Accept-Encoding: br
Vary: upstream
Content-Type: text/html
--- response_headers
Content-Encoding: br
Vary: upstream, Accept-Encoding
=== TEST 27: schema check
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
for _, case in ipairs({
{input = {
types = {}
}},
{input = {
min_length = 0
}},
{input = {
mode = 4
}},
{input = {
comp_level = 12
}},
{input = {
http_version = 2
}},
{input = {
lgwin = 100
}},
{input = {
lgblock = 8
}},
{input = {
vary = 0
}}
}) do
local code, body = t('/apisix/admin/global_rules/1',
ngx.HTTP_PUT,
{
id = "1",
plugins = {
["brotli"] = case.input
}
}
)
ngx.print(body)
end
}
}
--- response_body
{"error_msg":"failed to check the configuration of plugin brotli err: property \"types\" validation failed: object matches none of the required"}
{"error_msg":"failed to check the configuration of plugin brotli err: property \"min_length\" validation failed: expected 0 to be at least 1"}
{"error_msg":"failed to check the configuration of plugin brotli err: property \"mode\" validation failed: expected 4 to be at most 2"}
{"error_msg":"failed to check the configuration of plugin brotli err: property \"comp_level\" validation failed: expected 12 to be at most 11"}
{"error_msg":"failed to check the configuration of plugin brotli err: property \"http_version\" validation failed: matches none of the enum values"}
{"error_msg":"failed to check the configuration of plugin brotli err: property \"lgwin\" validation failed: matches none of the enum values"}
{"error_msg":"failed to check the configuration of plugin brotli err: property \"lgblock\" validation failed: matches none of the enum values"}
{"error_msg":"failed to check the configuration of plugin brotli err: property \"vary\" validation failed: wrong type: expected boolean, got number"}
=== TEST 28: body checksum
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"brotli": {
"types": "*"
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 29: hit - decompressed respone body same as requset body
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port
.. "/echo"
local httpc = http.new()
local req_body = ("abcdf01234"):rep(1024)
local res, err = httpc:request_uri(uri,
{method = "POST", headers = {["Accept-Encoding"] = "br"}, body = req_body})
if not res then
ngx.say(err)
return
end
local brotli = require "brotli"
local decompressor = brotli.decompressor:new()
local chunk = decompressor:decompress(res.body)
local chunk_fin = decompressor:finish()
local chunks = chunk .. chunk_fin
if #chunks == #req_body then
ngx.say("ok")
end
}
}
--- response_body
ok
=== TEST 30: mock upstream compressed response
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/mock_compressed_upstream_response",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"brotli": {
"types": "*"
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 31: hit - skip brotli compression of compressed upstream response
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port
.. "/mock_compressed_upstream_response"
local httpc = http.new()
local req_body = ("abcdf01234"):rep(1024)
local res, err = httpc:request_uri(uri,
{method = "POST", headers = {["Accept-Encoding"] = "gzip, br"}, body = req_body})
if not res then
ngx.say(err)
return
end
if res.headers["Content-Encoding"] == 'gzip' then
ngx.say("ok")
end
}
}
--- request
GET /t
--- more_headers
Accept-Encoding: gzip, br
Vary: upstream
Content-Type: text/html
--- response_body
ok

View File

@@ -0,0 +1,223 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
log_level('warn');
repeat_each(1);
no_long_string();
no_root_location();
no_shuffle();
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests;
__DATA__
=== TEST 1: Add route for sp1
--- config
location /t {
content_by_lua_block {
local kc = require("lib.keycloak_cas")
local core = require("apisix.core")
local default_opts = kc.get_default_opts()
local opts = core.table.deepcopy(default_opts)
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/cas1',
ngx.HTTP_PUT,
[[{
"methods": ["GET", "POST"],
"host" : "127.0.0.1",
"plugins": {
"cas-auth": ]] .. core.json.encode(opts) .. [[
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/*"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: login and logout ok
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local httpc = http.new()
local kc = require "lib.keycloak_cas"
local path = "/uri"
local uri = "http://127.0.0.1:" .. ngx.var.server_port
local username = "test"
local password = "test"
local res, err, cas_cookie, keycloak_cookie = kc.login_keycloak(uri .. path, username, password)
if err or res.headers['Location'] ~= path then
ngx.log(ngx.ERR, err)
ngx.exit(500)
end
res, err = httpc:request_uri(uri .. res.headers['Location'], {
method = "GET",
headers = {
["Cookie"] = cas_cookie
}
})
assert(res.status == 200)
ngx.say(res.body)
res, err = kc.logout_keycloak(uri .. "/logout", cas_cookie, keycloak_cookie)
assert(res.status == 200)
}
}
--- response_body_like
uri: /uri
cookie: .*
host: 127.0.0.1:1984
user-agent: .*
x-real-ip: 127.0.0.1
=== TEST 3: Add route for sp2
--- config
location /t {
content_by_lua_block {
local kc = require("lib.keycloak_cas")
local core = require("apisix.core")
local default_opts = kc.get_default_opts()
local opts = core.table.deepcopy(default_opts)
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/cas2',
ngx.HTTP_PUT,
[[{
"methods": ["GET", "POST"],
"host" : "127.0.0.2",
"plugins": {
"cas-auth": ]] .. core.json.encode(opts) .. [[
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/*"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 4: login sp1 and sp2, then do single logout
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local httpc = http.new()
local kc = require "lib.keycloak_cas"
local path = "/uri"
-- login to sp1
local uri = "http://127.0.0.1:" .. ngx.var.server_port
local username = "test"
local password = "test"
local res, err, cas_cookie, keycloak_cookie = kc.login_keycloak(uri .. path, username, password)
if err or res.headers['Location'] ~= path then
ngx.log(ngx.ERR, err)
ngx.exit(500)
end
res, err = httpc:request_uri(uri .. res.headers['Location'], {
method = "GET",
headers = {
["Cookie"] = cas_cookie
}
})
assert(res.status == 200)
-- login to sp2, which would skip login at keycloak side
local uri2 = "http://127.0.0.2:" .. ngx.var.server_port
local res, err, cas_cookie2 = kc.login_keycloak_for_second_sp(uri2 .. path, keycloak_cookie)
if err or res.headers['Location'] ~= path then
ngx.log(ngx.ERR, err)
ngx.exit(500)
end
res, err = httpc:request_uri(uri2 .. res.headers['Location'], {
method = "GET",
headers = {
["Cookie"] = cas_cookie2
}
})
assert(res.status == 200)
-- SLO (single logout)
res, err = kc.logout_keycloak(uri .. "/logout", cas_cookie, keycloak_cookie)
assert(res.status == 200)
-- login to sp2, which would do normal login process at keycloak side
local res, err, cas_cookie2, keycloak_cookie = kc.login_keycloak(uri2 .. path, username, password)
if err or res.headers['Location'] ~= path then
ngx.log(ngx.ERR, err)
ngx.exit(500)
end
res, err = httpc:request_uri(uri .. res.headers['Location'], {
method = "GET",
headers = {
["Cookie"] = cas_cookie2
}
})
assert(res.status == 200)
-- logout sp2
res, err = kc.logout_keycloak(uri2 .. "/logout", cas_cookie2, keycloak_cookie)
assert(res.status == 200)
}
}

View File

@@ -0,0 +1,212 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
my $stream_default_server = <<_EOC_;
server {
listen 8088;
listen 8089;
content_by_lua_block {
require("lib.chaitin_waf_server").reject()
}
}
_EOC_
$block->set_value("extra_stream_config", $stream_default_server);
$block->set_value("stream_conf_enable", 1);
# setup default conf.yaml
my $extra_yaml_config = $block->extra_yaml_config // <<_EOC_;
apisix:
stream_proxy: # TCP/UDP L4 proxy
only: true # Enable L4 proxy only without L7 proxy.
tcp:
- addr: 9100 # Set the TCP proxy listening ports.
tls: true
- addr: "127.0.0.1:9101"
udp: # Set the UDP proxy listening ports.
- 9200
- "127.0.0.1:9201"
_EOC_
$block->set_value("extra_yaml_config", $extra_yaml_config);
if (!$block->request) {
# use /do instead of /t because stream server will inject a default /t location
$block->set_value("request", "GET /do");
}
if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
$block->set_value("no_error_log", "[error]");
}
});
run_tests;
__DATA__
=== TEST 1: set route
--- config
location /do {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/chaitin-waf',
ngx.HTTP_PUT,
[[{
"nodes": [
{
"host": "127.0.0.1",
"port": 8088
},
{
"host": "127.0.0.1",
"port": 8089
}
]
}]]
)
if code >= 300 then
ngx.status = code
return ngx.print(body)
end
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"plugins": {
"chaitin-waf": {
"upstream": {
"servers": ["httpbun.org"]
}
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/*"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: pass
--- request
GET /hello
--- error_code: 403
--- response_body
{"code": 403, "success":false, "message": "blocked by Chaitin SafeLine Web Application Firewall", "event_id": "b3c6ce574dc24f09a01f634a39dca83b"}
--- error_log
--- response_headers
X-APISIX-CHAITIN-WAF: yes
X-APISIX-CHAITIN-WAF-STATUS: 403
X-APISIX-CHAITIN-WAF-ACTION: reject
--- response_headers_like
X-APISIX-CHAITIN-WAF-TIME:
=== TEST 3: plugin mode monitor prepare
--- config
location /do {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/chaitin-waf',
ngx.HTTP_PUT,
[[{
"nodes": [
{
"host": "127.0.0.1",
"port": 8089
}
]
}]]
)
if code >= 300 then
ngx.status = code
return ngx.print(body)
end
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"plugins": {
"chaitin-waf": {
"mode": "monitor",
"match": [
{
"vars": [
["http_waf", "==", "true"]
]
}
]
}
},
"uri": "/*",
"upstream": {
"nodes": { "127.0.0.1:1980": 1 },
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
return ngx.print(body)
end
ngx.say("passed")
}
}
--- response_body
passed
=== TEST 4: plugin mode monitor
--- request
GET /hello
--- more_headers
waf: true
trigger: block
--- error_code: 200
--- response_body
hello world
--- response_headers
X-APISIX-CHAITIN-WAF: yes
X-APISIX-CHAITIN-WAF-STATUS: 403
X-APISIX-CHAITIN-WAF-ACTION: reject

View File

@@ -0,0 +1,139 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
my $stream_default_server = <<_EOC_;
server {
listen 8088;
listen 8089;
content_by_lua_block {
require("lib.chaitin_waf_server").timeout()
}
}
_EOC_
$block->set_value("extra_stream_config", $stream_default_server);
$block->set_value("stream_conf_enable", 1);
# setup default conf.yaml
my $extra_yaml_config = $block->extra_yaml_config // <<_EOC_;
apisix:
stream_proxy: # TCP/UDP L4 proxy
only: true # Enable L4 proxy only without L7 proxy.
tcp:
- addr: 9100 # Set the TCP proxy listening ports.
tls: true
- addr: "127.0.0.1:9101"
udp: # Set the UDP proxy listening ports.
- 9200
- "127.0.0.1:9201"
plugins:
- chaitin-waf
_EOC_
$block->set_value("extra_yaml_config", $extra_yaml_config);
if (!$block->request) {
# use /do instead of /t because stream server will inject a default /t location
$block->set_value("request", "GET /do");
}
if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
$block->set_value("no_error_log", "[error]");
}
});
run_tests;
__DATA__
=== TEST 1: set route
--- config
location /do {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/chaitin-waf',
ngx.HTTP_PUT,
[[{
"nodes": [
{
"host": "127.0.0.1",
"port": 8088
},
{
"host": "127.0.0.1",
"port": 8089
}
]
}]]
)
if code >= 300 then
ngx.status = code
return ngx.print(body)
end
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"plugins": {
"chaitin-waf": {
"upstream": {
"servers": ["httpbun.org"]
}
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/*"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: timeout
--- request
GET /hello
--- error_code: 200
--- response_body
hello world
--- error_log
--- response_headers
X-APISIX-CHAITIN-WAF: timeout
--- response_headers_like
X-APISIX-CHAITIN-WAF-TIME:

View File

@@ -0,0 +1,407 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
my $stream_default_server = <<_EOC_;
server {
listen 8088;
listen 8089;
listen unix:/tmp/safeline-snserver.sock;
content_by_lua_block {
require("lib.chaitin_waf_server").pass()
}
}
_EOC_
$block->set_value("extra_stream_config", $stream_default_server);
$block->set_value("stream_conf_enable", 1);
# setup default conf.yaml
my $extra_yaml_config = $block->extra_yaml_config // <<_EOC_;
apisix:
stream_proxy: # TCP/UDP L4 proxy
only: true # Enable L4 proxy only without L7 proxy.
tcp:
- addr: 9100 # Set the TCP proxy listening ports.
tls: true
- addr: "127.0.0.1:9101"
udp: # Set the UDP proxy listening ports.
- 9200
- "127.0.0.1:9201"
plugins:
- chaitin-waf
_EOC_
$block->set_value("extra_yaml_config", $extra_yaml_config);
if (!$block->request) {
# use /do instead of /t because stream server will inject a default /t location
$block->set_value("request", "GET /do");
}
if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
$block->set_value("no_error_log", "[error]");
}
});
run_tests;
__DATA__
=== TEST 1: wrong schema: nodes empty
--- config
location /do {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/chaitin-waf',
ngx.HTTP_PUT,
[[{
"nodes": []
}
]]
)
if code >= 300 then
ngx.status = code
end
ngx.print(body)
}
}
--- error_code: 400
--- response_body
{"error_msg":"invalid configuration: property \"nodes\" validation failed: expect array to have at least 1 items"}
=== TEST 2: wrong schema: nodes misses host
--- config
location /do {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/chaitin-waf',
ngx.HTTP_PUT,
[[{
"nodes": [
{}
]
}
]]
)
if code >= 300 then
ngx.status = code
end
ngx.print(body)
}
}
--- error_code: 400
--- response_body
{"error_msg":"invalid configuration: property \"nodes\" validation failed: failed to validate item 1: property \"host\" is required"}
=== TEST 3: sanity
--- config
location /do {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/chaitin-waf',
ngx.HTTP_PUT,
[[{
"nodes": [
{
"host": "127.0.0.1",
"port": 8088
},
{
"host": "127.0.0.1",
"port": 8089
},
{
"host": "unix:/tmp/safeline-snserver.sock",
"port": 8000
}
]
}]]
)
if code >= 300 then
ngx.status = code
return ngx.print(body)
end
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"plugins": {
"chaitin-waf": {
"upstream": {
"servers": ["httpbun.org"]
}
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/*"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 4: pass
--- request
GET /hello
--- error_code: 200
--- response_body
hello world
--- error_log
--- response_headers
X-APISIX-CHAITIN-WAF: yes
X-APISIX-CHAITIN-WAF-STATUS: 200
X-APISIX-CHAITIN-WAF-ACTION: pass
--- response_headers_like
X-APISIX-CHAITIN-WAF-TIME:
=== TEST 5: match condition
--- config
location /do {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"plugins": {
"chaitin-waf": {
"upstream": {
"servers": ["httpbun.org"]
},
"match": [
{
"vars": [
["http_waf","==","true"]
]
}
]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/*"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 6: no match
--- request
GET /hello
--- error_code: 200
--- response_body
hello world
--- error_log
--- response_headers
X-APISIX-CHAITIN-WAF: no
=== TEST 7: matched
--- request
GET /hello
--- more_headers
waf: true
--- error_code: 200
--- response_body
hello world
--- error_log
--- response_headers
X-APISIX-CHAITIN-WAF: yes
X-APISIX-CHAITIN-WAF-STATUS: 200
X-APISIX-CHAITIN-WAF-ACTION: pass
--- response_headers_like
X-APISIX-CHAITIN-WAF-TIME:
=== TEST 8: plugin mode off prepare
--- config
location /do {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"plugins": {
"chaitin-waf": {
"mode": "off",
"upstream": {
"servers": ["httpbun.org"]
},
"match": [
{
"vars": [
["http_waf","==","true"]
]
}
]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/*"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 9: plugin mode off
--- request
GET /hello
--- more_headers
trigger: true
--- error_code: 200
--- response_body
hello world
--- response_headers
X-APISIX-CHAITIN-WAF: off
=== TEST 10: real_client_ip = false prepare
--- config
location /do {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/chaitin-waf',
ngx.HTTP_PUT,
[[{
"nodes": [
{
"host": "127.0.0.1",
"port": 8088
}
]
}]]
)
if code >= 300 then
ngx.status = code
return ngx.print(body)
end
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"plugins": {
"chaitin-waf": {
"match": [
{
"vars": [
["http_trigger", "==", "true"]
]
}
],
"config": {
"real_client_ip": false
}
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/*"
}]]
)
if code >= 300 then
ngx.status = code
return ngx.print(body)
end
ngx.say("passed")
}
}
--- response_body
passed
=== TEST 11: real_client_ip = false
--- request
GET /hello
--- more_headers
X-Forwarded-For: 1.2.3.4
trigger: true
--- error_code: 200
--- response_body
hello world
--- response_headers
X-APISIX-CHAITIN-WAF: yes
X-APISIX-CHAITIN-WAF-ACTION: pass
X-APISIX-CHAITIN-WAF-STATUS: 200

View File

@@ -0,0 +1,315 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
log_level("info");
repeat_each(1);
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
my $http_config = $block->http_config // <<_EOC_;
server {
listen 10420;
location /clickhouse-logger/test1 {
content_by_lua_block {
ngx.req.read_body()
local data = ngx.req.get_body_data()
local headers = ngx.req.get_headers()
ngx.log(ngx.WARN, "clickhouse body: ", data)
for k, v in pairs(headers) do
ngx.log(ngx.WARN, "clickhouse headers: " .. k .. ":" .. v)
end
ngx.say("ok")
}
}
}
_EOC_
$block->set_value("http_config", $http_config);
});
run_tests();
__DATA__
=== TEST 1: Full configuration verification
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.clickhouse-logger")
local ok, err = plugin.check_schema({timeout = 3,
retry_delay = 1,
batch_max_size = 500,
user = "default",
password = "a",
database = "default",
logtable = "t",
endpoint_addr = "http://127.0.0.1:1980/clickhouse_logger_server",
max_retry_count = 1,
name = "clickhouse logger",
ssl_verify = false
})
if not ok then
ngx.say(err)
else
ngx.say("passed")
end
}
}
--- response_body
passed
=== TEST 2: Basic configuration verification
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.clickhouse-logger")
local ok, err = plugin.check_schema({user = "default",
password = "a",
database = "default",
logtable = "t",
endpoint_addr = "http://127.0.0.1:1980/clickhouse_logger_server"
})
if not ok then
ngx.say(err)
else
ngx.say("passed")
end
}
}
--- response_body
passed
=== TEST 3: auth configure undefined
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.clickhouse-logger")
local ok, err = plugin.check_schema({user = "default",
password = "a",
database = "default",
logtable = "t"
})
if not ok then
ngx.say(err)
else
ngx.say("passed")
end
}
}
--- response_body
value should match only one schema, but matches none
=== TEST 4: add plugin on routes
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"clickhouse-logger": {
"user": "default",
"password": "a",
"database": "default",
"logtable": "t",
"endpoint_addr": "http://127.0.0.1:1980/clickhouse_logger_server",
"batch_max_size":1,
"inactive_timeout":1
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/opentracing"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 5: add plugin on routes using multi clickhouse-logger
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"clickhouse-logger": {
"user": "default",
"password": "",
"database": "default",
"logtable": "test",
"endpoint_addrs": ["http://127.0.0.1:8123",
"http://127.0.0.1:8124"],
"batch_max_size":1,
"inactive_timeout":1
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/opentracing"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- error_code: 200
--- response_body
passed
=== TEST 6: hit route
--- request
GET /opentracing
--- error_code: 200
--- wait: 5
=== TEST 7: get log
--- exec
echo "select * from default.test" | curl 'http://localhost:8123/' --data-binary @-
echo "select * from default.test" | curl 'http://localhost:8124/' --data-binary @-
--- response_body_like
.*127.0.0.1.*1.*
=== TEST 8: to show that different endpoints will be chosen randomly
--- config
location /t {
content_by_lua_block {
local code_count = {}
local t = require("lib.test_admin").test
for i = 1, 12 do
local code, body = t('/opentracing', ngx.HTTP_GET)
if code ~= 200 then
ngx.say("code: ", code, " body: ", body)
end
code_count[code] = (code_count[code] or 0) + 1
end
local code_arr = {}
for code, count in pairs(code_count) do
table.insert(code_arr, {code = code, count = count})
end
ngx.say(require("toolkit.json").encode(code_arr))
ngx.exit(200)
}
}
--- response_body
[{"code":200,"count":12}]
--- error_log
sending a batch logs to http://127.0.0.1:8123
sending a batch logs to http://127.0.0.1:8124
=== TEST 9: use single clickhouse server
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"clickhouse-logger": {
"user": "default",
"password": "",
"database": "default",
"logtable": "test",
"endpoint_addr": "http://127.0.0.1:8123"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/opentracing"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 10: hit route
--- request
GET /opentracing
--- error_code: 200
--- wait: 5
=== TEST 11: get log
--- exec
echo "select * from default.test" | curl 'http://localhost:8123/' --data-binary @-
--- response_body_like
.*127.0.0.1.*1.*

View File

@@ -0,0 +1,244 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
log_level("info");
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests();
__DATA__
=== TEST 1: collect response body option
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"clickhouse-logger": {
"user": "default",
"password": "a",
"database": "default",
"logtable": "t",
"endpoint_addr": "http://127.0.0.1:1980/clickhouse_logger_server",
"inactive_timeout":1,
"include_resp_body": true
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: collect response log
--- request
GET /hello
--- error_log eval
qr/clickhouse body: .*\{.*"body":"hello world\\n"/
--- wait: 3
=== TEST 3: collect response body with eval option
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"clickhouse-logger": {
"user": "default",
"password": "a",
"database": "default",
"logtable": "t",
"endpoint_addr": "http://127.0.0.1:1980/clickhouse_logger_server",
"inactive_timeout":1,
"include_resp_body": true,
"include_resp_body_expr": [
[
"arg_foo",
"==",
"bar"
]
]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 4: skip collect response log for condition
--- request
GET /hello?foo=unknown
--- no_error_log eval
qr/clickhouse body: .*\{.*response":\{.*"body":"hello world\\n"/
--- wait: 3
=== TEST 5: collect request body log
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"clickhouse-logger": {
"user": "default",
"password": "a",
"database": "default",
"logtable": "t",
"endpoint_addr": "http://127.0.0.1:1980/clickhouse_logger_server",
"inactive_timeout":1,
"include_req_body": true
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 6: collect request body with eval option
--- request
POST /hello?foo=bar
{"sample":"hello"}
--- error_log eval
qr/clickhouse body: .*\{.*request":\{.*"body":"\{\\"sample\\":\\"hello\\"/
--- wait: 3
=== TEST 7: collect request body
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"clickhouse-logger": {
"user": "default",
"password": "a",
"database": "default",
"logtable": "t",
"endpoint_addr": "http://127.0.0.1:1980/clickhouse_logger_server",
"inactive_timeout":1,
"include_req_body": true,
"include_req_body_expr": [
[
"arg_foo",
"==",
"bar"
]
]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 8: skip collect request body log for condition
--- request
POST /hello?foo=unknown
{"sample":"hello"}
--- no_error_log eval
qr/clickhouse body: .*\{.*request":\{.*"body":"\{\\"sample\\":\\"hello\\"/
--- wait: 3

View File

@@ -0,0 +1,187 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX;
my $nginx_binary = $ENV{'TEST_NGINX_BINARY'} || 'nginx';
my $version = eval { `$nginx_binary -V 2>&1` };
if ($version !~ m/\/apisix-nginx-module/) {
plan(skip_all => "apisix-nginx-module not installed");
} else {
plan('no_plan');
}
repeat_each(1);
log_level('info');
no_root_location();
no_shuffle();
add_block_preprocessor(sub {
my ($block) = @_;
});
run_tests();
__DATA__
=== TEST 1: sanity
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local json = require("toolkit.json")
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"client-control": {
"max_body_size": 5
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 2: hit, failed
--- request
POST /hello
123456
--- error_code: 413
=== TEST 3: hit, failed with chunked
--- more_headers
Transfer-Encoding: chunked
--- request eval
qq{POST /hello
6\r
Hello \r
0\r
\r
}
--- error_code: 413
--- error_log
client intended to send too large chunked body
=== TEST 4: hit
--- request
POST /hello
12345
=== TEST 5: bad body size
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local json = require("toolkit.json")
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"client-control": {
"max_body_size": -1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.print(body)
}
}
--- request
GET /t
--- error_code: 400
--- response_body
{"error_msg":"failed to check the configuration of plugin client-control err: property \"max_body_size\" validation failed: expected -1 to be at least 0"}
=== TEST 6: 0 means no limit
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local json = require("toolkit.json")
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"client-control": {
"max_body_size": 0
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 7: hit
--- request
POST /hello
1

View File

@@ -0,0 +1,137 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_shuffle();
no_root_location();
run_tests;
__DATA__
=== TEST 1: add consumer jack1
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/consumers',
ngx.HTTP_PUT,
[[{
"username": "jack1",
"plugins": {
"key-auth": {
"key": "auth-one"
},
"echo":{"body": "before change"}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 2: add route
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"key-auth": {}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 3: verify 20 times
--- pipelined_requests eval
["GET /hello", "GET /hello", "GET /hello", "GET /hello","GET /hello", "GET /hello", "GET /hello", "GET /hello","GET /hello", "GET /hello", "GET /hello", "GET /hello","GET /hello", "GET /hello", "GET /hello", "GET /hello","GET /hello", "GET /hello", "GET /hello", "GET /hello"]
--- more_headers
apikey: auth-one
--- response_body eval
["before change","before change","before change","before change","before change","before change","before change","before change","before change","before change","before change","before change","before change","before change","before change","before change","before change","before change","before change","before change"]
=== TEST 4: modify consumer
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/consumers',
ngx.HTTP_PUT,
[[{
"username": "jack1",
"plugins": {
"key-auth": {
"key": "auth-one"
},
"echo":{"body": "after change"}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 5: verify 20 times
--- pipelined_requests eval
["GET /hello", "GET /hello", "GET /hello", "GET /hello","GET /hello", "GET /hello", "GET /hello", "GET /hello","GET /hello", "GET /hello", "GET /hello", "GET /hello","GET /hello", "GET /hello", "GET /hello", "GET /hello","GET /hello", "GET /hello", "GET /hello", "GET /hello"]
--- more_headers
apikey: auth-one
--- response_body eval
["after change","after change","after change","after change","after change","after change","after change","after change","after change","after change","after change","after change","after change","after change","after change","after change","after change","after change","after change","after change"]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,414 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_shuffle();
no_root_location();
run_tests;
__DATA__
=== TEST 1: create consumer group(group1)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/consumer_groups/group1',
ngx.HTTP_PUT,
[[{
"plugins": {}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 2: create consumer group(group2)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/consumer_groups/group2',
ngx.HTTP_PUT,
[[{
"plugins": {}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 3: consumer jack1 with consumer group(group1)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/consumers',
ngx.HTTP_PUT,
[[{
"username": "jack1",
"plugins": {
"basic-auth": {
"username": "jack2019",
"password": "123456"
}
},
"group_id": "group1"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 4: consumer jack2 with consumer group(group2)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/consumers',
ngx.HTTP_PUT,
[[{
"username": "jack2",
"plugins": {
"basic-auth": {
"username": "jack2020",
"password": "123456"
}
},
"group_id": "group2"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 5: consumer jack3 with no consumer group
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/consumers',
ngx.HTTP_PUT,
[[{
"username": "jack3",
"plugins": {
"basic-auth": {
"username": "jack2021",
"password": "123456"
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 6: set whitelist
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"basic-auth": {},
"consumer-restriction": {
"type": "consumer_group_id",
"whitelist": [
"group1"
]
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 7: verify unauthorized
--- request
GET /hello
--- error_code: 401
--- response_body
{"message":"Missing authorization in request"}
=== TEST 8: verify jack1
--- request
GET /hello
--- more_headers
Authorization: Basic amFjazIwMTk6MTIzNDU2
--- response_body
hello world
=== TEST 9: verify jack2
--- request
GET /hello
--- more_headers
Authorization: Basic amFjazIwMjA6MTIzNDU2
--- error_code: 403
--- response_body
{"message":"The consumer_group_id is forbidden."}
=== TEST 10: set blacklist
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"basic-auth": {},
"consumer-restriction": {
"type": "consumer_group_id",
"blacklist": [
"group1"
],
"rejected_msg": "request is forbidden"
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 11: verify unauthorized
--- request
GET /hello
--- error_code: 401
--- response_body
{"message":"Missing authorization in request"}
=== TEST 12: verify jack1
--- request
GET /hello
--- more_headers
Authorization: Basic amFjazIwMTk6MTIzNDU2
--- error_code: 403
--- response_body
{"message":"request is forbidden"}
=== TEST 13: verify jack2
--- request
GET /hello
--- more_headers
Authorization: Basic amFjazIwMjA6MTIzNDU2
--- response_body
hello world
=== TEST 14: verify jack2
--- request
GET /hello
--- more_headers
Authorization: Basic amFjazIwMjE6MTIzNDU2
--- error_code: 401
--- response_body
{"message":"The request is rejected, please check the consumer_group_id for this request"}
=== TEST 15: set blacklist with service_id
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"consumer-restriction": {
"type": "service_id",
"blacklist": [
"1"
],
"rejected_msg": "request is forbidden"
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 16: hit
--- request
GET /hello
--- error_code: 401
--- response_body
{"message":"The request is rejected, please check the service_id for this request"}
=== TEST 17: set whitelist with service_id
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"consumer-restriction": {
"type": "service_id",
"whitelist": [
"1"
],
"rejected_msg": "request is forbidden"
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 18: hit
--- request
GET /hello
--- error_code: 401
--- response_body
{"message":"The request is rejected, please check the service_id for this request"}

View File

@@ -0,0 +1,929 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_shuffle();
no_root_location();
run_tests;
__DATA__
=== TEST 1: sanity
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.cors")
local ok, err = plugin.check_schema({
allow_origins = 'http://test.com',
allow_methods = '',
allow_headers = '',
expose_headers = '',
max_age = 600,
allow_credential = true
})
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
done
=== TEST 2: wrong value of key
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.cors")
local ok, err = plugin.check_schema({
allow_origins = 'http://test.com',
allow_methods = '',
allow_headers = '',
expose_headers = '',
max_age = '600',
allow_credential = true
})
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
property "max_age" validation failed: wrong type: expected integer, got string
done
=== TEST 3: add plugin
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 4: update plugin
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"allow_origins": "**",
"allow_methods": "**",
"allow_headers": "request-h",
"expose_headers": "expose-h",
"madx_age": 5,
"allow_credential": true
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 5: disable plugin
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 6: set route(default)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"plugins": {
"cors": {
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 7: cors default
--- request
GET /hello HTTP/1.1
--- response_body
hello world
--- response_headers
Access-Control-Allow-Origin: *
Vary:
Access-Control-Allow-Methods: *
Access-Control-Allow-Headers: *
Access-Control-Expose-Headers:
Access-Control-Max-Age: 5
Access-Control-Allow-Credentials:
=== TEST 8: set route (cors specified)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"allow_origins": "http://sub.domain.com,http://sub2.domain.com",
"allow_methods": "GET,POST",
"allow_headers": "headr1,headr2",
"expose_headers": "ex-headr1,ex-headr2",
"max_age": 50,
"allow_credential": true
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 9: cors specified
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://sub2.domain.com
resp-vary: Via
--- response_body
hello world
--- response_headers
Access-Control-Allow-Origin: http://sub2.domain.com
Vary: Via, Origin
Access-Control-Allow-Methods: GET,POST
Access-Control-Allow-Headers: headr1,headr2
Access-Control-Expose-Headers: ex-headr1,ex-headr2
Access-Control-Max-Age: 50
Access-Control-Allow-Credentials: true
=== TEST 10: cors specified no match origin
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://sub3.domain.com
--- response_body
hello world
--- response_headers
Access-Control-Allow-Origin:
Access-Control-Allow-Methods:
Access-Control-Allow-Headers:
Access-Control-Expose-Headers:
Access-Control-Max-Age:
Access-Control-Allow-Credentials:
=== TEST 11: set route(force wildcard)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"allow_origins": "**",
"allow_methods": "**",
"allow_headers": "**",
"expose_headers": "*"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 12: cors force wildcard
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: https://sub.domain.com
ExternalHeader1: val
ExternalHeader2: val
ExternalHeader3: val
Access-Control-Request-Headers: req-header1,req-header2
--- response_body
hello world
--- response_headers
Access-Control-Allow-Origin: https://sub.domain.com
Vary: Origin
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS,CONNECT,TRACE
Access-Control-Allow-Headers: req-header1,req-header2
Access-Control-Expose-Headers: *
Access-Control-Max-Age: 5
Access-Control-Allow-Credentials:
=== TEST 13: cors force wildcard no origin
--- request
GET /hello HTTP/1.1
--- more_headers
ExternalHeader1: val
ExternalHeader2: val
ExternalHeader3: val
--- response_body
hello world
--- response_headers
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS,CONNECT,TRACE
Access-Control-Allow-Headers:
Access-Control-Expose-Headers: *
Access-Control-Max-Age: 5
Access-Control-Allow-Credentials:
=== TEST 14: options return directly
--- request
OPTIONS /hello HTTP/1.1
--- response_body
=== TEST 15: set route(auth plugins fails)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"key-auth": {},
"cors": {
"allow_origins": "**",
"allow_methods": "**",
"allow_headers": "*",
"expose_headers": "*"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 16: auth failed still work
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: https://sub.domain.com
ExternalHeader1: val
ExternalHeader2: val
ExternalHeader3: val
--- response_body
{"message":"Missing API key in request"}
--- error_code: 401
--- response_headers
Access-Control-Allow-Origin: https://sub.domain.com
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS,CONNECT,TRACE
Access-Control-Allow-Headers: *
Access-Control-Expose-Headers: *
Access-Control-Max-Age: 5
Access-Control-Allow-Credentials:
=== TEST 17: set route(overwrite upstream)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"allow_origins": "**",
"allow_methods": "**",
"allow_headers": "request-h",
"expose_headers": "expose-h",
"allow_credential": true
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/headers"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 18: overwrite upstream
--- request
GET /headers?Access-Control-Allow-Origin=https://sub.domain.com HTTP/1.1
--- more_headers
Origin: https://sub.domain.com
--- response_body
/headers
--- response_headers
Access-Control-Allow-Origin: https://sub.domain.com
=== TEST 19: overwrite upstream(Access-Control-Allow-Methods)
--- request
GET /headers?Access-Control-Allow-Methods=methods HTTP/1.1
--- more_headers
Origin: https://sub.domain.com
--- response_body
/headers
--- response_headers
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS,CONNECT,TRACE
=== TEST 20: overwrite upstream(Access-Control-Allow-Headers)
--- request
GET /headers?Access-Control-Allow-Headers=a-headers HTTP/1.1
--- more_headers
Origin: https://sub.domain.com
--- response_body
/headers
--- response_headers
Access-Control-Allow-Headers: request-h
=== TEST 21: overwrite upstream(Access-Control-Expose-Headers)
--- request
GET /headers?Access-Control-Expose-Headers=e-headers HTTP/1.1
--- more_headers
Origin: https://sub.domain.com
--- response_body
/headers
--- response_headers
Access-Control-Expose-Headers: expose-h
=== TEST 22: overwrite upstream(Access-Control-Max-Age)
--- request
GET /headers?Access-Control-Max-Age=10 HTTP/1.1
--- more_headers
Origin: https://sub.domain.com
--- response_body
/headers
--- response_headers
Access-Control-Max-Age: 5
=== TEST 23: not overwrite upstream(Access-Control-Allow-Credentials)
--- request
GET /headers?Access-Control-Allow-Credentials=false HTTP/1.1
--- more_headers
Origin: https://sub.domain.com
--- response_body
/headers
--- response_headers
Access-Control-Allow-Credentials: true
=== TEST 24: should not set * to allow_origins when allow_credential is true
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"allow_origins": "*",
"allow_credential": true
}
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- error_code: 400
--- response_body eval
qr/failed to check the configuration of plugin cors err: you can not/
=== TEST 25: should not set * to allow_methods when allow_credential is true
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"allow_methods": "*",
"allow_credential": true
}
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- error_code: 400
--- response_body eval
qr/failed to check the configuration of plugin cors err: you can not/
=== TEST 26: should not set * to allow_headers when allow_credential is true
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"allow_headers": "*",
"allow_credential": true
}
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- error_code: 400
--- response_body eval
qr/failed to check the configuration of plugin cors err: you can not/
=== TEST 27: should not set * to expose_headers when allow_credential is true
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"expose_headers": "*",
"allow_credential": true
}
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- error_code: 400
--- response_body eval
qr/failed to check the configuration of plugin cors err: you can not/
--- no_error_log
=== TEST 28: set route (regex specified)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"allow_origins": "http://sub.domain.com,http://sub2.domain.com",
"allow_methods": "GET,POST",
"allow_headers": "headr1,headr2",
"expose_headers": "ex-headr1,ex-headr2",
"max_age": 50,
"allow_credential": true,
"allow_origins_by_regex":[".*\\.test.com$"]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 29: regex specified
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://a.test.com
resp-vary: Via
--- response_body
hello world
--- response_headers
Access-Control-Allow-Origin: http://a.test.com
Vary: Via, Origin
Access-Control-Allow-Methods: GET,POST
Access-Control-Allow-Headers: headr1,headr2
Access-Control-Expose-Headers: ex-headr1,ex-headr2
Access-Control-Max-Age: 50
Access-Control-Allow-Credentials: true
=== TEST 30: regex specified not match
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://a.test2.com
resp-vary: Via
--- response_body
hello world
--- response_headers
Access-Control-Allow-Origin:
Access-Control-Allow-Methods:
Access-Control-Allow-Headers:
Access-Control-Expose-Headers:
Access-Control-Max-Age:
Access-Control-Allow-Credentials:
=== TEST 31: set route (multiple regex specified )
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"allow_origins": "http://sub.domain.com,http://sub2.domain.com",
"allow_methods": "GET,POST",
"allow_headers": "headr1,headr2",
"expose_headers": "ex-headr1,ex-headr2",
"max_age": 50,
"allow_credential": true,
"allow_origins_by_regex":[".*\\.test.com$",".*\\.example.org$"]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 32: multiple regex specified match
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://foo.example.org
resp-vary: Via
--- response_body
hello world
--- response_headers
Access-Control-Allow-Origin: http://foo.example.org
Vary: Via, Origin
Access-Control-Allow-Methods: GET,POST
Access-Control-Allow-Headers: headr1,headr2
Access-Control-Expose-Headers: ex-headr1,ex-headr2
Access-Control-Max-Age: 50
Access-Control-Allow-Credentials: true
=== TEST 33: multiple regex specified not match
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://foo.example.com
resp-vary: Via
--- response_body
hello world
--- response_headers
Access-Control-Allow-Origin:
Access-Control-Allow-Methods:
Access-Control-Allow-Headers:
Access-Control-Expose-Headers:
Access-Control-Max-Age:
Access-Control-Allow-Credentials:
=== TEST 34: origin was modified by the proxy_rewrite plugin
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"allow_origins": "http://sub.domain.com",
"allow_methods": "GET,POST",
"allow_headers": "headr1,headr2",
"expose_headers": "ex-headr1,ex-headr2",
"max_age": 50,
"allow_credential": true
},
"proxy-rewrite": {
"headers": {
"Origin": "http://example.com"
}
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 35: origin is not affected by proxy_rewrite plugins
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://sub.domain.com
resp-vary: Via
--- response_body
hello world
--- response_headers
Access-Control-Allow-Origin: http://sub.domain.com
Vary: Via, Origin
Access-Control-Allow-Methods: GET,POST
Access-Control-Allow-Headers: headr1,headr2
Access-Control-Expose-Headers: ex-headr1,ex-headr2
Access-Control-Max-Age: 50
Access-Control-Allow-Credentials: true

View File

@@ -0,0 +1,176 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_root_location();
no_shuffle();
log_level("info");
add_block_preprocessor(sub {
my ($block) = @_;
if (!$block->request) {
$block->set_value("request", "GET /t");
}
if (!$block->no_error_log && !$block->error_log) {
$block->set_value("no_error_log", "[error]\n[alert]");
}
});
run_tests;
__DATA__
=== TEST 1: validate allow_origins
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.cors")
local function validate(val)
local conf = {}
conf.allow_origins = val
return plugin.check_schema(conf)
end
local good = {
"*",
"**",
"null",
"http://y.com.uk",
"https://x.com",
"https://x.com,http://y.com.uk",
"https://x.com,http://y.com.uk,http://c.tv",
"https://x.com,http://y.com.uk:12000,http://c.tv",
}
for _, g in ipairs(good) do
local ok, err = validate(g)
if not ok then
ngx.say("failed to validate ", g, ", ", err)
end
end
local bad = {
"",
"*a",
"*,http://y.com",
"nulll",
"http//y.com.uk",
"x.com",
"https://x.com,y.com.uk",
"https://x.com,*,https://y.com.uk",
"https://x.com,http://y.com.uk,http:c.tv",
}
for _, b in ipairs(bad) do
local ok, err = validate(b)
if ok then
ngx.say("failed to reject ", b)
end
end
ngx.say("done")
}
}
--- response_body
done
=== TEST 2: set route ( regex specified and allow_origins is default value )
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"allow_origins": "*",
"allow_methods": "GET,POST",
"allow_headers": "request-h",
"expose_headers": "expose-h",
"max_age": 10,
"allow_origins_by_regex":[".*\\.domain.com$"]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 3: regex specified not match
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://sub.test.com
resp-vary: Via
--- response_headers
Access-Control-Allow-Origin:
Access-Control-Allow-Methods:
Access-Control-Allow-Headers:
Access-Control-Expose-Headers:
Access-Control-Max-Age:
Access-Control-Allow-Credentials:
=== TEST 4: regex no origin specified
--- request
GET /hello HTTP/1.1
--- response_headers
Access-Control-Allow-Origin:
Access-Control-Allow-Methods:
Access-Control-Allow-Headers:
Access-Control-Expose-Headers:
Access-Control-Max-Age:
Access-Control-Allow-Credentials:
=== TEST 5: regex specified match
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://sub.domain.com
resp-vary: Via
--- response_headers
Access-Control-Allow-Origin: http://sub.domain.com
Vary: Via
Access-Control-Allow-Methods: GET,POST
Access-Control-Allow-Headers: request-h
Access-Control-Expose-Headers: expose-h
Access-Control-Max-Age: 10

View File

@@ -0,0 +1,422 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_root_location();
no_shuffle();
log_level("info");
add_block_preprocessor(sub {
my ($block) = @_;
if (!$block->request) {
$block->set_value("request", "GET /t");
}
if (!$block->no_error_log && !$block->error_log) {
$block->set_value("no_error_log", "[error]\n[alert]");
}
});
run_tests;
__DATA__
=== TEST 1: validate metadata allow_origins
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.cors")
local schema_type = require("apisix.core").schema.TYPE_METADATA
local function validate(val)
local conf = {}
conf.allow_origins = val
return plugin.check_schema(conf, schema_type)
end
local good = {
key_1 = "*",
key_2 = "**",
key_3 = "null",
key_4 = "http://y.com.uk",
key_5 = "https://x.com",
key_6 = "https://x.com,http://y.com.uk",
key_7 = "https://x.com,http://y.com.uk,http://c.tv",
key_8 = "https://x.com,http://y.com.uk:12000,http://c.tv",
}
local ok, err = validate(good)
if not ok then
ngx.say("failed to validate ", g, ", ", err)
end
local bad = {
"",
"*a",
"*,http://y.com",
"nulll",
"http//y.com.uk",
"x.com",
"https://x.com,y.com.uk",
"https://x.com,*,https://y.com.uk",
"https://x.com,http://y.com.uk,http:c.tv",
}
for _, b in ipairs(bad) do
local ok, err = validate({key = b})
if ok then
ngx.say("failed to reject ", b)
end
end
ngx.say("done")
}
}
--- response_body
done
=== TEST 2: set plugin metadata
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/cors',
ngx.HTTP_PUT,
[[{
"allow_origins": {
"key_1": "https://domain.com",
"key_2": "https://sub.domain.com,https://sub2.domain.com",
"key_3": "*"
},
"inactive_timeout": 1
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 3: set route (allow_origins_by_metadata specified)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"allow_origins": "https://test.com",
"allow_origins_by_metadata": ["key_1"]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 4: origin not match
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://foo.example.org
--- response_body
hello world
--- response_headers
Access-Control-Allow-Origin:
Vary: Origin
Access-Control-Allow-Methods:
Access-Control-Allow-Headers:
Access-Control-Expose-Headers:
Access-Control-Max-Age:
Access-Control-Allow-Credentials:
=== TEST 5: origin matches with allow_origins
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: https://test.com
resp-vary: Via
--- response_body
hello world
--- response_headers
Access-Control-Allow-Origin: https://test.com
Vary: Via, Origin
Access-Control-Allow-Methods: *
Access-Control-Allow-Headers: *
Access-Control-Expose-Headers:
Access-Control-Max-Age: 5
Access-Control-Allow-Credentials:
=== TEST 6: origin matches with allow_origins_by_metadata
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: https://domain.com
resp-vary: Via
--- response_body
hello world
--- response_headers
Access-Control-Allow-Origin: https://domain.com
Vary: Via, Origin
Access-Control-Allow-Methods: *
Access-Control-Allow-Headers: *
Access-Control-Expose-Headers:
Access-Control-Max-Age: 5
Access-Control-Allow-Credentials:
=== TEST 7: set route (multiple allow_origins_by_metadata specified)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"allow_origins": "https://test.com",
"allow_origins_by_metadata": ["key_1", "key_2"]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 8: origin not match
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://foo.example.org
--- response_body
hello world
--- response_headers
Access-Control-Allow-Origin:
Vary: Origin
Access-Control-Allow-Methods:
Access-Control-Allow-Headers:
Access-Control-Expose-Headers:
Access-Control-Max-Age:
Access-Control-Allow-Credentials:
=== TEST 9: origin matches with first allow_origins_by_metadata
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: https://domain.com
resp-vary: Via
--- response_body
hello world
--- response_headers
Access-Control-Allow-Origin: https://domain.com
Vary: Via, Origin
Access-Control-Allow-Methods: *
Access-Control-Allow-Headers: *
Access-Control-Expose-Headers:
Access-Control-Max-Age: 5
Access-Control-Allow-Credentials:
=== TEST 10: origin matches with second allow_origins_by_metadata
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: https://sub.domain.com
resp-vary: Via
--- response_body
hello world
--- response_headers
Access-Control-Allow-Origin: https://sub.domain.com
Vary: Via, Origin
Access-Control-Allow-Methods: *
Access-Control-Allow-Headers: *
Access-Control-Expose-Headers:
Access-Control-Max-Age: 5
Access-Control-Allow-Credentials:
=== TEST 11: set route (wildcard in allow_origins_by_metadata)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"allow_origins": "https://test.com",
"allow_origins_by_metadata": ["key_3"]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 12: origin matches by wildcard
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://foo.example.org
--- response_body
hello world
--- response_headers
Access-Control-Allow-Origin: http://foo.example.org
Vary: Origin
Access-Control-Allow-Methods: *
Access-Control-Allow-Headers: *
Access-Control-Expose-Headers:
Access-Control-Max-Age: 5
Access-Control-Allow-Credentials:
=== TEST 13: set route (allow_origins_by_metadata specified and allow_origins * is invalid while set allow_origins_by_metadata)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"allow_origins": "*",
"allow_origins_by_metadata": ["key_1"]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 14: origin not match because allow_origins * invalid
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://foo.example.org
--- response_body
hello world
--- response_headers
Access-Control-Allow-Origin:
Access-Control-Allow-Methods:
Access-Control-Allow-Headers:
Access-Control-Expose-Headers:
Access-Control-Max-Age:
Access-Control-Allow-Credentials:
=== TEST 15: origin matches with first allow_origins_by_metadata
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: https://domain.com
--- response_body
hello world
--- response_headers
Access-Control-Allow-Origin: https://domain.com
Access-Control-Allow-Methods: *
Access-Control-Allow-Headers: *
Access-Control-Expose-Headers:
Access-Control-Max-Age: 5
Access-Control-Allow-Credentials:

View File

@@ -0,0 +1,751 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_root_location();
no_shuffle();
log_level("info");
add_block_preprocessor(sub {
my ($block) = @_;
if (!$block->request) {
$block->set_value("request", "GET /t");
}
if (!$block->no_error_log && !$block->error_log) {
$block->set_value("no_error_log", "[error]\n[alert]");
}
});
run_tests;
__DATA__
=== TEST 1: validate timing_allow_origins
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.cors")
local function validate(val)
local conf = {}
conf.timing_allow_origins = val
return plugin.check_schema(conf)
end
local good = {
"*",
"**",
"null",
"http://y.com.uk",
"https://x.com",
"https://x.com,http://y.com.uk",
"https://x.com,http://y.com.uk,http://c.tv",
"https://x.com,http://y.com.uk:12000,http://c.tv",
}
for _, g in ipairs(good) do
local ok, err = validate(g)
if not ok then
ngx.say("failed to validate ", g, ", ", err)
end
end
local bad = {
"",
"*a",
"*,http://y.com",
"nulll",
"http//y.com.uk",
"x.com",
"https://x.com,y.com.uk",
"https://x.com,*,https://y.com.uk",
"https://x.com,http://y.com.uk,http:c.tv",
}
for _, b in ipairs(bad) do
local ok, err = validate(b)
if ok then
ngx.say("failed to reject ", b)
end
end
ngx.say("done")
}
}
--- response_body
done
=== TEST 2: set route ( allow_origins default, timing_allow_origins specified )
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"allow_origins": "*",
"allow_methods": "GET,POST",
"allow_headers": "request-h",
"expose_headers": "expose-h",
"max_age": 10,
"timing_allow_origins": "http://sub.domain.com"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 3: origin matching
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://sub.domain.com
--- response_headers
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,POST
Access-Control-Allow-Headers: request-h
Access-Control-Expose-Headers: expose-h
Access-Control-Max-Age: 10
Access-Control-Allow-Credentials:
Timing-Allow-Origin: http://sub.domain.com
=== TEST 4: origin not matching timing_allow_origins
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://other.domain.com
--- response_headers
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,POST
Access-Control-Allow-Headers: request-h
Access-Control-Expose-Headers: expose-h
Access-Control-Max-Age: 10
Access-Control-Allow-Credentials:
Timing-Allow-Origin:
=== TEST 5: set route ( allow_origins same as timing_allow_origins )
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"allow_origins": "http://sub.domain.com",
"allow_methods": "GET,POST",
"allow_headers": "request-h",
"expose_headers": "expose-h",
"max_age": 10,
"timing_allow_origins": "http://sub.domain.com"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 6: origin matching
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://sub.domain.com
--- response_headers
Access-Control-Allow-Origin: http://sub.domain.com
Access-Control-Allow-Methods: GET,POST
Access-Control-Allow-Headers: request-h
Access-Control-Expose-Headers: expose-h
Access-Control-Max-Age: 10
Access-Control-Allow-Credentials:
Timing-Allow-Origin: http://sub.domain.com
=== TEST 7: origin not matching
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://other.domain.com
--- response_headers
Access-Control-Allow-Origin:
Access-Control-Allow-Methods:
Access-Control-Allow-Headers:
Access-Control-Expose-Headers:
Access-Control-Max-Age:
Access-Control-Allow-Credentials:
Timing-Allow-Origin:
=== TEST 8: set route ( allow_origins differs from timing_allow_origins )
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"allow_origins": "http://one.domain.com",
"allow_methods": "GET,POST",
"allow_headers": "request-h",
"expose_headers": "expose-h",
"max_age": 10,
"timing_allow_origins": "http://another.domain.com"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 9: origin matching allow_origins
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://one.domain.com
--- response_headers
Access-Control-Allow-Origin: http://one.domain.com
Access-Control-Allow-Methods: GET,POST
Access-Control-Allow-Headers: request-h
Access-Control-Expose-Headers: expose-h
Access-Control-Max-Age: 10
Access-Control-Allow-Credentials:
Timing-Allow-Origin:
=== TEST 10: origin matching timing_allow_origins
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://another.domain.com
--- response_headers
Access-Control-Allow-Origin:
Access-Control-Allow-Methods:
Access-Control-Allow-Headers:
Access-Control-Expose-Headers:
Access-Control-Max-Age:
Access-Control-Allow-Credentials:
Timing-Allow-Origin: http://another.domain.com
=== TEST 11: origin not matching
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://notexistent.domain.com
--- response_headers
Access-Control-Allow-Origin:
Access-Control-Allow-Methods:
Access-Control-Allow-Headers:
Access-Control-Expose-Headers:
Access-Control-Max-Age:
Access-Control-Allow-Credentials:
Timing-Allow-Origin:
=== TEST 12: set route ( allow_origins superset of timing_allow_origins )
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"allow_origins": "http://one.domain.com,http://two.domain.com",
"allow_methods": "GET,POST",
"allow_headers": "request-h",
"expose_headers": "expose-h",
"max_age": 10,
"timing_allow_origins": "http://one.domain.com"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 13: origin matching allow_origins and timing_allow_origins
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://one.domain.com
--- response_headers
Access-Control-Allow-Origin: http://one.domain.com
Access-Control-Allow-Methods: GET,POST
Access-Control-Allow-Headers: request-h
Access-Control-Expose-Headers: expose-h
Access-Control-Max-Age: 10
Access-Control-Allow-Credentials:
Timing-Allow-Origin: http://one.domain.com
=== TEST 14: origin matching only allow_origins
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://two.domain.com
--- response_headers
Access-Control-Allow-Origin: http://two.domain.com
Access-Control-Allow-Methods: GET,POST
Access-Control-Allow-Headers: request-h
Access-Control-Expose-Headers: expose-h
Access-Control-Max-Age: 10
Access-Control-Allow-Credentials:
Timing-Allow-Origin:
=== TEST 15: origin not matching
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://notexistent.domain.com
--- response_headers
Access-Control-Allow-Origin:
Access-Control-Allow-Methods:
Access-Control-Allow-Headers:
Access-Control-Expose-Headers:
Access-Control-Max-Age:
Access-Control-Allow-Credentials:
Timing-Allow-Origin:
=== TEST 16: set route ( allow_origins and timing_allow_origins are two different sets with intersection )
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"allow_origins": "http://one.domain.com,http://two.domain.com",
"allow_methods": "GET,POST",
"allow_headers": "request-h",
"expose_headers": "expose-h",
"max_age": 10,
"timing_allow_origins": "http://one.domain.com,http://three.domain.com"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 17: origin matching allow_origins and timing_allow_origins
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://one.domain.com
--- response_headers
Access-Control-Allow-Origin: http://one.domain.com
Access-Control-Allow-Methods: GET,POST
Access-Control-Allow-Headers: request-h
Access-Control-Expose-Headers: expose-h
Access-Control-Max-Age: 10
Access-Control-Allow-Credentials:
Timing-Allow-Origin: http://one.domain.com
=== TEST 18: origin matching only allow_origins
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://two.domain.com
--- response_headers
Access-Control-Allow-Origin: http://two.domain.com
Access-Control-Allow-Methods: GET,POST
Access-Control-Allow-Headers: request-h
Access-Control-Expose-Headers: expose-h
Access-Control-Max-Age: 10
Access-Control-Allow-Credentials:
Timing-Allow-Origin:
=== TEST 19: origin matching only timing_allow_origins
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://three.domain.com
--- response_headers
Access-Control-Allow-Origin:
Access-Control-Allow-Methods:
Access-Control-Allow-Headers:
Access-Control-Expose-Headers:
Access-Control-Max-Age:
Access-Control-Allow-Credentials:
Timing-Allow-Origin: http://three.domain.com
=== TEST 20: origin not matching
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://notexistent.domain.com
--- response_headers
Access-Control-Allow-Origin:
Access-Control-Allow-Methods:
Access-Control-Allow-Headers:
Access-Control-Expose-Headers:
Access-Control-Max-Age:
Access-Control-Allow-Credentials:
Timing-Allow-Origin:
=== TEST 21: set route ( allow_origins and timing_allow_origins specified with regex )
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"allow_origins_by_regex": ["http://.*?\\.domain\\.com"],
"allow_methods": "GET,POST",
"allow_headers": "request-h",
"expose_headers": "expose-h",
"max_age": 10,
"timing_allow_origins_by_regex": ["http://.*?\\.domain\\.com"]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 22: regex specified match
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://sub.domain.com
--- response_headers
Access-Control-Allow-Origin: http://sub.domain.com
Access-Control-Allow-Methods: GET,POST
Access-Control-Allow-Headers: request-h
Access-Control-Expose-Headers: expose-h
Access-Control-Max-Age: 10
Timing-Allow-Origin: http://sub.domain.com
=== TEST 23: regex no match
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://other.newdomain.com
--- response_headers
Access-Control-Allow-Origin:
Access-Control-Allow-Methods:
Access-Control-Allow-Headers:
Access-Control-Expose-Headers:
Access-Control-Max-Age:
Timing-Allow-Origin:
=== TEST 24: set route ( allow_origins and timing_allow_origins specified with different regex )
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"allow_origins_by_regex": ["http://.*?\\.domain\\.com"],
"allow_methods": "GET,POST",
"allow_headers": "request-h",
"expose_headers": "expose-h",
"max_age": 10,
"timing_allow_origins_by_regex": ["http://test.*?\\.domain\\.com"],
"timing_allow_origins": "http://nonexistent.newdomain.com"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 25: regex specified match, test priority of regex over list of origins
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://testurl.domain.com
--- response_headers
Access-Control-Allow-Origin: http://testurl.domain.com
Access-Control-Allow-Methods: GET,POST
Access-Control-Allow-Headers: request-h
Access-Control-Expose-Headers: expose-h
Access-Control-Max-Age: 10
Timing-Allow-Origin: http://testurl.domain.com
=== TEST 26: set route ( expose_headers not specified )
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"allow_credential": true,
"allow_headers": "**",
"allow_methods": "**",
"allow_origins": "**",
"expose_headers": "",
"max_age": 3500
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 27: remove Access-Control-Expose-Headers match
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://sub.domain.com
--- response_headers
Access-Control-Allow-Origin: http://sub.domain.com
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS,CONNECT,TRACE
Access-Control-Expose-Headers:
Access-Control-Allow-Headers:
Access-Control-Max-Age: 3500
Access-Control-Allow-Credentials: true
=== TEST 28: set route ( expose_headers set value )
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"allow_credential": true,
"allow_headers": "**",
"allow_methods": "**",
"allow_origins": "**",
"expose_headers": "ex-headr1,ex-headr2",
"max_age": 3500
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 29: Access-Control-Expose-Headers match
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://sub.domain.com
--- response_headers
Access-Control-Allow-Origin: http://sub.domain.com
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS,CONNECT,TRACE
Access-Control-Expose-Headers: ex-headr1,ex-headr2
Access-Control-Allow-Headers:
Access-Control-Max-Age: 3500
Access-Control-Allow-Credentials: true

View File

@@ -0,0 +1,390 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
no_long_string();
no_shuffle();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests();
__DATA__
=== TEST 1: sanity
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local plugin = require("apisix.plugins.csrf")
local ok, err = plugin.check_schema({name = '_csrf', expires = 3600, key = 'testkey'})
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- response_body
done
=== TEST 2: set csrf plugin
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"csrf": {
"key": "userkey",
"expires": 1000000000
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 3: have csrf cookie
--- request
GET /hello
--- response_headers_like
Set-Cookie: apisix-csrf-token\s*=\s*[^;]+(.*)?$
=== TEST 4: block request
--- request
POST /hello
--- error_code: 401
--- response_body
{"error_msg":"no csrf token in headers"}
=== TEST 5: only header
--- request
POST /hello
--- more_headers
apisix-csrf-token: wrongtoken
--- error_code: 401
--- response_body
{"error_msg":"no csrf cookie"}
=== TEST 6: only cookie
--- request
POST /hello
--- more_headers
Cookie: apisix-csrf-token=testcookie
--- error_code: 401
--- response_body
{"error_msg":"no csrf token in headers"}
=== TEST 7: header and cookie mismatch
--- request
POST /hello
--- more_headers
apisix-csrf-token: wrongtoken
Cookie: apisix-csrf-token=testcookie
--- error_code: 401
--- response_body
{"error_msg":"csrf token mismatch"}
=== TEST 8: invalid csrf token
--- request
POST /hello
--- more_headers
apisix-csrf-token: eyJyYW5kb20iOjAuMTYwOTgzMDYwMTg0NDksInNpZ24iOiI2YTEyYmViYTI4MzAyNDg4MDRmNGU0N2VkZDY5MWFmNjg5N2IyNzQ4YTY1YWMwMDJiMGFjMzFlN2NlMDdlZTViIiwiZXhwaXJlcyI6MTc0MzExOTkxMX0=
Cookie: apisix-csrf-token=eyJyYW5kb20iOjAuMTYwOTgzMDYwMTg0NDksInNpZ24iOiI2YTEyYmViYTI4MzAyNDg4MDRmNGU0N2VkZDY5MWFmNjg5N2IyNzQ4YTY1YWMwMDJiMGFjMzFlN2NlMDdlZTViIiwiZXhwaXJlcyI6MTc0MzExOTkxMX0=
--- error_code: 401
--- error_log: Invalid signatures
--- response_body
{"error_msg":"Failed to verify the csrf token signature"}
=== TEST 9: valid csrf token
--- request
POST /hello
--- more_headers
apisix-csrf-token: eyJyYW5kb20iOjAuNDI5ODYzMTk3MTYxMzksInNpZ24iOiI0ODRlMDY4NTkxMWQ5NmJhMDc5YzQ1ZGI0OTE2NmZkYjQ0ODhjODVkNWQ0NmE1Y2FhM2UwMmFhZDliNjE5OTQ2IiwiZXhwaXJlcyI6MjY0MzExOTYyNH0=
Cookie: apisix-csrf-token=eyJyYW5kb20iOjAuNDI5ODYzMTk3MTYxMzksInNpZ24iOiI0ODRlMDY4NTkxMWQ5NmJhMDc5YzQ1ZGI0OTE2NmZkYjQ0ODhjODVkNWQ0NmE1Y2FhM2UwMmFhZDliNjE5OTQ2IiwiZXhwaXJlcyI6MjY0MzExOTYyNH0=
=== TEST 10: change expired
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"csrf": {
"key": "userkey",
"expires": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 11: expired csrf token
--- request
POST /hello
--- more_headers
apisix-csrf-token: eyJyYW5kb20iOjAuMDY3NjAxMDQwMDM5MzI4LCJzaWduIjoiOTE1Yjg2MjBhNTg1N2FjZmIzNjIxOTNhYWVlN2RkYjY5NmM0NWYwZjE5YjY5Zjg3NjM4ZTllNGNjNjYxYjQwNiIsImV4cGlyZXMiOjE2NDMxMjAxOTN9
Cookie: apisix-csrf-token=eyJyYW5kb20iOjAuMDY3NjAxMDQwMDM5MzI4LCJzaWduIjoiOTE1Yjg2MjBhNTg1N2FjZmIzNjIxOTNhYWVlN2RkYjY5NmM0NWYwZjE5YjY5Zjg3NjM4ZTllNGNjNjYxYjQwNiIsImV4cGlyZXMiOjE2NDMxMjAxOTN9
--- error_code: 401
--- error_log: token has expired
=== TEST 12: token has expired after sleep 2s
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port
.. "/hello"
local httpc = http.new()
local res, err = httpc:request_uri(uri, {method = "GET"})
if not res then
ngx.say(err)
return
end
local cookie = res.headers["Set-Cookie"]
local token = cookie:match("=([^;]+)")
ngx.sleep(2)
local res, err = httpc:request_uri(uri, {
method = "POST",
headers = {
["apisix-csrf-token"] = token,
["Cookie"] = cookie,
}
})
if not res then
ngx.say(err)
return
end
if res.status >= 300 then
ngx.status = res.status
end
}
}
--- error_code: 401
--- error_log: token has expired
=== TEST 13: set expires 0
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"csrf": {
"key": "userkey",
"expires": 0
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 14: token no expired after sleep 1s
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port
.. "/hello"
local httpc = http.new()
local res, err = httpc:request_uri(uri, {method = "GET"})
if not res then
ngx.say(err)
return
end
ngx.sleep(1)
local cookie = res.headers["Set-Cookie"]
local token = cookie:match("=([^;]+)")
local res, err = httpc:request_uri(uri, {
method = "POST",
headers = {
["apisix-csrf-token"] = token,
["Cookie"] = cookie,
}
})
if not res then
ngx.say(err)
return
end
if res.status >= 300 then
ngx.status = res.status
end
ngx.status = res.status
ngx.print(res.body)
}
}
--- response_body
hello world
=== TEST 15: data encryption for key
--- yaml_config
apisix:
data_encryption:
enable_encrypt_fields: true
keyring:
- edd1c9f0985e76a2
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"csrf": {
"key": "userkey",
"expires": 1000000000
}
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(0.1)
-- get plugin conf from admin api, password is decrypted
local code, message, res = t('/apisix/admin/routes/1',
ngx.HTTP_GET
)
res = json.decode(res)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(res.value.plugins["csrf"].key)
-- get plugin conf from etcd, password is encrypted
local etcd = require("apisix.core.etcd")
local res = assert(etcd.get('/routes/1'))
ngx.say(res.body.node.value.plugins["csrf"].key)
}
}
--- response_body
userkey
mt39FazQccyMqt4ctoRV7w==

View File

@@ -0,0 +1,633 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
add_block_preprocessor(sub {
my ($block) = @_;
if (!$block->request) {
$block->set_value("request", "GET /t");
}
if (!$block->error_log && !$block->no_error_log) {
$block->set_value("no_error_log", "[error]\n[alert]");
}
});
no_long_string();
no_root_location();
log_level("info");
run_tests;
__DATA__
=== TEST 1: custom priority and default priority on different routes
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"serverless-post-function": {
"_meta": {
"priority": 10000
},
"phase": "rewrite",
"functions" : ["return function(conf, ctx)
ngx.say(\"serverless-post-function\");
end"]
},
"serverless-pre-function": {
"_meta": {
"priority": -2000
},
"phase": "rewrite",
"functions": ["return function(conf, ctx)
ngx.say(\"serverless-pre-function\");
end"]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
end
local code, body = t('/apisix/admin/routes/2',
ngx.HTTP_PUT,
[[{
"plugins": {
"serverless-post-function": {
"phase": "rewrite",
"functions" : ["return function(conf, ctx)
ngx.say(\"serverless-post-function\");
end"]
},
"serverless-pre-function": {
"phase": "rewrite",
"functions": ["return function(conf, ctx)
ngx.say(\"serverless-pre-function\");
end"]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello1"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: verify order
--- request
GET /hello
--- response_body
serverless-post-function
serverless-pre-function
=== TEST 3: routing without custom plugin order is not affected
--- request
GET /hello1
--- response_body
serverless-pre-function
serverless-post-function
=== TEST 4: custom priority and default priority on same route
# the priority of serverless-post-function is -2000, execute serverless-post-function first
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"serverless-post-function": {
"phase": "rewrite",
"functions" : ["return function(conf, ctx)
ngx.say(\"serverless-post-function\");
end"]
},
"serverless-pre-function": {
"_meta": {
"priority": -2001
},
"phase": "rewrite",
"functions": ["return function(conf, ctx)
ngx.say(\"serverless-pre-function\");
end"]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 5: verify order
--- request
GET /hello
--- response_body
serverless-post-function
serverless-pre-function
=== TEST 6: merge plugins from consumer and route, execute the rewrite phase
# in the rewrite phase, the plugins on the route must be executed first,
# and then executed the rewrite phase of the plugins on the consumer,
# and the custom plugin order fails for this case.
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/consumers',
ngx.HTTP_PUT,
[[{
"username": "jack",
"plugins": {
"key-auth": {
"key": "auth-one"
},
"serverless-post-function": {
"_meta": {
"priority": 10000
},
"phase": "rewrite",
"functions" : ["return function(conf, ctx)
ngx.say(\"serverless-post-function\");
end"]
}
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
end
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"key-auth": {},
"serverless-pre-function": {
"_meta": {
"priority": -2000
},
"phase": "rewrite",
"functions": ["return function(conf, ctx)
ngx.say(\"serverless-pre-function\");
end"]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 7: verify order(more requests)
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port
.. "/hello"
local httpc = http.new()
local headers = {}
headers["apikey"] = "auth-one"
local res, err = httpc:request_uri(uri, {method = "GET", headers = headers})
if not res then
ngx.say(err)
return
end
ngx.print(res.body)
local res, err = httpc:request_uri(uri, {method = "GET", headers = headers})
if not res then
ngx.say(err)
return
end
ngx.print(res.body)
}
}
--- response_body
serverless-pre-function
serverless-post-function
serverless-pre-function
serverless-post-function
=== TEST 8: merge plugins form custom and route, execute the access phase
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/consumers',
ngx.HTTP_PUT,
[[{
"username": "jack",
"plugins": {
"key-auth": {
"key": "auth-one"
},
"serverless-post-function": {
"_meta": {
"priority": 10000
},
"phase": "access",
"functions" : ["return function(conf, ctx)
ngx.say(\"serverless-post-function\");
end"]
}
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
end
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"key-auth": {},
"serverless-pre-function": {
"_meta": {
"priority": -2000
},
"phase": "access",
"functions": ["return function(conf, ctx)
ngx.say(\"serverless-pre-function\");
end"]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 9: verify order
--- request
GET /hello
--- more_headers
apikey: auth-one
--- response_body
serverless-post-function
serverless-pre-function
=== TEST 10: merge plugins form service and route
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/services/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"serverless-post-function": {
"_meta": {
"priority": 10000
},
"phase": "rewrite",
"functions" : ["return function(conf, ctx)
ngx.say(\"serverless-post-function\");
end"]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
end
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"serverless-pre-function": {
"_meta": {
"priority": -2000
},
"phase": "rewrite",
"functions": ["return function(conf, ctx)
ngx.say(\"serverless-pre-function\");
end"]
}
},
"service_id": "1",
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 11: verify order
--- request
GET /hello
--- response_body
serverless-post-function
serverless-pre-function
=== TEST 12: custom plugins sort is not affected by plugins reload
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port
.. "/hello"
local httpc = http.new()
local res, err = httpc:request_uri(uri)
if not res then
ngx.say(err)
return
end
ngx.print(res.body)
local t = require("lib.test_admin").test
local code, _, org_body = t('/apisix/admin/plugins/reload',
ngx.HTTP_PUT)
ngx.say(org_body)
ngx.sleep(0.2)
local res, err = httpc:request_uri(uri)
if not res then
ngx.say(err)
return
end
ngx.print(res.body)
}
}
--- response_body
serverless-post-function
serverless-pre-function
done
serverless-post-function
serverless-pre-function
=== TEST 13: merge plugins form plugin_configs and route
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, err = t('/apisix/admin/plugin_configs/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"serverless-post-function": {
"_meta": {
"priority": 10000
},
"phase": "rewrite",
"functions" : ["return function(conf, ctx)
ngx.say(\"serverless-post-function\");
end"]
}
}
}]]
)
if code > 300 then
ngx.status = code
ngx.say(body)
end
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"serverless-pre-function": {
"_meta": {
"priority": -2000
},
"phase": "rewrite",
"functions": ["return function(conf, ctx)
ngx.say(\"serverless-pre-function\");
end"]
}
},
"plugin_config_id": 1,
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 14: verify order
--- request
GET /hello
--- response_body
serverless-post-function
serverless-pre-function
=== TEST 15: custom plugins sort on global_rule
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/global_rules/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"serverless-post-function": {
"_meta": {
"priority": 10000
},
"phase": "rewrite",
"functions" : ["return function(conf, ctx)
ngx.say(\"serverless-post-function on global rule\");
end"]
},
"serverless-pre-function": {
"_meta": {
"priority": -2000
},
"phase": "rewrite",
"functions": ["return function(conf, ctx)
ngx.say(\"serverless-pre-function on global rule\");
end"]
}
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 16: verify order
--- request
GET /hello
--- response_body
serverless-post-function on global rule
serverless-pre-function on global rule
serverless-post-function
serverless-pre-function
=== TEST 17: delete global rule
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/global_rules/1',
ngx.HTTP_DELETE
)
if code >= 300 then
ngx.status = code
ngx.say(body)
end
ngx.say(body)
}
}
--- response_body
passed

View File

@@ -0,0 +1,537 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_root_location();
no_shuffle();
add_block_preprocessor(sub {
my ($block) = @_;
$block->set_value("stream_conf_enable", 1);
if (!defined $block->extra_stream_config) {
my $stream_config = <<_EOC_;
server {
listen 8125 udp;
content_by_lua_block {
require("lib.mock_layer4").dogstatsd()
}
}
_EOC_
$block->set_value("extra_stream_config", $stream_config);
}
if (!$block->request) {
$block->set_value("request", "GET /t");
}
if (!$block->no_error_log && !$block->error_log) {
$block->set_value("no_error_log", "[error]\n[alert]");
}
});
run_tests;
__DATA__
=== TEST 1: sanity check metadata
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local plugin = require("apisix.plugins.datadog")
local ok, err = plugin.check_schema({host = "127.0.0.1", port = 8125}, core.schema.TYPE_METADATA)
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- response_body
done
=== TEST 2: add plugin
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
-- setting the metadata
local code, meta_body = t('/apisix/admin/plugin_metadata/datadog',
ngx.HTTP_PUT,
[[{
"host":"127.0.0.1",
"port": 8125
}]]
)
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"datadog": {
"batch_max_size" : 1,
"max_retry_count": 0
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"name": "datadog",
"uri": "/opentracing"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
ngx.say(meta_body)
ngx.say(body)
}
}
--- response_body
passed
passed
=== TEST 3: testing behaviour with mock suite
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, _, body = t("/opentracing", "GET")
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
ngx.print(body)
}
}
--- wait: 0.5
--- response_body
opentracing
--- grep_error_log eval
qr/message received: apisix(.+?(?=, ))/
--- grep_error_log_out eval
qr/message received: apisix\.request\.counter:1\|c\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.request\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.upstream\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.apisix\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.ingress\.size:[\d]+\|ms\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.egress\.size:[\d]+\|ms\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
/
=== TEST 4: testing behaviour with multiple requests
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, _, body = t("/opentracing", "GET")
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
ngx.print(body)
-- request 2
local code, _, body = t("/opentracing", "GET")
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
ngx.print(body)
}
}
--- wait: 0.5
--- response_body
opentracing
opentracing
--- grep_error_log eval
qr/message received: apisix(.+?(?=, ))/
--- grep_error_log_out eval
qr/message received: apisix\.request\.counter:1\|c\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.request\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.upstream\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.apisix\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.ingress\.size:[\d]+\|ms\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.egress\.size:[\d]+\|ms\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.request\.counter:1\|c\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.request\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.upstream\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.apisix\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.ingress\.size:[\d]+\|ms\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.egress\.size:[\d]+\|ms\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
/
=== TEST 5: testing behaviour with different namespace
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
-- Change the metadata
local code, meta_body = t('/apisix/admin/plugin_metadata/datadog',
ngx.HTTP_PUT,
[[{
"host":"127.0.0.1",
"port": 8125,
"namespace": "mycompany"
}]])
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
ngx.say(meta_body)
local code, _, body = t("/opentracing", "GET")
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
ngx.print(body)
}
}
--- wait: 0.5
--- response_body
passed
opentracing
--- grep_error_log eval
qr/message received: mycompany(.+?(?=, ))/
--- grep_error_log_out eval
qr/message received: mycompany\.request\.counter:1\|c\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: mycompany\.request\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: mycompany\.upstream\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: mycompany\.apisix\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: mycompany\.ingress\.size:[\d]+\|ms\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: mycompany\.egress\.size:[\d]+\|ms\|#source:apisix,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
/
=== TEST 6: testing behaviour with different constant tags
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
-- Change the metadata
local code, meta_body = t('/apisix/admin/plugin_metadata/datadog',
ngx.HTTP_PUT,
[[{
"host":"127.0.0.1",
"port": 8125,
"constant_tags": [
"source:apisix",
"new_tag:must"
]
}]])
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
ngx.say(meta_body)
local code, _, body = t("/opentracing", "GET")
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
ngx.print(body)
}
}
--- wait: 0.5
--- response_body
passed
opentracing
--- grep_error_log eval
qr/message received: apisix(.+?(?=, ))/
--- grep_error_log_out eval
qr/message received: apisix\.request\.counter:1\|c\|#source:apisix,new_tag:must,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.request\.latency:[\d.]+\|h\|#source:apisix,new_tag:must,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.upstream\.latency:[\d.]+\|h\|#source:apisix,new_tag:must,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.apisix\.latency:[\d.]+\|h\|#source:apisix,new_tag:must,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.ingress\.size:[\d]+\|ms\|#source:apisix,new_tag:must,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.egress\.size:[\d]+\|ms\|#source:apisix,new_tag:must,route_name:datadog,balancer_ip:[\d.]+,response_status:200,scheme:http
/
=== TEST 7: testing behaviour when route_name is missing - must fallback to route_id
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"datadog": {
"batch_max_size" : 1
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/opentracing"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
ngx.say(body)
-- making a request to the route
local code, _, body = t("/opentracing", "GET")
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
ngx.print(body)
}
}
--- response_body
passed
opentracing
--- wait: 0.5
--- grep_error_log eval
qr/message received: apisix(.+?(?=, ))/
--- grep_error_log_out eval
qr/message received: apisix\.request\.counter:1\|c\|#source:apisix,new_tag:must,route_name:1,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.request\.latency:[\d.]+\|h\|#source:apisix,new_tag:must,route_name:1,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.upstream\.latency:[\d.]+\|h\|#source:apisix,new_tag:must,route_name:1,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.apisix\.latency:[\d.]+\|h\|#source:apisix,new_tag:must,route_name:1,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.ingress\.size:[\d]+\|ms\|#source:apisix,new_tag:must,route_name:1,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.egress\.size:[\d]+\|ms\|#source:apisix,new_tag:must,route_name:1,balancer_ip:[\d.]+,response_status:200,scheme:http
/
=== TEST 8: testing behaviour with service id
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/services/1',
ngx.HTTP_PUT,
[[{
"name": "service-1",
"plugins": {
"datadog": {
"batch_max_size" : 1
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
ngx.say(body)
-- create a route with service level abstraction
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"name": "route-1",
"uri": "/opentracing",
"service_id": "1"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
ngx.say(body)
-- making a request to the route
local code, _, body = t("/opentracing", "GET")
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
ngx.print(body)
}
}
--- response_body
passed
passed
opentracing
--- wait: 0.5
--- grep_error_log eval
qr/message received: apisix(.+?(?=, ))/
--- grep_error_log_out eval
qr/message received: apisix\.request\.counter:1\|c\|#source:apisix,new_tag:must,route_name:route-1,service_name:service-1,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.request\.latency:[\d.]+\|h\|#source:apisix,new_tag:must,route_name:route-1,service_name:service-1,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.upstream\.latency:[\d.]+\|h\|#source:apisix,new_tag:must,route_name:route-1,service_name:service-1,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.apisix\.latency:[\d.]+\|h\|#source:apisix,new_tag:must,route_name:route-1,service_name:service-1,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.ingress\.size:[\d]+\|ms\|#source:apisix,new_tag:must,route_name:route-1,service_name:service-1,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.egress\.size:[\d]+\|ms\|#source:apisix,new_tag:must,route_name:route-1,service_name:service-1,balancer_ip:[\d.]+,response_status:200,scheme:http
/
=== TEST 9: testing behaviour with prefer_name is false and service name is nil
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/services/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"datadog": {
"batch_max_size" : 1,
"prefer_name": false
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
ngx.say(body)
-- making a request to the route
local code, _, body = t("/opentracing", "GET")
if code >= 300 then
ngx.status = code
ngx.say("fail")
return
end
ngx.print(body)
}
}
--- response_body
passed
opentracing
--- wait: 0.5
--- grep_error_log eval
qr/message received: apisix(.+?(?=, ))/
--- grep_error_log_out eval
qr/message received: apisix\.request\.counter:1\|c\|#source:apisix,new_tag:must,route_name:1,service_name:1,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.request\.latency:[\d.]+\|h\|#source:apisix,new_tag:must,route_name:1,service_name:1,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.upstream\.latency:[\d.]+\|h\|#source:apisix,new_tag:must,route_name:1,service_name:1,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.apisix\.latency:[\d.]+\|h\|#source:apisix,new_tag:must,route_name:1,service_name:1,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.ingress\.size:[\d]+\|ms\|#source:apisix,new_tag:must,route_name:1,service_name:1,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.egress\.size:[\d]+\|ms\|#source:apisix,new_tag:must,route_name:1,service_name:1,balancer_ip:[\d.]+,response_status:200,scheme:http
/
=== TEST 10: testing behaviour with consumer
--- apisix_yaml
consumers:
- username: user0
plugins:
key-auth:
key: user0
routes:
- uri: /opentracing
name: datadog
upstream:
nodes:
"127.0.0.1:1982": 1
plugins:
datadog:
batch_max_size: 1
max_retry_count: 0
key-auth: {}
#END
--- request
GET /opentracing?apikey=user0
--- response_body
opentracing
--- wait: 0.5
--- grep_error_log eval
qr/message received: apisix(.+?(?=, ))/
--- grep_error_log_out eval
qr/message received: apisix\.request\.counter:1\|c\|#source:apisix,route_name:datadog,consumer:user0,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.request\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,consumer:user0,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.upstream\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,consumer:user0,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.apisix\.latency:[\d.]+\|h\|#source:apisix,route_name:datadog,consumer:user0,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.ingress\.size:[\d]+\|ms\|#source:apisix,route_name:datadog,consumer:user0,balancer_ip:[\d.]+,response_status:200,scheme:http
message received: apisix\.egress\.size:[\d]+\|ms\|#source:apisix,route_name:datadog,consumer:user0,balancer_ip:[\d.]+,response_status:200,scheme:http
/

View File

@@ -0,0 +1,422 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
log_level('info');
worker_connections(256);
no_root_location();
no_shuffle();
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests();
__DATA__
=== TEST 1: query list
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/graphql",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:8888": 1
}
},
"plugins": {
"degraphql": {
"query": "{\n persons {\n id\n name\n }\n}\n"
}
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(0.1)
local http = require "resty.http"
local httpc = http.new()
local uri = "http://127.0.0.1:" .. ngx.var.server_port
.. "/graphql"
local headers = {
["Content-Type"] = "application/json"
}
local res, err = httpc:request_uri(uri, {headers = headers, method = "POST"})
if not res then
ngx.say(err)
return
end
local json = require("toolkit.json")
ngx.say(json.encode(res.body))
}
}
--- response_body
"{\"data\":{\"persons\":[{\"id\":\"7\",\"name\":\"Niek\"},{\"id\":\"8\",\"name\":\"Josh\"},{\"id\":\"9\",\"name\":\"Simon\"},{\"id\":\"10\",\"name\":\"Audun\"},{\"id\":\"11\",\"name\":\"Truls\"},{\"id\":\"12\",\"name\":\"Maria\"},{\"id\":\"13\",\"name\":\"Zahin\"},{\"id\":\"14\",\"name\":\"Roberto\"},{\"id\":\"15\",\"name\":\"Susanne\"},{\"id\":\"16\",\"name\":\"Live JS\"},{\"id\":\"17\",\"name\":\"Dave\"},{\"id\":\"18\",\"name\":\"Matt\"}]}}"
=== TEST 2: query with variables
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/graphql",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:8888": 1
}
},
"plugins": {
"degraphql": {
"query": "query($name: String!) {\n persons(filter: { name: $name }) {\n id\n name\n blog\n githubAccount\n talks {\n id\n title\n }\n }\n}",
"variables": [
"name"
]
}
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 3: hit
--- request
POST /graphql
{
"name": "Josh",
"githubAccount":"npalm"
}
--- more_headers
Content-Type: application/json
--- response_body chomp
{"data":{"persons":[{"id":"8","name":"Josh","blog":"","githubAccount":"joshlong","talks":[]}]}}
=== TEST 4: query with more variables
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/graphql",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:8888": 1
}
},
"plugins": {
"degraphql": {
"query": "query($name: String!, $githubAccount: String!) {\n persons(filter: { name: $name, githubAccount: $githubAccount }) {\n id\n name\n blog\n githubAccount\n talks {\n id\n title\n }\n }\n}",
"variables": [
"name",
"githubAccount"
]
}
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 5: hit
--- request
POST /graphql
{
"name":"Niek",
"githubAccount":"npalm"
}
--- more_headers
Content-Type: application/json
--- response_body chomp
{"data":{"persons":[{"id":"7","name":"Niek","blog":"https://040code.github.io","githubAccount":"npalm","talks":[{"id":"19","title":"GraphQL - The Next API Language"},{"id":"20","title":"Immutable Infrastructure"}]}]}}
=== TEST 6: without body
--- request
POST /graphql
--- error_log
missing request body
--- error_code: 400
=== TEST 7: invalid body
--- request
POST /graphql
"AA"
--- more_headers
Content-Type: application/json
--- error_log
invalid request body can't be decoded
--- error_code: 400
=== TEST 8: proxy should ensure the Content-Type is correct
--- request
POST /graphql
{
"name":"Niek",
"githubAccount":"npalm"
}
--- response_body chomp
{"data":{"persons":[{"id":"7","name":"Niek","blog":"https://040code.github.io","githubAccount":"npalm","talks":[{"id":"19","title":"GraphQL - The Next API Language"},{"id":"20","title":"Immutable Infrastructure"}]}]}}
=== TEST 9: schema check
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local query1 = "query persons($name: String!) {\n persons(filter: { name: $name }) {\n id\n name\n blog\n githubAccount\n talks {\n id\n title\n }\n }\n}"
local query2 = "query githubAccount($name: String!, $githubAccount: String!) {\n persons(filter: { name: $name, githubAccount: $githubAccount }) {\n id\n name\n blog\n githubAccount\n talks {\n id\n title\n }\n }\n}"
for _, case in ipairs({
{input = {
}},
{input = {
query = "uery {}",
}},
{input = {
query = "query {}",
variables = {},
}},
{input = {
query = query1 .. query2,
}},
}) do
local code, body = t('/apisix/admin/global_rules/1',
ngx.HTTP_PUT,
{
id = "1",
plugins = {
["degraphql"] = case.input
}
}
)
ngx.print(body)
end
}
}
--- response_body
{"error_msg":"failed to check the configuration of plugin degraphql err: property \"query\" is required"}
{"error_msg":"failed to check the configuration of plugin degraphql err: failed to parse query: Syntax error near line 1"}
{"error_msg":"failed to check the configuration of plugin degraphql err: property \"variables\" validation failed: expect array to have at least 1 items"}
{"error_msg":"failed to check the configuration of plugin degraphql err: operation_name is required if multiple operations are present in the query"}
=== TEST 10: check operation_name
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local json = require("apisix.core.json")
local query1 = "query persons($name: String!) {\n persons(filter: { name: $name }) {\n id\n name\n blog\n githubAccount\n talks {\n id\n title\n }\n }\n}"
local query2 = "query githubAccount($name: String!, $githubAccount: String!) {\n persons(filter: { name: $name, githubAccount: $githubAccount }) {\n id\n name\n blog\n githubAccount\n talks {\n id\n title\n }\n }\n}"
local query = json.encode(query1 .. query2)
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/graphql",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:8888": 1
}
},
"plugins": {
"degraphql": {
"query": ]] .. query .. [[,
"operation_name": "persons",
"variables": [
"name"
]
}
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 11: hit
--- request
POST /graphql
{
"name": "Josh",
"githubAccount":"npalm"
}
--- response_body chomp
{"data":{"persons":[{"id":"8","name":"Josh","blog":"","githubAccount":"joshlong","talks":[]}]}}
=== TEST 12: GET with variables
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/graphql",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:8888": 1
}
},
"plugins": {
"degraphql": {
"query": "query($name: String!, $githubAccount: String!) {\n persons(filter: { name: $name, githubAccount: $githubAccount }) {\n id\n name\n blog\n githubAccount\n talks {\n id\n title\n }\n }\n}",
"variables": [
"name",
"githubAccount"
]
}
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 13: hit
--- request
GET /graphql?name=Niek&githubAccount=npalm
--- response_body chomp
{"data":{"persons":[{"id":"7","name":"Niek","blog":"https://040code.github.io","githubAccount":"npalm","talks":[{"id":"19","title":"GraphQL - The Next API Language"},{"id":"20","title":"Immutable Infrastructure"}]}]}}
=== TEST 14: GET without variables
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/graphql",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:8888": 1
}
},
"plugins": {
"degraphql": {
"query": "{\n persons {\n id\n name\n }\n}\n"
}
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 15: hit
--- request
GET /graphql
--- response_body chomp
{"data":{"persons":[{"id":"7","name":"Niek"},{"id":"8","name":"Josh"},{"id":"9","name":"Simon"},{"id":"10","name":"Audun"},{"id":"11","name":"Truls"},{"id":"12","name":"Maria"},{"id":"13","name":"Zahin"},{"id":"14","name":"Roberto"},{"id":"15","name":"Susanne"},{"id":"16","name":"Live JS"},{"id":"17","name":"Dave"},{"id":"18","name":"Matt"}]}}

View File

@@ -0,0 +1,321 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX;
my $nginx_binary = $ENV{'TEST_NGINX_BINARY'} || 'nginx';
my $version = eval { `$nginx_binary -V 2>&1` };
if ($version !~ m/\/mod_dubbo/) {
plan(skip_all => "mod_dubbo not installed");
} else {
plan('no_plan');
}
repeat_each(1);
log_level('info');
no_root_location();
no_shuffle();
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->disable_dubbo) {
my $extra_yaml_config = <<_EOC_;
plugins:
- dubbo-proxy
- response-rewrite
- proxy-rewrite
- key-auth
_EOC_
$block->set_value("extra_yaml_config", $extra_yaml_config);
}
if (!$block->yaml_config) {
my $yaml_config = <<_EOC_;
apisix:
node_listen: 1984
enable_admin: false
deployment:
role: data_plane
role_data_plane:
config_provider: yaml
_EOC_
$block->set_value("yaml_config", $yaml_config);
}
if ($block->apisix_yaml) {
my $upstream = <<_EOC_;
upstreams:
- nodes:
"127.0.0.1:20880": 1
type: roundrobin
id: 1
#END
_EOC_
$block->set_value("apisix_yaml", $block->apisix_yaml . $upstream);
}
if (!$block->request) {
$block->set_value("request", "GET /hello");
}
});
run_tests();
__DATA__
=== TEST 1: ignore route's dubbo configuration if dubbo is disable globally
--- disable_dubbo
--- apisix_yaml
routes:
-
uri: /hello
plugins:
dubbo-proxy:
service_name: org.apache.dubbo.backend.DemoService
service_version: 1.0.0
method: hello
upstream:
nodes:
"127.0.0.1:1980": 1
type: roundrobin
--- response_body
hello world
=== TEST 2: check schema
--- apisix_yaml
routes:
-
uri: /hello
plugins:
dubbo-proxy:
service_name: org.apache.dubbo.backend.DemoService
method: hello
upstream_id: 1
--- error_log
property "service_version" is required
--- error_code: 404
=== TEST 3: sanity
--- apisix_yaml
routes:
-
uri: /hello
plugins:
dubbo-proxy:
service_name: org.apache.dubbo.backend.DemoService
service_version: 1.0.0
method: hello
upstream_id: 1
--- more_headers
Extra-Arg-K: V
--- response_headers
Got-extra-arg-k: V
--- response_body
dubbo success
=== TEST 4: enabled in service
--- apisix_yaml
routes:
- uri: /hello
service_id: 1
services:
-
plugins:
dubbo-proxy:
service_name: org.apache.dubbo.backend.DemoService
service_version: 1.0.0
method: hello
id: 1
upstream_id: 1
--- response_body
dubbo success
=== TEST 5: work with consumer
--- yaml_config
apisix:
node_listen: 1984
enable_admin: true
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin").test
local code, message = t('/apisix/admin/consumers',
ngx.HTTP_PUT,
[[{
"username":"jack",
"plugins": {
"key-auth": {
"key": "jack"
}
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
local code, message = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"upstream":{
"nodes": {
"127.0.0.1:20880": 1
},
"type": "roundrobin"
},
"plugins": {
"dubbo-proxy": {
"service_name": "org.apache.dubbo.backend.DemoService",
"service_version": "1.0.0",
"method": "hello"
},
"key-auth": {}
},
"uris": ["/hello"]
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(message)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 6: blocked
--- yaml_config
apisix:
node_listen: 1984
enable_admin: true
--- error_code: 401
=== TEST 7: passed
--- yaml_config
apisix:
node_listen: 1984
enable_admin: true
--- more_headers
apikey: jack
--- response_body
dubbo success
=== TEST 8: rewrite response
--- apisix_yaml
routes:
-
uri: /hello
plugins:
response-rewrite:
headers:
fruit: banana
body: "hello world\n"
dubbo-proxy:
service_name: org.apache.dubbo.backend.DemoService
service_version: 1.0.0
method: hello
upstream_id: 1
--- response_body
hello world
--- response_headers
fruit: banana
=== TEST 9: rewrite request
--- apisix_yaml
routes:
-
uri: /hello
plugins:
proxy-rewrite:
headers:
extra-arg-fruit: banana
dubbo-proxy:
service_name: org.apache.dubbo.backend.DemoService
service_version: 1.0.0
method: hello
upstream_id: 1
--- response_body
dubbo success
--- response_headers
Got-extra-arg-fruit: banana
=== TEST 10: use uri as default method
--- apisix_yaml
routes:
-
uri: /hello
plugins:
dubbo-proxy:
service_name: org.apache.dubbo.backend.DemoService
service_version: 1.0.0
upstream_id: 1
--- response_body
dubbo success
=== TEST 11: version mismatch
--- apisix_yaml
routes:
-
uri: /hello
plugins:
dubbo-proxy:
service_name: org.apache.dubbo.backend.DemoService
service_version: 0.1.0
method: hello
upstream_id: 1
--- more_headers
Extra-Arg-K: V
--- error_code: 502
--- error_log
may be version or group mismatch

View File

@@ -0,0 +1,163 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX;
my $nginx_binary = $ENV{'TEST_NGINX_BINARY'} || 'nginx';
my $version = eval { `$nginx_binary -V 2>&1` };
if ($version !~ m/\/mod_dubbo/) {
plan(skip_all => "mod_dubbo not installed");
} else {
plan('no_plan');
}
repeat_each(1);
log_level('info');
no_root_location();
no_shuffle();
worker_connections(256);
add_block_preprocessor(sub {
my ($block) = @_;
if (!$block->request) {
$block->set_value("request", "GET /hello");
}
if (!defined $block->disable_dubbo) {
my $extra_yaml_config = <<_EOC_;
plugins:
- dubbo-proxy
_EOC_
$block->set_value("extra_yaml_config", $extra_yaml_config);
}
my $yaml_config = $block->yaml_config // <<_EOC_;
apisix:
node_listen: 1984
enable_admin: false
deployment:
role: data_plane
role_data_plane:
config_provider: yaml
_EOC_
$block->set_value("yaml_config", $yaml_config);
});
run_tests();
__DATA__
=== TEST 1: retry
--- apisix_yaml
upstreams:
- nodes:
- host: 127.0.0.1
port: 20881
weight: 1
- host: 127.0.0.1
port: 20880
weight: 1
type: roundrobin
id: 1
routes:
-
uri: /hello
plugins:
dubbo-proxy:
service_name: org.apache.dubbo.backend.DemoService
service_version: 1.0.0
method: hello
upstream_id: 1
#END
--- response_body
dubbo success
--- ignore_error_log
=== TEST 2: upstream return error
--- apisix_yaml
routes:
-
uri: /hello
plugins:
dubbo-proxy:
service_name: org.apache.dubbo.backend.DemoService
service_version: 1.0.0
method: fail
upstream_id: 1
upstreams:
- nodes:
"127.0.0.1:20880": 1
type: roundrobin
id: 1
#END
--- response_body
dubbo fail
--- error_code: 503
=== TEST 3: upstream timeout
--- apisix_yaml
routes:
-
uri: /hello
plugins:
dubbo-proxy:
service_name: org.apache.dubbo.backend.DemoService
service_version: 1.0.0
method: timeout
upstream_id: 1
upstreams:
- nodes:
"127.0.0.1:20880": 1
type: roundrobin
timeout:
connect: 0.1
read: 0.1
send: 0.1
id: 1
#END
--- error_log
upstream timed out
--- error_code: 504
=== TEST 4: upstream return non-string status code
--- apisix_yaml
routes:
-
uri: /hello
plugins:
dubbo-proxy:
service_name: org.apache.dubbo.backend.DemoService
service_version: 1.0.0
method: badStatus
upstream_id: 1
upstreams:
- nodes:
"127.0.0.1:20880": 1
type: roundrobin
id: 1
#END
--- response_body
ok

View File

@@ -0,0 +1,298 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_root_location();
run_tests;
__DATA__
=== TEST 1: sanity
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.echo")
local ok, err = plugin.check_schema({before_body = "body before", body = "body to attach",
after_body = "body to attach"})
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
done
=== TEST 2: wrong type of integer
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.echo")
local ok, err = plugin.check_schema({before_body = "body before", body = "body to attach",
after_body = 10})
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
property "after_body" validation failed: wrong type: expected string, got number
done
=== TEST 3: add plugin
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"echo": {
"before_body": "before the body modification ",
"body":"hello upstream",
"after_body": " after the body modification.",
"headers": {
"Location":"https://www.iresty.com",
"Authorization": "userpass"
}
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 4: access
--- request
GET /hello
--- response_body chomp
before the body modification hello upstream after the body modification.
--- response_headers
Location: https://www.iresty.com
Authorization: userpass
--- wait: 0.2
=== TEST 5: update plugin
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"echo": {
"before_body": "before the body modification ",
"headers": {
"Location":"https://www.iresty.com"
}
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 6: access without upstream body change
--- request
GET /hello
--- response_body
before the body modification hello world
--- response_headers
Location: https://www.iresty.com
--- wait: 0.2
--- wait: 0.2
=== TEST 7: print the `conf` in etcd, no dirty data
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local encode_with_keys_sorted = require("toolkit.json").encode
local code, _, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"echo": {
"before_body": "before the body modification ",
"headers": {
"Location":"https://www.iresty.com"
}
}
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
local resp_data = core.json.decode(body)
ngx.say(encode_with_keys_sorted(resp_data.value.plugins))
}
}
--- request
GET /t
--- response_body
{"echo":{"before_body":"before the body modification ","headers":{"Location":"https://www.iresty.com"}}}
=== TEST 8: set body with chunked upstream
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"echo": {
"body":"hello upstream"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello_chunked"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 9: access
--- request
GET /hello_chunked
--- response_body chomp
hello upstream
=== TEST 10: add before/after body with chunked upstream
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"echo": {
"before_body": "before the body modification ",
"after_body": " after the body modification."
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello_chunked"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 11: access
--- request
GET /hello_chunked
--- response_body chomp
before the body modification hello world
after the body modification.

View File

@@ -0,0 +1,994 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
log_level('debug');
repeat_each(1);
no_long_string();
no_root_location();
no_shuffle();
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests();
__DATA__
=== TEST 1: sanity
--- config
location /t {
content_by_lua_block {
local ok, err
local configs = {
-- full configuration
{
endpoint_addr = "http://127.0.0.1:9200",
field = {
index = "services"
},
auth = {
username = "elastic",
password = "123456"
},
ssl_verify = false,
timeout = 60,
max_retry_count = 0,
retry_delay = 1,
buffer_duration = 60,
inactive_timeout = 2,
batch_max_size = 10,
},
-- minimize configuration
{
endpoint_addr = "http://127.0.0.1:9200",
field = {
index = "services"
}
},
-- property "endpoint_addr" is required
{
field = {
index = "services"
}
},
-- property "field" is required
{
endpoint_addr = "http://127.0.0.1:9200",
},
-- property "index" is required
{
endpoint_addr = "http://127.0.0.1:9200",
field = {}
},
-- property "endpoint" must not end with "/"
{
endpoint_addr = "http://127.0.0.1:9200/",
field = {
index = "services"
}
}
}
local plugin = require("apisix.plugins.elasticsearch-logger")
for i = 1, #configs do
ok, err = plugin.check_schema(configs[i])
if err then
ngx.say(err)
else
ngx.say("passed")
end
end
}
}
--- response_body_like
passed
passed
value should match only one schema, but matches none
value should match only one schema, but matches none
property "field" validation failed: property "index" is required
property "endpoint_addr" validation failed: failed to match pattern "\[\^/\]\$" with "http://127.0.0.1:9200/"
=== TEST 2: set route
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/elasticsearch-logger',
ngx.HTTP_DELETE)
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["elasticsearch-logger"] = {
endpoint_addr = "http://127.0.0.1:9200",
field = {
index = "services"
},
batch_max_size = 1,
inactive_timeout = 1
}
}
})
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 3: test route (success write)
--- extra_init_by_lua
local core = require("apisix.core")
local http = require("resty.http")
local ngx_re = require("ngx.re")
local log_util = require("apisix.utils.log-util")
log_util.inject_get_full_log(function(ngx, conf)
return {
test = "test"
}
end)
http.request_uri = function(self, uri, params)
if params.method == "GET" then
return {
status = 200,
body = [[
{
"version": {
"number": "8.10.2"
}
}
]]
}
end
if not params.body or type(params.body) ~= "string" then
return nil, "invalid params body"
end
local arr = ngx_re.split(params.body, "\n")
if not arr or #arr ~= 2 then
return nil, "invalid params body"
end
local entry = core.json.decode(arr[2])
local origin_entry = log_util.get_full_log(ngx, {})
for k, v in pairs(origin_entry) do
local vv = entry[k]
if not vv or vv ~= v then
return nil, "invalid params body"
end
end
core.log.error("check elasticsearch full log body success")
return {
status = 200,
body = "success"
}, nil
end
--- request
GET /hello
--- wait: 2
--- response_body
hello world
--- error_log
check elasticsearch full log body success
=== TEST 4: set route (auth)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["elasticsearch-logger"] = {
endpoint_addr = "http://127.0.0.1:9201",
field = {
index = "services"
},
auth = {
username = "elastic",
password = "123456"
},
batch_max_size = 1,
inactive_timeout = 1
}
}
})
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 5: test route (auth success)
--- request
GET /hello
--- wait: 2
--- response_body
hello world
--- error_log
Batch Processor[elasticsearch-logger] successfully processed the entries
=== TEST 6: set route (no auth)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["elasticsearch-logger"] = {
endpoint_addr = "http://127.0.0.1:9201",
field = {
index = "services"
},
batch_max_size = 1,
inactive_timeout = 1
}
}
})
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 7: test route (no auth, failed)
--- request
GET /hello
--- wait: 2
--- response_body
hello world
--- error_log
failed to process entries: elasticsearch server returned status: 401
=== TEST 8: set route (error auth)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["elasticsearch-logger"] = {
endpoint_addr = "http://127.0.0.1:9201",
field = {
index = "services"
},
auth = {
username = "elastic",
password = "111111"
},
batch_max_size = 1,
inactive_timeout = 1
}
}
})
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 9: test route (error auth failed)
--- request
GET /hello
--- wait: 2
--- response_body
hello world
--- error_log
Batch Processor[elasticsearch-logger] failed to process entries
Batch Processor[elasticsearch-logger] exceeded the max_retry_count
=== TEST 10: add plugin metadata
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/elasticsearch-logger',
ngx.HTTP_PUT, [[{
"log_format": {
"custom_host": "$host",
"custom_timestamp": "$time_iso8601",
"custom_client_ip": "$remote_addr"
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["elasticsearch-logger"] = {
endpoint_addr = "http://127.0.0.1:9201",
field = {
index = "services"
},
batch_max_size = 1,
inactive_timeout = 1
}
}
})
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body_like
passed
passed
=== TEST 11: hit route and check custom elasticsearch logger
--- extra_init_by_lua
local core = require("apisix.core")
local http = require("resty.http")
local ngx_re = require("ngx.re")
local log_util = require("apisix.utils.log-util")
log_util.inject_get_custom_format_log(function(ctx, format)
return {
test = "test"
}
end)
http.request_uri = function(self, uri, params)
if params.method == "GET" then
return {
status = 200,
body = [[
{
"version": {
"number": "8.10.2"
}
}
]]
}
end
if not params.body or type(params.body) ~= "string" then
return nil, "invalid params body"
end
local arr = ngx_re.split(params.body, "\n")
if not arr or #arr ~= 2 then
return nil, "invalid params body"
end
local entry = core.json.decode(arr[2])
local origin_entry = log_util.get_custom_format_log(nil, nil)
for k, v in pairs(origin_entry) do
local vv = entry[k]
if not vv or vv ~= v then
return nil, "invalid params body"
end
end
core.log.error("check elasticsearch custom body success")
return {
status = 200,
body = "success"
}, nil
end
--- request
GET /hello
--- response_body
hello world
--- wait: 2
--- error_log
check elasticsearch custom body success
=== TEST 12: data encryption for auth.password
--- yaml_config
apisix:
data_encryption:
enable_encrypt_fields: true
keyring:
- edd1c9f0985e76a2
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["elasticsearch-logger"] = {
endpoint_addr = "http://127.0.0.1:9201",
field = {
index = "services"
},
auth = {
username = "elastic",
password = "123456"
},
batch_max_size = 1,
inactive_timeout = 1
}
}
})
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(0.1)
-- get plugin conf from admin api, password is decrypted
local code, message, res = t('/apisix/admin/routes/1',
ngx.HTTP_GET
)
res = json.decode(res)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(res.value.plugins["elasticsearch-logger"].auth.password)
-- get plugin conf from etcd, password is encrypted
local etcd = require("apisix.core.etcd")
local res = assert(etcd.get('/routes/1'))
ngx.say(res.body.node.value.plugins["elasticsearch-logger"].auth.password)
}
}
--- response_body
123456
PTQvJEaPcNOXcOHeErC0XQ==
=== TEST 13: add plugin on routes using multi elasticsearch-logger
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["elasticsearch-logger"] = {
endpoint_addrs = {"http://127.0.0.1:9200", "http://127.0.0.1:9201"},
field = {
index = "services"
},
batch_max_size = 1,
inactive_timeout = 1
}
}
})
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 14: to show that different endpoints will be chosen randomly
--- config
location /t {
content_by_lua_block {
local code_count = {}
local t = require("lib.test_admin").test
for i = 1, 12 do
local code, body = t('/hello', ngx.HTTP_GET)
if code ~= 200 then
ngx.say("code: ", code, " body: ", body)
end
code_count[code] = (code_count[code] or 0) + 1
end
local code_arr = {}
for code, count in pairs(code_count) do
table.insert(code_arr, {code = code, count = count})
end
ngx.say(require("toolkit.json").encode(code_arr))
ngx.exit(200)
}
}
--- response_body
[{"code":200,"count":12}]
--- error_log
http://127.0.0.1:9200/_bulk
http://127.0.0.1:9201/_bulk
=== TEST 15: log format in plugin
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["elasticsearch-logger"] = {
endpoint_addr = "http://127.0.0.1:9201",
field = {
index = "services"
},
log_format = {
custom_host = "$host"
},
batch_max_size = 1,
inactive_timeout = 1
}
}
})
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 16: hit route and check custom elasticsearch logger
--- extra_init_by_lua
local core = require("apisix.core")
local http = require("resty.http")
local ngx_re = require("ngx.re")
local log_util = require("apisix.utils.log-util")
log_util.inject_get_custom_format_log(function(ctx, format)
return {
test = "test"
}
end)
http.request_uri = function(self, uri, params)
if params.method == "GET" then
return {
status = 200,
body = [[
{
"version": {
"number": "8.10.2"
}
}
]]
}
end
if not params.body or type(params.body) ~= "string" then
return nil, "invalid params body"
end
local arr = ngx_re.split(params.body, "\n")
if not arr or #arr ~= 2 then
return nil, "invalid params body"
end
local entry = core.json.decode(arr[2])
local origin_entry = log_util.get_custom_format_log(nil, nil)
for k, v in pairs(origin_entry) do
local vv = entry[k]
if not vv or vv ~= v then
return nil, "invalid params body"
end
end
core.log.error("check elasticsearch custom body success")
return {
status = 200,
body = "success"
}, nil
end
--- request
GET /hello
--- response_body
hello world
--- wait: 2
--- error_log
check elasticsearch custom body success
=== TEST 17: using unsupported field (type) for elasticsearch v8 should work normally
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["elasticsearch-logger"] = {
endpoint_addr = "http://127.0.0.1:9201",
field = {
index = "services",
type = "collector"
},
auth = {
username = "elastic",
password = "123456"
},
batch_max_size = 1,
inactive_timeout = 1
}
}
})
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 18: test route (auth success)
--- request
GET /hello
--- wait: 2
--- response_body
hello world
--- no_error_log
Action/metadata line [1] contains an unknown parameter [_type]
=== TEST 19: add plugin with 'include_req_body' setting, collect request log
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
t('/apisix/admin/plugin_metadata/elasticsearch-logger', ngx.HTTP_DELETE)
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["elasticsearch-logger"] = {
endpoint_addr = "http://127.0.0.1:9201",
field = {
index = "services"
},
auth = {
username = "elastic",
password = "123456"
},
batch_max_size = 1,
inactive_timeout = 1,
include_req_body = true
}
}
})
if code >= 300 then
ngx.status = code
end
local code, _, body = t("/hello", "POST", "{\"sample_payload\":\"hello\"}")
}
}
--- error_log
"body":"{\"sample_payload\":\"hello\"}"
=== TEST 20: add plugin with 'include_resp_body' setting, collect response log
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
t('/apisix/admin/plugin_metadata/elasticsearch-logger', ngx.HTTP_DELETE)
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["elasticsearch-logger"] = {
endpoint_addr = "http://127.0.0.1:9201",
field = {
index = "services"
},
auth = {
username = "elastic",
password = "123456"
},
batch_max_size = 1,
inactive_timeout = 1,
include_req_body = true,
include_resp_body = true
}
}
})
if code >= 300 then
ngx.status = code
end
local code, _, body = t("/hello", "POST", "{\"sample_payload\":\"hello\"}")
}
}
--- error_log
"body":"hello world\n"
=== TEST 21: set route (auth) - check compat with version 9
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["elasticsearch-logger"] = {
endpoint_addr = "http://127.0.0.1:9301",
field = {
index = "services"
},
auth = {
username = "elastic",
password = "123456"
},
batch_max_size = 1,
inactive_timeout = 1
}
}
})
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 22: test route (auth success)
--- request
GET /hello
--- wait: 2
--- response_body
hello world
--- error_log
Batch Processor[elasticsearch-logger] successfully processed the entries
=== TEST 23: set route (auth) - check compat with version 7
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["elasticsearch-logger"] = {
endpoint_addr = "http://127.0.0.1:9401",
field = {
index = "services"
},
auth = {
username = "elastic",
password = "123456"
},
batch_max_size = 1,
inactive_timeout = 1
}
}
})
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 24: test route (auth success)
--- request
GET /hello
--- wait: 2
--- response_body
hello world
--- error_log
Batch Processor[elasticsearch-logger] successfully processed the entries
=== TEST 25: set route (auth) - check compat with version 6
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["elasticsearch-logger"] = {
endpoint_addr = "http://127.0.0.1:9501",
field = {
index = "services"
},
auth = {
username = "elastic",
password = "123456"
},
batch_max_size = 1,
inactive_timeout = 1
}
}
})
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 26: test route (auth success)
--- request
GET /hello
--- wait: 2
--- response_body
hello world
--- error_log
Batch Processor[elasticsearch-logger] successfully processed the entries

View File

@@ -0,0 +1,295 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
log_level("info");
repeat_each(1);
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
if (!defined $block->extra_yaml_config) {
my $extra_yaml_config = <<_EOC_;
plugins:
- error-log-logger
_EOC_
$block->set_value("extra_yaml_config", $extra_yaml_config);
}
});
run_tests();
__DATA__
=== TEST 1: test schema checker
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local plugin = require("apisix.plugins.error-log-logger")
local ok, err = plugin.check_schema(
{
clickhouse = {
user = "default",
password = "a",
database = "default",
logtable = "t",
endpoint_addr = "http://127.0.0.1:1980/clickhouse_logger_server"
}
},
core.schema.TYPE_METADATA
)
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- response_body
done
=== TEST 2: test unreachable server
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/error-log-logger',
ngx.HTTP_PUT,
[[{
"clickhouse": {
"user": "default",
"password": "a",
"database": "default",
"logtable": "t",
"endpoint_addr": "http://127.0.0.1:1980/clickhouse_logger_server"
},
"inactive_timeout": 1
}]]
)
ngx.sleep(2)
core.log.warn("this is a warning message for test2.")
}
}
--- response_body
--- error_log
this is a warning message for test2
clickhouse body: INSERT INTO t FORMAT JSONEachRow
clickhouse headers: x-clickhouse-key:a
clickhouse headers: x-clickhouse-user:default
clickhouse headers: x-clickhouse-database:default
--- wait: 3
=== TEST 3: put plugin metadata and log an error level message
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/error-log-logger',
ngx.HTTP_PUT,
[[{
"clickhouse": {
"user": "default",
"password": "a",
"database": "default",
"logtable": "t",
"endpoint_addr": "http://127.0.0.1:1980/clickhouse_logger_server"
},
"batch_max_size": 15,
"inactive_timeout": 1
}]]
)
ngx.sleep(2)
core.log.warn("this is a warning message for test3.")
}
}
--- response_body
--- error_log
this is a warning message for test3
clickhouse body: INSERT INTO t FORMAT JSONEachRow
clickhouse headers: x-clickhouse-key:a
clickhouse headers: x-clickhouse-user:default
clickhouse headers: x-clickhouse-database:default
--- wait: 5
=== TEST 4: log a warn level message
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
core.log.warn("this is a warning message for test4.")
}
}
--- response_body
--- error_log
this is a warning message for test4
clickhouse body: INSERT INTO t FORMAT JSONEachRow
clickhouse headers: x-clickhouse-key:a
clickhouse headers: x-clickhouse-user:default
clickhouse headers: x-clickhouse-database:default
--- wait: 5
=== TEST 5: log some messages
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
core.log.warn("this is a warning message for test5.")
}
}
--- response_body
--- error_log
this is a warning message for test5
clickhouse body: INSERT INTO t FORMAT JSONEachRow
clickhouse headers: x-clickhouse-key:a
clickhouse headers: x-clickhouse-user:default
clickhouse headers: x-clickhouse-database:default
--- wait: 5
=== TEST 6: log an info level message
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
core.log.info("this is an info message for test6.")
}
}
--- response_body
--- error_log
this is an info message for test6
--- wait: 5
=== TEST 7: delete metadata for the plugin, recover to the default
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/error-log-logger',
ngx.HTTP_DELETE)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 8: data encryption for clickhouse.password
--- yaml_config
apisix:
data_encryption:
enable_encrypt_fields: true
keyring:
- edd1c9f0985e76a2
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/error-log-logger',
ngx.HTTP_PUT,
[[{
"clickhouse": {
"user": "default",
"password": "bar",
"database": "default",
"logtable": "t",
"endpoint_addr": "http://127.0.0.1:1980/clickhouse_logger_server"
},
"batch_max_size": 15,
"inactive_timeout": 1
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(0.1)
-- get plugin conf from admin api, password is decrypted
local code, message, res = t('/apisix/admin/plugin_metadata/error-log-logger',
ngx.HTTP_GET
)
res = json.decode(res)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(res.value["clickhouse"].password)
-- get plugin conf from etcd, password is encrypted
local etcd = require("apisix.core.etcd")
local res = assert(etcd.get('/plugin_metadata/error-log-logger'))
ngx.say(res.body.node.value["clickhouse"].password)
}
}
--- response_body
bar
77+NmbYqNfN+oLm0aX5akg==
=== TEST 9: verify use the decrypted password to connect to clickhouse
--- yaml_config
apisix:
data_encryption:
enable_encrypt_fields: true
keyring:
- edd1c9f0985e76a2
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
core.log.warn("this is a warning message for test9")
}
}
--- response_body
--- error_log
this is a warning message for test9
clickhouse headers: x-clickhouse-key:bar
--- wait: 5

View File

@@ -0,0 +1,203 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
log_level("info");
repeat_each(1);
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
if (!defined $block->extra_yaml_config) {
my $extra_yaml_config = <<_EOC_;
plugins:
- error-log-logger
_EOC_
$block->set_value("extra_yaml_config", $extra_yaml_config);
}
});
run_tests();
__DATA__
=== TEST 1: test schema checker
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local plugin = require("apisix.plugins.error-log-logger")
local ok, err = plugin.check_schema(
{
kafka = {
brokers = {
{
host = "127.0.0.1",
port = 9092
}
},
kafka_topic = "test2"
}
},
core.schema.TYPE_METADATA
)
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- response_body
done
=== TEST 2: put plugin metadata and log an error level message - no auth kafka
--- extra_init_by_lua
local core = require("apisix.core")
local producer = require("resty.kafka.producer")
local old_producer_new = producer.new
producer.new = function(self, broker_list, producer_config, cluster_name)
core.log.info("broker_config is: ", core.json.delay_encode(producer_config))
return old_producer_new(self, broker_list, producer_config, cluster_name)
end
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/error-log-logger',
ngx.HTTP_PUT,
[[{
"kafka": {
"brokers": [{
"host": "127.0.0.1",
"port": 9092
}],
"kafka_topic": "test2",
"meta_refresh_interval": 1
},
"level": "ERROR",
"inactive_timeout": 1
}]]
)
ngx.sleep(2)
core.log.error("this is a error message for test2.")
}
}
--- error_log eval
[qr/this is a error message for test2/,
qr/send data to kafka: .*test2/,
qr/broker_config is: \{.*"refresh_interval":1000/,
]
--- wait: 3
=== TEST 3: log a error level message
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
core.log.error("this is a error message for test3.")
}
}
--- error_log eval
[qr/this is a error message for test3/,
qr/send data to kafka: .*test3/]
--- wait: 5
=== TEST 4: log an warning level message - will not send to kafka brokers
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
core.log.warn("this is an warning message for test4.")
}
}
--- error_log
this is an warning message for test4
--- no_error_log eval
qr/send data to kafka: .*test4/
--- wait: 5
=== TEST 5: put plugin metadata and log an error level message - auth kafka
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/error-log-logger',
ngx.HTTP_PUT,
[[{
"kafka": {
"brokers": [{
"host": "127.0.0.1",
"port": 19094,
"sasl_config": {
"mechanism": "PLAIN",
"user": "admin",
"password": "admin-secret"
}
}],
"producer_type": "sync",
"kafka_topic": "test4"
},
"level": "ERROR",
"inactive_timeout": 1
}]]
)
ngx.sleep(2)
core.log.error("this is a error message for test5.")
}
}
--- error_log eval
[qr/this is a error message for test5/,
qr/send data to kafka: .*test5/]
--- wait: 3
=== TEST 6: delete metadata for the plugin, recover to the default
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/error-log-logger',
ngx.HTTP_DELETE)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed

View File

@@ -0,0 +1,229 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
log_level('debug');
repeat_each(1);
no_long_string();
no_root_location();
worker_connections(128);
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->extra_yaml_config) {
my $extra_yaml_config = <<_EOC_;
plugins:
- error-log-logger
_EOC_
$block->set_value("extra_yaml_config", $extra_yaml_config);
}
});
run_tests;
__DATA__
=== TEST 1: test schema checker
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local plugin = require("apisix.plugins.error-log-logger")
local ok, err = plugin.check_schema(
{
skywalking = {
endpoint_addr = "http://127.0.0.1"
}
},
core.schema.TYPE_METADATA
)
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
done
=== TEST 2: test unreachable server
--- config
location /tg {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/error-log-logger',
ngx.HTTP_PUT,
[[{
"skywalking": {
"endpoint_addr": "http://127.0.0.1:1988/log"
},
"inactive_timeout": 1
}]]
)
ngx.sleep(2)
core.log.warn("this is a warning message for test.")
}
}
--- request
GET /tg
--- response_body
--- error_log eval
qr/Batch Processor\[error-log-logger\] failed to process entries: error while sending data to skywalking\[http:\/\/127.0.0.1:1988\/log\] connection refused, context: ngx.timer/
--- wait: 3
=== TEST 3: put plugin metadata and log an error level message
--- config
location /tg {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/error-log-logger',
ngx.HTTP_PUT,
[[{
"skywalking": {
"endpoint_addr": "http://127.0.0.1:1982/log",
"service_instance_name": "instance"
},
"batch_max_size": 15,
"inactive_timeout": 1
}]]
)
ngx.sleep(2)
core.log.error("this is an error message for test.")
}
}
--- request
GET /tg
--- response_body
--- error_log
this is an error message for test
--- wait: 5
=== TEST 4: log a warn level message
--- config
location /tg {
content_by_lua_block {
local core = require("apisix.core")
core.log.warn("this is a warning message for test.")
}
}
--- request
GET /tg
--- response_body
--- error_log eval
qr/.*\[\{\"body\":\{\"text\":\{\"text\":\".*this is a warning message for test.*\"\}\},\"endpoint\":\"\",\"service\":\"APISIX\",\"serviceInstance\":\"instance\".*/
--- wait: 5
=== TEST 5: log some messages
--- config
location /tg {
content_by_lua_block {
local core = require("apisix.core")
core.log.error("this is an error message for test.")
core.log.warn("this is a warning message for test.")
}
}
--- request
GET /tg
--- response_body
--- error_log eval
qr/.*\[\{\"body\":\{\"text\":\{\"text\":\".*this is an error message for test.*\"\}\},\"endpoint\":\"\",\"service\":\"APISIX\",\"serviceInstance\":\"instance\".*\},\{\"body\":\{\"text\":\{\"text\":\".*this is a warning message for test.*\"\}\}.*/
--- wait: 5
=== TEST 6: log an info level message
--- config
location /tg {
content_by_lua_block {
local core = require("apisix.core")
core.log.info("this is an info message for test.")
}
}
--- request
GET /tg
--- response_body
--- no_error_log eval
qr/.*\[\{\"body\":\{\"text\":\{\"text\":\".*this is an info message for test.*\"\}\},\"endpoint\":\"\",\"service\":\"APISIX\",\"serviceInstance\":\"instance\".*/
--- wait: 5
=== TEST 7: delete metadata for the plugin, recover to the default
--- config
location /tg {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/error-log-logger',
ngx.HTTP_DELETE)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /tg
--- response_body
passed
=== TEST 8: put plugin metadata with $hostname and log an error level message
--- config
location /tg {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/error-log-logger',
ngx.HTTP_PUT,
[[{
"skywalking": {
"endpoint_addr": "http://127.0.0.1:1982/log",
"service_instance_name": "$hostname"
},
"batch_max_size": 15,
"inactive_timeout": 1
}]]
)
ngx.sleep(2)
core.log.error("this is an error message for test.")
}
}
--- request
GET /tg
--- response_body
--- no_error_log eval
qr/\\\"serviceInstance\\\":\\\"\$hostname\\\"/
qr/\\\"serviceInstance\\\":\\\"\\\"/
--- wait: 0.5

View File

@@ -0,0 +1,447 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
my $stream_single_server = <<_EOC_;
# fake server, only for test
server {
listen 1999;
content_by_lua_block {
local exiting = ngx.worker.exiting
local sock, err = ngx.req.socket(true)
if not sock then
ngx.log(ngx.WARN, "socket error:", err)
return
end
sock:settimeout(30 * 1000)
while(not exiting())
do
local data, err = sock:receive()
if (data) then
ngx.log(ngx.INFO, "[Server] receive data:", data)
else
if err ~= "timeout" then
ngx.log(ngx.WARN, "socket error:", err)
return
end
end
end
}
}
_EOC_
$block->set_value("stream_config", $stream_single_server);
my $stream_default_server = <<_EOC_;
content_by_lua_block {
ngx.log(ngx.INFO, "a stream server")
}
_EOC_
$block->set_value("stream_server_config", $stream_default_server);
if (!defined $block->extra_yaml_config) {
my $extra_yaml_config = <<_EOC_;
plugins:
- error-log-logger
_EOC_
$block->set_value("extra_yaml_config", $extra_yaml_config);
}
});
run_tests;
__DATA__
=== TEST 1: not enable the plugin
--- extra_yaml_config
--- config
location /tg {
content_by_lua_block {
local core = require("apisix.core")
core.log.warn("this is a warning message for test.")
}
}
--- request
GET /tg
--- response_body
--- no_error_log
error-log-logger
--- wait: 2
=== TEST 2: enable the plugin, but not init the metadata
--- config
location /tg {
content_by_lua_block {
local core = require("apisix.core")
core.log.warn("this is a warning message for test.")
}
}
--- request
GET /tg
--- response_body
--- error_log eval
qr/please set the correct plugin_metadata for error-log-logger/
--- wait: 2
=== TEST 3: set a wrong metadata
--- config
location /tg {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/error-log-logger',
ngx.HTTP_PUT,
[[{
"tcp": {
"port": 1999
},
"inactive_timeout": 1
}]]
)
-- ensure the request is rejected even this plugin doesn't
-- have check_schema method
ngx.status = code
core.log.warn("this is a warning message for test.")
}
}
--- request
GET /tg
--- error_code: 400
--- response_body
--- error_log eval
qr/please set the correct plugin_metadata for error-log-logger/
--- wait: 2
=== TEST 4: test unreachable server
--- config
location /tg {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/error-log-logger',
ngx.HTTP_PUT,
[[{
"tcp": {
"host": "127.0.0.1",
"port": 2999
},
"inactive_timeout": 1
}]]
)
ngx.sleep(2)
core.log.warn("this is a warning message for test.")
}
}
--- request
GET /tg
--- response_body
--- no_error_log eval
qr/\[Server\] receive data:.*this is a warning message for test./
--- wait: 3
=== TEST 5: log a warn level message
--- config
location /tg {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/error-log-logger',
ngx.HTTP_PUT,
[[{
"tcp": {
"host": "127.0.0.1",
"port": 1999
},
"inactive_timeout": 1
}]]
)
ngx.sleep(2)
core.log.warn("this is a warning message for test.")
}
}
--- request
GET /tg
--- response_body
--- error_log eval
qr/\[Server\] receive data:.*this is a warning message for test./
--- wait: 5
=== TEST 6: log an error level message
--- config
location /tg {
content_by_lua_block {
local core = require("apisix.core")
ngx.sleep(2)
core.log.error("this is an error message for test.")
}
}
--- request
GET /tg
--- response_body
--- error_log eval
qr/\[Server\] receive data:.*this is an error message for test./
--- wait: 5
=== TEST 7: log an info level message
--- config
location /tg {
content_by_lua_block {
local core = require("apisix.core")
ngx.sleep(2)
core.log.info("this is an info message for test.")
}
}
--- request
GET /tg
--- response_body
--- no_error_log eval
qr/\[Server\] receive data:.*this is an info message for test./
--- wait: 5
=== TEST 8: delete metadata for the plugin, recover to the default
--- config
location /tg {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/error-log-logger',
ngx.HTTP_DELETE)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /tg
--- response_body
passed
=== TEST 9: want to reload the plugin by route
--- config
location /tg {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"error-log-logger": {
"tcp": {
"host": "127.0.0.1",
"port": 1999
},
"inactive_timeout": 1
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello1"
}]]
)
-- reload
code, body = t('/apisix/admin/plugins/reload',
ngx.HTTP_PUT)
core.log.warn("this is a warning message for test.")
}
}
--- request
GET /tg
--- response_body
--- error_log eval
qr/please set the correct plugin_metadata for error-log-logger/
--- wait: 2
=== TEST 10: avoid sending stale error log
--- config
location /tg {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
core.log.warn("this is a warning message for test.")
local code, body = t('/apisix/admin/plugin_metadata/error-log-logger',
ngx.HTTP_PUT,
[[{
"tcp": {
"host": "127.0.0.1",
"port": 1999
},
"level": "ERROR",
"inactive_timeout": 1
}]]
)
ngx.sleep(2)
core.log.error("this is an error message for test.")
}
}
--- request
GET /tg
--- response_body
--- no_error_log eval
qr/\[Server\] receive data:.*this is a warning message for test./
--- error_log eval
qr/\[Server\] receive data:.*this is an error message for test./
--- wait: 5
=== TEST 11: delete the route
--- config
location /tg {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_DELETE)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /tg
--- response_body
passed
=== TEST 12: log a warn level message (schema compatibility testing)
--- config
location /tg {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/error-log-logger',
ngx.HTTP_PUT,
[[{
"tcp": {
"host": "127.0.0.1",
"port": 1999
},
"inactive_timeout": 1
}]]
)
ngx.sleep(2)
core.log.warn("this is a warning message for test.")
}
}
--- request
GET /tg
--- response_body
--- error_log eval
qr/\[Server\] receive data:.*this is a warning message for test./
--- wait: 5
=== TEST 13: log an error level message (schema compatibility testing)
--- config
location /tg {
content_by_lua_block {
local core = require("apisix.core")
ngx.sleep(2)
core.log.error("this is an error message for test.")
}
}
--- request
GET /tg
--- response_body
--- error_log eval
qr/\[Server\] receive data:.*this is an error message for test./
--- wait: 5
=== TEST 14: log an info level message (schema compatibility testing)
--- config
location /tg {
content_by_lua_block {
local core = require("apisix.core")
ngx.sleep(2)
core.log.info("this is an info message for test.")
}
}
--- request
GET /tg
--- response_body
--- no_error_log eval
qr/\[Server\] receive data:.*this is an info message for test./
--- wait: 5
=== TEST 15: delete metadata for the plugin, recover to the default (schema compatibility testing)
--- config
location /tg {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/error-log-logger',
ngx.HTTP_DELETE)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /tg
--- response_body
passed

View File

@@ -0,0 +1,341 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(2);
no_long_string();
no_root_location();
no_shuffle();
run_tests;
__DATA__
=== TEST 1: sanity
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.example-plugin")
local ok, err = plugin.check_schema({i = 1, s = "s", t = {1}})
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
done
=== TEST 2: missing args
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.example-plugin")
local ok, err = plugin.check_schema({s = "s", t = {1}})
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
property "i" is required
done
=== TEST 3: small then minimum
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.example-plugin")
local ok, err = plugin.check_schema({i = -1, s = "s", t = {1, 2}})
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
property "i" validation failed: expected -1 to be at least 0
done
=== TEST 4: wrong type of string
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.example-plugin")
local ok, err = plugin.check_schema({i = 1, s = 123, t = {1}})
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
property "s" validation failed: wrong type: expected string, got number
done
=== TEST 5: the size of array < minItems
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.example-plugin")
local ok, err = plugin.check_schema({i = 1, s = '123', t = {}})
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
property "t" validation failed: expect array to have at least 1 items
done
=== TEST 6: load plugins
--- config
location /t {
content_by_lua_block {
local plugins, err = require("apisix.plugin").load()
if not plugins then
ngx.say("failed to load plugins: ", err)
end
local encode_json = require("toolkit.json").encode
local conf = {}
local ctx = {}
for _, plugin in ipairs(plugins) do
ngx.say("plugin name: ", plugin.name,
" priority: ", plugin.priority)
plugin.rewrite(conf, ctx)
end
}
}
--- request
GET /t
--- response_body
plugin name: example-plugin priority: 0
--- yaml_config
deployment:
role: traditional
role_traditional:
config_provider: etcd
etcd:
host:
- "http://127.0.0.1:2379" # etcd address
prefix: "/apisix" # apisix configurations prefix
timeout: 1
plugins:
- example-plugin
- not-exist-plugin
--- grep_error_log eval
qr/\[error\].*/
--- grep_error_log_out eval
qr/module 'apisix.plugins.not-exist-plugin' not found/
=== TEST 7: filter plugins
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugin")
local all_plugins, err = plugin.load()
if not all_plugins then
ngx.say("failed to load plugins: ", err)
end
local filter_plugins = plugin.filter(nil, {
value = {
plugins = {
["example-plugin"] = {i = 1, s = "s", t = {1, 2}},
["new-plugin"] = {a = "a"},
}
},
modifiedIndex = 1,
})
local encode_json = require("toolkit.json").encode
for i = 1, #filter_plugins, 2 do
local plugin = filter_plugins[i]
local plugin_conf = filter_plugins[i + 1]
ngx.say("plugin [", plugin.name, "] config: ",
encode_json(plugin_conf))
end
}
}
--- request
GET /t
--- response_body
plugin [example-plugin] config: {"i":1,"s":"s","t":[1,2]}
=== TEST 8: set route(id: 1)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"example-plugin": {
"i": 11,
"ip": "127.0.0.1",
"port": 1981
}
},
"uri": "/server_port"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 9: hit route
--- request
GET /server_port
--- response_body_like eval
qr/1981/
=== TEST 10: set disable = true
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.example-plugin")
local ok, err = plugin.check_schema({
i = 1, s = "s", t = {1},
disable = true,
})
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
done
=== TEST 11: set disable = false
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.example-plugin")
local ok, err = plugin.check_schema({
i = 1, s = "s", t = {1},
disable = true,
})
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
done
=== TEST 12: body filter
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"example-plugin": {
"i": 11,
"ip": "127.0.0.1",
"port": 1981
}
},
"uri": "/server_port"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 13: hit route
--- request
GET /server_port
--- grep_error_log eval
qr/plugin (body_filter|delayed_body_filter) phase, eof: (false|true)/
--- grep_error_log_out
plugin body_filter phase, eof: false
plugin delayed_body_filter phase, eof: false
plugin body_filter phase, eof: true
plugin delayed_body_filter phase, eof: true

View File

@@ -0,0 +1,141 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
workers(3);
repeat_each(1);
no_long_string();
no_root_location();
no_shuffle();
log_level("info");
worker_connections(1024);
$ENV{"PATH"} = $ENV{PATH} . ":" . $ENV{TEST_NGINX_HTML_DIR};
add_block_preprocessor(sub {
my ($block) = @_;
$block->set_value("stream_conf_enable", 1);
if (!defined $block->extra_stream_config) {
my $stream_config = <<_EOC_;
server {
listen unix:\$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({})
}
}
_EOC_
$block->set_value("extra_stream_config", $stream_config);
}
my $unix_socket_path = $ENV{"TEST_NGINX_HTML_DIR"} . "/nginx.sock";
my $orig_extra_yaml_config = $block->extra_yaml_config // "";
my $cmd = $block->ext_plugin_cmd // "['sleep', '5s']";
my $extra_yaml_config = <<_EOC_;
ext-plugin:
path_for_test: $unix_socket_path
cmd: $cmd
_EOC_
$extra_yaml_config = $extra_yaml_config . $orig_extra_yaml_config;
$block->set_value("extra_yaml_config", $extra_yaml_config);
if (!$block->request) {
$block->set_value("request", "GET /t");
}
if (!$block->error_log) {
$block->set_value("no_error_log", "[error]\n[alert]");
}
});
run_tests;
__DATA__
=== TEST 1: sanity
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, message, res = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"plugins": {
"ext-plugin-pre-req": {"a":"b"}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(message)
}
}
--- response_body
passed
=== TEST 2: share conf token in different workers
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
local t = {}
for i = 1, 16 do
local th = assert(ngx.thread.spawn(function(i)
local httpc = http.new()
local res, err = httpc:request_uri(uri)
if not res then
ngx.log(ngx.ERR, err)
return
end
end, i))
table.insert(t, th)
end
for i, th in ipairs(t) do
ngx.thread.wait(th)
end
ngx.say("done")
}
}
--- response_body
done
--- grep_error_log eval
qr/fetch token from shared dict, token: 233/
--- grep_error_log_out eval
qr/(fetch token from shared dict, token: 233){1,}/

View File

@@ -0,0 +1,355 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_root_location();
no_shuffle();
add_block_preprocessor(sub {
my ($block) = @_;
$block->set_value("stream_conf_enable", 1);
if (!defined $block->extra_stream_config) {
my $stream_config = <<_EOC_;
server {
listen unix:\$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({})
}
}
_EOC_
$block->set_value("extra_stream_config", $stream_config);
}
my $unix_socket_path = $ENV{"TEST_NGINX_HTML_DIR"} . "/nginx.sock";
my $cmd = $block->ext_plugin_cmd // "['sleep', '5s']";
my $extra_yaml_config = <<_EOC_;
ext-plugin:
path_for_test: $unix_socket_path
cmd: $cmd
_EOC_
$block->set_value("extra_yaml_config", $extra_yaml_config);
if (!$block->request) {
$block->set_value("request", "GET /t");
}
if (!$block->error_log) {
$block->set_value("no_error_log", "[error]\n[alert]");
}
});
run_tests;
__DATA__
=== TEST 1: add route
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, message, res = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"plugins": {
"ext-plugin-pre-req": {
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(message)
}
}
--- response_body
passed
=== TEST 2: var
--- request
GET /hello?x=
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
local actions = {
{type = "var", name = "server_addr", result = "127.0.0.1"},
{type = "var", name = "remote_addr", result = "127.0.0.1"},
{type = "var", name = "route_id", result = "1"},
{type = "var", name = "arg_x", result = ""},
}
ext.go({extra_info = actions, stop = true})
}
}
--- error_code: 405
--- grep_error_log eval
qr/send extra info req successfully/
--- grep_error_log_out
send extra info req successfully
send extra info req successfully
send extra info req successfully
send extra info req successfully
=== TEST 3: ask nonexistent var
--- request
GET /hello
--- more_headers
X-Change: foo
X-Delete: foo
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
local actions = {
{type = "var", name = "erver_addr"},
}
ext.go({extra_info = actions, rewrite = true})
}
}
--- response_body
uri: /uri
host: localhost
x-add: bar
x-change: bar
x-real-ip: 127.0.0.1
--- grep_error_log eval
qr/send extra info req successfully/
--- grep_error_log_out
send extra info req successfully
=== TEST 4: network is down in the middle
--- request
GET /hello
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
local actions = {
{type = "var", name = "server_addr", result = "127.0.0.1"},
{type = "closed"},
}
ext.go({extra_info = actions, stop = true})
}
}
--- error_code: 503
--- error_log
failed to receive RPC_HTTP_REQ_CALL: closed
=== TEST 5: ask response body (not exist)
--- request
GET /hello
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
local actions = {
{type = "respbody", result = nil}
}
ext.go({extra_info = actions})
}
}
--- error_log: failed to read response body: not exits
=== TEST 6: add route with ext-plugin-post-resp
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, message, res = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/*",
"plugins": {
"ext-plugin-post-resp": {
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(message)
}
}
--- response_body
passed
=== TEST 7: ask var
--- request
GET /hello?x=
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
local actions = {
{type = "var", name = "server_addr", result = "127.0.0.1"},
{type = "var", name = "remote_addr", result = "127.0.0.1"},
{type = "var", name = "route_id", result = "1"},
{type = "var", name = "arg_x", result = ""},
}
ext.go({extra_info = actions})
}
}
--- grep_error_log eval
qr/send extra info req successfully/
--- grep_error_log_out
send extra info req successfully
send extra info req successfully
send extra info req successfully
send extra info req successfully
--- response_body
hello world
=== TEST 8: ask response body
--- request
GET /hello
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
local actions = {
{type = "respbody", result = "hello world\n"},
}
ext.go({extra_info = actions})
}
}
--- grep_error_log eval
qr/send extra info req successfully/
--- grep_error_log_out
send extra info req successfully
--- response_body
hello world
=== TEST 9: ask response body (chunked)
--- request
GET /hello_chunked
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
local actions = {
{type = "respbody", result = "hello world\n"},
}
ext.go({extra_info = actions})
}
}
--- grep_error_log eval
qr/send extra info req successfully/
--- grep_error_log_out
send extra info req successfully
--- response_body
hello world
=== TEST 10: ask request body (empty)
--- request
GET /hello
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
local actions = {
{type = "reqbody", result = nil}
}
ext.go({extra_info = actions})
}
}
=== TEST 11: ask request body
--- request
POST /hello
123
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
local actions = {
{type = "reqbody", result = "123"}
}
ext.go({extra_info = actions})
}
}

View File

@@ -0,0 +1,809 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_root_location();
no_shuffle();
log_level("info");
add_block_preprocessor(sub {
my ($block) = @_;
$block->set_value("stream_conf_enable", 1);
if (!defined $block->extra_stream_config) {
my $stream_config = <<_EOC_;
server {
listen unix:\$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({})
}
}
_EOC_
$block->set_value("extra_stream_config", $stream_config);
}
my $unix_socket_path = $ENV{"TEST_NGINX_HTML_DIR"} . "/nginx.sock";
my $cmd = $block->ext_plugin_cmd // "['sleep', '5s']";
my $extra_yaml_config = <<_EOC_;
ext-plugin:
path_for_test: $unix_socket_path
cmd: $cmd
_EOC_
$block->set_value("extra_yaml_config", $extra_yaml_config);
if (!$block->request) {
$block->set_value("request", "GET /t");
}
if (!$block->error_log) {
$block->set_value("no_error_log", "[error]\n[alert]");
}
});
run_tests;
__DATA__
=== TEST 1: add route
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, message, res = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"plugins": {
"ext-plugin-pre-req": {
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(message)
}
}
--- response_body
passed
=== TEST 2: stop
--- request
GET /hello
--- response_body chomp
cat
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({stop = true})
}
}
--- error_code: 405
--- response_headers
X-Resp: foo
X-Req: bar
=== TEST 3: check input
--- request
PUT /hello?xx=y&xx=z&&y=&&z
--- more_headers
X-Req: foo
X-Req: bar
X-Resp: cat
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({check_input = true})
}
}
=== TEST 4: check input (ipv6)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test_ipv6
t('/hello')
}
}
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({check_input_ipv6 = true})
}
}
--- listen_ipv6
=== TEST 5: rewrite
--- request
GET /hello
--- more_headers
X-Change: foo
X-Delete: foo
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({rewrite = true})
}
}
--- response_body
uri: /uri
host: localhost
x-add: bar
x-change: bar
x-real-ip: 127.0.0.1
=== TEST 6: rewrite host
--- request
GET /hello
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({rewrite_host = true})
}
}
--- response_body
uri: /uri
host: 127.0.0.1
x-real-ip: 127.0.0.1
=== TEST 7: rewrite args
--- request
GET /hello?c=foo&d=bar
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({rewrite_args = true})
}
}
--- response_body
uri: /plugin_proxy_rewrite_args
a: foo,bar
c: bar
=== TEST 8: proxy-rewrite + rewrite host
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, message, res = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"plugins": {
"proxy-rewrite": {
"host": "test.com"
},
"ext-plugin-post-req": {
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(message)
}
}
--- response_body
passed
=== TEST 9: hit
--- request
GET /hello
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({rewrite_host = true, check_input_rewrite_host = true})
}
}
--- response_body
uri: /uri
host: 127.0.0.1
x-real-ip: 127.0.0.1
=== TEST 10: proxy-rewrite + rewrite path
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, message, res = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"plugins": {
"proxy-rewrite": {
"uri": "/xxx"
},
"ext-plugin-post-req": {
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(message)
}
}
--- response_body
passed
=== TEST 11: hit
--- request
GET /hello
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({rewrite_host = true, check_input_rewrite_path = true})
}
}
--- response_body
uri: /uri
host: 127.0.0.1
x-real-ip: 127.0.0.1
=== TEST 12: proxy-rewrite + rewrite path with args
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, message, res = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"plugins": {
"proxy-rewrite": {
"uri": "/xxx?x=z"
},
"ext-plugin-post-req": {
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(message)
}
}
--- response_body
passed
=== TEST 13: hit
--- request
GET /hello
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({rewrite_args = true, check_input_rewrite_args = true})
}
}
--- response_body
uri: /plugin_proxy_rewrite_args
a: foo,bar
c: bar
x: z
=== TEST 14: rewrite args only
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, message, res = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/plugin_proxy_rewrite_args",
"plugins": {
"ext-plugin-post-req": {
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(message)
}
}
--- response_body
passed
=== TEST 15: hit
--- request
GET /plugin_proxy_rewrite_args
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({rewrite_args_only = true})
}
}
--- response_body
uri: /plugin_proxy_rewrite_args
a: foo,bar
c: bar
=== TEST 16: rewrite, bad path
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, message, res = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"plugins": {
"ext-plugin-post-req": {
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(message)
}
}
--- response_body
passed
=== TEST 17: hit
--- request
GET /hello
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({rewrite_bad_path = true})
}
}
--- error_log
undefined path in test server, uri: /plugin_proxy_rewrite_args%3Fa=2
--- error_code: 404
=== TEST 18: stop without setting status code
--- request
GET /hello
--- response_body chomp
cat
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({stop = true, check_default_status = true})
}
}
--- response_headers
X-Resp: foo
X-Req: bar
=== TEST 19: rewrite response header and call the upstream service
--- request
GET /hello
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({rewrite_resp_header = true})
}
}
--- response_body
plugin_proxy_rewrite_resp_header
--- response_headers
X-Resp: foo
X-Req: bar
=== TEST 20: rewrite non-important response headers and call the upstream service
--- request
GET /hello
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({rewrite_vital_resp_header = true})
}
}
--- response_body
plugin_proxy_rewrite_resp_header
--- response_headers
X-Resp: foo
X-Req: bar
Content-Type: text/plain
Content-Encoding:
=== TEST 21: trace stopped request
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, message, res = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"plugins": {
"zipkin": {
"endpoint": "http://127.0.0.1:1980/mock_zipkin",
"sample_ratio": 1
},
"ext-plugin-pre-req": {
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(message)
}
}
--- response_body
passed
=== TEST 22: hit
--- extra_init_by_lua
local prev_new = require("opentracing.tracer").new
local function new(...)
ngx.log(ngx.WARN, "tracer attached to stopped request")
return prev_new(...)
end
require("opentracing.tracer").new = new
--- request
GET /hello
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({stop = true})
}
}
--- error_code: 405
--- error_log
tracer attached to stopped request
=== TEST 23: set header with OpenResty API should invalidate the cache
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, message, res = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"plugins": {
"serverless-pre-function": {
"functions" : ["return function(conf, ctx)
require('apisix.core').request.headers();
ngx.req.set_header('X-Req', 'foo');
require('ngx.req').add_header('X-Req', 'bar');
ngx.req.set_header('X-Resp', 'cat');
end"]
},
"ext-plugin-post-req": {
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(message)
}
}
--- response_body
passed
=== TEST 24: check input
--- request
PUT /hello?xx=y&xx=z&&y=&&z
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({check_input = true})
}
}
=== TEST 25: rewrite same response headers and call the upstream service
--- request
GET /hello
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({rewrite_same_resp_header = true})
}
}
--- response_body
plugin_proxy_rewrite_resp_header
--- response_headers
X-Resp: foo
X-Req: bar
X-Same: one, two
=== TEST 26: stop with modify same response headers
--- request
GET /hello
--- response_body chomp
cat
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({stop = true})
}
}
--- error_code: 405
--- response_headers
X-Resp: foo
X-Req: bar
X-Same: one, two
=== TEST 27: add route
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, message, res = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"plugins": {
"ext-plugin-pre-req": {
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(message)
}
}
--- response_body
passed
=== TEST 28: test rewrite request body
--- request
GET /echo
--- response_body chomp
cat
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({rewrite_request_body = true})
}
}
--- response_body
abc

View File

@@ -0,0 +1,201 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_root_location();
no_shuffle();
add_block_preprocessor(sub {
my ($block) = @_;
$block->set_value("stream_conf_enable", 1);
if (!defined $block->extra_stream_config) {
my $stream_config = <<_EOC_;
server {
listen unix:\$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({})
}
}
_EOC_
$block->set_value("extra_stream_config", $stream_config);
}
my $unix_socket_path = $ENV{"TEST_NGINX_HTML_DIR"} . "/nginx.sock";
my $cmd = $block->ext_plugin_cmd // "['sleep', '5s']";
my $extra_yaml_config = <<_EOC_;
ext-plugin:
path_for_test: $unix_socket_path
cmd: $cmd
_EOC_
$block->set_value("extra_yaml_config", $extra_yaml_config);
if (!$block->request) {
$block->set_value("request", "GET /t");
}
if (!$block->error_log) {
$block->set_value("no_error_log", "[error]\n[alert]");
}
});
run_tests;
__DATA__
=== TEST 1: add route
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, message, res = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"plugins": {
"ext-plugin-pre-req": {
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(message)
}
}
--- response_body
passed
=== TEST 2: request body(text)
--- request
POST /hello
123
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
local actions = {
{type = "reqbody", result = "123"},
}
ext.go({extra_info = actions, stop = true, get_request_body = true})
}
}
--- error_code: 405
--- grep_error_log eval
qr/send extra info req successfully/
--- grep_error_log_out
send extra info req successfully
=== TEST 3: request body(x-www-form-urlencoded)
--- request
POST /hello
foo=bar
--- more_headers
Content-Type: application/x-www-form-urlencoded
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
local actions = {
{type = "reqbody", result = "foo=bar"},
}
ext.go({extra_info = actions, stop = true, get_request_body = true})
}
}
--- error_code: 405
--- grep_error_log eval
qr/send extra info req successfully/
--- grep_error_log_out
send extra info req successfully
=== TEST 4: request body(json)
--- request
POST /hello
{"foo":"bar"}
--- more_headers
Content-Type: application/json
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
local actions = {
{type = "reqbody", result = "{\"foo\":\"bar\"}"},
}
ext.go({extra_info = actions, stop = true, get_request_body = true})
}
}
--- error_code: 405
--- grep_error_log eval
qr/send extra info req successfully/
--- grep_error_log_out
send extra info req successfully
=== TEST 5: request body(nil)
--- request
POST /hello
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
local actions = {
{type = "reqbody", result = nil},
}
ext.go({extra_info = actions, stop = true, get_request_body = true})
}
}
--- error_code: 405
--- grep_error_log eval
qr/send extra info req successfully/
--- grep_error_log_out
send extra info req successfully

View File

@@ -0,0 +1,432 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_root_location();
no_shuffle();
add_block_preprocessor(sub {
my ($block) = @_;
$block->set_value("stream_conf_enable", 1);
if (!defined $block->extra_stream_config) {
my $stream_config = <<_EOC_;
server {
listen unix:\$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({})
}
}
_EOC_
$block->set_value("extra_stream_config", $stream_config);
}
my $unix_socket_path = $ENV{"TEST_NGINX_HTML_DIR"} . "/nginx.sock";
my $cmd = $block->ext_plugin_cmd // "['sleep', '5s']";
my $extra_yaml_config = <<_EOC_;
ext-plugin:
path_for_test: $unix_socket_path
cmd: $cmd
_EOC_
$block->set_value("extra_yaml_config", $extra_yaml_config);
if (!$block->request) {
$block->set_value("request", "GET /t");
}
if (!$block->error_log) {
$block->set_value("no_error_log", "[error]\n[alert]");
}
});
run_tests;
__DATA__
=== TEST 1: add route
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, message, res = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/*",
"plugins": {
"ext-plugin-post-resp": {
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(message)
}
}
--- response_body
passed
=== TEST 2: check input
--- request
GET /hello
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({check_input = true})
}
}
--- error_code: 200
--- response_body
hello world
=== TEST 3: modify body
--- request
GET /hello
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({modify_body = true})
}
}
--- error_code: 200
--- response_body chomp
cat
=== TEST 4: modify header
--- request
GET /hello
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({modify_header = true})
}
}
--- more_headers
resp-X-Runner: Go-runner
--- error_code: 200
--- response_headers
X-Runner: Test-Runner
Content-Type: application/json
--- response_body
hello world
=== TEST 5: modify same response headers
--- request
GET /hello
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({modify_header = true, same_header = true})
}
}
--- error_code: 200
--- response_headers
X-Same: one, two
--- response_body
hello world
=== TEST 6: modify status
--- request
GET /hello
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({modify_status = true})
}
}
--- error_code: 304
=== TEST 7: default allow_degradation
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, message, res = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"plugins": {
"ext-plugin-post-resp": {
"conf": [
{"name":"foo", "value":"bar"}
]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(message)
}
}
--- response_body
passed
=== TEST 8: ext-plugin-resp wrong, req reject
--- request
GET /hello
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock1;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({})
}
}
--- error_code: 503
--- error_log eval
qr/failed to connect to the unix socket/
=== TEST 9: open allow_degradation
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, message, res = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"plugins": {
"ext-plugin-post-req": {
"conf": [
{"name":"foo", "value":"bar"}
],
"allow_degradation": true
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(message)
}
}
--- response_body
passed
=== TEST 10: ext-plugin-resp wrong, req access
--- request
GET /hello
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock1;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({})
}
}
--- response_body
hello world
--- error_log eval
qr/Plugin Runner.*allow degradation/
=== TEST 11: add route: wrong upstream
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, message, res = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/*",
"plugins": {
"ext-plugin-post-resp": {
}
},
"upstream": {
"nodes": {
"127.0.0.1:3980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(message)
}
}
--- response_body
passed
=== TEST 12: request upstream failed
--- request
GET /hello
--- error_code: 502
--- error_log eval
qr/failed to request/
=== TEST 13: add route
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, message, res = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/*",
"plugins": {
"ext-plugin-post-resp": {
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(message)
}
}
--- response_body
passed
=== TEST 14: body_reader error
--- request
GET /hello1
--- more_headers
resp-Content-Length: 14
--- error_code: 502
--- error_log eval
qr/read response failed/
=== TEST 15: response chunked
--- request
GET /hello_chunked
--- error_code: 200
--- response_body
hello world
=== TEST 16: check upstream uri with args
--- request
GET /plugin_proxy_rewrite_args?aaa=bbb&ccc=ddd
--- error_code: 200
--- response_body
uri: /plugin_proxy_rewrite_args
aaa: bbb
ccc: ddd

View File

@@ -0,0 +1,23 @@
#!/usr/bin/env bash
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
echo "LISTEN $APISIX_LISTEN_ADDRESS"
echo "EXPIRE $APISIX_CONF_EXPIRE_TIME"
echo "MY_ENV_VAR $MY_ENV_VAR"
sleep "$1"
exit 111

View File

@@ -0,0 +1,23 @@
#!/usr/bin/env bash
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
term() {
eval sleep 1800
}
trap term SIGTERM
sleep 1800

View File

@@ -0,0 +1,713 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX;
my $nginx_binary = $ENV{'TEST_NGINX_BINARY'} || 'nginx';
my $version = eval { `$nginx_binary -V 2>&1` };
if ($version !~ m/\/apisix-nginx-module/) {
plan(skip_all => "apisix-nginx-module not installed");
} else {
plan('no_plan');
}
repeat_each(1);
no_long_string();
no_root_location();
no_shuffle();
log_level("info");
$ENV{"PATH"} = $ENV{PATH} . ":" . $ENV{TEST_NGINX_HTML_DIR};
add_block_preprocessor(sub {
my ($block) = @_;
$block->set_value("stream_conf_enable", 1);
if (!defined $block->extra_stream_config) {
my $stream_config = <<_EOC_;
server {
listen unix:\$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({})
}
}
_EOC_
$block->set_value("extra_stream_config", $stream_config);
}
my $unix_socket_path = $ENV{"TEST_NGINX_HTML_DIR"} . "/nginx.sock";
my $orig_extra_yaml_config = $block->extra_yaml_config // "";
my $cmd = $block->ext_plugin_cmd // "['sleep', '5s']";
my $extra_yaml_config = <<_EOC_;
ext-plugin:
path_for_test: $unix_socket_path
cmd: $cmd
_EOC_
$extra_yaml_config = $extra_yaml_config . $orig_extra_yaml_config;
$block->set_value("extra_yaml_config", $extra_yaml_config);
if (!$block->request) {
$block->set_value("request", "GET /t");
}
if (!$block->error_log) {
$block->set_value("no_error_log", "[error]\n[alert]");
}
});
run_tests;
__DATA__
=== TEST 1: sanity
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, message, res = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"plugins": {
"ext-plugin-pre-req": {"a":"b"},
"ext-plugin-post-req": {"c":"d"},
"ext-plugin-post-resp": {"e":"f"}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(message)
}
}
--- response_body
passed
=== TEST 2: hit
--- request
GET /hello
--- response_body
hello world
--- error_log
get conf token: 233
--- no_error_log
flush conf token lrucache
[error]
--- grep_error_log eval
qr/(sending|receiving) rpc type: \d data length:/
--- grep_error_log_out
sending rpc type: 1 data length:
receiving rpc type: 1 data length:
sending rpc type: 1 data length:
receiving rpc type: 1 data length:
sending rpc type: 2 data length:
receiving rpc type: 2 data length:
sending rpc type: 2 data length:
receiving rpc type: 2 data length:
sending rpc type: 1 data length:
receiving rpc type: 1 data length:
sending rpc type: 1 data length:
receiving rpc type: 1 data length:
sending rpc type: 2 data length:
receiving rpc type: 2 data length:
sending rpc type: 2 data length:
receiving rpc type: 2 data length:
sending rpc type: 1 data length:
receiving rpc type: 1 data length:
sending rpc type: 1 data length:
receiving rpc type: 1 data length:
sending rpc type: 4 data length:
receiving rpc type: 4 data length:
sending rpc type: 4 data length:
receiving rpc type: 4 data length:
=== TEST 3: header too short
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.header_too_short()
}
}
--- request
GET /hello
--- error_code: 503
--- error_log
failed to receive RPC_PREPARE_CONF
=== TEST 4: data too short
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.data_too_short()
}
}
--- request
GET /hello
--- error_code: 503
--- error_log
failed to receive RPC_PREPARE_CONF
=== TEST 5: not listen
--- extra_stream_config
--- request
GET /hello
--- error_code: 503
--- error_log
failed to connect to the unix socket
=== TEST 6: spawn runner
--- ext_plugin_cmd
["t/plugin/ext-plugin/runner.sh", "3600"]
--- config
location /t {
access_by_lua_block {
-- ensure the runner is spawned before the request finishes
ngx.sleep(0.1)
ngx.exit(200)
}
}
--- grep_error_log eval
qr/LISTEN unix:\S+/
--- grep_error_log_out eval
qr/LISTEN unix:.+\/nginx.sock/
--- error_log
EXPIRE 3600
=== TEST 7: respawn runner when it exited
--- ext_plugin_cmd
["t/plugin/ext-plugin/runner.sh", "0.1"]
--- config
location /t {
content_by_lua_block {
ngx.sleep(0.2)
}
}
--- error_log
runner exited with reason: exit, status: 111
respawn runner 3 seconds later with cmd: ["t/plugin/ext-plugin/runner.sh","0.1"]
=== TEST 8: flush cache when runner exited
--- ext_plugin_cmd
["t/plugin/ext-plugin/runner.sh", "0.4"]
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
local function r()
local httpc = http.new()
local res, err = httpc:request_uri(uri)
if not res then
ngx.log(ngx.ERR, err)
return
else
ngx.print(res.body)
end
end
r()
r()
ngx.sleep(0.5)
r()
}
}
--- response_body
hello world
hello world
hello world
--- grep_error_log eval
qr/(sending|receiving) rpc type: 1 data length:/
--- grep_error_log_out
sending rpc type: 1 data length:
receiving rpc type: 1 data length:
sending rpc type: 1 data length:
receiving rpc type: 1 data length:
sending rpc type: 1 data length:
receiving rpc type: 1 data length:
sending rpc type: 1 data length:
receiving rpc type: 1 data length:
sending rpc type: 1 data length:
receiving rpc type: 1 data length:
sending rpc type: 1 data length:
receiving rpc type: 1 data length:
sending rpc type: 1 data length:
receiving rpc type: 1 data length:
sending rpc type: 1 data length:
receiving rpc type: 1 data length:
sending rpc type: 1 data length:
receiving rpc type: 1 data length:
sending rpc type: 1 data length:
receiving rpc type: 1 data length:
sending rpc type: 1 data length:
receiving rpc type: 1 data length:
sending rpc type: 1 data length:
receiving rpc type: 1 data length:
--- error_log
flush conf token lrucache
flush conf token in shared dict
=== TEST 9: prepare conf
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, message, res = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"plugins": {
"ext-plugin-pre-req": {
"conf": [
{"name":"foo", "value":"bar"},
{"name":"cat", "value":"dog"}
]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(message)
}
}
--- response_body
passed
=== TEST 10: hit
--- request
GET /hello
--- response_body
hello world
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({with_conf = true, expect_key_pattern = [[^route#1#ext-plugin-pre-req#]]})
}
}
--- error_log eval
qr/get conf token: 233 conf: \[(\{"value":"bar","name":"foo"\}|\{"name":"foo","value":"bar"\}),(\{"value":"dog","name":"cat"\}|\{"name":"cat","value":"dog"\})\]/
=== TEST 11: handle error code
--- request
GET /hello
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({inject_error = true})
}
}
--- error_code: 503
--- error_log
failed to receive RPC_PREPARE_CONF: bad request
=== TEST 12: refresh token
--- request
GET /hello
--- response_body
hello world
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
if not package.loaded.count then
package.loaded.count = 1
else
package.loaded.count = package.loaded.count + 1
end
if package.loaded.count == 1 then
ext.go({no_token = true})
else
ext.go({with_conf = true})
end
}
}
--- error_log
refresh cache and try again
flush conf token in shared dict
=== TEST 13: runner can access the environment variable
--- main_config
env MY_ENV_VAR=foo;
--- ext_plugin_cmd
["t/plugin/ext-plugin/runner.sh", "3600"]
--- config
location /t {
access_by_lua_block {
-- ensure the runner is spawned before the request finishes
ngx.sleep(0.1)
ngx.exit(200)
}
}
--- error_log
MY_ENV_VAR foo
=== TEST 14: bad conf
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, message, res = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"plugins": {
"ext-plugin-pre-req": {
"conf": [
{"value":"bar"}
]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.say(message)
end
local code, message, res = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"plugins": {
"ext-plugin-post-req": {
"conf": [
{"name":"bar"}
]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.print(message)
end
}
}
--- response_body
{"error_msg":"failed to check the configuration of plugin ext-plugin-pre-req err: property \"conf\" validation failed: failed to validate item 1: property \"name\" is required"}
{"error_msg":"failed to check the configuration of plugin ext-plugin-post-req err: property \"conf\" validation failed: failed to validate item 1: property \"value\" is required"}
=== TEST 15: spawn runner which can't be terminated, ensure APISIX won't be blocked
--- ext_plugin_cmd
["t/plugin/ext-plugin/runner_can_not_terminated.sh"]
--- config
location /t {
return 200;
}
=== TEST 16: prepare conf with global rule
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, message, res = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.say(message)
return
end
local code, message, res = t.test('/apisix/admin/global_rules/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"ext-plugin-post-req": {
"conf": [
{"name":"foo", "value":"bar"},
{"name":"cat", "value":"dog"}
]
}
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(message)
}
}
--- response_body
passed
=== TEST 17: hit
--- request
GET /hello
--- response_body
hello world
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({with_conf = true, expect_key_pattern = [[^global_rule#1#ext-plugin-post-req#]]})
}
}
--- error_log eval
qr/get conf token: 233 conf: \[(\{"value":"bar","name":"foo"\}|\{"name":"foo","value":"bar"\}),(\{"value":"dog","name":"cat"\}|\{"name":"cat","value":"dog"\})\]/
=== TEST 18: clean global rule
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, message, res = t.test('/apisix/admin/global_rules/1',
ngx.HTTP_DELETE)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
}
}
=== TEST 19: default allow_degradation
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, message, res = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"plugins": {
"ext-plugin-post-req": {
"conf": [
{"name":"foo", "value":"bar"},
{"name":"cat", "value":"dog"}
]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(message)
}
}
--- response_body
passed
=== TEST 20: ext-plugin wrong, req reject
--- request
GET /hello
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock1;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({})
}
}
--- error_code: 503
--- error_log eval
qr/failed to connect to the unix socket/
=== TEST 21: open allow_degradation
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, message, res = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"plugins": {
"ext-plugin-post-req": {
"conf": [
{"name":"foo", "value":"bar"},
{"name":"cat", "value":"dog"}
],
"allow_degradation": true
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(message)
}
}
--- response_body
passed
=== TEST 22: ext-plugin wrong, req access
--- request
GET /hello
--- extra_stream_config
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock1;
content_by_lua_block {
local ext = require("lib.ext-plugin")
ext.go({})
}
}
--- response_body
hello world
--- error_log eval
qr/Plugin Runner.*allow degradation/

View File

@@ -0,0 +1,65 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX;
my $nginx_binary = $ENV{'TEST_NGINX_BINARY'} || 'nginx';
my $version = eval { `$nginx_binary -V 2>&1` };
if ($version !~ m/\/apisix-nginx-module/) {
plan(skip_all => "apisix-nginx-module not installed");
} else {
plan('no_plan');
}
repeat_each(1);
no_long_string();
no_root_location();
no_shuffle();
log_level("info");
add_block_preprocessor(sub {
my ($block) = @_;
my $cmd = $block->ext_plugin_cmd // "['sleep', '5s']";
my $extra_yaml_config = <<_EOC_;
ext-plugin:
cmd: $cmd
_EOC_
$block->set_value("extra_yaml_config", $extra_yaml_config);
if (!$block->request) {
$block->set_value("request", "GET /t");
}
if (!$block->error_log) {
$block->set_value("no_error_log", "[error]\n[alert]");
}
});
run_tests;
__DATA__
=== TEST 1: terminate spawn runner
--- ext_plugin_cmd
["t/plugin/ext-plugin/runner.sh", "3600"]
--- config
location /t {
return 200;
}
--- shutdown_error_log eval
qr/terminate runner \d+ with SIGTERM/

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,186 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
log_level('info');
no_root_location();
no_shuffle();
add_block_preprocessor(sub {
my ($block) = @_;
if (!$block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests();
__DATA__
=== TEST 1: vars rule with ! (set)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[=[{
"plugins": {
"fault-injection": {
"abort": {
"http_status": 403,
"body": "Fault Injection!\n",
"vars": [
[
"!AND",
["arg_name","==","jack"],
["arg_age","!","<",18]
]
]
}
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]=]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: vars rule with ! (hit)
--- request
GET /hello?name=jack&age=17
--- error_code: 403
--- response_body
Fault Injection!
=== TEST 3: vars rule with ! (miss)
--- request
GET /hello?name=jack&age=18
--- response_body
hello world
=== TEST 4: inject header config
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[=[{
"plugins": {
"fault-injection": {
"abort": {
"http_status": 200,
"headers" : {
"h1": "v1",
"h2": 2,
"h3": "$uri"
}
}
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]=]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 5: inject header
--- request
GET /hello
--- response_headers
h1: v1
h2: 2
h3: /hello
=== TEST 6: closing curly brace not should not be a part of variable
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[=[{
"plugins": {
"fault-injection": {
"abort": {
"http_status": 200,
"body": "{\"count\": $arg_count}"
}
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]=]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 7: test route
--- request
GET /hello?count=2
--- response_body chomp
{"count": 2}

View File

@@ -0,0 +1,169 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX;
my $nginx_binary = $ENV{'TEST_NGINX_BINARY'} || 'nginx';
my $version = eval { `$nginx_binary -V 2>&1` };
if ($version !~ m/\/apisix-nginx-module/) {
plan(skip_all => "apisix-nginx-module not installed");
} else {
plan('no_plan');
}
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (! $block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests;
__DATA__
=== TEST 1: prepare
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/file-logger',
ngx.HTTP_PUT,
[[{
"log_format": {
"host": "$host",
"client_ip": "$remote_addr"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"file-logger": {
"path": "file.log"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: cache file
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code = t("/hello", ngx.HTTP_GET)
assert(io.open("file.log", 'r'))
os.remove("file.log")
local code = t("/hello", ngx.HTTP_GET)
local _, err = io.open("file.log", 'r')
ngx.say(err)
}
}
--- response_body
file.log: No such file or directory
=== TEST 3: reopen file
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code = t("/hello", ngx.HTTP_GET)
assert(io.open("file.log", 'r'))
os.remove("file.log")
ngx.sleep(0.01) -- make sure last reopen file is expired
local process = require "ngx.process"
local resty_signal = require "resty.signal"
local pid = process.get_master_pid()
local ok, err = resty_signal.kill(pid, "USR1")
if not ok then
ngx.log(ngx.ERR, "failed to kill process of pid ", pid, ": ", err)
return
end
local code = t("/hello", ngx.HTTP_GET)
assert(code == 200)
-- file is reopened
local fd, err = io.open("file.log", 'r')
local msg
if not fd then
core.log.error("failed to open file: file.log, error info: ", err)
return
end
msg = fd:read()
local new_msg = core.json.decode(msg)
if new_msg.client_ip == '127.0.0.1' and new_msg.route_id == '1'
and new_msg.host == '127.0.0.1'
then
msg = "write file log success"
ngx.status = code
ngx.say(msg)
end
os.remove("file.log")
local code = t("/hello", ngx.HTTP_GET)
local _, err = io.open("file.log", 'r')
ngx.say(err)
}
}
--- response_body
write file log success
file.log: No such file or directory
--- grep_error_log eval
qr/reopen cached log file: file.log/
--- grep_error_log_out
reopen cached log file: file.log

View File

@@ -0,0 +1,340 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (! $block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests;
__DATA__
=== TEST 1: sanity
--- config
location /t {
content_by_lua_block {
local configs = {
-- full configuration
{
path = "file.log"
},
-- property "path" is required
{
path = nil
}
}
local plugin = require("apisix.plugins.file-logger")
for i = 1, #configs do
ok, err = plugin.check_schema(configs[i])
if err then
ngx.say(err)
else
ngx.say("done")
end
end
}
}
--- response_body_like
done
property "path" is required
=== TEST 2: add plugin metadata
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/file-logger',
ngx.HTTP_PUT,
[[{
"log_format": {
"host": "$host",
"client_ip": "$remote_addr"
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 3: add plugin
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"file-logger": {
"path": "file.log"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 4: verify plugin
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code = t("/hello", ngx.HTTP_GET)
local fd, err = io.open("file.log", 'r')
local msg
if not fd then
core.log.error("failed to open file: file.log, error info: ", err)
return
end
msg = fd:read()
local new_msg = core.json.decode(msg)
if new_msg.client_ip == '127.0.0.1' and new_msg.route_id == '1'
and new_msg.host == '127.0.0.1'
then
msg = "write file log success"
ngx.status = code
ngx.say(msg)
end
--- a new request is logged
t("/hello", ngx.HTTP_GET)
msg = fd:read("*l")
local new_msg = core.json.decode(msg)
if new_msg.client_ip == '127.0.0.1' and new_msg.route_id == '1'
and new_msg.host == '127.0.0.1'
then
msg = "write file log success"
ngx.say(msg)
end
}
}
--- response_body
write file log success
write file log success
=== TEST 5: failed to open the path
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"file-logger": {
"path": "/log/file.log"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
end
local code, messages = t("/hello", GET)
core.log.warn("messages: ", messages)
if code >= 300 then
ngx.status = code
end
}
}
--- error_log
failed to open file: /log/file.log, error info: /log/file.log: No such file or directory
=== TEST 6: log format in plugin
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
-- ensure the format is not set
t('/apisix/admin/plugin_metadata/file-logger',
ngx.HTTP_DELETE
)
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"file-logger": {
"path": "file.log",
"log_format": {
"host": "$host",
"client_ip": "$remote_addr"
}
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 7: verify plugin
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code = t("/hello", ngx.HTTP_GET)
local fd, err = io.open("file.log", 'r')
local msg
if not fd then
core.log.error("failed to open file: file.log, error info: ", err)
return
end
msg = fd:read()
local new_msg = core.json.decode(msg)
if new_msg.client_ip == '127.0.0.1' and new_msg.route_id == '1'
and new_msg.host == '127.0.0.1'
then
msg = "write file log success"
ngx.status = code
ngx.say(msg)
end
}
}
--- response_body
write file log success
=== TEST 8: add plugin metadata
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/file-logger',
ngx.HTTP_PUT,
[[{
"log_format": {
"host": "$host"
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 9: ensure config in plugin is prior to the one in plugin metadata
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code = t("/hello", ngx.HTTP_GET)
local fd, err = io.open("file.log", 'r')
local msg
if not fd then
core.log.error("failed to open file: file.log, error info: ", err)
return
end
msg = fd:read()
local new_msg = core.json.decode(msg)
if new_msg.client_ip == '127.0.0.1' and new_msg.route_id == '1'
and new_msg.host == '127.0.0.1'
then
msg = "write file log success"
ngx.status = code
ngx.say(msg)
end
}
}
--- response_body
write file log success

View File

@@ -0,0 +1,516 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (! $block->request) {
$block->set_value("request", "GET /t");
if (!$block->response_body) {
$block->set_value("response_body", "passed\n");
}
}
});
run_tests;
__DATA__
=== TEST 1: add plugin with 'include_resp_body' setting
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
-- delete plugin metadata for response body format
t('/apisix/admin/plugin_metadata/file-logger', ngx.HTTP_DELETE)
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"file-logger": {
"path": "file-with-resp-body.log",
"include_resp_body": true
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
=== TEST 2: verify plugin for file-logger with response
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code = t("/hello", ngx.HTTP_GET)
local fd, err = io.open("file-with-resp-body.log", 'r')
local msg
if not fd then
core.log.error("failed to open file: file-resp-check.log, error info: ", err)
return
end
-- note only for first line
msg = fd:read()
local new_msg = core.json.decode(msg)
ngx.status = code
if new_msg.response ~= nil and new_msg.response.body == "hello world\n" then
ngx.status = code
ngx.say('contain with target')
end
}
}
--- response_body
contain with target
=== TEST 3: check file-logger 'include_resp_body' with 'expr'
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"file-logger": {
"path": "file-with-resp-expr-body.log",
"include_resp_body": true,
"include_resp_body_expr": [
[
"arg_foo",
"==",
"bar"
]
]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
=== TEST 4: verify file-logger resp with expression of concern
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code = t("/hello?foo=bar", ngx.HTTP_GET)
local fd, err = io.open("file-with-resp-expr-body.log", 'r')
local msg
if not fd then
core.log.error("failed to open file: file-with-resp-expr-body.log, error info: ", err)
return
end
-- note only for first line
msg = fd:read()
local new_msg = core.json.decode(msg)
ngx.status = code
if new_msg.response ~= nil and new_msg.response.body == "hello world\n" then
ngx.status = code
ngx.say('contain target body hits with expr')
end
--- a new request is logged
t("/hello?name=pix", ngx.HTTP_GET)
msg = fd:read("*l")
local new_msg = core.json.decode(msg)
if new_msg.response.body == nil then
ngx.say('skip unconcern body')
end
}
}
--- response_body
contain target body hits with expr
skip unconcern body
=== TEST 5: add plugin metadata
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/file-logger',
ngx.HTTP_PUT,
[[{
"log_format": {
"host": "$host",
"client_ip": "$remote_addr",
"resp_body": "$resp_body"
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 6: add plugin
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"file-logger": {
"path": "file-with-resp-body2.log",
"include_resp_body": true
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 7: verify plugin
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code = t("/hello", ngx.HTTP_GET)
local fd, err = io.open("file-with-resp-body2.log", 'r')
local msg
if not fd then
core.log.error("failed to open file: file.log, error info: ", err)
return
end
msg = fd:read()
local new_msg = core.json.decode(msg)
if new_msg.resp_body == 'hello world\n'
then
msg = "write file log success"
ngx.status = code
ngx.say(msg)
end
}
}
--- response_body
write file log success
=== TEST 8: Add new configuration with match
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"file-logger": {
"path": "file-with-match.log",
"match": [
[
[ "arg_name","==","jack" ]
]
],
"log_format": {
"request": "$request"
}
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 9: Request match
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code = t("/hello?name=jack", ngx.HTTP_GET)
local fd, err = io.open("file-with-match.log", 'r')
if not fd then
core.log.error("failed to open file: file-with-match.log, error info: ", err)
return
end
local msg = fd:read()
local new_msg = core.json.decode(msg)
if new_msg.request == 'GET /hello?name=jack HTTP/1.1'
and new_msg.route_id == '1'
then
msg = "write file log success"
ngx.status = code
ngx.say(msg)
end
os.remove("file-with-match.log")
}
}
--- response_body
write file log success
=== TEST 10: Request not match
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code = t("/hello?name=tony", ngx.HTTP_GET)
local fd, err = io.open("file-with-match.log", 'r')
if not fd then
local msg = "not write file log"
ngx.say(msg)
return
end
}
}
--- response_body
not write file log
=== TEST 11: add plugin with 'include_req_body' setting
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
t('/apisix/admin/plugin_metadata/file-logger', ngx.HTTP_DELETE)
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"file-logger": {
"path": "file-with-req-body.log",
"include_req_body": true
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
=== TEST 12: verify plugin for file-logger with request
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code = t("/hello", ngx.HTTP_POST, "body-data")
local fd, err = io.open("file-with-req-body.log", 'r')
local msg
if not fd then
core.log.error("failed to open file: file-with-req-body.log, error info: ", err)
return
end
-- note only for first line
msg = fd:read()
local new_msg = core.json.decode(msg)
ngx.status = code
if new_msg.request ~= nil and new_msg.request.body == "body-data" then
ngx.status = code
ngx.say('contain with target')
end
}
}
--- response_body
contain with target
=== TEST 13: check file-logger 'include_req_body' with 'expr'
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"file-logger": {
"path": "file-with-req-expr-body.log",
"include_req_body": true,
"include_req_body_expr": [
[
"arg_log_body",
"==",
"yes"
]
]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1982": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
=== TEST 14: verify file-logger req with expression of concern
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code = t("/hello?log_body=yes",
ngx.HTTP_POST,
[[{"foo": "bar"}]]
)
local fd, err = io.open("file-with-req-expr-body.log", 'r')
local msg
if not fd then
core.log.error("failed to open file: file-with-req-expr-body.log, error info: ", err)
return
end
-- note only for first line
msg = fd:read()
local new_msg = core.json.decode(msg)
ngx.status = code
if new_msg.request ~= nil and new_msg.request.body ~= nil then
ngx.status = code
ngx.say('contain target body hits with expr')
end
--- a new request is logged
t("/hello?log_body=no", ngx.HTTP_POST, [[{"foo": "b"}]])
msg = fd:read("*l")
local new_msg = core.json.decode(msg)
if new_msg.request.body == nil then
ngx.say('skip unconcern body')
end
}
}
--- response_body
contain target body hits with expr
skip unconcern body

View File

@@ -0,0 +1,405 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests();
__DATA__
=== TEST 1: sanity
--- config
location /t {
content_by_lua_block {
local test_cases = {
{uri = "http://127.0.0.1:8199"},
{request_headers = {"test"}},
{uri = 3233},
{uri = "http://127.0.0.1:8199", request_headers = "test"},
{uri = "http://127.0.0.1:8199", request_method = "POST"},
{uri = "http://127.0.0.1:8199", request_method = "PUT"}
}
local plugin = require("apisix.plugins.forward-auth")
for _, case in ipairs(test_cases) do
local ok, err = plugin.check_schema(case)
ngx.say(ok and "done" or err)
end
}
}
--- response_body
done
property "uri" is required
property "uri" validation failed: wrong type: expected string, got number
property "request_headers" validation failed: wrong type: expected array, got string
done
property "request_method" validation failed: matches none of the enum values
=== TEST 2: setup route with plugin
--- config
location /t {
content_by_lua_block {
local data = {
{
url = "/apisix/admin/upstreams/u1",
data = [[{
"nodes": {
"127.0.0.1:1984": 1
},
"type": "roundrobin"
}]],
},
{
url = "/apisix/admin/routes/auth",
data = {
plugins = {
["serverless-pre-function"] = {
phase = "rewrite",
functions = {
[[return function(conf, ctx)
local core = require("apisix.core");
if core.request.header(ctx, "Authorization") == "111" then
core.response.exit(200);
end
end]],
[[return function(conf, ctx)
local core = require("apisix.core");
if core.request.header(ctx, "Authorization") == "222" then
core.response.set_header("X-User-ID", "i-am-an-user");
core.response.exit(200);
end
end]],
[[return function(conf, ctx)
local core = require("apisix.core");
if core.request.header(ctx, "Authorization") == "333" then
core.response.set_header("Location", "http://example.com/auth");
core.response.exit(403);
end
end]],
[[return function(conf, ctx)
local core = require("apisix.core");
if core.request.header(ctx, "Authorization") == "444" then
core.response.exit(403, core.request.headers(ctx));
end
end]],
[[return function(conf, ctx)
local core = require("apisix.core")
if core.request.get_method() == "POST" then
local req_body, err = core.request.get_body()
if err then
core.response.exit(400)
end
if req_body then
local data, err = core.json.decode(req_body)
if err then
core.response.exit(400)
end
if data["authorization"] == "555" then
core.response.set_header("X-User-ID", "i-am-an-user")
core.response.exit(200)
elseif data["authorization"] == "666" then
core.response.set_header("Location", "http://example.com/auth")
core.response.exit(403)
end
end
end
end]]
}
}
},
uri = "/auth"
},
},
{
url = "/apisix/admin/routes/echo",
data = [[{
"plugins": {
"serverless-pre-function": {
"phase": "rewrite",
"functions": [
"return function (conf, ctx)
local core = require(\"apisix.core\");
core.response.exit(200, core.request.headers(ctx));
end"
]
}
},
"uri": "/echo"
}]],
},
{
url = "/apisix/admin/routes/1",
data = [[{
"plugins": {
"forward-auth": {
"uri": "http://127.0.0.1:1984/auth",
"request_headers": ["Authorization"],
"upstream_headers": ["X-User-ID"],
"client_headers": ["Location"]
},
"proxy-rewrite": {
"uri": "/echo"
}
},
"upstream_id": "u1",
"uri": "/hello"
}]],
},
{
url = "/apisix/admin/routes/2",
data = [[{
"plugins": {
"forward-auth": {
"uri": "http://127.0.0.1:1984/auth",
"request_headers": ["Authorization"]
},
"proxy-rewrite": {
"uri": "/echo"
}
},
"upstream_id": "u1",
"uri": "/empty"
}]],
},
{
url = "/apisix/admin/routes/3",
data = [[{
"plugins": {
"forward-auth": {
"uri": "http://127.0.0.1:1984/auth",
"request_method": "POST",
"upstream_headers": ["X-User-ID"],
"client_headers": ["Location"]
},
"proxy-rewrite": {
"uri": "/echo"
}
},
"upstream_id": "u1",
"uri": "/ping"
}]],
},
{
url = "/apisix/admin/routes/4",
data = [[{
"plugins": {
"serverless-pre-function": {
"phase": "rewrite",
"functions" : ["return function() require(\"apisix.core\").response.exit(444); end"]
}
},
"upstream_id": "u1",
"uri": "/crashed-auth"
}]],
},
{
url = "/apisix/admin/routes/5",
data = [[{
"plugins": {
"forward-auth": {
"uri": "http://127.0.0.1:1984/crashed-auth",
"request_headers": ["Authorization"],
"upstream_headers": ["X-User-ID"],
"client_headers": ["Location"]
}
},
"upstream_id": "u1",
"uri": "/nodegr"
}]],
},
{
url = "/apisix/admin/routes/6",
data = [[{
"uri": "/hello",
"plugins": {
"forward-auth": {
"uri": "http://127.0.0.1:1984/crashed-auth",
"request_headers": ["Authorization"],
"upstream_headers": ["X-User-ID"],
"client_headers": ["Location"],
"allow_degradation": true
}
},
"upstream": {
"nodes": {
"test.com:1980": 1
},
"type": "roundrobin"
}
}]],
},
{
url = "/apisix/admin/routes/8",
data = [[{
"plugins": {
"forward-auth": {
"uri": "http://127.39.40.1:9999/auth",
"request_headers": ["Authorization"],
"upstream_headers": ["X-User-ID"],
"client_headers": ["Location"],
"status_on_error": 503,
"allow_degradation": false
},
"proxy-rewrite": {
"uri": "/echo"
}
},
"upstream_id": "u1",
"uri": "/onerror"
}]],
}
}
local t = require("lib.test_admin").test
for _, data in ipairs(data) do
local code, body = t(data.url, ngx.HTTP_PUT, data.data)
ngx.say(body)
end
}
}
--- response_body eval
"passed\n" x 10
=== TEST 3: hit route (test request_headers)
--- request
GET /hello
--- more_headers
Authorization: 111
--- response_body_like eval
qr/\"authorization\":\"111\"/
=== TEST 4: hit route (test upstream_headers)
--- request
GET /hello
--- more_headers
Authorization: 222
--- response_body_like eval
qr/\"x-user-id\":\"i-am-an-user\"/
=== TEST 5: hit route (test client_headers)
--- request
GET /hello
--- more_headers
Authorization: 333
--- error_code: 403
--- response_headers
Location: http://example.com/auth
=== TEST 6: hit route (check APISIX generated headers and ignore client headers)
--- request
GET /hello
--- more_headers
Authorization: 444
X-Forwarded-Host: apisix.apache.org
--- error_code: 403
--- response_body eval
qr/\"x-forwarded-proto\":\"http\"/ and qr/\"x-forwarded-method\":\"GET\"/ and
qr/\"x-forwarded-host\":\"localhost\"/ and qr/\"x-forwarded-uri\":\"\\\/hello\"/ and
qr/\"x-forwarded-for\":\"127.0.0.1\"/
--- response_body_unlike eval
qr/\"x-forwarded-host\":\"apisix.apache.org\"/
=== TEST 7: hit route (not send upstream headers)
--- request
GET /empty
--- more_headers
Authorization: 222
--- response_body_unlike eval
qr/\"x-user-id\":\"i-am-an-user\"/
=== TEST 8: hit route (not send client headers)
--- request
GET /empty
--- more_headers
Authorization: 333
--- error_code: 403
--- response_headers
!Location
=== TEST 9: hit route (test upstream_headers when use post method)
--- request
POST /ping
{"authorization": "555"}
--- response_body_like eval
qr/\"x-user-id\":\"i-am-an-user\"/
=== TEST 10: hit route (test client_headers when use post method)
--- request
POST /ping
{"authorization": "666"}
--- error_code: 403
--- response_headers
Location: http://example.com/auth
=== TEST 11: hit route (unavailable auth server, expect failure)
--- request
GET /nodegr
--- more_headers
Authorization: 111
--- error_code: 403
--- error_log
failed to process forward auth, err: closed
=== TEST 12: hit route (unavailable auth server, allow degradation)
--- request
GET /hello
--- more_headers
Authorization: 111
--- error_code: 200
=== TEST 13: Verify status_on_error
--- request
GET /onerror
--- more_headers
Authorization: 333
--- error_code: 503

View File

@@ -0,0 +1,185 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests();
__DATA__
=== TEST 1: setup route with plugin
--- config
location /t {
content_by_lua_block {
local data = {
{
url = "/apisix/admin/upstreams/u1",
data = [[{
"nodes": {
"127.0.0.1:1984": 1
},
"type": "roundrobin"
}]],
},
{
url = "/apisix/admin/routes/auth",
data = {
plugins = {
["serverless-pre-function"] = {
phase = "rewrite",
functions = {
[[return function(conf, ctx)
local core = require("apisix.core");
local token = "token-headers-test";
if core.request.header(ctx, "Authorization") == token then
if core.request.get_method() == "POST" then
if core.request.header(ctx, "Content-Length") or
core.request.header(ctx, "Transfer-Encoding") or
core.request.header(ctx, "Content-Encoding") then
core.response.exit(200)
else
core.response.exit(403)
end
else
if core.request.header(ctx, "Content-Length") or
core.request.header(ctx, "Transfer-Encoding") or
core.request.header(ctx, "Content-Encoding") then
core.response.exit(403)
else
core.response.exit(200)
end
end
end
end]]
}
}
},
uri = "/auth"
},
},
{
url = "/apisix/admin/routes/echo",
data = [[{
"plugins": {
"serverless-pre-function": {
"phase": "rewrite",
"functions": [
"return function (conf, ctx)
local core = require(\"apisix.core\");
core.response.exit(200, core.request.headers(ctx));
end"
]
}
},
"uri": "/echo"
}]],
},
{
url = "/apisix/admin/routes/1",
data = [[{
"plugins": {
"forward-auth": {
"uri": "http://127.0.0.1:1984/auth",
"request_headers": ["Authorization"],
"request_method": "POST"
},
"proxy-rewrite": {
"uri": "/echo"
}
},
"upstream_id": "u1",
"uri": "/verify-auth-post"
}]],
},
{
url = "/apisix/admin/routes/2",
data = [[{
"plugins": {
"forward-auth": {
"uri": "http://127.0.0.1:1984/auth",
"request_headers": ["Authorization"],
"request_method": "GET"
},
"proxy-rewrite": {
"uri": "/echo"
}
},
"upstream_id": "u1",
"uri": "/verify-auth-get"
}]],
}
}
local t = require("lib.test_admin").test
for _, data in ipairs(data) do
local code, body = t(data.url, ngx.HTTP_PUT, data.data)
ngx.say(body)
end
}
}
--- response_body eval
"passed\n" x 5
=== TEST 2: verify auth server forward headers for request_method=GET
--- request
GET /verify-auth-get
--- more_headers
Authorization: token-headers-test
--- error_code: 200
=== TEST 3: verify auth server forward headers for request_method=POST for GET upstream
--- request
GET /verify-auth-post
--- more_headers
Authorization: token-headers-test
--- error_code: 200
=== TEST 4: verify auth server forward headers for request_method=POST
--- request
POST /verify-auth-post
{"authorization": "token-headers-test"}
--- more_headers
Authorization: token-headers-test
--- error_code: 200
=== TEST 5: verify auth server forward headers for request_method=GET for POST upstream
--- request
POST /verify-auth-get
{"authorization": "token-headers-test"}
--- more_headers
Authorization: token-headers-test
--- error_code: 200

View File

@@ -0,0 +1,832 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests();
__DATA__
=== TEST 1: Full configuration verification (Auth File)
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.google-cloud-logging")
local ok, err = plugin.check_schema({
auth_file = "/path/to/apache/apisix/auth.json",
resource = {
type = "global"
},
scope = {
"https://www.googleapis.com/auth/logging.admin"
},
log_id = "syslog",
max_retry_count = 0,
retry_delay = 1,
buffer_duration = 60,
inactive_timeout = 10,
batch_max_size = 100,
})
if not ok then
ngx.say(err)
else
ngx.say("passed")
end
}
}
--- response_body
passed
=== TEST 2: Full configuration verification (Auth Config)
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.google-cloud-logging")
local ok, err = plugin.check_schema({
auth_config = {
client_email = "email@apisix.iam.gserviceaccount.com",
private_key = "private_key",
project_id = "apisix",
token_uri = "http://127.0.0.1:1980/token",
},
resource = {
type = "global"
},
scope = {
"https://www.googleapis.com/auth/logging.admin"
},
log_id = "syslog",
max_retry_count = 0,
retry_delay = 1,
buffer_duration = 60,
inactive_timeout = 10,
batch_max_size = 100,
})
if not ok then
ngx.say(err)
else
ngx.say("passed")
end
}
}
--- response_body
passed
=== TEST 3: Basic configuration verification (Auth File)
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.google-cloud-logging")
local ok, err = plugin.check_schema({
auth_file = "/path/to/apache/apisix/auth.json",
})
if not ok then
ngx.say(err)
else
ngx.say("passed")
end
}
}
--- response_body
passed
=== TEST 4: Basic configuration verification (Auth Config)
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.google-cloud-logging")
local ok, err = plugin.check_schema({
auth_config = {
client_email = "email@apisix.iam.gserviceaccount.com",
private_key = "private_key",
project_id = "apisix",
token_uri = "http://127.0.0.1:1980/token",
},
})
if not ok then
ngx.say(err)
else
ngx.say("passed")
end
}
}
--- response_body
passed
=== TEST 5: auth configure undefined
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.google-cloud-logging")
local ok, err = plugin.check_schema({
log_id = "syslog",
max_retry_count = 0,
retry_delay = 1,
buffer_duration = 60,
inactive_timeout = 10,
batch_max_size = 100,
})
if not ok then
ngx.say(err)
else
ngx.say("passed")
end
}
}
--- response_body
value should match only one schema, but matches none
=== TEST 6: set route (identity authentication failed)
--- config
location /t {
content_by_lua_block {
local config = {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["google-cloud-logging"] = {
auth_config = {
client_email = "email@apisix.iam.gserviceaccount.com",
private_key = [[
-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBAKeXgPvU/dAfVhOPk5BTBXCaOXy/0S3mY9VHyqvWZBJ97g6tGbLZ
psn6Gw0wC4mxDfEY5ER4YwU1NWCVtIr1XxcCAwEAAQJADkoowVBD4/8IA9r2JhQu
Ho/H3w8r8tH2KTVZ3pUFK15WGJf8vCF9LznVNKCP0X1NMLGvf4yRELx8jjpwJztI
gQIhANdWaJ3AGftJNaF5qXWwniFP1BcyCPSzn3q0rn19NhyHAiEAxz0HN8Yd+7vR
pi0w/L2I/2nLqgPFtqSGpL2KkJYcXPECIQCdM/PD1k4haNzCOXNA++M1JnYLSPfI
zKkMh4MrEZHDWQIhAKasRiKBaUnTCIJ04bs9L6NDtO4Ic9jj8ANW0Nk9yoJxAiAA
tBXLQH7fw5H8RaxBN91yQUZombw6JnRBXKKohWHZ3Q==
-----END RSA PRIVATE KEY-----]],
project_id = "apisix",
token_uri = "http://127.0.0.1:1980/google/logging/token",
scope = {
"https://apisix.apache.org/logs:admin"
},
entries_uri = "http://127.0.0.1:1980/google/logging/entries",
},
inactive_timeout = 1,
batch_max_size = 1,
}
}
}
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, config)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 7: test route (identity authentication failed)
--- request
GET /hello
--- wait: 2
--- response_body
hello world
--- grep_error_log eval
qr/\{\"error\"\:\"[\w+\s+]*\"\}/
--- grep_error_log_out
{"error":"identity authentication failed"}
--- error_log
Batch Processor[google-cloud-logging] failed to process entries
Batch Processor[google-cloud-logging] exceeded the max_retry_count
=== TEST 8: set route (no access to this scopes)
--- config
location /t {
content_by_lua_block {
local config = {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["google-cloud-logging"] = {
auth_config = {
client_email = "email@apisix.iam.gserviceaccount.com",
private_key = [[
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDDzrFwnA3EvYyR
aeMgaLD3hBjvxKrz10uox1X8q7YYhf2ViRtLRUMa2bEMYksE5hbhwpNf6mKAnLOC
UuAT6cPPdUl/agKpJXviBPIR2LuzD17WsLJHp1HxUDssSkgfCaGcOGGNfLUhhIpF
2JUctLmxiZoAZySlSjcwupSuDJ0aPm0XO8r9H8Qu5kF2Vkz5e5bFivLTmvzrQTe4
v5V1UI6hThElCSeUmdNF3uG3wopxlvq4zXgLTnuLbrNf/Gc4mlpV+UDgTISj32Ep
AB2vxKEbvQw4ti8YJnGXWjxLerhfrszFw+V8lpeduiDYA44ZFoVqvzxeIsVZNtcw
Iu7PvEPNAgMBAAECggEAVpyN9m7A1F631/aLheFpLgMbeKt4puV7zQtnaJ2XrZ9P
PR7pmNDpTu4uF3k/D8qrIm+L+uhVa+hkquf3wDct6w1JVnfQ93riImbnoKdK13ic
DcEZCwLjByfjFMNCxZ/gAZca55fbExlqhFy6EHmMjhB8s2LsXcTHRuGxNI/Vyi49
sxECibe0U53aqdJbVWrphIS67cpwl4TUkN6mrHsNuDYNJ9dgkpapoqp4FTFQsBqC
afOK5qgJ68dWZ47FBUng+AZjdCncqAIuJxxItGVQP6YPsFs+OXcivIVHJr363TpC
l85FfdvqWV5OGBbwSKhNwiTNUVvfSQVmtURGWG/HbQKBgQD4gZ1z9+Lx19kT9WTz
lw93lxso++uhAPDTKviyWSRoEe5aN3LCd4My+/Aj+sk4ON/s2BV3ska5Im93j+vC
rCv3uPn1n2jUhWuJ3bDqipeTW4n/CQA2m/8vd26TMk22yOkkqw2MIA8sjJ//SD7g
tdG7up6DgGMP4hgbO89uGU7DAwKBgQDJtkKd0grh3u52Foeh9YaiAgYRwc65IE16
UyD1OJxIuX/dYQDLlo5KyyngFa1ZhWIs7qC7r3xXH+10kfJY+Q+5YMjmZjlL8SR1
Ujqd02R9F2//6OeswyReachJZbZdtiEw3lPa4jVFYfhSe0M2ZPxMwvoXb25eyCNI
1lYjSKq87wKBgHnLTNghjeDp4UKe6rNYPgRm0rDrhziJtX5JeUov1mALKb6dnmkh
GfRK9g8sQqKDfXwfC6Z2gaMK9YaryujGaWYoCpoPXtmJ6oLPXH4XHuLh4mhUiP46
xn8FEfSimuQS4/FMxH8A128GHQSI7AhGFFzlwfrBWcvXC+mNDsTvMmLxAoGARc+4
upppfccETQZ7JsitMgD1TMwA2f2eEwoWTAitvlXFNT9PYSbYVHaAJbga6PLLCbYF
FzAjHpxEOKYSdEyu7n/ayDL0/Z2V+qzc8KarDsg/0RgwppBbU/nUgeKb/U79qcYo
y4ai3UKNCS70Ei1dTMvmdpnwXwlxfNIBufB6dy0CgYBMYq9Lc31GkC6PcGEEbx6W
vjImOadWZbuOVnvEQjb5XCdcOsWsMcg96PtoeuyyHmhnEF1GsMzcIdQv/PHrvYpK
Yp8D0aqsLEgwGrJQER26FPpKmyIwvcL+nm6q5W31PnU9AOC/WEkB6Zs58hsMzD2S
kEJQcmfVew5mFXyxuEn3zA==
-----END PRIVATE KEY-----]],
project_id = "apisix",
token_uri = "http://127.0.0.1:1980/google/logging/token",
entries_uri = "http://127.0.0.1:1980/google/logging/entries",
},
inactive_timeout = 1,
batch_max_size = 1,
}
}
}
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, config)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 9: test route (no access to this scopes)
--- request
GET /hello
--- wait: 2
--- response_body
hello world
--- grep_error_log eval
qr/\{\"error\"\:\"[\w+\s+]*\"\}/
--- grep_error_log_out
{"error":"no access to this scopes"}
--- error_log
Batch Processor[google-cloud-logging] failed to process entries
Batch Processor[google-cloud-logging] exceeded the max_retry_count
=== TEST 10: set route (succeed write)
--- config
location /t {
content_by_lua_block {
local config = {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["google-cloud-logging"] = {
auth_config = {
client_email = "email@apisix.iam.gserviceaccount.com",
private_key = [[
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDDzrFwnA3EvYyR
aeMgaLD3hBjvxKrz10uox1X8q7YYhf2ViRtLRUMa2bEMYksE5hbhwpNf6mKAnLOC
UuAT6cPPdUl/agKpJXviBPIR2LuzD17WsLJHp1HxUDssSkgfCaGcOGGNfLUhhIpF
2JUctLmxiZoAZySlSjcwupSuDJ0aPm0XO8r9H8Qu5kF2Vkz5e5bFivLTmvzrQTe4
v5V1UI6hThElCSeUmdNF3uG3wopxlvq4zXgLTnuLbrNf/Gc4mlpV+UDgTISj32Ep
AB2vxKEbvQw4ti8YJnGXWjxLerhfrszFw+V8lpeduiDYA44ZFoVqvzxeIsVZNtcw
Iu7PvEPNAgMBAAECggEAVpyN9m7A1F631/aLheFpLgMbeKt4puV7zQtnaJ2XrZ9P
PR7pmNDpTu4uF3k/D8qrIm+L+uhVa+hkquf3wDct6w1JVnfQ93riImbnoKdK13ic
DcEZCwLjByfjFMNCxZ/gAZca55fbExlqhFy6EHmMjhB8s2LsXcTHRuGxNI/Vyi49
sxECibe0U53aqdJbVWrphIS67cpwl4TUkN6mrHsNuDYNJ9dgkpapoqp4FTFQsBqC
afOK5qgJ68dWZ47FBUng+AZjdCncqAIuJxxItGVQP6YPsFs+OXcivIVHJr363TpC
l85FfdvqWV5OGBbwSKhNwiTNUVvfSQVmtURGWG/HbQKBgQD4gZ1z9+Lx19kT9WTz
lw93lxso++uhAPDTKviyWSRoEe5aN3LCd4My+/Aj+sk4ON/s2BV3ska5Im93j+vC
rCv3uPn1n2jUhWuJ3bDqipeTW4n/CQA2m/8vd26TMk22yOkkqw2MIA8sjJ//SD7g
tdG7up6DgGMP4hgbO89uGU7DAwKBgQDJtkKd0grh3u52Foeh9YaiAgYRwc65IE16
UyD1OJxIuX/dYQDLlo5KyyngFa1ZhWIs7qC7r3xXH+10kfJY+Q+5YMjmZjlL8SR1
Ujqd02R9F2//6OeswyReachJZbZdtiEw3lPa4jVFYfhSe0M2ZPxMwvoXb25eyCNI
1lYjSKq87wKBgHnLTNghjeDp4UKe6rNYPgRm0rDrhziJtX5JeUov1mALKb6dnmkh
GfRK9g8sQqKDfXwfC6Z2gaMK9YaryujGaWYoCpoPXtmJ6oLPXH4XHuLh4mhUiP46
xn8FEfSimuQS4/FMxH8A128GHQSI7AhGFFzlwfrBWcvXC+mNDsTvMmLxAoGARc+4
upppfccETQZ7JsitMgD1TMwA2f2eEwoWTAitvlXFNT9PYSbYVHaAJbga6PLLCbYF
FzAjHpxEOKYSdEyu7n/ayDL0/Z2V+qzc8KarDsg/0RgwppBbU/nUgeKb/U79qcYo
y4ai3UKNCS70Ei1dTMvmdpnwXwlxfNIBufB6dy0CgYBMYq9Lc31GkC6PcGEEbx6W
vjImOadWZbuOVnvEQjb5XCdcOsWsMcg96PtoeuyyHmhnEF1GsMzcIdQv/PHrvYpK
Yp8D0aqsLEgwGrJQER26FPpKmyIwvcL+nm6q5W31PnU9AOC/WEkB6Zs58hsMzD2S
kEJQcmfVew5mFXyxuEn3zA==
-----END PRIVATE KEY-----]],
project_id = "apisix",
token_uri = "http://127.0.0.1:1980/google/logging/token",
scope = {
"https://apisix.apache.org/logs:admin"
},
entries_uri = "http://127.0.0.1:1980/google/logging/entries",
},
inactive_timeout = 1,
batch_max_size = 1,
}
}
}
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, config)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 11: test route(succeed write)
--- request
GET /hello
--- wait: 2
--- response_body
hello world
=== TEST 12: set route (customize auth type)
--- config
location /t {
content_by_lua_block {
local config = {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["google-cloud-logging"] = {
auth_config = {
client_email = "email@apisix.iam.gserviceaccount.com",
private_key = [[
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDDzrFwnA3EvYyR
aeMgaLD3hBjvxKrz10uox1X8q7YYhf2ViRtLRUMa2bEMYksE5hbhwpNf6mKAnLOC
UuAT6cPPdUl/agKpJXviBPIR2LuzD17WsLJHp1HxUDssSkgfCaGcOGGNfLUhhIpF
2JUctLmxiZoAZySlSjcwupSuDJ0aPm0XO8r9H8Qu5kF2Vkz5e5bFivLTmvzrQTe4
v5V1UI6hThElCSeUmdNF3uG3wopxlvq4zXgLTnuLbrNf/Gc4mlpV+UDgTISj32Ep
AB2vxKEbvQw4ti8YJnGXWjxLerhfrszFw+V8lpeduiDYA44ZFoVqvzxeIsVZNtcw
Iu7PvEPNAgMBAAECggEAVpyN9m7A1F631/aLheFpLgMbeKt4puV7zQtnaJ2XrZ9P
PR7pmNDpTu4uF3k/D8qrIm+L+uhVa+hkquf3wDct6w1JVnfQ93riImbnoKdK13ic
DcEZCwLjByfjFMNCxZ/gAZca55fbExlqhFy6EHmMjhB8s2LsXcTHRuGxNI/Vyi49
sxECibe0U53aqdJbVWrphIS67cpwl4TUkN6mrHsNuDYNJ9dgkpapoqp4FTFQsBqC
afOK5qgJ68dWZ47FBUng+AZjdCncqAIuJxxItGVQP6YPsFs+OXcivIVHJr363TpC
l85FfdvqWV5OGBbwSKhNwiTNUVvfSQVmtURGWG/HbQKBgQD4gZ1z9+Lx19kT9WTz
lw93lxso++uhAPDTKviyWSRoEe5aN3LCd4My+/Aj+sk4ON/s2BV3ska5Im93j+vC
rCv3uPn1n2jUhWuJ3bDqipeTW4n/CQA2m/8vd26TMk22yOkkqw2MIA8sjJ//SD7g
tdG7up6DgGMP4hgbO89uGU7DAwKBgQDJtkKd0grh3u52Foeh9YaiAgYRwc65IE16
UyD1OJxIuX/dYQDLlo5KyyngFa1ZhWIs7qC7r3xXH+10kfJY+Q+5YMjmZjlL8SR1
Ujqd02R9F2//6OeswyReachJZbZdtiEw3lPa4jVFYfhSe0M2ZPxMwvoXb25eyCNI
1lYjSKq87wKBgHnLTNghjeDp4UKe6rNYPgRm0rDrhziJtX5JeUov1mALKb6dnmkh
GfRK9g8sQqKDfXwfC6Z2gaMK9YaryujGaWYoCpoPXtmJ6oLPXH4XHuLh4mhUiP46
xn8FEfSimuQS4/FMxH8A128GHQSI7AhGFFzlwfrBWcvXC+mNDsTvMmLxAoGARc+4
upppfccETQZ7JsitMgD1TMwA2f2eEwoWTAitvlXFNT9PYSbYVHaAJbga6PLLCbYF
FzAjHpxEOKYSdEyu7n/ayDL0/Z2V+qzc8KarDsg/0RgwppBbU/nUgeKb/U79qcYo
y4ai3UKNCS70Ei1dTMvmdpnwXwlxfNIBufB6dy0CgYBMYq9Lc31GkC6PcGEEbx6W
vjImOadWZbuOVnvEQjb5XCdcOsWsMcg96PtoeuyyHmhnEF1GsMzcIdQv/PHrvYpK
Yp8D0aqsLEgwGrJQER26FPpKmyIwvcL+nm6q5W31PnU9AOC/WEkB6Zs58hsMzD2S
kEJQcmfVew5mFXyxuEn3zA==
-----END PRIVATE KEY-----]],
project_id = "apisix",
token_uri = "http://127.0.0.1:1980/google/logging/token?token_type=Basic",
scope = {
"https://apisix.apache.org/logs:admin"
},
entries_uri = "http://127.0.0.1:1980/google/logging/entries?token_type=Basic",
},
inactive_timeout = 1,
batch_max_size = 1,
}
}
}
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, config)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 13: test route(customize auth type)
--- request
GET /hello
--- wait: 2
--- response_body
hello world
=== TEST 14: set route (customize auth type error)
--- config
location /t {
content_by_lua_block {
local config = {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["google-cloud-logging"] = {
auth_config = {
client_email = "email@apisix.iam.gserviceaccount.com",
private_key = [[
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDDzrFwnA3EvYyR
aeMgaLD3hBjvxKrz10uox1X8q7YYhf2ViRtLRUMa2bEMYksE5hbhwpNf6mKAnLOC
UuAT6cPPdUl/agKpJXviBPIR2LuzD17WsLJHp1HxUDssSkgfCaGcOGGNfLUhhIpF
2JUctLmxiZoAZySlSjcwupSuDJ0aPm0XO8r9H8Qu5kF2Vkz5e5bFivLTmvzrQTe4
v5V1UI6hThElCSeUmdNF3uG3wopxlvq4zXgLTnuLbrNf/Gc4mlpV+UDgTISj32Ep
AB2vxKEbvQw4ti8YJnGXWjxLerhfrszFw+V8lpeduiDYA44ZFoVqvzxeIsVZNtcw
Iu7PvEPNAgMBAAECggEAVpyN9m7A1F631/aLheFpLgMbeKt4puV7zQtnaJ2XrZ9P
PR7pmNDpTu4uF3k/D8qrIm+L+uhVa+hkquf3wDct6w1JVnfQ93riImbnoKdK13ic
DcEZCwLjByfjFMNCxZ/gAZca55fbExlqhFy6EHmMjhB8s2LsXcTHRuGxNI/Vyi49
sxECibe0U53aqdJbVWrphIS67cpwl4TUkN6mrHsNuDYNJ9dgkpapoqp4FTFQsBqC
afOK5qgJ68dWZ47FBUng+AZjdCncqAIuJxxItGVQP6YPsFs+OXcivIVHJr363TpC
l85FfdvqWV5OGBbwSKhNwiTNUVvfSQVmtURGWG/HbQKBgQD4gZ1z9+Lx19kT9WTz
lw93lxso++uhAPDTKviyWSRoEe5aN3LCd4My+/Aj+sk4ON/s2BV3ska5Im93j+vC
rCv3uPn1n2jUhWuJ3bDqipeTW4n/CQA2m/8vd26TMk22yOkkqw2MIA8sjJ//SD7g
tdG7up6DgGMP4hgbO89uGU7DAwKBgQDJtkKd0grh3u52Foeh9YaiAgYRwc65IE16
UyD1OJxIuX/dYQDLlo5KyyngFa1ZhWIs7qC7r3xXH+10kfJY+Q+5YMjmZjlL8SR1
Ujqd02R9F2//6OeswyReachJZbZdtiEw3lPa4jVFYfhSe0M2ZPxMwvoXb25eyCNI
1lYjSKq87wKBgHnLTNghjeDp4UKe6rNYPgRm0rDrhziJtX5JeUov1mALKb6dnmkh
GfRK9g8sQqKDfXwfC6Z2gaMK9YaryujGaWYoCpoPXtmJ6oLPXH4XHuLh4mhUiP46
xn8FEfSimuQS4/FMxH8A128GHQSI7AhGFFzlwfrBWcvXC+mNDsTvMmLxAoGARc+4
upppfccETQZ7JsitMgD1TMwA2f2eEwoWTAitvlXFNT9PYSbYVHaAJbga6PLLCbYF
FzAjHpxEOKYSdEyu7n/ayDL0/Z2V+qzc8KarDsg/0RgwppBbU/nUgeKb/U79qcYo
y4ai3UKNCS70Ei1dTMvmdpnwXwlxfNIBufB6dy0CgYBMYq9Lc31GkC6PcGEEbx6W
vjImOadWZbuOVnvEQjb5XCdcOsWsMcg96PtoeuyyHmhnEF1GsMzcIdQv/PHrvYpK
Yp8D0aqsLEgwGrJQER26FPpKmyIwvcL+nm6q5W31PnU9AOC/WEkB6Zs58hsMzD2S
kEJQcmfVew5mFXyxuEn3zA==
-----END PRIVATE KEY-----]],
project_id = "apisix",
token_uri = "http://127.0.0.1:1980/google/logging/token?token_type=Basic",
scope = {
"https://apisix.apache.org/logs:admin"
},
entries_uri = "http://127.0.0.1:1980/google/logging/entries",
},
inactive_timeout = 1,
batch_max_size = 1,
}
}
}
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, config)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 15: test route(customize auth type error)
--- request
GET /hello
--- wait: 2
--- response_body
hello world
--- grep_error_log eval
qr/\{\"error\"\:\"[\w+\s+]*\"\}/
--- grep_error_log_out
{"error":"identity authentication failed"}
--- error_log
Batch Processor[google-cloud-logging] failed to process entries
Batch Processor[google-cloud-logging] exceeded the max_retry_count
=== TEST 16: set route (file configuration is successful)
--- config
location /t {
content_by_lua_block {
local config = {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["google-cloud-logging"] = {
auth_file = "t/plugin/google-cloud-logging/config.json",
inactive_timeout = 1,
batch_max_size = 1,
}
}
}
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, config)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 17: test route(file configuration is successful)
--- request
GET /hello
--- wait: 2
--- response_body
hello world
=== TEST 18: set route (file configuration is failed)
--- config
location /t {
content_by_lua_block {
local config = {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["google-cloud-logging"] = {
auth_file = "google-cloud-logging/config.json",
inactive_timeout = 1,
batch_max_size = 1,
}
}
}
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, config)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 19: test route(file configuration is failed)
--- request
GET /hello
--- wait: 2
--- response_body
hello world
--- error_log
config.json: No such file or directory
=== TEST 20: set route (https file configuration is successful)
--- config
location /t {
content_by_lua_block {
local config = {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["google-cloud-logging"] = {
auth_file = "t/plugin/google-cloud-logging/config-https-domain.json",
inactive_timeout = 1,
batch_max_size = 1,
ssl_verify = true,
}
}
}
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, config)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 21: test route(https file configuration is successful)
--- request
GET /hello
--- wait: 2
--- response_body
hello world
=== TEST 22: set route (https file configuration SSL authentication failed: ssl_verify = true)
--- config
location /t {
content_by_lua_block {
local config = {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["google-cloud-logging"] = {
auth_file = "t/plugin/google-cloud-logging/config-https-ip.json",
inactive_timeout = 1,
batch_max_size = 1,
ssl_verify = true,
}
}
}
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, config)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 23: test route(https file configuration SSL authentication failed: ssl_verify = true)
--- request
GET /hello
--- wait: 2
--- response_body
hello world
--- error_log
failed to refresh google oauth access token, certificate host mismatch
=== TEST 24: set route (https file configuration SSL authentication succeed: ssl_verify = false)
--- config
location /t {
content_by_lua_block {
local config = {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["google-cloud-logging"] = {
auth_file = "t/plugin/google-cloud-logging/config-https-ip.json",
inactive_timeout = 1,
batch_max_size = 1,
ssl_verify = false,
}
}
}
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, config)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 25: test route(https file configuration SSL authentication succeed: ssl_verify = false)
--- request
GET /hello
--- wait: 2
--- response_body
hello world

View File

@@ -0,0 +1,9 @@
{
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDDzrFwnA3EvYyR\naeMgaLD3hBjvxKrz10uox1X8q7YYhf2ViRtLRUMa2bEMYksE5hbhwpNf6mKAnLOC\nUuAT6cPPdUl/agKpJXviBPIR2LuzD17WsLJHp1HxUDssSkgfCaGcOGGNfLUhhIpF\n2JUctLmxiZoAZySlSjcwupSuDJ0aPm0XO8r9H8Qu5kF2Vkz5e5bFivLTmvzrQTe4\nv5V1UI6hThElCSeUmdNF3uG3wopxlvq4zXgLTnuLbrNf/Gc4mlpV+UDgTISj32Ep\nAB2vxKEbvQw4ti8YJnGXWjxLerhfrszFw+V8lpeduiDYA44ZFoVqvzxeIsVZNtcw\nIu7PvEPNAgMBAAECggEAVpyN9m7A1F631/aLheFpLgMbeKt4puV7zQtnaJ2XrZ9P\nPR7pmNDpTu4uF3k/D8qrIm+L+uhVa+hkquf3wDct6w1JVnfQ93riImbnoKdK13ic\nDcEZCwLjByfjFMNCxZ/gAZca55fbExlqhFy6EHmMjhB8s2LsXcTHRuGxNI/Vyi49\nsxECibe0U53aqdJbVWrphIS67cpwl4TUkN6mrHsNuDYNJ9dgkpapoqp4FTFQsBqC\nafOK5qgJ68dWZ47FBUng+AZjdCncqAIuJxxItGVQP6YPsFs+OXcivIVHJr363TpC\nl85FfdvqWV5OGBbwSKhNwiTNUVvfSQVmtURGWG/HbQKBgQD4gZ1z9+Lx19kT9WTz\nlw93lxso++uhAPDTKviyWSRoEe5aN3LCd4My+/Aj+sk4ON/s2BV3ska5Im93j+vC\nrCv3uPn1n2jUhWuJ3bDqipeTW4n/CQA2m/8vd26TMk22yOkkqw2MIA8sjJ//SD7g\ntdG7up6DgGMP4hgbO89uGU7DAwKBgQDJtkKd0grh3u52Foeh9YaiAgYRwc65IE16\nUyD1OJxIuX/dYQDLlo5KyyngFa1ZhWIs7qC7r3xXH+10kfJY+Q+5YMjmZjlL8SR1\nUjqd02R9F2//6OeswyReachJZbZdtiEw3lPa4jVFYfhSe0M2ZPxMwvoXb25eyCNI\n1lYjSKq87wKBgHnLTNghjeDp4UKe6rNYPgRm0rDrhziJtX5JeUov1mALKb6dnmkh\nGfRK9g8sQqKDfXwfC6Z2gaMK9YaryujGaWYoCpoPXtmJ6oLPXH4XHuLh4mhUiP46\nxn8FEfSimuQS4/FMxH8A128GHQSI7AhGFFzlwfrBWcvXC+mNDsTvMmLxAoGARc+4\nupppfccETQZ7JsitMgD1TMwA2f2eEwoWTAitvlXFNT9PYSbYVHaAJbga6PLLCbYF\nFzAjHpxEOKYSdEyu7n/ayDL0/Z2V+qzc8KarDsg/0RgwppBbU/nUgeKb/U79qcYo\ny4ai3UKNCS70Ei1dTMvmdpnwXwlxfNIBufB6dy0CgYBMYq9Lc31GkC6PcGEEbx6W\nvjImOadWZbuOVnvEQjb5XCdcOsWsMcg96PtoeuyyHmhnEF1GsMzcIdQv/PHrvYpK\nYp8D0aqsLEgwGrJQER26FPpKmyIwvcL+nm6q5W31PnU9AOC/WEkB6Zs58hsMzD2S\nkEJQcmfVew5mFXyxuEn3zA==\n-----END PRIVATE KEY-----",
"project_id": "apisix",
"token_uri": "https://test.com:1983/google/logging/token",
"scope": [
"https://apisix.apache.org/logs:admin"
],
"entries_uri": "https://test.com:1983/google/logging/entries"
}

View File

@@ -0,0 +1,9 @@
{
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDDzrFwnA3EvYyR\naeMgaLD3hBjvxKrz10uox1X8q7YYhf2ViRtLRUMa2bEMYksE5hbhwpNf6mKAnLOC\nUuAT6cPPdUl/agKpJXviBPIR2LuzD17WsLJHp1HxUDssSkgfCaGcOGGNfLUhhIpF\n2JUctLmxiZoAZySlSjcwupSuDJ0aPm0XO8r9H8Qu5kF2Vkz5e5bFivLTmvzrQTe4\nv5V1UI6hThElCSeUmdNF3uG3wopxlvq4zXgLTnuLbrNf/Gc4mlpV+UDgTISj32Ep\nAB2vxKEbvQw4ti8YJnGXWjxLerhfrszFw+V8lpeduiDYA44ZFoVqvzxeIsVZNtcw\nIu7PvEPNAgMBAAECggEAVpyN9m7A1F631/aLheFpLgMbeKt4puV7zQtnaJ2XrZ9P\nPR7pmNDpTu4uF3k/D8qrIm+L+uhVa+hkquf3wDct6w1JVnfQ93riImbnoKdK13ic\nDcEZCwLjByfjFMNCxZ/gAZca55fbExlqhFy6EHmMjhB8s2LsXcTHRuGxNI/Vyi49\nsxECibe0U53aqdJbVWrphIS67cpwl4TUkN6mrHsNuDYNJ9dgkpapoqp4FTFQsBqC\nafOK5qgJ68dWZ47FBUng+AZjdCncqAIuJxxItGVQP6YPsFs+OXcivIVHJr363TpC\nl85FfdvqWV5OGBbwSKhNwiTNUVvfSQVmtURGWG/HbQKBgQD4gZ1z9+Lx19kT9WTz\nlw93lxso++uhAPDTKviyWSRoEe5aN3LCd4My+/Aj+sk4ON/s2BV3ska5Im93j+vC\nrCv3uPn1n2jUhWuJ3bDqipeTW4n/CQA2m/8vd26TMk22yOkkqw2MIA8sjJ//SD7g\ntdG7up6DgGMP4hgbO89uGU7DAwKBgQDJtkKd0grh3u52Foeh9YaiAgYRwc65IE16\nUyD1OJxIuX/dYQDLlo5KyyngFa1ZhWIs7qC7r3xXH+10kfJY+Q+5YMjmZjlL8SR1\nUjqd02R9F2//6OeswyReachJZbZdtiEw3lPa4jVFYfhSe0M2ZPxMwvoXb25eyCNI\n1lYjSKq87wKBgHnLTNghjeDp4UKe6rNYPgRm0rDrhziJtX5JeUov1mALKb6dnmkh\nGfRK9g8sQqKDfXwfC6Z2gaMK9YaryujGaWYoCpoPXtmJ6oLPXH4XHuLh4mhUiP46\nxn8FEfSimuQS4/FMxH8A128GHQSI7AhGFFzlwfrBWcvXC+mNDsTvMmLxAoGARc+4\nupppfccETQZ7JsitMgD1TMwA2f2eEwoWTAitvlXFNT9PYSbYVHaAJbga6PLLCbYF\nFzAjHpxEOKYSdEyu7n/ayDL0/Z2V+qzc8KarDsg/0RgwppBbU/nUgeKb/U79qcYo\ny4ai3UKNCS70Ei1dTMvmdpnwXwlxfNIBufB6dy0CgYBMYq9Lc31GkC6PcGEEbx6W\nvjImOadWZbuOVnvEQjb5XCdcOsWsMcg96PtoeuyyHmhnEF1GsMzcIdQv/PHrvYpK\nYp8D0aqsLEgwGrJQER26FPpKmyIwvcL+nm6q5W31PnU9AOC/WEkB6Zs58hsMzD2S\nkEJQcmfVew5mFXyxuEn3zA==\n-----END PRIVATE KEY-----",
"project_id": "apisix",
"token_uri": "https://127.0.0.1:1983/google/logging/token",
"scope": [
"https://apisix.apache.org/logs:admin"
],
"entries_uri": "https://127.0.0.1:1983/google/logging/entries"
}

View File

@@ -0,0 +1,9 @@
{
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDDzrFwnA3EvYyR\naeMgaLD3hBjvxKrz10uox1X8q7YYhf2ViRtLRUMa2bEMYksE5hbhwpNf6mKAnLOC\nUuAT6cPPdUl/agKpJXviBPIR2LuzD17WsLJHp1HxUDssSkgfCaGcOGGNfLUhhIpF\n2JUctLmxiZoAZySlSjcwupSuDJ0aPm0XO8r9H8Qu5kF2Vkz5e5bFivLTmvzrQTe4\nv5V1UI6hThElCSeUmdNF3uG3wopxlvq4zXgLTnuLbrNf/Gc4mlpV+UDgTISj32Ep\nAB2vxKEbvQw4ti8YJnGXWjxLerhfrszFw+V8lpeduiDYA44ZFoVqvzxeIsVZNtcw\nIu7PvEPNAgMBAAECggEAVpyN9m7A1F631/aLheFpLgMbeKt4puV7zQtnaJ2XrZ9P\nPR7pmNDpTu4uF3k/D8qrIm+L+uhVa+hkquf3wDct6w1JVnfQ93riImbnoKdK13ic\nDcEZCwLjByfjFMNCxZ/gAZca55fbExlqhFy6EHmMjhB8s2LsXcTHRuGxNI/Vyi49\nsxECibe0U53aqdJbVWrphIS67cpwl4TUkN6mrHsNuDYNJ9dgkpapoqp4FTFQsBqC\nafOK5qgJ68dWZ47FBUng+AZjdCncqAIuJxxItGVQP6YPsFs+OXcivIVHJr363TpC\nl85FfdvqWV5OGBbwSKhNwiTNUVvfSQVmtURGWG/HbQKBgQD4gZ1z9+Lx19kT9WTz\nlw93lxso++uhAPDTKviyWSRoEe5aN3LCd4My+/Aj+sk4ON/s2BV3ska5Im93j+vC\nrCv3uPn1n2jUhWuJ3bDqipeTW4n/CQA2m/8vd26TMk22yOkkqw2MIA8sjJ//SD7g\ntdG7up6DgGMP4hgbO89uGU7DAwKBgQDJtkKd0grh3u52Foeh9YaiAgYRwc65IE16\nUyD1OJxIuX/dYQDLlo5KyyngFa1ZhWIs7qC7r3xXH+10kfJY+Q+5YMjmZjlL8SR1\nUjqd02R9F2//6OeswyReachJZbZdtiEw3lPa4jVFYfhSe0M2ZPxMwvoXb25eyCNI\n1lYjSKq87wKBgHnLTNghjeDp4UKe6rNYPgRm0rDrhziJtX5JeUov1mALKb6dnmkh\nGfRK9g8sQqKDfXwfC6Z2gaMK9YaryujGaWYoCpoPXtmJ6oLPXH4XHuLh4mhUiP46\nxn8FEfSimuQS4/FMxH8A128GHQSI7AhGFFzlwfrBWcvXC+mNDsTvMmLxAoGARc+4\nupppfccETQZ7JsitMgD1TMwA2f2eEwoWTAitvlXFNT9PYSbYVHaAJbga6PLLCbYF\nFzAjHpxEOKYSdEyu7n/ayDL0/Z2V+qzc8KarDsg/0RgwppBbU/nUgeKb/U79qcYo\ny4ai3UKNCS70Ei1dTMvmdpnwXwlxfNIBufB6dy0CgYBMYq9Lc31GkC6PcGEEbx6W\nvjImOadWZbuOVnvEQjb5XCdcOsWsMcg96PtoeuyyHmhnEF1GsMzcIdQv/PHrvYpK\nYp8D0aqsLEgwGrJQER26FPpKmyIwvcL+nm6q5W31PnU9AOC/WEkB6Zs58hsMzD2S\nkEJQcmfVew5mFXyxuEn3zA==\n-----END PRIVATE KEY-----",
"project_id": "apisix",
"token_uri": "http://127.0.0.1:1980/google/logging/token",
"scope": [
"https://apisix.apache.org/logs:admin"
],
"entries_uri": "http://127.0.0.1:1980/google/logging/entries"
}

View File

@@ -0,0 +1,441 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests();
__DATA__
=== TEST 1: set route (verify batch queue default params)
--- config
location /t {
content_by_lua_block {
local config = {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["google-cloud-logging"] = {
auth_file = "t/plugin/google-cloud-logging/config.json",
}
}
}
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, config)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: data encryption for auth_config.private_key
--- yaml_config
apisix:
data_encryption:
enable_encrypt_fields: true
keyring:
- edd1c9f0985e76a2
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local config = {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["google-cloud-logging"] = {
auth_config = {
client_email = "email@apisix.iam.gserviceaccount.com",
private_key = [[
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDDzrFwnA3EvYyR
aeMgaLD3hBjvxKrz10uox1X8q7YYhf2ViRtLRUMa2bEMYksE5hbhwpNf6mKAnLOC
UuAT6cPPdUl/agKpJXviBPIR2LuzD17WsLJHp1HxUDssSkgfCaGcOGGNfLUhhIpF
2JUctLmxiZoAZySlSjcwupSuDJ0aPm0XO8r9H8Qu5kF2Vkz5e5bFivLTmvzrQTe4
v5V1UI6hThElCSeUmdNF3uG3wopxlvq4zXgLTnuLbrNf/Gc4mlpV+UDgTISj32Ep
AB2vxKEbvQw4ti8YJnGXWjxLerhfrszFw+V8lpeduiDYA44ZFoVqvzxeIsVZNtcw
Iu7PvEPNAgMBAAECggEAVpyN9m7A1F631/aLheFpLgMbeKt4puV7zQtnaJ2XrZ9P
PR7pmNDpTu4uF3k/D8qrIm+L+uhVa+hkquf3wDct6w1JVnfQ93riImbnoKdK13ic
DcEZCwLjByfjFMNCxZ/gAZca55fbExlqhFy6EHmMjhB8s2LsXcTHRuGxNI/Vyi49
sxECibe0U53aqdJbVWrphIS67cpwl4TUkN6mrHsNuDYNJ9dgkpapoqp4FTFQsBqC
afOK5qgJ68dWZ47FBUng+AZjdCncqAIuJxxItGVQP6YPsFs+OXcivIVHJr363TpC
l85FfdvqWV5OGBbwSKhNwiTNUVvfSQVmtURGWG/HbQKBgQD4gZ1z9+Lx19kT9WTz
lw93lxso++uhAPDTKviyWSRoEe5aN3LCd4My+/Aj+sk4ON/s2BV3ska5Im93j+vC
rCv3uPn1n2jUhWuJ3bDqipeTW4n/CQA2m/8vd26TMk22yOkkqw2MIA8sjJ//SD7g
tdG7up6DgGMP4hgbO89uGU7DAwKBgQDJtkKd0grh3u52Foeh9YaiAgYRwc65IE16
UyD1OJxIuX/dYQDLlo5KyyngFa1ZhWIs7qC7r3xXH+10kfJY+Q+5YMjmZjlL8SR1
Ujqd02R9F2//6OeswyReachJZbZdtiEw3lPa4jVFYfhSe0M2ZPxMwvoXb25eyCNI
1lYjSKq87wKBgHnLTNghjeDp4UKe6rNYPgRm0rDrhziJtX5JeUov1mALKb6dnmkh
GfRK9g8sQqKDfXwfC6Z2gaMK9YaryujGaWYoCpoPXtmJ6oLPXH4XHuLh4mhUiP46
xn8FEfSimuQS4/FMxH8A128GHQSI7AhGFFzlwfrBWcvXC+mNDsTvMmLxAoGARc+4
upppfccETQZ7JsitMgD1TMwA2f2eEwoWTAitvlXFNT9PYSbYVHaAJbga6PLLCbYF
FzAjHpxEOKYSdEyu7n/ayDL0/Z2V+qzc8KarDsg/0RgwppBbU/nUgeKb/U79qcYo
y4ai3UKNCS70Ei1dTMvmdpnwXwlxfNIBufB6dy0CgYBMYq9Lc31GkC6PcGEEbx6W
vjImOadWZbuOVnvEQjb5XCdcOsWsMcg96PtoeuyyHmhnEF1GsMzcIdQv/PHrvYpK
Yp8D0aqsLEgwGrJQER26FPpKmyIwvcL+nm6q5W31PnU9AOC/WEkB6Zs58hsMzD2S
kEJQcmfVew5mFXyxuEn3zA==
-----END PRIVATE KEY-----]],
project_id = "apisix",
token_uri = "http://127.0.0.1:1980/google/logging/token",
scope = {
"https://apisix.apache.org/logs:admin"
},
entries_uri = "http://127.0.0.1:1980/google/logging/entries",
},
inactive_timeout = 1,
batch_max_size = 1,
}
}
}
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, config)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(0.1)
-- get plugin conf from admin api, password is decrypted
local code, message, res = t('/apisix/admin/routes/1',
ngx.HTTP_GET
)
res = json.decode(res)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
ngx.say(res.value.plugins["google-cloud-logging"].auth_config.private_key)
-- get plugin conf from etcd, password is encrypted
local etcd = require("apisix.core.etcd")
local res = assert(etcd.get('/routes/1'))
ngx.say(res.body.node.value.plugins["google-cloud-logging"].auth_config.private_key)
}
}
--- response_body
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDDzrFwnA3EvYyR
aeMgaLD3hBjvxKrz10uox1X8q7YYhf2ViRtLRUMa2bEMYksE5hbhwpNf6mKAnLOC
UuAT6cPPdUl/agKpJXviBPIR2LuzD17WsLJHp1HxUDssSkgfCaGcOGGNfLUhhIpF
2JUctLmxiZoAZySlSjcwupSuDJ0aPm0XO8r9H8Qu5kF2Vkz5e5bFivLTmvzrQTe4
v5V1UI6hThElCSeUmdNF3uG3wopxlvq4zXgLTnuLbrNf/Gc4mlpV+UDgTISj32Ep
AB2vxKEbvQw4ti8YJnGXWjxLerhfrszFw+V8lpeduiDYA44ZFoVqvzxeIsVZNtcw
Iu7PvEPNAgMBAAECggEAVpyN9m7A1F631/aLheFpLgMbeKt4puV7zQtnaJ2XrZ9P
PR7pmNDpTu4uF3k/D8qrIm+L+uhVa+hkquf3wDct6w1JVnfQ93riImbnoKdK13ic
DcEZCwLjByfjFMNCxZ/gAZca55fbExlqhFy6EHmMjhB8s2LsXcTHRuGxNI/Vyi49
sxECibe0U53aqdJbVWrphIS67cpwl4TUkN6mrHsNuDYNJ9dgkpapoqp4FTFQsBqC
afOK5qgJ68dWZ47FBUng+AZjdCncqAIuJxxItGVQP6YPsFs+OXcivIVHJr363TpC
l85FfdvqWV5OGBbwSKhNwiTNUVvfSQVmtURGWG/HbQKBgQD4gZ1z9+Lx19kT9WTz
lw93lxso++uhAPDTKviyWSRoEe5aN3LCd4My+/Aj+sk4ON/s2BV3ska5Im93j+vC
rCv3uPn1n2jUhWuJ3bDqipeTW4n/CQA2m/8vd26TMk22yOkkqw2MIA8sjJ//SD7g
tdG7up6DgGMP4hgbO89uGU7DAwKBgQDJtkKd0grh3u52Foeh9YaiAgYRwc65IE16
UyD1OJxIuX/dYQDLlo5KyyngFa1ZhWIs7qC7r3xXH+10kfJY+Q+5YMjmZjlL8SR1
Ujqd02R9F2//6OeswyReachJZbZdtiEw3lPa4jVFYfhSe0M2ZPxMwvoXb25eyCNI
1lYjSKq87wKBgHnLTNghjeDp4UKe6rNYPgRm0rDrhziJtX5JeUov1mALKb6dnmkh
GfRK9g8sQqKDfXwfC6Z2gaMK9YaryujGaWYoCpoPXtmJ6oLPXH4XHuLh4mhUiP46
xn8FEfSimuQS4/FMxH8A128GHQSI7AhGFFzlwfrBWcvXC+mNDsTvMmLxAoGARc+4
upppfccETQZ7JsitMgD1TMwA2f2eEwoWTAitvlXFNT9PYSbYVHaAJbga6PLLCbYF
FzAjHpxEOKYSdEyu7n/ayDL0/Z2V+qzc8KarDsg/0RgwppBbU/nUgeKb/U79qcYo
y4ai3UKNCS70Ei1dTMvmdpnwXwlxfNIBufB6dy0CgYBMYq9Lc31GkC6PcGEEbx6W
vjImOadWZbuOVnvEQjb5XCdcOsWsMcg96PtoeuyyHmhnEF1GsMzcIdQv/PHrvYpK
Yp8D0aqsLEgwGrJQER26FPpKmyIwvcL+nm6q5W31PnU9AOC/WEkB6Zs58hsMzD2S
kEJQcmfVew5mFXyxuEn3zA==
-----END PRIVATE KEY-----
YnwwDKc5vNzo0OU4StTRQbwgCnTZ3dmYiBFm8aGnvTxlE86D2nT07Q3BWhUdky6OGIox4MRLbiHz13NZjyUao/Nudh4PeTj5wMldPD5YvNWtbTG4ig/TNSdBncmIQPLPaUqSweE61pnASxodpTlBJ5k9yxfTmwBTOkzZevoKy9D2E4wF9vGCdkcPK/tAkvRoJTj6xD3xVuAbkcap/81oHplUZZ+ghlEnBZgOH8UMa73UfeNbOQVHD2mlU0LxkTXtwFhHWl50adrt890VDHev0+FUUDjv5Ysl8r/nnnlyq3SV4oqJfs/IVRKROe93e8sJ2/49o7kEv2XT1/6DjM/VsSLKfAi5rLNobcSaSzztSSLkrBFKQvvy2rRA7GFWKbIk+rPZhYmTMItDJv23XP6uzaLRPoq2f/AnRTKpWmA8Dk9TfFHsZLupKi1bmjCdtK8lpMCf9Au1rezt7+2BybQrtbbDbwPzC5bKHmKhc0GPTUzLAWQBin3tuZxSfk/MqRtG+AemwnFTHivJrfRwmc3db+b9W6WX09mV488f2M4qbqBmkiFU5VARWCGZ5vbop2KGhmB2fQPXTmj8QSYk6fBxFDnfzTfnYMIu2cQsbSBPCnoPinQNpBfFD3RQkkCiNtJ8GA8DWsivWsnW4jWyPmkIN/P1eLW1DSsU6V4cbhTQJs6/LzOCGAZB/ewu3mr1SDLWJPlIWW6atC/g0uiXkZ3VLUsS0BQffITf8sVXyz/BEbflLlT777zERDKyz/qS2JyR6U8s2h3Yg+GncPUCEF6Lx5Veb1lL+zs+Stvv+5/t2GfDlNYiwTU8HeffhEGgAv1s86OPo3CfWe7lEnu/MFHIm0czVenYdEVy449xj66DHqXUQVzVc+3NelW15FrKhcvU0Cxwqfk+xEOE185ssD06L+tOGjxPPvADjlcQQ1crH+tEcTTLnZZ/e16I10kcc5rBJwDy4COoeY6DZ0dFwtAdbjoR/KaSTGLK6n/u9Ow7OGDPZog4LhrzMOn2T7hk/oaMOKhlDvKroiSijhhkrQf5ZDhhh3GQn/ZRXjyiPWBqKEQiBJGyZ/iRONzJLsF8U8vsBzBToxmTe9prlwHusgAEIBUFrZRSvsVgsPCFOyJ6XJXDTdcCInHUGI9LsxWdlojYvvNuSvavkw1I4K+VBmlEG5FCMx56eX2X49hfXwRcM1ZyRRrmq6cRh+33aMeMLAtpKgTsQgmB/I01mGNZlstvU0XEFnCPuWcks50BTnvPEbU7GZJLE3HFmGb3vyC57E8oTR2FjhDrevPlLkxMPrLvXhwbmV+3YiZYq+8k6oBKfrrq41JRKr+SJDb7m6xL8AuZccMrNhDrkByQLi6zn95dIYc3+vNU4XBzvhpb7HMj2wvorxEW2HpQ+OVSZiZSCU6m4Fx6juj1D5pGs1nr68ybihqMrXuZBKP4b9Y6sw99kNmnWBdwNiY95sWy1qUe0MJq3r44hhVHvCUmzOVyO4aBmhMwgkaSQWpEeQwyIWENM1IMU6WUrKCSuLuKJAl5bM++ThBaLvIIMCyXl39136jHp97aVmHRXbSMPcSAb8l/YQ6SLK0HBxmFTXvroxHmPxPqrJ5jz65C72+uArgOZxJN6tyimIcTMyoJoN7N+QKxDLjgmqnJyEcthycEK3gikyloWsLppzEmHLHBDXlKpJLflvUujYrNsKf2xohx31gIlxBWCHP/1KL3QAehn+FEWUWsXn2hWAR0KAtmIOM7gZuCY8yKNDfXrAZJs14rwDlTbnhJvyijt1Tr6gleehmJDKSm2vM/NbznVTKwJDyMRner+vvc4zD06az/Y6Y4oM0e0IWM2fMaiiwjNAaKhhwJzqvM1c8+ZOfuRajmHFECEkYgXCKZiQxQihFG2wWp2i+xEGGwP2e+FbDdY9Ygyvw5SUvahyoX36AYbbTBOFY6E9aYUIM/Et8ZuXoWs1QaxGfJwcVvueqke45y3GKkp54sHXhrqfKX0TTiw6DCUs6dRTybxOjmjJCKp6Yw4KGWY0t3J0xbK08KTUMeHNxgtfYcz1/Wg/Q61CkUJkRNBninAAkEz8rV2olBHy1GZFFjCQySAyPH4PtWm1S4sBzdsui5wT+m2pC/DsCcQW++TGH9LdaHeT8B9u32lYToVN1/L2j5kjkhN13sNKfb6I9yYTnUqweQFU79toBfDt+6KNNfIA1TcmvZw8RcuMOArEqJQ6OPOhgUQBwsZaGeqFmAE4q64n5raS4OCdWtasFtItW3c5QHxkKoEEER04glVsCoxOvc80U=
=== TEST 3: set route to test custom log format
--- config
location /t {
content_by_lua_block {
local config = {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["google-cloud-logging"] = {
auth_config = {
client_email = "email@apisix.iam.gserviceaccount.com",
private_key = [[
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDDzrFwnA3EvYyR
aeMgaLD3hBjvxKrz10uox1X8q7YYhf2ViRtLRUMa2bEMYksE5hbhwpNf6mKAnLOC
UuAT6cPPdUl/agKpJXviBPIR2LuzD17WsLJHp1HxUDssSkgfCaGcOGGNfLUhhIpF
2JUctLmxiZoAZySlSjcwupSuDJ0aPm0XO8r9H8Qu5kF2Vkz5e5bFivLTmvzrQTe4
v5V1UI6hThElCSeUmdNF3uG3wopxlvq4zXgLTnuLbrNf/Gc4mlpV+UDgTISj32Ep
AB2vxKEbvQw4ti8YJnGXWjxLerhfrszFw+V8lpeduiDYA44ZFoVqvzxeIsVZNtcw
Iu7PvEPNAgMBAAECggEAVpyN9m7A1F631/aLheFpLgMbeKt4puV7zQtnaJ2XrZ9P
PR7pmNDpTu4uF3k/D8qrIm+L+uhVa+hkquf3wDct6w1JVnfQ93riImbnoKdK13ic
DcEZCwLjByfjFMNCxZ/gAZca55fbExlqhFy6EHmMjhB8s2LsXcTHRuGxNI/Vyi49
sxECibe0U53aqdJbVWrphIS67cpwl4TUkN6mrHsNuDYNJ9dgkpapoqp4FTFQsBqC
afOK5qgJ68dWZ47FBUng+AZjdCncqAIuJxxItGVQP6YPsFs+OXcivIVHJr363TpC
l85FfdvqWV5OGBbwSKhNwiTNUVvfSQVmtURGWG/HbQKBgQD4gZ1z9+Lx19kT9WTz
lw93lxso++uhAPDTKviyWSRoEe5aN3LCd4My+/Aj+sk4ON/s2BV3ska5Im93j+vC
rCv3uPn1n2jUhWuJ3bDqipeTW4n/CQA2m/8vd26TMk22yOkkqw2MIA8sjJ//SD7g
tdG7up6DgGMP4hgbO89uGU7DAwKBgQDJtkKd0grh3u52Foeh9YaiAgYRwc65IE16
UyD1OJxIuX/dYQDLlo5KyyngFa1ZhWIs7qC7r3xXH+10kfJY+Q+5YMjmZjlL8SR1
Ujqd02R9F2//6OeswyReachJZbZdtiEw3lPa4jVFYfhSe0M2ZPxMwvoXb25eyCNI
1lYjSKq87wKBgHnLTNghjeDp4UKe6rNYPgRm0rDrhziJtX5JeUov1mALKb6dnmkh
GfRK9g8sQqKDfXwfC6Z2gaMK9YaryujGaWYoCpoPXtmJ6oLPXH4XHuLh4mhUiP46
xn8FEfSimuQS4/FMxH8A128GHQSI7AhGFFzlwfrBWcvXC+mNDsTvMmLxAoGARc+4
upppfccETQZ7JsitMgD1TMwA2f2eEwoWTAitvlXFNT9PYSbYVHaAJbga6PLLCbYF
FzAjHpxEOKYSdEyu7n/ayDL0/Z2V+qzc8KarDsg/0RgwppBbU/nUgeKb/U79qcYo
y4ai3UKNCS70Ei1dTMvmdpnwXwlxfNIBufB6dy0CgYBMYq9Lc31GkC6PcGEEbx6W
vjImOadWZbuOVnvEQjb5XCdcOsWsMcg96PtoeuyyHmhnEF1GsMzcIdQv/PHrvYpK
Yp8D0aqsLEgwGrJQER26FPpKmyIwvcL+nm6q5W31PnU9AOC/WEkB6Zs58hsMzD2S
kEJQcmfVew5mFXyxuEn3zA==
-----END PRIVATE KEY-----]],
project_id = "apisix",
token_uri = "http://127.0.0.1:1980/google/logging/token",
scope = {
"https://apisix.apache.org/logs:admin"
},
entries_uri = "http://127.0.0.1:1980/google/logging/entries",
},
inactive_timeout = 1,
batch_max_size = 1,
}
}
}
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, config)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local code, body = t('/apisix/admin/plugin_metadata/google-cloud-logging',
ngx.HTTP_PUT,
[[{
"log_format": {
"host": "$host",
"@timestamp": "$time_iso8601",
"client_ip": "$remote_addr"
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 4: hit
--- extra_init_by_lua
local decode = require("toolkit.json").decode
local up = require("lib.server")
up.google_logging_entries = function()
ngx.log(ngx.WARN, "the mock backend is hit")
ngx.req.read_body()
local data = ngx.req.get_body_data()
data = decode(data)
assert(data.entries[1].jsonPayload.client_ip == "127.0.0.1")
assert(data.entries[1].resource.type == "global")
ngx.say('{}')
end
--- request
GET /hello
--- wait: 2
--- response_body
hello world
--- error_log
the mock backend is hit
--- no_error_log
[error]
=== TEST 5: bad custom log format
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/plugin_metadata/google-cloud-logging',
ngx.HTTP_PUT,
[[{
"log_format": "'$host' '$time_iso8601'"
}]]
)
if code >= 300 then
ngx.status = code
ngx.print(body)
return
end
ngx.say(body)
}
}
--- error_code: 400
--- response_body
{"error_msg":"invalid configuration: property \"log_format\" validation failed: wrong type: expected object, got string"}
=== TEST 6: set route to test custom log format in route
--- config
location /t {
content_by_lua_block {
local config = {
uri = "/hello",
upstream = {
type = "roundrobin",
nodes = {
["127.0.0.1:1980"] = 1
}
},
plugins = {
["google-cloud-logging"] = {
auth_config = {
client_email = "email@apisix.iam.gserviceaccount.com",
private_key = [[
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDDzrFwnA3EvYyR
aeMgaLD3hBjvxKrz10uox1X8q7YYhf2ViRtLRUMa2bEMYksE5hbhwpNf6mKAnLOC
UuAT6cPPdUl/agKpJXviBPIR2LuzD17WsLJHp1HxUDssSkgfCaGcOGGNfLUhhIpF
2JUctLmxiZoAZySlSjcwupSuDJ0aPm0XO8r9H8Qu5kF2Vkz5e5bFivLTmvzrQTe4
v5V1UI6hThElCSeUmdNF3uG3wopxlvq4zXgLTnuLbrNf/Gc4mlpV+UDgTISj32Ep
AB2vxKEbvQw4ti8YJnGXWjxLerhfrszFw+V8lpeduiDYA44ZFoVqvzxeIsVZNtcw
Iu7PvEPNAgMBAAECggEAVpyN9m7A1F631/aLheFpLgMbeKt4puV7zQtnaJ2XrZ9P
PR7pmNDpTu4uF3k/D8qrIm+L+uhVa+hkquf3wDct6w1JVnfQ93riImbnoKdK13ic
DcEZCwLjByfjFMNCxZ/gAZca55fbExlqhFy6EHmMjhB8s2LsXcTHRuGxNI/Vyi49
sxECibe0U53aqdJbVWrphIS67cpwl4TUkN6mrHsNuDYNJ9dgkpapoqp4FTFQsBqC
afOK5qgJ68dWZ47FBUng+AZjdCncqAIuJxxItGVQP6YPsFs+OXcivIVHJr363TpC
l85FfdvqWV5OGBbwSKhNwiTNUVvfSQVmtURGWG/HbQKBgQD4gZ1z9+Lx19kT9WTz
lw93lxso++uhAPDTKviyWSRoEe5aN3LCd4My+/Aj+sk4ON/s2BV3ska5Im93j+vC
rCv3uPn1n2jUhWuJ3bDqipeTW4n/CQA2m/8vd26TMk22yOkkqw2MIA8sjJ//SD7g
tdG7up6DgGMP4hgbO89uGU7DAwKBgQDJtkKd0grh3u52Foeh9YaiAgYRwc65IE16
UyD1OJxIuX/dYQDLlo5KyyngFa1ZhWIs7qC7r3xXH+10kfJY+Q+5YMjmZjlL8SR1
Ujqd02R9F2//6OeswyReachJZbZdtiEw3lPa4jVFYfhSe0M2ZPxMwvoXb25eyCNI
1lYjSKq87wKBgHnLTNghjeDp4UKe6rNYPgRm0rDrhziJtX5JeUov1mALKb6dnmkh
GfRK9g8sQqKDfXwfC6Z2gaMK9YaryujGaWYoCpoPXtmJ6oLPXH4XHuLh4mhUiP46
xn8FEfSimuQS4/FMxH8A128GHQSI7AhGFFzlwfrBWcvXC+mNDsTvMmLxAoGARc+4
upppfccETQZ7JsitMgD1TMwA2f2eEwoWTAitvlXFNT9PYSbYVHaAJbga6PLLCbYF
FzAjHpxEOKYSdEyu7n/ayDL0/Z2V+qzc8KarDsg/0RgwppBbU/nUgeKb/U79qcYo
y4ai3UKNCS70Ei1dTMvmdpnwXwlxfNIBufB6dy0CgYBMYq9Lc31GkC6PcGEEbx6W
vjImOadWZbuOVnvEQjb5XCdcOsWsMcg96PtoeuyyHmhnEF1GsMzcIdQv/PHrvYpK
Yp8D0aqsLEgwGrJQER26FPpKmyIwvcL+nm6q5W31PnU9AOC/WEkB6Zs58hsMzD2S
kEJQcmfVew5mFXyxuEn3zA==
-----END PRIVATE KEY-----]],
project_id = "apisix",
token_uri = "http://127.0.0.1:1980/google/logging/token",
scope = {
"https://apisix.apache.org/logs:admin"
},
entries_uri = "http://127.0.0.1:1980/google/logging/entries",
},
log_format = {
host = "$host",
["@timestamp"] = "$time_iso8601",
vip = "$remote_addr"
},
inactive_timeout = 1,
batch_max_size = 1,
}
}
}
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, config)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 7: hit
--- extra_init_by_lua
local decode = require("toolkit.json").decode
local up = require("lib.server")
up.google_logging_entries = function()
ngx.log(ngx.WARN, "the mock backend is hit")
ngx.req.read_body()
local data = ngx.req.get_body_data()
data = decode(data)
assert(data.entries[1].jsonPayload.vip == "127.0.0.1")
assert(data.entries[1].resource.type == "global")
ngx.say('{}')
end
--- request
GET /hello
--- wait: 2
--- response_body
hello world
--- error_log
the mock backend is hit
--- no_error_log
[error]

View File

@@ -0,0 +1,75 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
log_level('warn');
repeat_each(1);
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (!$block->request) {
$block->set_value("request", "GET /t");
}
my $extra_init_by_lua = <<_EOC_;
local core = require("apisix.core")
local orig_new = core.config.new
close_cnt = 0
core.config.new = function(key, opts)
local obj, err = orig_new(key, opts)
if key == "/protos" then
local orig_close = obj.close
obj.close = function(...)
core.log.warn("call config close")
close_cnt = close_cnt + 1
return orig_close(...)
end
end
return obj, err
end
_EOC_
$block->set_value("extra_init_by_lua", $extra_init_by_lua);
});
run_tests;
__DATA__
=== TEST 1: close protos when grpc-transcode plugin reload
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code = t('/apisix/admin/plugins/reload',
ngx.HTTP_PUT)
if code >= 300 then
ngx.status = code
return
end
ngx.sleep(2)
if close_cnt ~= 1 then
ngx.status = 500
end
}
}
--- error_log
call config close

View File

@@ -0,0 +1,763 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
BEGIN {
if ($ENV{TEST_NGINX_CHECK_LEAK}) {
$SkipReason = "unavailable for the hup tests";
} else {
$ENV{TEST_NGINX_USE_HUP} = 1;
undef $ENV{TEST_NGINX_USE_STAP};
}
}
use t::APISIX 'no_plan';
repeat_each(1);
no_long_string();
no_shuffle();
no_root_location();
log_level('debug');
run_tests;
__DATA__
=== TEST 1: set proto(id: 1)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local etcd = require("apisix.core.etcd")
local code, body = t('/apisix/admin/protos/1',
ngx.HTTP_PUT,
[[{
"content" : "syntax = \"proto3\";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
local res = assert(etcd.get('/protos/1'))
local create_time = res.body.node.value.create_time
assert(create_time ~= nil, "create_time is nil")
local update_time = res.body.node.value.update_time
assert(update_time ~= nil, "update_time is nil")
}
}
--- request
GET /t
--- response_body
passed
=== TEST 2: set proto(id: 2)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/protos/2',
ngx.HTTP_PUT,
[[{
"content" : "syntax = \"proto3\";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 3: delete proto(id: 2)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/protos/2',
ngx.HTTP_DELETE
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 4: set routes(id: 1)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET", "POST"],
"uri": "/grpctest",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "SayHello"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 5: hit route
--- request
GET /grpctest?name=world
--- response_body eval
qr/\{"message":"Hello world"\}/
=== TEST 6: hit route by post
--- request
POST /grpctest
name=world
--- response_body eval
qr/\{"message":"Hello world"\}/
=== TEST 7: hit route by post json
--- request
POST /grpctest
{"name": "world"}
--- more_headers
Content-Type: application/json
--- response_body eval
qr/\{"message":"Hello world"\}/
=== TEST 8: wrong upstream scheme
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"uri": "/grpctest",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "SayHello"
}
},
"upstream": {
"scheme": "asf",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- error_code: 400
=== TEST 9: wrong upstream address
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"uri": "/grpctest",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "SayHello"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:1970": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 10: hit route (Connection refused)
--- request
GET /grpctest
--- response_body eval
qr/502 Bad Gateway/
--- error_log
Connection refused) while connecting to upstream
--- error_code: 502
=== TEST 11: update proto(id: 1)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/protos/1',
ngx.HTTP_PUT,
[[{
"content" : "syntax = \"proto3\";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
rpc Plus (PlusRequest) returns (PlusReply) {}
rpc SayHelloAfterDelay (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
message PlusRequest {
int64 a = 1;
int64 b = 2;
}
message PlusReply {
int64 result = 1;
}"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 12: set routes(id: 2)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/2',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"uri": "/grpc_plus",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "Plus",
"pb_option":["int64_as_string", "enum_as_name"]
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 13: hit route
--- request
GET /grpc_plus?a=1&b=2
--- response_body eval
qr/\{"result":3\}/
=== TEST 14: hit route
--- request
GET /grpc_plus?a=1&b=2251799813685260
--- response_body eval
qr/\{"result":"#2251799813685261"\}/
=== TEST 15: set route3 deadline nodelay
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/3',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"uri": "/grpc_deadline",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "SayHello",
"deadline": 500
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 16: hit route
--- request
GET /grpc_deadline?name=apisix
--- response_body eval
qr/\{"message":"Hello apisix"\}/
=== TEST 17: set route4 deadline delay
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/4',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"uri": "/grpc_delay",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "SayHelloAfterDelay",
"deadline": 500
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 18: hit route
--- request
GET /grpc_delay?name=apisix
--- error_code: 504
=== TEST 19: set routes: missing method
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/grpctest",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter"
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.print(body)
}
}
--- request
GET /t
--- error_code: 400
--- response_body
{"error_msg":"failed to check the configuration of plugin grpc-transcode err: property \"method\" is required"}
=== TEST 20: set proto(id: 1, with array parameter)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/protos/1',
ngx.HTTP_PUT,
[[{
"content" : "syntax = \"proto3\";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
repeated string items = 2;
}
message HelloReply {
string message = 1;
repeated string items = 2;
}"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 21: set routes(id: 1, with array parameter)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET", "POST"],
"uri": "/grpctest",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "SayHello"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 22: hit route
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/grpctest',
ngx.HTTP_POST,
[[
{"name":"apisix", "items": ["a","b","c"]}
]],
[[
{"message":"Hello apisix", "items": ["a","b","c"]}
]],
{["Content-Type"] = "application/json"}
)
ngx.status = code
}
}
--- request
GET /t
=== TEST 23: set proto with enum
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/protos/1',
ngx.HTTP_PUT,
[[{
"content" : "syntax = \"proto3\";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
enum Gender {
GENDER_UNKNOWN = 0;
GENDER_MALE = 1;
GENDER_FEMALE = 2;
}
message HelloRequest {
string name = 1;
repeated string items = 2;
Gender gender = 3;
}
message HelloReply {
string message = 1;
repeated string items = 2;
Gender gender = 3;
}"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 24: hit route, no gender
--- request
POST /grpctest
{"name":"world"}
--- more_headers
Content-Type: application/json
--- response_body eval
qr/"gender":"GENDER_UNKNOWN"/
=== TEST 25: hit route, gender is a value
--- request
POST /grpctest
{"name":"world","gender":2}
--- more_headers
Content-Type: application/json
--- response_body eval
qr/"gender":"GENDER_FEMALE"/
=== TEST 26: hit route, gender is a name
--- request
POST /grpctest
{"name":"world","gender":"GENDER_MALE"}
--- more_headers
Content-Type: application/json
--- response_body eval
qr/"gender":"GENDER_MALE"/
=== TEST 27: hit route, bad gender
--- request
POST /grpctest
{"name":"world","gender":"GENDER_MA"}
--- more_headers
Content-Type: application/json
--- error_code: 400
--- error_log
failed to encode request data to protobuf
=== TEST 28: set routes(decode enum as value)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET", "POST"],
"uri": "/grpctest",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "SayHello",
"pb_option":["enum_as_value"]
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 29: hit route
--- request
POST /grpctest
{"name":"world","gender":2}
--- more_headers
Content-Type: application/json
--- response_body eval
qr/"gender":2/

View File

@@ -0,0 +1,796 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
no_long_string();
no_shuffle();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (!$block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests;
__DATA__
=== TEST 1: set rule
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/protos/1',
ngx.HTTP_PUT,
[[{
"content" : "syntax = \"proto3\";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
enum Gender {
GENDER_UNKNOWN = 0;
GENDER_MALE = 1;
GENDER_FEMALE = 2;
}
message Person {
string name = 1;
int32 age = 2;
}
message HelloRequest {
string name = 1;
repeated string items = 2;
Gender gender = 3;
Person person = 4;
}
message HelloReply {
string message = 1;
}"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/grpctest",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "SayHello"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: hit route
--- request
POST /grpctest
{"name":"world","person":{"name":"Joe","age":1}}
--- more_headers
Content-Type: application/json
--- response_body chomp
{"message":"Hello world, name: Joe, age: 1"}
=== TEST 3: hit route, missing some fields
--- request
POST /grpctest
{"name":"world","person":{"name":"Joe"}}
--- more_headers
Content-Type: application/json
--- response_body chomp
{"message":"Hello world, name: Joe"}
=== TEST 4: set rule to check if each proto is separate
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/protos/2',
ngx.HTTP_PUT,
[[{
"content" : "syntax = \"proto3\";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// same message, different fields. use to pollute the type info
message HelloRequest {
string name = 1;
string person = 2;
}
message HelloReply {
string message = 1;
}"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local code, body = t('/apisix/admin/routes/2',
ngx.HTTP_PUT,
[[{
"uri": "/fail",
"plugins": {
"grpc-transcode": {
"proto_id": "2",
"service": "helloworld.Greeter",
"method": "SayHello"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 5: hit route
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port
local body = [[{"name":"world","person":{"name":"John"}}]]
local opt = {method = "POST", body = body, headers = {["Content-Type"] = "application/json"}}
local function access(path)
local httpc = http.new()
local res, err = httpc:request_uri(uri .. path, opt)
if not res then
ngx.say(err)
return
end
if res.status > 300 then
ngx.say(res.status)
else
ngx.say(res.body)
end
end
access("/fail")
access("/grpctest")
access("/fail")
access("/grpctest")
}
}
--- response_body
400
{"message":"Hello world, name: John"}
400
{"message":"Hello world, name: John"}
--- error_log
failed to encode request data to protobuf
=== TEST 6: set binary rule
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin")
local json = require("toolkit.json")
local content = t.read_file("t/grpc_server_example/proto.pb")
local data = {content = ngx.encode_base64(content)}
local code, body = t.test('/apisix/admin/protos/1',
ngx.HTTP_PUT,
json.encode(data)
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local code, body = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/grpctest",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.TestImport",
"method": "Run"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 7: hit route
--- request
POST /grpctest
{"body":"world","user":{"name":"Hello"}}
--- more_headers
Content-Type: application/json
--- response_body chomp
{"body":"Hello world"}
=== TEST 8: service/method not found
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/service_not_found",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.TestImportx",
"method": "Run"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local code, body = t('/apisix/admin/routes/2',
ngx.HTTP_PUT,
[[{
"uri": "/method_not_found",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.TestImport",
"method": "Runx"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 9: hit route
--- request
POST /service_not_found
{"body":"world","user":{"name":"Hello"}}
--- more_headers
Content-Type: application/json
--- error_log
Undefined service method
--- error_code: 503
=== TEST 10: hit route
--- request
POST /method_not_found
{"body":"world","user":{"name":"Hello"}}
--- more_headers
Content-Type: application/json
--- error_log
Undefined service method
--- error_code: 503
=== TEST 11: set proto(id: 1)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/protos/1',
ngx.HTTP_PUT,
[[{
"content" : "syntax = \"proto3\";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
rpc Plus (PlusRequest) returns (PlusReply) {}
rpc SayHelloAfterDelay (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
message PlusRequest {
int64 a = 1;
int64 b = 2;
}
message PlusReply {
int64 result = 1;
}"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 12: work with logger plugin which on global rule and read response body (logger plugins store undecoded body)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"uri": "/grpc_plus",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "Plus",
"pb_option":["int64_as_string", "enum_as_name"]
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
return
end
local code, body = t('/apisix/admin/global_rules/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"http-logger": {
"uri": "http://127.0.0.1:1980/log",
"batch_max_size": 1,
"include_resp_body": true
}
}
}]]
)
if code >= 300 then
ngx.status = code
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 13: hit route
--- request
GET /grpc_plus?a=1&b=2
--- response_body eval
qr/\{"result":3\}/
--- error_log eval
qr/request log: \{.*body":\"\\u0000\\u0000\\u0000\\u0000\\u0002\\b\\u0003"/
=== TEST 14: delete global rules
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, message = t('/apisix/admin/global_rules/1',
ngx.HTTP_DELETE
)
ngx.say("[delete] code: ", code, " message: ", message)
}
}
--- response_body
[delete] code: 200 message: passed
=== TEST 15: work with logger plugin which on route and read response body (logger plugins store decoded body)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"uri": "/grpc_plus",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "Plus",
"pb_option":["int64_as_string", "enum_as_name"]
},
"http-logger": {
"uri": "http://127.0.0.1:1980/log",
"batch_max_size": 1,
"include_resp_body": true
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 16: hit route
--- request
GET /grpc_plus?a=1&b=2
--- response_body eval
qr/\{"result":3\}/
--- error_log eval
qr/request log: \{.*body":\"\{\\"result\\":3}/
=== TEST 17: pb_option should be be set on the route level
--- extra_init_by_lua
local pb = require("pb")
local old_f = pb.option
pb.option = function(o)
if o ~= "int64_as_string" and o ~= "int64_as_number" then
-- filter out options set by other components.
-- we can still test some options like enum_as_name
ngx.log(ngx.WARN, "set protobuf option: ", o)
end
return old_f(o)
end
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/protos/1',
ngx.HTTP_PUT,
[[{
"content" : "syntax = \"proto3\";
package helloworld;
service Greeter {
rpc Plus (PlusRequest) returns (PlusReply) {}
}
message PlusRequest {
int64 a = 1;
int64 b = 2;
}
message PlusReply {
int64 result = 1;
}"
}]]
)
if code >= 300 then
ngx.say(body)
return
end
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"uri": "/grpc_plus2",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "Plus"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.say(body)
return
end
local code, body = t('/apisix/admin/routes/2',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"uri": "/grpc_plus",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "Plus",
"pb_option":["int64_as_string", "enum_as_name"]
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.say(body)
return
end
for i = 1, 3 do
local uri = "http://127.0.0.1:" .. ngx.var.server_port ..
(i == 2 and "/grpc_plus2" or "/grpc_plus") ..
"?a=1&b=2251799813685260"
local httpc = http.new()
local res = assert(httpc:request_uri(uri, {keepalive = false}))
ngx.say(res.body)
end
}
}
--- response_body
{"result":"#2251799813685261"}
{"result":2.2517998136853e+15}
{"result":"#2251799813685261"}
--- grep_error_log eval
qr/set protobuf option: \w+/
--- grep_error_log_out
set protobuf option: enum_as_name
set protobuf option: auto_default_values
set protobuf option: disable_hooks
set protobuf option: enum_as_name
set protobuf option: enum_as_name
=== TEST 18: pb_option should be be set on the route level, two route have the same options
--- extra_init_by_lua
local pb = require("pb")
local old_f = pb.option
pb.option = function(o)
if o ~= "int64_as_string" and o ~= "int64_as_number" then
-- filter out options set by other components
-- we can still test some options like enum_as_name
ngx.log(ngx.WARN, "set protobuf option: ", o)
end
return old_f(o)
end
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/protos/1',
ngx.HTTP_PUT,
[[{
"content" : "syntax = \"proto3\";
package helloworld;
service Greeter {
rpc Plus (PlusRequest) returns (PlusReply) {}
}
message PlusRequest {
int64 a = 1;
int64 b = 2;
}
message PlusReply {
int64 result = 1;
}"
}]]
)
if code >= 300 then
ngx.say(body)
return
end
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"uri": "/grpc_plus2",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "Plus"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.say(body)
return
end
local code, body = t('/apisix/admin/routes/2',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"uri": "/grpc_plus",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "Plus"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.say(body)
return
end
for i = 1, 3 do
local uri = "http://127.0.0.1:" .. ngx.var.server_port ..
(i == 2 and "/grpc_plus2" or "/grpc_plus") ..
"?a=1&b=2251799813685260"
local httpc = http.new()
local res = assert(httpc:request_uri(uri, {keepalive = false}))
ngx.say(res.body)
end
}
}
--- response_body
{"result":2.2517998136853e+15}
{"result":2.2517998136853e+15}
{"result":2.2517998136853e+15}
--- grep_error_log eval
qr/set protobuf option: \w+/
--- grep_error_log_out
set protobuf option: auto_default_values
set protobuf option: disable_hooks
set protobuf option: enum_as_name

View File

@@ -0,0 +1,621 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
no_long_string();
no_shuffle();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (!$block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests;
__DATA__
=== TEST 1: set rule
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/protos/1',
ngx.HTTP_PUT,
[[{
"content" : "syntax = \"proto3\";
package helloworld;
service Greeter {
rpc SayMultipleHello(MultipleHelloRequest) returns (MultipleHelloReply) {}
}
enum Gender {
GENDER_UNKNOWN = 0;
GENDER_MALE = 1;
GENDER_FEMALE = 2;
}
message Person {
string name = 1;
int32 age = 2;
}
message MultipleHelloRequest {
string name = 1;
repeated string items = 2;
repeated Gender genders = 3;
repeated Person persons = 4;
}
message MultipleHelloReply{
string message = 1;
}"
}]]
)
if code >= 300 then
ngx.say(body)
return
end
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["POST"],
"uri": "/grpctest",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "SayMultipleHello"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.say(body)
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: hit route
--- request
POST /grpctest
{"name":"world","persons":[{"name":"Joe","age":1},{"name":"Jake","age":2}]}
--- more_headers
Content-Type: application/json
--- response_body chomp
{"message":"Hello world, name: Joe, age: 1, name: Jake, age: 2"}
=== TEST 3: set proto (id: 1, get error response from rpc)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/protos/1',
ngx.HTTP_PUT,
[[{
"content" : "syntax = \"proto3\";
package helloworld;
service Greeter {
rpc GetErrResp (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
repeated string items = 2;
}
message HelloReply {
string message = 1;
repeated string items = 2;
}
message ErrorDetail {
int64 code = 1;
string message = 2;
string type = 3;
}"
}]]
)
if code >= 300 then
ngx.status = code
return
end
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET", "POST"],
"uri": "/grpctest",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "GetErrResp"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 4: hit route (error response in header)
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin").test
local code, body, headers = t('/grpctest?name=world',
ngx.HTTP_GET
)
ngx.status = code
ngx.header['grpc-status'] = headers['grpc-status']
ngx.header['grpc-message'] = headers['grpc-message']
ngx.header['grpc-status-details-bin'] = headers['grpc-status-details-bin']
body = json.encode(body)
ngx.say(body)
}
}
--- response_headers
grpc-status: 14
grpc-message: Out of service
grpc-status-details-bin: CA4SDk91dCBvZiBzZXJ2aWNlGlcKKnR5cGUuZ29vZ2xlYXBpcy5jb20vaGVsbG93b3JsZC5FcnJvckRldGFpbBIpCAESHFRoZSBzZXJ2ZXIgaXMgb3V0IG9mIHNlcnZpY2UaB3NlcnZpY2U
--- response_body_unlike eval
qr/error/
--- error_code: 503
=== TEST 5: set routes (id: 1, show error response in body)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET", "POST"],
"uri": "/grpctest",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "GetErrResp",
"show_status_in_body": true
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 6: hit route (show error status in body)
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin").test
local code, body, headers = t('/grpctest?name=world',
ngx.HTTP_GET
)
ngx.status = code
ngx.header['grpc-status'] = headers['grpc-status']
ngx.header['grpc-message'] = headers['grpc-message']
ngx.header['grpc-status-details-bin'] = headers['grpc-status-details-bin']
body = json.decode(body)
body = json.encode(body)
ngx.say(body)
}
}
--- response_headers
grpc-status: 14
grpc-message: Out of service
grpc-status-details-bin: CA4SDk91dCBvZiBzZXJ2aWNlGlcKKnR5cGUuZ29vZ2xlYXBpcy5jb20vaGVsbG93b3JsZC5FcnJvckRldGFpbBIpCAESHFRoZSBzZXJ2ZXIgaXMgb3V0IG9mIHNlcnZpY2UaB3NlcnZpY2U
--- response_body
{"error":{"code":14,"details":[{"type_url":"type.googleapis.com/helloworld.ErrorDetail","value":"\b\u0001\u0012\u001cThe server is out of service\u001a\u0007service"}],"message":"Out of service"}}
--- error_code: 503
=== TEST 7: set routes (id: 1, show error details in body)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET", "POST"],
"uri": "/grpctest",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "GetErrResp",
"show_status_in_body": true,
"status_detail_type": "helloworld.ErrorDetail"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 8: hit route (show error details in body)
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin").test
local code, body, headers = t('/grpctest?name=world',
ngx.HTTP_GET
)
ngx.status = code
ngx.header['grpc-status'] = headers['grpc-status']
ngx.header['grpc-message'] = headers['grpc-message']
ngx.header['grpc-status-details-bin'] = headers['grpc-status-details-bin']
body = json.decode(body)
body = json.encode(body)
ngx.say(body)
}
}
--- response_headers
grpc-status: 14
grpc-message: Out of service
grpc-status-details-bin: CA4SDk91dCBvZiBzZXJ2aWNlGlcKKnR5cGUuZ29vZ2xlYXBpcy5jb20vaGVsbG93b3JsZC5FcnJvckRldGFpbBIpCAESHFRoZSBzZXJ2ZXIgaXMgb3V0IG9mIHNlcnZpY2UaB3NlcnZpY2U
--- response_body
{"error":{"code":14,"details":[{"code":1,"message":"The server is out of service","type":"service"}],"message":"Out of service"}}
--- error_code: 503
=== TEST 9: set routes (id: 1, show error details in body and wrong status_detail_type)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET", "POST"],
"uri": "/grpctest",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "GetErrResp",
"show_status_in_body": true,
"status_detail_type": "helloworld.error"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 10: hit route (show error details in body and wrong status_detail_type)
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin").test
local code, body, headers = t('/grpctest?name=world',
ngx.HTTP_GET
)
ngx.status = code
ngx.header['grpc-status'] = headers['grpc-status']
ngx.header['grpc-message'] = headers['grpc-message']
ngx.header['grpc-status-details-bin'] = headers['grpc-status-details-bin']
ngx.say(body)
}
}
--- response_headers
grpc-status: 14
grpc-message: Out of service
grpc-status-details-bin: CA4SDk91dCBvZiBzZXJ2aWNlGlcKKnR5cGUuZ29vZ2xlYXBpcy5jb20vaGVsbG93b3JsZC5FcnJvckRldGFpbBIpCAESHFRoZSBzZXJ2ZXIgaXMgb3V0IG9mIHNlcnZpY2UaB3NlcnZpY2U
--- response_body
failed to call pb.decode to decode details in grpc-status-details-bin
--- error_log
transform response error: failed to call pb.decode to decode details in grpc-status-details-bin, err:
--- error_code: 503
=== TEST 11: set binary rule for EchoStruct
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin")
local json = require("toolkit.json")
local content = t.read_file("t/grpc_server_example/echo.pb")
local data = {content = ngx.encode_base64(content)}
local code, body = t.test('/apisix/admin/protos/1',
ngx.HTTP_PUT,
json.encode(data)
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local code, body = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/grpctest",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "echo.Echo",
"method": "EchoStruct"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 12: hit route to test EchoStruct
--- config
location /t {
content_by_lua_block {
local core = require "apisix.core"
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/grpctest"
local body = [[{"data":{"fields":{"foo":{"string_value":"xxx"},"bar":{"number_value":666}}}}]]
local opt = {method = "POST", body = body, headers = {["Content-Type"] = "application/json"}, keepalive = false}
local httpc = http.new()
local res, err = httpc:request_uri(uri, opt)
if not res then
ngx.log(ngx.ERR, err)
return ngx.exit(500)
end
if res.status > 300 then
return ngx.exit(res.status)
else
local req = core.json.decode(body)
local rsp = core.json.decode(res.body)
for k, v in pairs(req.data.fields) do
if rsp.data.fields[k] == nil then
ngx.log(ngx.ERR, "rsp missing field=", k, ", rsp: ", res.body)
else
for k1, v1 in pairs(v) do
if v1 ~= rsp.data.fields[k][k1] then
ngx.log(ngx.ERR, "rsp mismatch: k=", k1,
", req=", v1, ", rsp=", rsp.data.fields[k][k1])
end
end
end
end
end
}
}
=== TEST 13: bugfix - filter out illegal INT(string) formats
--- config
location /t {
content_by_lua_block {
local pcall = pcall
local require = require
local protoc = require("protoc")
local pb = require("pb")
local pb_encode = pb.encode
assert(protoc:load [[
syntax = "proto3";
message IntStringPattern {
int64 value = 1;
}]])
local patterns
do
local function G(pattern)
return {pattern, true}
end
local function B(pattern)
return {pattern, [[bad argument #2 to '?' (number/'#number' expected for field 'value', got string)]]}
end
patterns = {
G(1), G(2), G(-3), G("#123"), G("0xabF"), G("#-0x123abcdef"), G("-#0x123abcdef"), G("#0x123abcdef"), G("123"),
B("#a"), B("+aaa"), B("#aaaa"), B("#-aa"),
}
end
for _, p in pairs(patterns) do
local pattern = {
value = p[1],
}
local status, err = pcall(pb_encode, "IntStringPattern", pattern)
local res = status
if not res then
res = err
end
assert(res == p[2])
end
ngx.say("passed")
}
}
--- response_body
passed
=== TEST 14: pb_option - check the matchings between enum and category
--- config
location /t {
content_by_lua_block {
local ngx_re_match = ngx.re.match
local plugin = require("apisix.plugins.grpc-transcode")
local pb_option_def = plugin.schema.properties.pb_option.items.anyOf
local patterns = {
[[^enum_as_.+$]],
[[^int64_as_.+$]],
[[^.+_default_.+$]],
[[^.+_hooks$]],
}
local function check_pb_option_enum_category()
for i, category in ipairs(pb_option_def) do
for _, enum in ipairs(category.enum) do
if not ngx_re_match(enum, patterns[i], "jo") then
return ([[mismatch between enum("%s") and category("%s")]]):format(
enum, category.description)
end
end
end
end
local err = check_pb_option_enum_category()
if err then
ngx.say(err)
return
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
done

View File

@@ -0,0 +1,355 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
no_long_string();
no_shuffle();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (!$block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests;
__DATA__
=== TEST 1: set route (default grpc web proxy route)
--- config
location /t {
content_by_lua_block {
local config = {
uri = "/grpc/web/*",
upstream = {
scheme = "grpc",
type = "roundrobin",
nodes = {
["127.0.0.1:50001"] = 1
}
},
plugins = {
["grpc-web"] = {}
}
}
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, config)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: Proxy unary request using APISIX with trailers gRPC-Web plugin
Status should be printed at most once per request, otherwise this would be out of specification.
--- exec
node ./t/plugin/grpc-web/client.js BIN UNARY
node ./t/plugin/grpc-web/client.js TEXT UNARY
--- response_body
Status: { code: 0, details: '', metadata: {} }
{"name":"hello","path":"/hello"}
Status: { code: 0, details: '', metadata: {} }
{"name":"hello","path":"/hello"}
=== TEST 3: Proxy server-side streaming request using APISIX with trailers gRPC-Web plugin
--- exec
node ./t/plugin/grpc-web/client.js BIN STREAM
node ./t/plugin/grpc-web/client.js TEXT STREAM
--- response_body
{"name":"hello","path":"/hello"}
{"name":"world","path":"/world"}
Status: { code: 0, details: '', metadata: {} }
{"name":"hello","path":"/hello"}
{"name":"world","path":"/world"}
Status: { code: 0, details: '', metadata: {} }
=== TEST 4: test options request
--- request
OPTIONS /grpc/web/a6.RouteService/GetRoute
--- error_code: 204
--- response_headers
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: content-type,x-grpc-web,x-user-agent
Access-Control-Allow-Origin: *
=== TEST 5: test non-options request
--- request
GET /grpc/web/a6.RouteService/GetRoute
--- error_code: 405
--- response_headers
Access-Control-Allow-Origin: *
--- error_log
request method: `GET` invalid
=== TEST 6: test non gRPC Web MIME type request
--- request
POST /grpc/web/a6.RouteService/GetRoute
--- more_headers
Content-Type: application/json
--- error_code: 400
--- response_headers
Access-Control-Allow-Origin: *
Content-Type: text/html
--- error_log
request Content-Type: `application/json` invalid
=== TEST 7: set route (absolute match)
--- config
location /t {
content_by_lua_block {
local config = {
uri = "/grpc/web2/a6.RouteService/GetRoute",
upstream = {
scheme = "grpc",
type = "roundrobin",
nodes = {
["127.0.0.1:50001"] = 1
}
},
plugins = {
["grpc-web"] = {}
}
}
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, config)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 8: test route (absolute match)
--- request
POST /grpc/web2/a6.RouteService/GetRoute
--- more_headers
Content-Type: application/grpc-web
--- error_code: 400
--- response_headers
Access-Control-Allow-Origin: *
Content-Type: text/html
--- error_log
routing configuration error, grpc-web plugin only supports `prefix matching` pattern routing
=== TEST 9: set route (with cors plugin)
--- config
location /t {
content_by_lua_block {
local config = {
uri = "/grpc/web/*",
upstream = {
scheme = "grpc",
type = "roundrobin",
nodes = {
["127.0.0.1:50001"] = 1
}
},
plugins = {
["grpc-web"] = {},
cors = {
allow_origins = "http://test.com",
allow_methods = "POST,OPTIONS",
allow_headers = "application/grpc-web",
expose_headers = "application/grpc-web",
max_age = 5,
allow_credential = true
}
}
}
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, config)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 10: don't override Access-Control-Allow-Origin header in response
--- exec
curl -iv --location 'http://127.0.0.1:1984/grpc/web/a6.RouteService/GetRoute' \
--header 'Origin: http://test.com' \
--header 'Content-Type: application/grpc-web-text' \
--data-raw 'AAAAAAcKBXdvcmxkCgo='
--- response_body eval
qr/HTTP\/1.1 200 OK/ and qr/Access-Control-Allow-Origin: http:\/\/test.com/
=== TEST 11: check for Access-Control-Expose-Headers header in response
--- exec
curl -iv --location 'http://127.0.0.1:1984/grpc/web/a6.RouteService/GetRoute' \
--header 'Origin: http://test.com' \
--header 'Content-Type: application/grpc-web-text' \
--data-raw 'AAAAAAcKBXdvcmxkCgo='
--- response_body eval
qr/Access-Control-Expose-Headers: grpc-message,grpc-status/ and qr/Access-Control-Allow-Origin: http:\/\/test.com/
=== TEST 12: verify trailers in response
According to the gRPC documentation, the grpc-web proxy should not retain trailers received from upstream when
forwarding them, as the reference implementation envoy does, so the current test case is status quo rather
than "correct", which is not expected to have an impact since browsers ignore trailers.
Currently there is no API or hook point available in nginx/lua-nginx-module to remove specified trailers
on demand (grpc_hide_header can do it but it affects the grpc proxy), and some nginx patches may be needed
to allow for code-controlled removal of the trailer at runtime.
When we implement that, this use case will be removed.
--- exec
curl -iv --location 'http://127.0.0.1:1984/grpc/web/a6.RouteService/GetRoute' \
--header 'Content-Type: application/grpc-web+proto' \
--header 'X-Grpc-Web: 1' \
--data-binary '@./t/plugin/grpc-web/req.bin'
--- response_body eval
qr/grpc-status:0\x0d\x0agrpc-message:/
=== TEST 13: confg default response route
--- config
location /t {
content_by_lua_block {
local config = {
uri = "/grpc/web/*",
upstream = {
scheme = "grpc",
type = "roundrobin",
nodes = {
["127.0.0.1:50001"] = 1
}
},
plugins = {
["grpc-web"] = {}
}
}
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, config)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 14: check header in default response
--- request
OPTIONS /grpc/web/a6.RouteService/GetRoute
--- error_code: 204
--- response_headers
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: content-type,x-grpc-web,x-user-agent
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: grpc-message,grpc-status
=== TEST 15: Custom configuration routing
--- config
location /t {
content_by_lua_block {
local config = {
uri = "/grpc/web/*",
upstream = {
scheme = "grpc",
type = "roundrobin",
nodes = {
["127.0.0.1:50001"] = 1
}
},
plugins = {
["grpc-web"] = {
cors_allow_headers = "grpc-accept-encoding"
}
}
}
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, config)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 16: check header in default response
--- request
OPTIONS /grpc/web/a6.RouteService/GetRoute
--- error_code: 204
--- response_headers
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: grpc-accept-encoding
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: grpc-message,grpc-status

View File

@@ -0,0 +1,290 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package a6
import (
context "context"
fmt "fmt"
proto "github.com/golang/protobuf/proto"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type Query struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Query) Reset() { *m = Query{} }
func (m *Query) String() string { return proto.CompactTextString(m) }
func (*Query) ProtoMessage() {}
func (*Query) Descriptor() ([]byte, []int) {
return fileDescriptor_0984d49a362b6b9f, []int{0}
}
func (m *Query) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Query.Unmarshal(m, b)
}
func (m *Query) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Query.Marshal(b, m, deterministic)
}
func (m *Query) XXX_Merge(src proto.Message) {
xxx_messageInfo_Query.Merge(m, src)
}
func (m *Query) XXX_Size() int {
return xxx_messageInfo_Query.Size(m)
}
func (m *Query) XXX_DiscardUnknown() {
xxx_messageInfo_Query.DiscardUnknown(m)
}
var xxx_messageInfo_Query proto.InternalMessageInfo
func (m *Query) GetName() string {
if m != nil {
return m.Name
}
return ""
}
type Route struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Route) Reset() { *m = Route{} }
func (m *Route) String() string { return proto.CompactTextString(m) }
func (*Route) ProtoMessage() {}
func (*Route) Descriptor() ([]byte, []int) {
return fileDescriptor_0984d49a362b6b9f, []int{1}
}
func (m *Route) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Route.Unmarshal(m, b)
}
func (m *Route) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Route.Marshal(b, m, deterministic)
}
func (m *Route) XXX_Merge(src proto.Message) {
xxx_messageInfo_Route.Merge(m, src)
}
func (m *Route) XXX_Size() int {
return xxx_messageInfo_Route.Size(m)
}
func (m *Route) XXX_DiscardUnknown() {
xxx_messageInfo_Route.DiscardUnknown(m)
}
var xxx_messageInfo_Route proto.InternalMessageInfo
func (m *Route) GetName() string {
if m != nil {
return m.Name
}
return ""
}
func (m *Route) GetPath() string {
if m != nil {
return m.Path
}
return ""
}
func init() {
proto.RegisterType((*Query)(nil), "a6.Query")
proto.RegisterType((*Route)(nil), "a6.Route")
}
func init() { proto.RegisterFile("route.proto", fileDescriptor_0984d49a362b6b9f) }
var fileDescriptor_0984d49a362b6b9f = []byte{
// 149 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2e, 0xca, 0x2f, 0x2d,
0x49, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x4a, 0x34, 0x53, 0x92, 0xe6, 0x62, 0x0d,
0x2c, 0x4d, 0x2d, 0xaa, 0x14, 0x12, 0xe2, 0x62, 0xc9, 0x4b, 0xcc, 0x4d, 0x95, 0x60, 0x54, 0x60,
0xd4, 0xe0, 0x0c, 0x02, 0xb3, 0x95, 0xf4, 0xb9, 0x58, 0x83, 0x40, 0xea, 0xb1, 0x49, 0x82, 0xc4,
0x0a, 0x12, 0x4b, 0x32, 0x24, 0x98, 0x20, 0x62, 0x20, 0xb6, 0x51, 0x24, 0x17, 0x0f, 0x58, 0x43,
0x70, 0x6a, 0x51, 0x59, 0x66, 0x72, 0xaa, 0x90, 0x12, 0x17, 0x87, 0x7b, 0x6a, 0x09, 0xc4, 0x0c,
0x4e, 0xbd, 0x44, 0x33, 0x3d, 0xb0, 0x5d, 0x52, 0x60, 0x26, 0x58, 0x54, 0x89, 0x41, 0x48, 0x95,
0x8b, 0x13, 0xa6, 0xa6, 0x18, 0x97, 0x22, 0x03, 0x46, 0x27, 0xf6, 0x28, 0x56, 0x3d, 0x7d, 0xeb,
0x44, 0xb3, 0x24, 0x36, 0xb0, 0xe3, 0x8d, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x54, 0xf0, 0x73,
0x63, 0xcb, 0x00, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// RouteServiceClient is the client API for RouteService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type RouteServiceClient interface {
GetRoute(ctx context.Context, in *Query, opts ...grpc.CallOption) (*Route, error)
GetRoutes(ctx context.Context, in *Query, opts ...grpc.CallOption) (RouteService_GetRoutesClient, error)
}
type routeServiceClient struct {
cc *grpc.ClientConn
}
func NewRouteServiceClient(cc *grpc.ClientConn) RouteServiceClient {
return &routeServiceClient{cc}
}
func (c *routeServiceClient) GetRoute(ctx context.Context, in *Query, opts ...grpc.CallOption) (*Route, error) {
out := new(Route)
err := c.cc.Invoke(ctx, "/a6.RouteService/GetRoute", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *routeServiceClient) GetRoutes(ctx context.Context, in *Query, opts ...grpc.CallOption) (RouteService_GetRoutesClient, error) {
stream, err := c.cc.NewStream(ctx, &_RouteService_serviceDesc.Streams[0], "/a6.RouteService/GetRoutes", opts...)
if err != nil {
return nil, err
}
x := &routeServiceGetRoutesClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type RouteService_GetRoutesClient interface {
Recv() (*Route, error)
grpc.ClientStream
}
type routeServiceGetRoutesClient struct {
grpc.ClientStream
}
func (x *routeServiceGetRoutesClient) Recv() (*Route, error) {
m := new(Route)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// RouteServiceServer is the server API for RouteService service.
type RouteServiceServer interface {
GetRoute(context.Context, *Query) (*Route, error)
GetRoutes(*Query, RouteService_GetRoutesServer) error
}
// UnimplementedRouteServiceServer can be embedded to have forward compatible implementations.
type UnimplementedRouteServiceServer struct {
}
func (*UnimplementedRouteServiceServer) GetRoute(ctx context.Context, req *Query) (*Route, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRoute not implemented")
}
func (*UnimplementedRouteServiceServer) GetRoutes(req *Query, srv RouteService_GetRoutesServer) error {
return status.Errorf(codes.Unimplemented, "method GetRoutes not implemented")
}
func RegisterRouteServiceServer(s *grpc.Server, srv RouteServiceServer) {
s.RegisterService(&_RouteService_serviceDesc, srv)
}
func _RouteService_GetRoute_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Query)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RouteServiceServer).GetRoute(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/a6.RouteService/GetRoute",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RouteServiceServer).GetRoute(ctx, req.(*Query))
}
return interceptor(ctx, in, info, handler)
}
func _RouteService_GetRoutes_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(Query)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(RouteServiceServer).GetRoutes(m, &routeServiceGetRoutesServer{stream})
}
type RouteService_GetRoutesServer interface {
Send(*Route) error
grpc.ServerStream
}
type routeServiceGetRoutesServer struct {
grpc.ServerStream
}
func (x *routeServiceGetRoutesServer) Send(m *Route) error {
return x.ServerStream.SendMsg(m)
}
var _RouteService_serviceDesc = grpc.ServiceDesc{
ServiceName: "a6.RouteService",
HandlerType: (*RouteServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GetRoute",
Handler: _RouteService_GetRoute_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "GetRoutes",
Handler: _RouteService_GetRoutes_Handler,
ServerStreams: true,
},
},
Metadata: "route.proto",
}

View File

@@ -0,0 +1,36 @@
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
syntax = "proto3";
package a6;
option go_package = "./;a6";
service RouteService {
rpc GetRoute(Query) returns (Route) {}
rpc GetRoutes(Query) returns (stream Route) {}
}
message Query {
string name = 1;
}
message Route {
string name = 1;
string path = 2;
}

View File

@@ -0,0 +1,194 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const grpc = {};
grpc.web = require('grpc-web');
const proto = {};
proto.a6 = require('./route_pb.js');
/**
* @param {string} hostname
* @param {?Object} credentials
* @param {?grpc.web.ClientOptions} options
* @constructor
* @struct
* @final
*/
proto.a6.RouteServiceClient =
function(hostname, credentials, options) {
if (!options) options = {};
options.format = 'binary';
/**
* @private @const {!grpc.web.GrpcWebClientBase} The client
*/
this.client_ = new grpc.web.GrpcWebClientBase(options);
/**
* @private @const {string} The hostname
*/
this.hostname_ = hostname;
};
/**
* @param {string} hostname
* @param {?Object} credentials
* @param {?grpc.web.ClientOptions} options
* @constructor
* @struct
* @final
*/
proto.a6.RouteServicePromiseClient =
function(hostname, credentials, options) {
if (!options) options = {};
options.format = 'binary';
/**
* @private @const {!grpc.web.GrpcWebClientBase} The client
*/
this.client_ = new grpc.web.GrpcWebClientBase(options);
/**
* @private @const {string} The hostname
*/
this.hostname_ = hostname;
};
/**
* @const
* @type {!grpc.web.MethodDescriptor<
* !proto.a6.Query,
* !proto.a6.Route>}
*/
const methodDescriptor_RouteService_GetRoute = new grpc.web.MethodDescriptor(
'/a6.RouteService/GetRoute',
grpc.web.MethodType.UNARY,
proto.a6.Query,
proto.a6.Route,
/**
* @param {!proto.a6.Query} request
* @return {!Uint8Array}
*/
function(request) {
return request.serializeBinary();
},
proto.a6.Route.deserializeBinary
);
/**
* @param {!proto.a6.Query} request The
* request proto
* @param {?Object<string, string>} metadata User defined
* call metadata
* @param {function(?grpc.web.RpcError, ?proto.a6.Route)}
* callback The callback function(error, response)
* @return {!grpc.web.ClientReadableStream<!proto.a6.Route>|undefined}
* The XHR Node Readable Stream
*/
proto.a6.RouteServiceClient.prototype.getRoute =
function(request, metadata, callback) {
return this.client_.rpcCall(this.hostname_ +
'/a6.RouteService/GetRoute',
request,
metadata || {},
methodDescriptor_RouteService_GetRoute,
callback);
};
/**
* @param {!proto.a6.Query} request The
* request proto
* @param {?Object<string, string>=} metadata User defined
* call metadata
* @return {!Promise<!proto.a6.Route>}
* Promise that resolves to the response
*/
proto.a6.RouteServicePromiseClient.prototype.getRoute =
function(request, metadata) {
return this.client_.unaryCall(this.hostname_ +
'/a6.RouteService/GetRoute',
request,
metadata || {},
methodDescriptor_RouteService_GetRoute);
};
/**
* @const
* @type {!grpc.web.MethodDescriptor<
* !proto.a6.Query,
* !proto.a6.Route>}
*/
const methodDescriptor_RouteService_GetRoutes = new grpc.web.MethodDescriptor(
'/a6.RouteService/GetRoutes',
grpc.web.MethodType.SERVER_STREAMING,
proto.a6.Query,
proto.a6.Route,
/**
* @param {!proto.a6.Query} request
* @return {!Uint8Array}
*/
function(request) {
return request.serializeBinary();
},
proto.a6.Route.deserializeBinary
);
/**
* @param {!proto.a6.Query} request The request proto
* @param {?Object<string, string>=} metadata User defined
* call metadata
* @return {!grpc.web.ClientReadableStream<!proto.a6.Route>}
* The XHR Node Readable Stream
*/
proto.a6.RouteServiceClient.prototype.getRoutes =
function(request, metadata) {
return this.client_.serverStreaming(this.hostname_ +
'/a6.RouteService/GetRoutes',
request,
metadata || {},
methodDescriptor_RouteService_GetRoutes);
};
/**
* @param {!proto.a6.Query} request The request proto
* @param {?Object<string, string>=} metadata User defined
* call metadata
* @return {!grpc.web.ClientReadableStream<!proto.a6.Route>}
* The XHR Node Readable Stream
*/
proto.a6.RouteServicePromiseClient.prototype.getRoutes =
function(request, metadata) {
return this.client_.serverStreaming(this.hostname_ +
'/a6.RouteService/GetRoutes',
request,
metadata || {},
methodDescriptor_RouteService_GetRoutes);
};
module.exports = proto.a6;

View File

@@ -0,0 +1,194 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const grpc = {};
grpc.web = require('grpc-web');
const proto = {};
proto.a6 = require('./route_pb.js');
/**
* @param {string} hostname
* @param {?Object} credentials
* @param {?grpc.web.ClientOptions} options
* @constructor
* @struct
* @final
*/
proto.a6.RouteServiceClient =
function(hostname, credentials, options) {
if (!options) options = {};
options.format = 'text';
/**
* @private @const {!grpc.web.GrpcWebClientBase} The client
*/
this.client_ = new grpc.web.GrpcWebClientBase(options);
/**
* @private @const {string} The hostname
*/
this.hostname_ = hostname;
};
/**
* @param {string} hostname
* @param {?Object} credentials
* @param {?grpc.web.ClientOptions} options
* @constructor
* @struct
* @final
*/
proto.a6.RouteServicePromiseClient =
function(hostname, credentials, options) {
if (!options) options = {};
options.format = 'text';
/**
* @private @const {!grpc.web.GrpcWebClientBase} The client
*/
this.client_ = new grpc.web.GrpcWebClientBase(options);
/**
* @private @const {string} The hostname
*/
this.hostname_ = hostname;
};
/**
* @const
* @type {!grpc.web.MethodDescriptor<
* !proto.a6.Query,
* !proto.a6.Route>}
*/
const methodDescriptor_RouteService_GetRoute = new grpc.web.MethodDescriptor(
'/a6.RouteService/GetRoute',
grpc.web.MethodType.UNARY,
proto.a6.Query,
proto.a6.Route,
/**
* @param {!proto.a6.Query} request
* @return {!Uint8Array}
*/
function(request) {
return request.serializeBinary();
},
proto.a6.Route.deserializeBinary
);
/**
* @param {!proto.a6.Query} request The
* request proto
* @param {?Object<string, string>} metadata User defined
* call metadata
* @param {function(?grpc.web.RpcError, ?proto.a6.Route)}
* callback The callback function(error, response)
* @return {!grpc.web.ClientReadableStream<!proto.a6.Route>|undefined}
* The XHR Node Readable Stream
*/
proto.a6.RouteServiceClient.prototype.getRoute =
function(request, metadata, callback) {
return this.client_.rpcCall(this.hostname_ +
'/a6.RouteService/GetRoute',
request,
metadata || {},
methodDescriptor_RouteService_GetRoute,
callback);
};
/**
* @param {!proto.a6.Query} request The
* request proto
* @param {?Object<string, string>=} metadata User defined
* call metadata
* @return {!Promise<!proto.a6.Route>}
* Promise that resolves to the response
*/
proto.a6.RouteServicePromiseClient.prototype.getRoute =
function(request, metadata) {
return this.client_.unaryCall(this.hostname_ +
'/a6.RouteService/GetRoute',
request,
metadata || {},
methodDescriptor_RouteService_GetRoute);
};
/**
* @const
* @type {!grpc.web.MethodDescriptor<
* !proto.a6.Query,
* !proto.a6.Route>}
*/
const methodDescriptor_RouteService_GetRoutes = new grpc.web.MethodDescriptor(
'/a6.RouteService/GetRoutes',
grpc.web.MethodType.SERVER_STREAMING,
proto.a6.Query,
proto.a6.Route,
/**
* @param {!proto.a6.Query} request
* @return {!Uint8Array}
*/
function(request) {
return request.serializeBinary();
},
proto.a6.Route.deserializeBinary
);
/**
* @param {!proto.a6.Query} request The request proto
* @param {?Object<string, string>=} metadata User defined
* call metadata
* @return {!grpc.web.ClientReadableStream<!proto.a6.Route>}
* The XHR Node Readable Stream
*/
proto.a6.RouteServiceClient.prototype.getRoutes =
function(request, metadata) {
return this.client_.serverStreaming(this.hostname_ +
'/a6.RouteService/GetRoutes',
request,
metadata || {},
methodDescriptor_RouteService_GetRoutes);
};
/**
* @param {!proto.a6.Query} request The request proto
* @param {?Object<string, string>=} metadata User defined
* call metadata
* @return {!grpc.web.ClientReadableStream<!proto.a6.Route>}
* The XHR Node Readable Stream
*/
proto.a6.RouteServicePromiseClient.prototype.getRoutes =
function(request, metadata) {
return this.client_.serverStreaming(this.hostname_ +
'/a6.RouteService/GetRoutes',
request,
metadata || {},
methodDescriptor_RouteService_GetRoutes);
};
module.exports = proto.a6;

Some files were not shown because too many files have changed in this diff Show More