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,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';
repeat_each(1);
log_level('info');
worker_connections(256);
no_root_location();
no_shuffle();
run_tests();
__DATA__
=== TEST 1: setup 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,
[[{
"methods": ["GET"],
"upstream": {
"nodes": {
"httpbin.org:80": 1,
"mockbin.org:80": 1
},
"type": "roundrobin"
},
"uri": "/*"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
=== TEST 2: hit the route
--- request
GET /status/403
--- error_code: 403
=== TEST 3: hit control api
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local passed = true
for i = 1, 40 do
local code, body, res = t.test('/v1/routes/1', ngx.HTTP_GET)
if code ~= ngx.HTTP_OK then
passed = code
break
end
end
if passed then
ngx.say("passed")
else
ngx.say("failed. got status code: ", passed)
end
}
}
--- request
GET /t
--- response_body
passed
=== TEST 4: hit the route again
--- request
GET /status/403
--- error_code: 403
=== TEST 5: hit control api
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local passed = true
for i = 1, 40 do
local code, body, res = t.test('/v1/routes/1', ngx.HTTP_GET)
if code ~= ngx.HTTP_OK then
passed = code
break
end
end
if passed then
ngx.say("passed")
else
ngx.say("failed. got status code: ", passed)
end
}
}
--- request
GET /t
--- response_body
passed

View File

@@ -0,0 +1,221 @@
#
# 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");
# Because this whole test file is only used to verify the configuration set or not,
# but the configuration content is invalid, which contains non-exist consul server address,
# so we have to ignore consul connect errors in some test cases.
our $yaml_config = <<_EOC_;
apisix:
enable_control: true
node_listen: 1984
discovery:
eureka:
host:
- "http://127.0.0.1:8761"
prefix: "/eureka/"
fetch_interval: 10
weight: 80
timeout:
connect: 1500
send: 1500
read: 1500
consul_kv:
servers:
- "http://127.0.0.1:8500"
- "http://127.0.0.1:8600"
dns:
servers:
- "127.0.0.1:1053"
_EOC_
run_tests();
__DATA__
=== TEST 1: test consul_kv dump_data api
--- yaml_config eval: $::yaml_config
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, body, res = t.test('/v1/discovery/consul_kv/dump',
ngx.HTTP_GET)
local entity = json.decode(res)
ngx.say(json.encode(entity.services))
ngx.say(json.encode(entity.config))
}
}
--- request
GET /t
--- error_code: 200
--- response_body
{}
{"fetch_interval":3,"keepalive":true,"prefix":"upstreams","servers":["http://127.0.0.1:8500","http://127.0.0.1:8600"],"timeout":{"connect":2000,"read":2000,"wait":60},"token":"","weight":1}
--- error_log
connect consul
=== TEST 2: test eureka dump_data api
--- yaml_config eval: $::yaml_config
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, body, res = t.test('/v1/discovery/eureka/dump',
ngx.HTTP_GET, nil,
[[{
"config": {
"fetch_interval": 10,
"host": [
"http://127.0.0.1:8761"
],
"prefix": "/eureka/",
"timeout": {
"connect": 1500,
"read": 1500,
"send": 1500
},
"weight": 80
},
"services": {}
}]]
)
ngx.status = code
ngx.say(body)
}
}
--- request
GET /t
--- error_code: 200
--- response_body
passed
--- error_log
connect consul
=== TEST 3: test dns api
--- yaml_config eval: $::yaml_config
--- request
GET /v1/discovery/dns/dump
--- error_code: 404
--- error_log
connect consul
=== TEST 4: test unconfigured eureka dump_data api
--- yaml_config
apisix:
enable_control: true
node_listen: 1984
discovery:
consul_kv:
servers:
- "http://127.0.0.1:8500"
- "http://127.0.0.1:8600"
#END
--- request
GET /v1/discovery/eureka/dump
--- error_code: 404
--- error_log
connect consul
=== TEST 5: prepare consul kv register nodes
--- config
location /consul1 {
rewrite ^/consul1/(.*) /v1/kv/$1 break;
proxy_pass http://127.0.0.1:8500;
}
location /consul2 {
rewrite ^/consul2/(.*) /v1/kv/$1 break;
proxy_pass http://127.0.0.1:8600;
}
--- pipelined_requests eval
[
"DELETE /consul1/upstreams/?recurse=true",
"DELETE /consul2/upstreams/?recurse=true",
"PUT /consul1/upstreams/webpages/127.0.0.1:30511\n" . "{\"weight\": 1, \"max_fails\": 2, \"fail_timeout\": 1}",
"PUT /consul1/upstreams/webpages/127.0.0.1:30512\n" . "{\"weight\": 1, \"max_fails\": 2, \"fail_timeout\": 1}",
"PUT /consul2/upstreams/webpages/127.0.0.1:30513\n" . "{\"weight\": 1, \"max_fails\": 2, \"fail_timeout\": 1}",
"PUT /consul2/upstreams/webpages/127.0.0.1:30514\n" . "{\"weight\": 1, \"max_fails\": 2, \"fail_timeout\": 1}",
]
--- response_body eval
["true", "true", "true", "true", "true", "true"]
=== TEST 6: dump consul_kv services
--- yaml_config eval: $::yaml_config
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
ngx.sleep(2)
local code, body, res = t.test('/v1/discovery/consul_kv/dump',
ngx.HTTP_GET)
local entity = json.decode(res)
ngx.say(json.encode(entity.services))
}
}
--- request
GET /t
--- error_code: 200
--- response_body
{"http://127.0.0.1:8500/v1/kv/upstreams/webpages/":[{"host":"127.0.0.1","port":30511,"weight":1},{"host":"127.0.0.1","port":30512,"weight":1}],"http://127.0.0.1:8600/v1/kv/upstreams/webpages/":[{"host":"127.0.0.1","port":30513,"weight":1},{"host":"127.0.0.1","port":30514,"weight":1}]}
=== TEST 7: clean consul kv register nodes
--- config
location /consul1 {
rewrite ^/consul1/(.*) /v1/kv/$1 break;
proxy_pass http://127.0.0.1:8500;
}
location /consul2 {
rewrite ^/consul2/(.*) /v1/kv/$1 break;
proxy_pass http://127.0.0.1:8600;
}
--- pipelined_requests eval
[
"DELETE /consul1/upstreams/?recurse=true",
"DELETE /consul2/upstreams/?recurse=true"
]
--- response_body eval
["true", "true"]

View File

@@ -0,0 +1,66 @@
#
# 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->set_value("no_error_log", "[error]\n[alert]");
}
});
run_tests;
__DATA__
=== TEST 1: trigger full gc
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin")
local before = collectgarbage("count")
do
local tab = {}
for i = 1, 10000 do
tab[i] = {"a", 1}
end
end
local after_alloc = collectgarbage("count")
local code = t.test('/v1/gc',
ngx.HTTP_POST
)
local after_gc = collectgarbage("count")
if code == 200 then
if after_alloc - after_gc > 0.9 * (after_alloc - before) then
ngx.say("ok")
else
ngx.say(before, " ", after_alloc, " ", after_gc)
end
end
}
}
--- response_body
ok

View File

@@ -0,0 +1,305 @@
#
# 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");
}
});
run_tests;
__DATA__
=== TEST 1: upstreams
--- yaml_config
apisix:
node_listen: 1984
deployment:
role: data_plane
role_data_plane:
config_provider: yaml
--- apisix_yaml
routes:
-
uris:
- /hello
upstream_id: 1
upstreams:
- nodes:
"127.0.0.1:1980": 1
"127.0.0.2:1988": 0
type: roundrobin
id: 1
checks:
active:
http_path: "/status"
healthy:
interval: 1
successes: 1
unhealthy:
interval: 1
http_failures: 1
#END
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local json = require("toolkit.json")
local t = require("lib.test_admin")
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"})
ngx.sleep(2.2)
local _, _, res = t.test('/v1/healthcheck',
ngx.HTTP_GET)
res = json.decode(res)
assert(#res == 1, "invalid number of results")
table.sort(res[1].nodes, function(a, b)
return a.ip < b.ip
end)
ngx.say(core.json.stably_encode(res[1].nodes))
local _, _, res = t.test('/v1/healthcheck/upstreams/1',
ngx.HTTP_GET)
res = json.decode(res)
table.sort(res.nodes, function(a, b)
return a.ip < b.ip
end)
ngx.say(core.json.stably_encode(res.nodes))
local _, _, res = t.test('/v1/healthcheck/upstreams/1',
ngx.HTTP_GET, nil, nil, {["Accept"] = "text/html"})
local xml2lua = require("xml2lua")
local xmlhandler = require("xmlhandler.tree")
local handler = xmlhandler:new()
local parser = xml2lua.parser(handler)
parser.parse(parser, res)
local matches = 0
for _, td in ipairs(handler.root.html.body.table.tr) do
if td.td then
if td.td[4] == "127.0.0.2:1988" then
assert(td.td[5] == "unhealthy", "127.0.0.2:1988 is not unhealthy")
matches = matches + 1
end
if td.td[4] == "127.0.0.1:1980" then
assert(td.td[5] == "healthy", "127.0.0.1:1980 is not healthy")
matches = matches + 1
end
end
end
assert(matches == 2, "unexpected html")
}
}
--- grep_error_log eval
qr/unhealthy TCP increment \(.+\) for '[^']+'/
--- grep_error_log_out
unhealthy TCP increment (1/2) for '127.0.0.2(127.0.0.2:1988)'
unhealthy TCP increment (2/2) for '127.0.0.2(127.0.0.2:1988)'
--- response_body
[{"counter":{"http_failure":0,"success":0,"tcp_failure":0,"timeout_failure":0},"hostname":"127.0.0.1","ip":"127.0.0.1","port":1980,"status":"healthy"},{"counter":{"http_failure":0,"success":0,"tcp_failure":2,"timeout_failure":0},"hostname":"127.0.0.2","ip":"127.0.0.2","port":1988,"status":"unhealthy"}]
[{"counter":{"http_failure":0,"success":0,"tcp_failure":0,"timeout_failure":0},"hostname":"127.0.0.1","ip":"127.0.0.1","port":1980,"status":"healthy"},{"counter":{"http_failure":0,"success":0,"tcp_failure":2,"timeout_failure":0},"hostname":"127.0.0.2","ip":"127.0.0.2","port":1988,"status":"unhealthy"}]
=== TEST 2: routes
--- yaml_config
apisix:
node_listen: 1984
deployment:
role: data_plane
role_data_plane:
config_provider: yaml
--- apisix_yaml
routes:
-
id: 1
uris:
- /hello
upstream:
nodes:
"127.0.0.1:1980": 1
"127.0.0.1:1988": 1
type: roundrobin
checks:
active:
http_path: "/status"
host: "127.0.0.1"
healthy:
interval: 1
successes: 1
unhealthy:
interval: 1
http_failures: 1
#END
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
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"})
ngx.sleep(2.2)
local code, body, res = t.test('/v1/healthcheck',
ngx.HTTP_GET)
res = json.decode(res)
table.sort(res[1].nodes, function(a, b)
return a.port < b.port
end)
ngx.say(json.encode(res))
local code, body, res = t.test('/v1/healthcheck/routes/1',
ngx.HTTP_GET)
res = json.decode(res)
table.sort(res.nodes, function(a, b)
return a.port < b.port
end)
ngx.say(json.encode(res))
}
}
--- grep_error_log eval
qr/unhealthy TCP increment \(.+\) for '[^']+'/
--- grep_error_log_out
unhealthy TCP increment (1/2) for '127.0.0.1(127.0.0.1:1988)'
unhealthy TCP increment (2/2) for '127.0.0.1(127.0.0.1:1988)'
--- response_body
[{"name":"/routes/1","nodes":[{"counter":{"http_failure":0,"success":0,"tcp_failure":0,"timeout_failure":0},"hostname":"127.0.0.1","ip":"127.0.0.1","port":1980,"status":"healthy"},{"counter":{"http_failure":0,"success":0,"tcp_failure":2,"timeout_failure":0},"hostname":"127.0.0.1","ip":"127.0.0.1","port":1988,"status":"unhealthy"}],"type":"http"}]
{"name":"/routes/1","nodes":[{"counter":{"http_failure":0,"success":0,"tcp_failure":0,"timeout_failure":0},"hostname":"127.0.0.1","ip":"127.0.0.1","port":1980,"status":"healthy"},{"counter":{"http_failure":0,"success":0,"tcp_failure":2,"timeout_failure":0},"hostname":"127.0.0.1","ip":"127.0.0.1","port":1988,"status":"unhealthy"}],"type":"http"}
=== TEST 3: services
--- yaml_config
apisix:
node_listen: 1984
deployment:
role: data_plane
role_data_plane:
config_provider: yaml
--- apisix_yaml
routes:
- id: 1
service_id: 1
uris:
- /hello
services:
-
id: 1
upstream:
nodes:
"127.0.0.1:1980": 1
"127.0.0.1:1988": 1
type: roundrobin
checks:
active:
http_path: "/status"
host: "127.0.0.1"
port: 1988
healthy:
interval: 1
successes: 1
unhealthy:
interval: 1
http_failures: 1
#END
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
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"})
ngx.sleep(2.2)
local code, body, res = t.test('/v1/healthcheck',
ngx.HTTP_GET)
res = json.decode(res)
table.sort(res[1].nodes, function(a, b)
return a.port < b.port
end)
ngx.say(json.encode(res))
local code, body, res = t.test('/v1/healthcheck/services/1',
ngx.HTTP_GET)
res = json.decode(res)
table.sort(res.nodes, function(a, b)
return a.port < b.port
end)
ngx.say(json.encode(res))
}
}
--- grep_error_log eval
qr/unhealthy TCP increment \(.+\) for '[^']+'/
--- grep_error_log_out
unhealthy TCP increment (1/2) for '127.0.0.1(127.0.0.1:1988)'
unhealthy TCP increment (2/2) for '127.0.0.1(127.0.0.1:1988)'
--- response_body
[{"name":"/services/1","nodes":[{"counter":{"http_failure":0,"success":0,"tcp_failure":2,"timeout_failure":0},"hostname":"127.0.0.1","ip":"127.0.0.1","port":1988,"status":"unhealthy"}],"type":"http"}]
{"name":"/services/1","nodes":[{"counter":{"http_failure":0,"success":0,"tcp_failure":2,"timeout_failure":0},"hostname":"127.0.0.1","ip":"127.0.0.1","port":1988,"status":"unhealthy"}],"type":"http"}
=== TEST 4: no checkers
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin")
local code, body, res = t.test('/v1/healthcheck',
ngx.HTTP_GET)
ngx.print(res)
}
}
--- response_body
{}
=== TEST 5: no checker
--- request
GET /v1/healthcheck/routes/1
--- error_code: 404
--- response_body
{"error_msg":"routes[1] not found"}
=== TEST 6: invalid src type
--- request
GET /v1/healthcheck/route/1
--- error_code: 400
--- response_body
{"error_msg":"invalid src type route"}

View File

@@ -0,0 +1,55 @@
#
# 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->set_value("no_error_log", "[error]\n[alert]");
}
});
run_tests;
__DATA__
=== TEST 1: sanity
--- request
GET /v1/plugin/example-plugin/hello
--- response_body
world
=== TEST 2: set Content-Type for table response
--- request
GET /v1/plugin/example-plugin/hello?json
--- response_body
{"msg":"world"}
--- response_headers
Content-Type: application/json

View File

@@ -0,0 +1,113 @@
#
# 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");
}
});
run_tests;
__DATA__
=== TEST 1: add plugin metadatas
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code = t('/apisix/admin/plugin_metadata/example-plugin',
ngx.HTTP_PUT,
[[{
"skey": "val",
"ikey": 1
}]]
)
if code >= 300 then
ngx.status = code
return
end
local code = t('/apisix/admin/plugin_metadata/file-logger',
ngx.HTTP_PUT,
[[
{"log_format": {"upstream_response_time": "$upstream_response_time"}}
]]
)
if code >= 300 then
ngx.status = code
return
end
}
}
--- error_code: 200
=== TEST 2: dump all plugin metadatas
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local _, _, res = t('/v1/plugin_metadatas', ngx.HTTP_GET)
local json = require("toolkit.json")
res = json.decode(res)
for _, metadata in ipairs(res) do
if metadata.id == "file-logger" then
ngx.say("check log_format: ", metadata.log_format.upstream_response_time == "$upstream_response_time")
elseif metadata.id == "example-plugin" then
ngx.say("check skey: ", metadata.skey == "val")
ngx.say("check ikey: ", metadata.ikey == 1)
end
end
}
}
--- response_body
check log_format: true
check skey: true
check ikey: true
=== TEST 3: dump file-logger metadata
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local _, _, res = t('/v1/plugin_metadata/file-logger', ngx.HTTP_GET)
local json = require("toolkit.json")
metadata = json.decode(res)
if metadata.id == "file-logger" then
ngx.say("check log_format: ", metadata.log_format.upstream_response_time == "$upstream_response_time")
end
}
}
--- response_body
check log_format: true
=== TEST 4: plugin without metadata
--- request
GET /v1/plugin_metadata/batch-requests
--- error_code: 404
--- response_body
{"error_msg":"plugin metadata[batch-requests] not found"}

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(1);
no_long_string();
no_root_location();
no_shuffle();
log_level("info");
workers(2);
add_block_preprocessor(sub {
my ($block) = @_;
$block;
});
run_tests;
__DATA__
=== TEST 1: reload plugins
--- yaml_config
apisix:
node_listen: 1984
enable_control: true
control:
ip: "127.0.0.1"
port: 9090
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin")
local code, body, res = t.test('/v1/plugins/reload',
ngx.HTTP_PUT)
ngx.say(res)
ngx.sleep(1)
}
}
--- request
GET /t
--- response_body
done
--- error_log
load plugin times: 2
load plugin times: 2
start to hot reload plugins
start to hot reload plugins
=== TEST 2: reload plugins when attributes changed
--- yaml_config
apisix:
node_listen: 1984
enable_admin: true
node_listen: 1984
plugins:
- example-plugin
plugin_attr:
example-plugin:
val: 0
--- config
location /t {
content_by_lua_block {
local core = require "apisix.core"
ngx.sleep(0.1)
local data = [[
deployment:
role: traditional
role_traditional:
config_provider: etcd
admin:
admin_key: null
apisix:
node_listen: 1984
enable_control: true
control:
ip: "127.0.0.1"
port: 9090
plugins:
- example-plugin
plugin_attr:
example-plugin:
val: 1
]]
require("lib.test_admin").set_config_yaml(data)
local t = require("lib.test_admin").test
local code, _, org_body = t('/v1/plugins/reload',
ngx.HTTP_PUT)
ngx.status = code
ngx.say(org_body)
ngx.sleep(0.1)
local data = [[
deployment:
role: traditional
role_traditional:
config_provider: etcd
admin:
admin_key: null
apisix:
node_listen: 1984
plugins:
- example-plugin
plugin_attr:
example-plugin:
val: 1
]]
require("lib.test_admin").set_config_yaml(data)
local t = require("lib.test_admin").test
local code, _, org_body = t('/v1/plugins/reload',
ngx.HTTP_PUT)
ngx.say(org_body)
ngx.sleep(0.1)
}
}
--- request
GET /t
--- response_body
done
done
--- grep_error_log eval
qr/example-plugin get plugin attr val: \d+/
--- grep_error_log_out
example-plugin get plugin attr val: 0
example-plugin get plugin attr val: 0
example-plugin get plugin attr val: 0
example-plugin get plugin attr val: 1
example-plugin get plugin attr val: 1
example-plugin get plugin attr val: 1
example-plugin get plugin attr val: 1
example-plugin get plugin attr val: 1
example-plugin get plugin attr val: 1
=== TEST 3: reload plugins to change prometheus' export uri
--- yaml_config
apisix:
node_listen: 1984
plugins:
- public-api
- prometheus
plugin_attr:
prometheus:
export_uri: /metrics
--- config
location /t {
content_by_lua_block {
local core = require "apisix.core"
ngx.sleep(0.1)
local t = require("lib.test_admin").test
-- setup public API route
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"public-api": {}
},
"uri": "/apisix/metrics"
}]]
)
ngx.say(code)
local code, _, org_body = t('/apisix/metrics',
ngx.HTTP_GET)
ngx.say(code)
local data = [[
deployment:
role: traditional
role_traditional:
config_provider: etcd
admin:
admin_key: null
apisix:
node_listen: 1984
enable_control: true
control:
ip: "127.0.0.1"
port: 9090
plugins:
- public-api
- prometheus
plugin_attr:
prometheus:
export_uri: /apisix/metrics
]]
require("lib.test_admin").set_config_yaml(data)
local code, _, org_body = t('/v1/plugins/reload',
ngx.HTTP_PUT)
ngx.say(org_body)
ngx.sleep(0.1)
local code, _, org_body = t('/apisix/metrics',
ngx.HTTP_GET)
ngx.say(code)
}
}
--- request
GET /t
--- response_body
201
404
done
200
=== TEST 4: reload plugins to disable skywalking
--- yaml_config
apisix:
node_listen: 1984
enable_control: true
control:
ip: "127.0.0.1"
port: 9090
plugins:
- skywalking
plugin_attr:
skywalking:
service_name: APISIX
service_instance_name: "APISIX Instance Name"
endpoint_addr: http://127.0.0.1:12801
report_interval: 1
--- config
location /t {
content_by_lua_block {
local core = require "apisix.core"
ngx.sleep(1.2)
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:
- prometheus
]]
require("lib.test_admin").set_config_yaml(data)
local code, _, org_body = t('/v1/plugins/reload',
ngx.HTTP_PUT)
ngx.say(org_body)
ngx.sleep(2)
}
}
--- request
GET /t
--- response_body
done
--- no_error_log
[alert]
--- grep_error_log eval
qr/Instance report fails/
--- grep_error_log_out
Instance report fails
=== TEST 5: wrong method to reload plugins
--- request
GET /v1/plugins/reload
--- error_code: 404
=== TEST 6: wrong method to reload plugins
--- request
POST /v1/plugins/reload
--- error_code: 404
=== TEST 7: reload plugin with data_plane deployment
--- yaml_config
apisix:
node_listen: 1984
enable_admin: false
deployment:
role: data_plane
role_data_plane:
config_provider: yaml
--- apisix_yaml
routes:
-
uri: /hello
upstream:
nodes:
"127.0.0.1:1980": 1
type: roundrobin
#END
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin")
local code, body, res = t.test('/v1/plugins/reload',
ngx.HTTP_PUT)
ngx.say(res)
ngx.sleep(1)
}
}
--- request
GET /t
--- response_body
done
--- error_log
load plugin times: 2
load plugin times: 2
start to hot reload plugins
start to hot reload plugins

View File

@@ -0,0 +1,142 @@
#
# 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->yaml_config) {
my $yaml_config = <<_EOC_;
apisix:
node_listen: 1984
deployment:
role: data_plane
role_data_plane:
config_provider: yaml
_EOC_
$block->set_value("yaml_config", $yaml_config);
}
if (!$block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests;
__DATA__
=== TEST 1: routes
--- apisix_yaml
routes:
-
id: 1
uris:
- /hello
upstream:
nodes:
"127.0.0.1:1980": 1
type: roundrobin
#END
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, body, res = t.test('/v1/routes',
ngx.HTTP_GET)
res = json.decode(res)
if res[1] then
local data = {}
data.uris = res[1].value.uris
data.upstream = res[1].value.upstream
ngx.say(json.encode(data))
end
}
}
--- response_body
{"upstream":{"hash_on":"vars","nodes":[{"host":"127.0.0.1","port":1980,"weight":1}],"pass_host":"pass","scheme":"http","type":"roundrobin"},"uris":["/hello"]}
=== TEST 2: get route with id 1
--- apisix_yaml
routes:
-
id: 1
uris:
- /hello
upstream:
nodes:
"127.0.0.1:1980": 1
type: roundrobin
#END
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, body, res = t.test('/v1/route/1',
ngx.HTTP_GET)
res = json.decode(res)
if res then
local data = {}
data.uris = res.value.uris
data.upstream = res.value.upstream
ngx.say(json.encode(data))
end
}
}
--- response_body
{"upstream":{"hash_on":"vars","nodes":[{"host":"127.0.0.1","port":1980,"weight":1}],"pass_host":"pass","scheme":"http","type":"roundrobin"},"uris":["/hello"]}
=== TEST 3: routes with invalid id
--- apisix_yaml
routes:
-
id: 1
uris:
- /hello
upstream:
nodes:
"127.0.0.1:1980": 1
type: roundrobin
#END
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, body, res = t.test('/v1/route/2',
ngx.HTTP_GET)
local data = {}
data.status = code
ngx.say(json.encode(data))
return
}
}
--- response_body
{"status":404}

View File

@@ -0,0 +1,149 @@
#
# 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->set_value("no_error_log", "[error]\n[alert]");
}
});
run_tests;
__DATA__
=== TEST 1: sanity
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin")
local code, body, res = t.test('/v1/schema',
ngx.HTTP_GET,
nil,
[[{
"main": {
"consumer": {"type":"object"},
"consumer_group": {"type":"object"},
"global_rule": {"type":"object"},
"plugin_config": {"type":"object"},
"plugins": {"type":"array"},
"proto": {"type":"object"},
"route": {"type":"object"},
"service": {"type":"object"},
"ssl": {"type":"object"},
"stream_route": {"type":"object"},
"upstream": {"type":"object"},
"upstream_hash_header_schema": {"type":"string"},
"upstream_hash_vars_schema": {"type":"string"},
},]] .. [[
"plugins": {
"example-plugin": {
"version": 0.1,
"priority": 0,
"schema": {
"type":"object",
"properties": {
"_meta": {
"properties": {
"disable": {"type": "boolean"}
}
}
}
},
"metadata_schema": {"type":"object"}
},
"basic-auth": {
"type": "auth",
"consumer_schema": {"type":"object"}
}
},
"stream_plugins": {
"mqtt-proxy": {
"schema": {
"type":"object",
"properties": {
"_meta": {
"properties": {
"disable": {"type": "boolean"}
}
}
}
},
"priority": 1000
}
}
}]]
)
ngx.status = code
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: confirm the scope of plugin
--- extra_yaml_config
plugins:
- batch-requests
- error-log-logger
- server-info
- example-plugin
- node-status
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin").test
local code, message, res = t('/v1/schema',
ngx.HTTP_GET
)
if code >= 300 then
ngx.status = code
ngx.say(message)
return
end
res = json.decode(res)
local global_plugins = {}
local plugins = res["plugins"]
for k, v in pairs(plugins) do
if v.scope == "global" then
global_plugins[k] = v.scope
end
end
ngx.say(json.encode(global_plugins))
}
}
--- response_body
{"batch-requests":"global","error-log-logger":"global","node-status":"global","server-info":"global"}

View File

@@ -0,0 +1,188 @@
#
# 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->yaml_config) {
my $yaml_config = <<_EOC_;
apisix:
node_listen: 1984
deployment:
role: data_plane
role_data_plane:
config_provider: yaml
_EOC_
$block->set_value("yaml_config", $yaml_config);
}
if (!$block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests;
__DATA__
=== TEST 1: services
--- apisix_yaml
services:
-
id: 200
upstream:
nodes:
"127.0.0.1:1980": 1
type: roundrobin
#END
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, body, res = t.test('/v1/services',
ngx.HTTP_GET)
res = json.decode(res)
if res[1] then
local data = {}
data.id = res[1].value.id
data.plugins = res[1].value.plugins
data.upstream = res[1].value.upstream
ngx.say(json.encode(data))
end
return
}
}
--- response_body
{"id":"200","upstream":{"hash_on":"vars","nodes":[{"host":"127.0.0.1","port":1980,"weight":1}],"pass_host":"pass","scheme":"http","type":"roundrobin"}}
=== TEST 2: multiple services
--- apisix_yaml
services:
-
id: 200
upstream:
nodes:
"127.0.0.1:1980": 1
type: roundrobin
-
id: 201
upstream:
nodes:
"127.0.0.2:1980": 1
type: roundrobin
#END
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local core = require("apisix.core")
local code, body, res = t.test('/v1/services',
ngx.HTTP_GET)
res = json.decode(res)
local g_data = {}
for _, r in core.config_util.iterate_values(res) do
local data = {}
data.id = r.value.id
data.plugins = r.value.plugins
data.upstream = r.value.upstream
core.table.insert(g_data, data)
end
ngx.say(json.encode(g_data))
return
}
}
--- response_body
[{"id":"200","upstream":{"hash_on":"vars","nodes":[{"host":"127.0.0.1","port":1980,"weight":1}],"pass_host":"pass","scheme":"http","type":"roundrobin"}},{"id":"201","upstream":{"hash_on":"vars","nodes":[{"host":"127.0.0.2","port":1980,"weight":1}],"pass_host":"pass","scheme":"http","type":"roundrobin"}}]
=== TEST 3: get service with id 5
--- apisix_yaml
services:
-
id: 5
plugins:
limit-count:
count: 2
time_window: 60
rejected_code: 503
key: remote_addr
upstream:
nodes:
"127.0.0.1:1980": 1
type: roundrobin
#END
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, body, res = t.test('/v1/service/5',
ngx.HTTP_GET)
res = json.decode(res)
if res then
local data = {}
data.id = res.value.id
data.plugins = res.value.plugins
data.upstream = res.value.upstream
ngx.say(json.encode(data))
end
return
}
}
--- response_body
{"id":"5","plugins":{"limit-count":{"allow_degradation":false,"count":2,"key":"remote_addr","key_type":"var","policy":"local","rejected_code":503,"show_limit_quota_header":true,"time_window":60}},"upstream":{"hash_on":"vars","nodes":[{"host":"127.0.0.1","port":1980,"weight":1}],"pass_host":"pass","scheme":"http","type":"roundrobin"}}
=== TEST 4: services with invalid id
--- apisix_yaml
services:
-
id: 1
upstream:
nodes:
"127.0.0.1:1980": 1
type: roundrobin
#END
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, body, res = t.test('/v1/service/2',
ngx.HTTP_GET)
local data = {}
data.status = code
ngx.say(json.encode(data))
return
}
}
--- response_body
{"status":404}

View File

@@ -0,0 +1,146 @@
#
# 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->yaml_config) {
my $yaml_config = <<_EOC_;
apisix:
node_listen: 1984
deployment:
role: data_plane
role_data_plane:
config_provider: yaml
_EOC_
$block->set_value("yaml_config", $yaml_config);
}
if (!$block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests;
__DATA__
=== TEST 1: dump all upstreams
--- apisix_yaml
upstreams:
-
id: 1
nodes:
"127.0.0.1:8001": 1
type: roundrobin
-
id: 2
nodes:
"127.0.0.1:8002": 1
type: roundrobin
#END
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, body, res = t.test('/v1/upstreams',
ngx.HTTP_GET)
res = json.decode(res)
if res[2] and table.getn(res) == 2 then
local data = {}
data.nodes = res[2].value.nodes
ngx.say(json.encode(data))
end
}
}
--- response_body
{"nodes":[{"host":"127.0.0.1","port":8002,"weight":1}]}
=== TEST 2: dump specific upstream with id 1
--- apisix_yaml
upstreams:
-
id: 1
nodes:
"127.0.0.1:8001": 1
type: roundrobin
-
id: 2
nodes:
"127.0.0.1:8002": 1
type: roundrobin
#END
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, body, res = t.test('/v1/upstream/1',
ngx.HTTP_GET)
res = json.decode(res)
if res then
local data = {}
data.nodes = res.value.nodes
ngx.say(json.encode(data))
end
}
}
--- response_body
{"nodes":[{"host":"127.0.0.1","port":8001,"weight":1}]}
=== TEST 3: upstreams with invalid id
--- apisix_yaml
upstreams:
-
id: 1
nodes:
"127.0.0.1:8001": 1
type: roundrobin
-
id: 2
nodes:
"127.0.0.1:8002": 1
type: roundrobin
#END
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin")
local code, body, res = t.test('/v1/upstream/3',
ngx.HTTP_GET)
local data = {}
data.status = code
ngx.say(json.encode(data))
return
}
}
--- response_body
{"status":404}