This is an automated email from the ASF dual-hosted git repository.
young pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new 20481ab27 feat(standalone): support JSON format (#12333)
20481ab27 is described below
commit 20481ab27113e055f6ba1a2510a8e433efe64e54
Author: YYYoung <[email protected]>
AuthorDate: Sat Jun 21 18:56:46 2025 +0800
feat(standalone): support JSON format (#12333)
Co-authored-by: Traky Deng <[email protected]>
---
apisix/cli/file.lua | 2 +
apisix/cli/schema.lua | 2 +-
apisix/core.lua | 11 +-
apisix/core/config_yaml.lua | 113 +++++---
docs/en/latest/deployment-modes.md | 464 ++++++++++++++++++++++++++++++++-
t/APISIX.pm | 24 ++
t/config-center-json/consumer-group.t | 212 +++++++++++++++
t/config-center-json/consumer.t | 93 +++++++
t/config-center-json/global-rule.t | 160 ++++++++++++
t/config-center-json/plugin-configs.t | 175 +++++++++++++
t/config-center-json/plugin-metadata.t | 100 +++++++
t/config-center-json/plugin.t | 291 +++++++++++++++++++++
t/config-center-json/route-service.t | 379 +++++++++++++++++++++++++++
t/config-center-json/route-upstream.t | 244 +++++++++++++++++
t/config-center-json/route.t | 364 ++++++++++++++++++++++++++
t/config-center-json/secret.t | 458 ++++++++++++++++++++++++++++++++
t/config-center-json/ssl.t | 191 ++++++++++++++
t/config-center-json/stream-route.t | 148 +++++++++++
18 files changed, 3384 insertions(+), 47 deletions(-)
diff --git a/apisix/cli/file.lua b/apisix/cli/file.lua
index e4d2a3161..36873631a 100644
--- a/apisix/cli/file.lua
+++ b/apisix/cli/file.lua
@@ -287,6 +287,8 @@ function _M.read_yaml_conf(apisix_home)
default_conf.etcd = default_conf.deployment.etcd
if default_conf.deployment.role_data_plane.config_provider ==
"yaml" then
default_conf.deployment.config_provider = "yaml"
+ elseif default_conf.deployment.role_data_plane.config_provider ==
"json" then
+ default_conf.deployment.config_provider = "json"
elseif default_conf.deployment.role_data_plane.config_provider ==
"xds" then
default_conf.deployment.config_provider = "xds"
end
diff --git a/apisix/cli/schema.lua b/apisix/cli/schema.lua
index 6e60a9166..36d758ca8 100644
--- a/apisix/cli/schema.lua
+++ b/apisix/cli/schema.lua
@@ -405,7 +405,7 @@ local deployment_schema = {
role_data_plane = {
properties = {
config_provider = {
- enum = {"etcd", "yaml", "xds"}
+ enum = {"etcd", "yaml", "json", "xds"}
},
},
required = {"config_provider"}
diff --git a/apisix/core.lua b/apisix/core.lua
index 8e23b8ed0..14c5186a4 100644
--- a/apisix/core.lua
+++ b/apisix/core.lua
@@ -24,7 +24,16 @@ end
local config_provider = local_conf.deployment and
local_conf.deployment.config_provider
or "etcd"
log.info("use config_provider: ", config_provider)
-local config = require("apisix.core.config_" .. config_provider)
+
+local config
+-- Currently, we handle JSON parsing in config_yaml, so special processing is
needed here.
+if config_provider == "json" then
+ config = require("apisix.core.config_yaml")
+ config.file_type = "json"
+else
+ config = require("apisix.core.config_" .. config_provider)
+end
+
config.type = config_provider
diff --git a/apisix/core/config_yaml.lua b/apisix/core/config_yaml.lua
index f7f54e18d..747b08743 100644
--- a/apisix/core/config_yaml.lua
+++ b/apisix/core/config_yaml.lua
@@ -46,7 +46,6 @@ local ngx = ngx
local re_find = ngx.re.find
local process = require("ngx.process")
local worker_id = ngx.worker.id
-local apisix_yaml_path = profile:yaml_path("apisix")
local created_obj = {}
local shared_dict
local status_report_shared_dict_name = "status-report"
@@ -56,6 +55,9 @@ local _M = {
local_conf = config_local.local_conf,
clear_local_cache = config_local.clear_cache,
+ -- yaml or json
+ file_type = "yaml",
+
ERR_NO_SHARED_DICT = "failed prepare standalone config shared dict, this
will degrade "..
"to event broadcasting, and if a worker crashes, the
configuration "..
"cannot be restored from other workers and shared dict"
@@ -69,10 +71,68 @@ local mt = {
end
}
-
local apisix_yaml
local apisix_yaml_mtime
+local config_yaml = {
+ path = profile:yaml_path("apisix"),
+ type = "yaml",
+ parse = function(self)
+ local f, err = io.open(self.path, "r")
+ if not f then
+ return nil, "failed to open file " .. self.path .. " : " .. err
+ end
+
+ f:seek('end', -10)
+ local end_flag = f:read("*a")
+ local found_end_flag = re_find(end_flag, [[#END\s*$]], "jo")
+
+ if not found_end_flag then
+ f:close()
+ return nil, "missing valid end flag in file " .. self.path
+ end
+
+ f:seek('set')
+ local raw_config = f:read("*a")
+ f:close()
+
+ return yaml.load(raw_config), nil
+ end
+}
+
+local config_json = {
+ -- `-5` to remove the "yaml" suffix
+ path = config_yaml.path:sub(1, -5) .. "json",
+ type = "json",
+ parse = function(self)
+ local f, err = io.open(self.path, "r")
+ if not f then
+ return nil, "failed to open file " .. self.path .. " : " .. err
+ end
+ local raw_config = f:read("*a")
+ f:close()
+
+ local config, err = json.decode(raw_config)
+ if err then
+ return nil, "failed to decode json: " .. err
+ end
+ return config, nil
+ end
+}
+
+local config_file_table = {
+ yaml = config_yaml,
+ json = config_json
+}
+
+
+local config_file = setmetatable({}, {
+ __index = function(_, key)
+ return config_file_table[_M.file_type][key]
+ end
+})
+
+
local function sync_status_to_shdict(status)
if process.type() ~= "worker" then
return
@@ -112,13 +172,13 @@ local function is_use_admin_api()
end
-local function read_apisix_yaml(premature, pre_mtime)
+local function read_apisix_config(premature, pre_mtime)
if premature then
return
end
- local attributes, err = lfs.attributes(apisix_yaml_path)
+ local attributes, err = lfs.attributes(config_file.path)
if not attributes then
- log.error("failed to fetch ", apisix_yaml_path, " attributes: ", err)
+ log.error("failed to fetch ", config_file.path, " attributes: ", err)
return
end
@@ -127,36 +187,15 @@ local function read_apisix_yaml(premature, pre_mtime)
return
end
- local f, err = io.open(apisix_yaml_path, "r")
- if not f then
- log.error("failed to open file ", apisix_yaml_path, " : ", err)
- return
- end
-
- f:seek('end', -10)
- local end_flag = f:read("*a")
- -- log.info("flag: ", end_flag)
- local found_end_flag = re_find(end_flag, [[#END\s*$]], "jo")
-
- if not found_end_flag then
- f:close()
- log.warn("missing valid end flag in file ", apisix_yaml_path)
- return
- end
-
- f:seek('set')
- local yaml_config = f:read("*a")
- f:close()
-
- local apisix_yaml_new = yaml.load(yaml_config)
- if not apisix_yaml_new then
- log.error("failed to parse the content of file " .. apisix_yaml_path)
+ local config_new, err = config_file:parse()
+ if err then
+ log.error("failed to parse the content of file ", config_file.path, ":
", err)
return
end
- update_config(apisix_yaml_new, last_modification_time)
+ update_config(config_new, last_modification_time)
- log.warn("config file ", apisix_yaml_path, " reloaded.")
+ log.warn("config file ", config_file.path, " reloaded.")
end
@@ -171,7 +210,7 @@ local function sync_data(self)
else
if not apisix_yaml_mtime then
log.warn("wait for more time")
- return nil, "failed to read local file " .. apisix_yaml_path
+ return nil, "failed to read local file " .. config_file.path
end
conf_version = apisix_yaml_mtime
end
@@ -395,7 +434,7 @@ local function _automatic_fetch(premature, self)
local ok, ok2, err = pcall(sync_data, self)
if not ok then
err = ok2
- log.error("failed to fetch data from local file " ..
apisix_yaml_path .. ": ",
+ log.error("failed to fetch data from local file " ..
config_file.path .. ": ",
err, ", ", tostring(self))
ngx_sleep(3)
break
@@ -403,7 +442,7 @@ local function _automatic_fetch(premature, self)
elseif not ok2 and err then
if err ~= "timeout" and err ~= "Key not found"
and self.last_err ~= err then
- log.error("failed to fetch data from local file " ..
apisix_yaml_path .. ": ",
+ log.error("failed to fetch data from local file " ..
config_file.path .. ": ",
err, ", ", tostring(self))
end
@@ -477,7 +516,7 @@ function _M.new(key, opts)
end
if err then
- log.error("failed to fetch data from local file ",
apisix_yaml_path, ": ",
+ log.error("failed to fetch data from local file ",
config_file.path, ": ",
err, ", ", key)
end
@@ -517,7 +556,7 @@ function _M.init()
return true
end
- read_apisix_yaml()
+ read_apisix_config()
return true
end
@@ -531,7 +570,7 @@ function _M.init_worker()
end
-- sync data in each non-master process
- ngx.timer.every(1, read_apisix_yaml)
+ ngx.timer.every(1, read_apisix_config)
return true
end
diff --git a/docs/en/latest/deployment-modes.md
b/docs/en/latest/deployment-modes.md
index 4e916ae14..3752858c0 100644
--- a/docs/en/latest/deployment-modes.md
+++ b/docs/en/latest/deployment-modes.md
@@ -6,6 +6,10 @@ keywords:
- APISIX deployment modes
description: Documentation about the three deployment modes of Apache APISIX.
---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
<!--
#
# Licensed to the Apache Software Foundation (ASF) under one or more
@@ -31,7 +35,7 @@ APISIX has three different deployment modes for different
production use cases.
|-----------------|----------------------------|---------------------------------------------------------------------------------------------------------------------|
| traditional | traditional | Data plane and control plane
are deployed together. `enable_admin` attribute should be disabled manually.
|
| decoupled | data_plane / control_plane | Data plane and control plane
are deployed independently.
|
-| standalone | data_plane / traditional | The `data_plane` mode loads
configuration from a local YAML file, while the traditional mode expects
configuration through Admin API. |
+| standalone | data_plane / traditional | The `data_plane` mode loads
configuration from a local YAML / JSON file, while the traditional mode expects
configuration through Admin API. |
Each of these deployment modes are explained in detail below.
@@ -134,6 +138,17 @@ deployment:
config_provider: yaml
```
+You can also provide the configuration in JSON format by placing it in
`conf/apisix.json`. Before proceeding, you should change the
`deployment.role_data_plane.config_provider` to `json`.
+
+Refer to the example below:
+
+```yaml
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: json
+```
+
This makes it possible to disable the Admin API and discover configuration
changes and reloads based on the local file system.
#### API-driven (Experimental)
@@ -275,6 +290,8 @@ The API accepts input in the same format as the file-based
mode, supporting both
### How to configure rules
+#### To `config_provider: yaml`
+
All of the rules are stored in one file which named `conf/apisix.yaml`,
APISIX checks if this file has any change **every second**.
If the file is changed & it ends with `#END`,
@@ -312,10 +329,40 @@ routes:
More information about using environment variables can be found
[here](./admin-api.md#using-environment-variables).
+#### To `config_provider: json`
+
+All of the rules are stored in one file which named `conf/apisix.json`,
+APISIX checks if this file has any change **every second**.
+If the file is changed,
+APISIX loads the rules from this file and updates its memory.
+
+Here is a mini example:
+
+```json
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+```
+
+*WARNING*: when using `conf/apisix.json`, the `#END` marker is not required,
as APISIX can directly parse and validate the JSON structure.
+
### How to configure Route
Single Route:
+<Tabs>
+<TabItem value="yaml" label="YAML" default>
+
```yaml
routes:
-
@@ -327,8 +374,34 @@ routes:
#END
```
+</TabItem>
+
+<TabItem value="json" label="JSON">
+
+```json
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+```
+
+</TabItem>
+</Tabs>
+
Multiple Routes:
+<Tabs>
+<TabItem value="yaml" label="YAML" default>
+
```yaml
routes:
-
@@ -346,9 +419,44 @@ routes:
#END
```
+</TabItem>
+
+<TabItem value="json" label="JSON">
+
+```json
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ },
+ {
+ "uri": "/hello2",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1981": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+```
+
+</TabItem>
+</Tabs>
+
### How to configure Route + Service
-```yml
+<Tabs>
+<TabItem value="yaml" label="YAML" default>
+
+```yaml
routes:
-
uri: /hello
@@ -363,9 +471,41 @@ services:
#END
```
+</TabItem>
+
+<TabItem value="json" label="JSON">
+
+```json
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "service_id": 1
+ }
+ ],
+ "services": [
+ {
+ "id": 1,
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+```
+
+</TabItem>
+</Tabs>
+
### How to configure Route + Upstream
-```yml
+<Tabs>
+<TabItem value="yaml" label="YAML" default>
+
+```yaml
routes:
-
uri: /hello
@@ -379,9 +519,39 @@ upstreams:
#END
```
+</TabItem>
+
+<TabItem value="json" label="JSON">
+
+```json
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "upstream_id": 1
+ }
+ ],
+ "upstreams": [
+ {
+ "id": 1,
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ ]
+}
+```
+
+</TabItem>
+</Tabs>
+
### How to configure Route + Service + Upstream
-```yml
+<Tabs>
+<TabItem value="yaml" label="YAML" default>
+
+```yaml
routes:
-
uri: /hello
@@ -399,9 +569,45 @@ upstreams:
#END
```
+</TabItem>
+
+<TabItem value="json" label="JSON">
+
+```json
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "service_id": 1
+ }
+ ],
+ "services": [
+ {
+ "id": 1,
+ "upstream_id": 2
+ }
+ ],
+ "upstreams": [
+ {
+ "id": 2,
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ ]
+}
+```
+
+</TabItem>
+</Tabs>
+
### How to configure Plugins
-```yml
+<Tabs>
+<TabItem value="yaml" label="YAML" default>
+
+```yaml
# plugins listed here will be hot reloaded and override the boot configuration
plugins:
- name: ip-restriction
@@ -411,9 +617,36 @@ plugins:
#END
```
+</TabItem>
+
+<TabItem value="json" label="JSON">
+
+```json
+{
+ "plugins": [
+ {
+ "name": "ip-restriction"
+ },
+ {
+ "name": "jwt-auth"
+ },
+ {
+ "name": "mqtt-proxy",
+ "stream": true
+ }
+ ]
+}
+```
+
+</TabItem>
+</Tabs>
+
### How to configure Plugin Configs
-```yml
+<Tabs>
+<TabItem value="yaml" label="YAML" default>
+
+```yaml
plugin_configs:
-
id: 1
@@ -431,9 +664,47 @@ routes:
#END
```
+</TabItem>
+
+<TabItem value="json" label="JSON">
+
+```json
+{
+ "plugin_configs": [
+ {
+ "id": 1,
+ "plugins": {
+ "response-rewrite": {
+ "body": "hello\n"
+ }
+ }
+ }
+ ],
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "plugin_config_id": 1,
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+```
+
+</TabItem>
+</Tabs>
+
### How to enable SSL
-```yml
+<Tabs>
+<TabItem value="yaml" label="YAML" default>
+
+```yaml
ssls:
-
cert: |
@@ -442,7 +713,7 @@ ssls:
BAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA8GA1UEBwwISGFuZ3pob3UxDTAL
BgNVBAoMBHRlc3QxDTALBgNVBAsMBHRlc3QxGzAZBgNVBAMMEmV0Y2QuY2x1c3Rl
ci5sb2NhbDAeFw0yMDEwMjgwMzMzMDJaFw0yMTEwMjgwMzMzMDJaMG4xCzAJBgNV
- BAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA8GA1UEBwwISGFuZ3pob3UxDTAL
+ BAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA0GA1UEBwwISGFuZ3pob3UxDTAL
BgNVBAoMBHRlc3QxDTALBgNVBAsMBHRlc3QxGzAZBgNVBAMMEmV0Y2QuY2x1c3Rl
ci5sb2NhbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ/qwxCR7g5S
s9+VleopkLi5pAszEkHYOBpwF/hDeRdxU0I0e1zZTdTlwwPy2vf8m3kwoq6fmNCt
@@ -493,8 +764,32 @@ ssls:
#END
```
+</TabItem>
+
+<TabItem value="json" label="JSON">
+
+```json
+{
+ "ssls": [
+ {
+ "cert": "-----BEGIN
CERTIFICATE-----\nMIIDrzCCApegAwIBAgIJAI3Meu/gJVTLMA0GCSqGSIb3DQEBCwUAMG4xCzAJBgNV\nBAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA8GA1UEBwwISGFuZ3pob3UxDTAL\nBgNVBAoMBHRlc3QxDTALBgNVBAsMBHRlc3QxGzAZBgNVBAMMEmV0Y2QuY2x1c3Rl\nci5sb2NhbDAeFw0yMDEwMjgwMzMzMDJaFw0yMTEwMjgwMzMzMDJaMG4xCzAJBgNV\nBAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA8GA1UEBwwISGFuZ3pob3UxDTAL\nBgNVBAoMBHRlc3QxDTALBgNVBAsMBHRlc3QxGzAZBgNVBAMMEmV0Y2QuY2x1c3Rl\nci5sb2NhbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
[...]
+ "key": "-----BEGIN PRIVATE
KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCf6sMQke4OUrPf\nlZXqKZC4uaQLMxJB2DgacBf4Q3kXcVNCNHtc2U3U5cMD8tr3/Jt5MKKun5jQrbXV\nFF4eVr4Iv9jgPBwQc2kBUC9QL/alsMhEuXMeqdGQcCK3F0CLJdV3zUlKUDU0kg+O\nExnbl1CHXrIbpD7zLy1i3s8p39v1pYFYf4WlrQxvfa/xo97gXY5dJv8RryryLzRc\nuhHYBvX5MHCGpbrY61JxpfZqBo8CmLuHl1tmbeXpdHdQB11LKiuL6HtKflNjc6rg\n5r8bXl1nZbM/KOZEE+muA1LVoaTyHzY/aGXz0bNy4QRUO+De9JFcTDgnXnNZVG5x\ncyyDBpc9AgMBAAECggEAatcEtehZPJaCeClPPF/Cwbe9YoIfe4BCk
[...]
+ "snis": [
+ "yourdomain.com"
+ ]
+ }
+ ]
+}
+```
+
+</TabItem>
+</Tabs>
+
### How to configure global rule
+<Tabs>
+<TabItem value="yaml" label="YAML" default>
+
```yaml
global_rules:
-
@@ -505,8 +800,33 @@ global_rules:
#END
```
+</TabItem>
+
+<TabItem value="json" label="JSON">
+
+```json
+{
+ "global_rules": [
+ {
+ "id": 1,
+ "plugins": {
+ "response-rewrite": {
+ "body": "hello\n"
+ }
+ }
+ }
+ ]
+}
+```
+
+</TabItem>
+</Tabs>
+
### How to configure consumer
+<Tabs>
+<TabItem value="yaml" label="YAML" default>
+
```yaml
consumers:
- username: jwt
@@ -517,8 +837,34 @@ consumers:
#END
```
+</TabItem>
+
+<TabItem value="json" label="JSON">
+
+```json
+{
+ "consumers": [
+ {
+ "username": "jwt",
+ "plugins": {
+ "jwt-auth": {
+ "key": "user-key",
+ "secret": "my-secret-key"
+ }
+ }
+ }
+ ]
+}
+```
+
+</TabItem>
+</Tabs>
+
### How to configure plugin metadata
+<Tabs>
+<TabItem value="yaml" label="YAML" default>
+
```yaml
upstreams:
- id: 1
@@ -541,8 +887,53 @@ plugin_metadata:
#END
```
+</TabItem>
+
+<TabItem value="json" label="JSON">
+
+```json
+{
+ "upstreams": [
+ {
+ "id": 1,
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ ],
+ "routes": [
+ {
+ "uri": "/hello",
+ "upstream_id": 1,
+ "plugins": {
+ "http-logger": {
+ "batch_max_size": 1,
+ "uri": "http://127.0.0.1:1980/log"
+ }
+ }
+ }
+ ],
+ "plugin_metadata": [
+ {
+ "id": "http-logger",
+ "log_format": {
+ "host": "$host",
+ "remote_addr": "$remote_addr"
+ }
+ }
+ ]
+}
+```
+
+</TabItem>
+</Tabs>
+
### How to configure stream route
+<Tabs>
+<TabItem value="yaml" label="YAML" default>
+
```yaml
stream_routes:
- server_addr: 127.0.0.1
@@ -561,8 +952,46 @@ upstreams:
#END
```
+</TabItem>
+
+<TabItem value="json" label="JSON">
+
+```json
+{
+ "stream_routes": [
+ {
+ "server_addr": "127.0.0.1",
+ "server_port": 1985,
+ "id": 1,
+ "upstream_id": 1,
+ "plugins": {
+ "mqtt-proxy": {
+ "protocol_name": "MQTT",
+ "protocol_level": 4
+ }
+ }
+ }
+ ],
+ "upstreams": [
+ {
+ "nodes": {
+ "127.0.0.1:1995": 1
+ },
+ "type": "roundrobin",
+ "id": 1
+ }
+ ]
+}
+```
+
+</TabItem>
+</Tabs>
+
### How to configure protos
+<Tabs>
+<TabItem value="yaml" label="YAML" default>
+
```yaml
protos:
- id: helloworld
@@ -582,3 +1011,22 @@ protos:
}
#END
```
+
+</TabItem>
+
+<TabItem value="json" label="JSON">
+
+```json
+{
+ "protos": [
+ {
+ "id": "helloworld",
+ "desc": "hello world",
+ "content": "syntax = \"proto3\";\npackage helloworld;\n\nservice Greeter
{\n rpc SayHello (HelloRequest) returns (HelloReply) {}\n}\nmessage
HelloRequest {\n string name = 1;\n}\nmessage HelloReply {\n string message =
1;\n}\n"
+ }
+ ]
+}
+```
+
+</TabItem>
+</Tabs>
diff --git a/t/APISIX.pm b/t/APISIX.pm
index 69f4b6fb1..f9d633880 100644
--- a/t/APISIX.pm
+++ b/t/APISIX.pm
@@ -125,14 +125,17 @@ my $profile = $ENV{"APISIX_PROFILE"};
my $apisix_file;
+my $apisix_file_json;
my $debug_file;
my $config_file;
if ($profile) {
$apisix_file = "apisix-$profile.yaml";
+ $apisix_file_json = "apisix-$profile.json";
$debug_file = "debug-$profile.yaml";
$config_file = "config-$profile.yaml";
} else {
$apisix_file = "apisix.yaml";
+ $apisix_file_json = "apisix.json";
$debug_file = "debug.yaml";
$config_file = "config.yaml";
}
@@ -254,6 +257,18 @@ deployment:
_EOC_
}
+ if ($block->apisix_json && (!defined $block->yaml_config)) {
+ $user_yaml_config = <<_EOC_;
+apisix:
+ node_listen: 1984
+ enable_admin: false
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: json
+_EOC_
+ }
+
my $lua_deps_path = $block->lua_deps_path // <<_EOC_;
lua_package_path
"$apisix_home/?.lua;$apisix_home/?/init.lua;$apisix_home/deps/share/lua/5.1/?/init.lua;$apisix_home/deps/share/lua/5.1/?.lua;$apisix_home/apisix/?.lua;$apisix_home/t/?.lua;$apisix_home/t/xrpc/?.lua;$apisix_home/t/xrpc/?/init.lua;;";
lua_package_cpath
"$apisix_home/?.so;$apisix_home/deps/lib/lua/5.1/?.so;$apisix_home/deps/lib64/lua/5.1/?.so;;";
@@ -915,6 +930,14 @@ $user_apisix_yaml
_EOC_
}
+ my $user_apisix_json = $block->apisix_json // "";
+ if ($user_apisix_json){
+ $user_apisix_json = <<_EOC_;
+>>> ../conf/$apisix_file_json
+$user_apisix_json
+_EOC_
+ }
+
my $yaml_config = $block->yaml_config // $user_yaml_config;
my $default_deployment = <<_EOC_;
@@ -959,6 +982,7 @@ $etcd_pem
>>> ../conf/cert/etcd.key
$etcd_key
$user_apisix_yaml
+$user_apisix_json
_EOC_
$block->set_value("user_files", $user_files);
diff --git a/t/config-center-json/consumer-group.t
b/t/config-center-json/consumer-group.t
new file mode 100644
index 000000000..329380059
--- /dev/null
+++ b/t/config-center-json/consumer-group.t
@@ -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);
+log_level('info');
+no_root_location();
+no_shuffle();
+
+add_block_preprocessor(sub {
+ my ($block) = @_;
+
+ if (!$block->request) {
+ $block->set_value("request", "GET /hello?apikey=one");
+ }
+
+ if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+ $block->set_value("no_error_log", "[error]");
+ }
+});
+
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- apisix_json
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "plugins": {
+ "key-auth": {}
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ],
+ "consumer_groups": [
+ {
+ "id": "foobar",
+ "plugins": {
+ "response-rewrite": {
+ "body": "hello\n"
+ }
+ }
+ }
+ ],
+ "consumers": [
+ {
+ "username": "one",
+ "group_id": "foobar",
+ "plugins": {
+ "key-auth": {
+ "key": "one"
+ }
+ }
+ }
+ ]
+}
+--- response_body
+hello
+
+
+
+=== TEST 2: consumer group not found
+--- apisix_json
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "plugins": {
+ "key-auth": {}
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ],
+ "consumers": [
+ {
+ "username": "one",
+ "group_id": "invalid_group",
+ "plugins": {
+ "key-auth": {
+ "key": "one"
+ }
+ }
+ }
+ ]
+}
+--- error_code: 503
+--- error_log
+failed to fetch consumer group config by id: invalid_group
+
+
+
+=== TEST 3: plugin priority
+--- apisix_json
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "plugins": {
+ "key-auth": {}
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ],
+ "consumer_groups": [
+ {
+ "id": "foobar",
+ "plugins": {
+ "response-rewrite": {
+ "body": "hello\n"
+ }
+ }
+ }
+ ],
+ "consumers": [
+ {
+ "username": "one",
+ "group_id": "foobar",
+ "plugins": {
+ "key-auth": {
+ "key": "one"
+ },
+ "response-rewrite": {
+ "body": "world\n"
+ }
+ }
+ }
+ ]
+}
+--- response_body
+world
+
+
+
+=== TEST 4: invalid plugin
+--- apisix_json
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "plugins": {
+ "key-auth": {}
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ],
+ "consumer_groups": [
+ {
+ "id": "foobar",
+ "plugins": {
+ "example-plugin": {
+ "skey": "s"
+ },
+ "response-rewrite": {
+ "body": "hello\n"
+ }
+ }
+ }
+ ],
+ "consumers": [
+ {
+ "username": "one",
+ "group_id": "foobar",
+ "plugins": {
+ "key-auth": {
+ "key": "one"
+ }
+ }
+ }
+ ]
+}
+--- error_code: 503
+--- error_log
+failed to check the configuration of plugin example-plugin
+failed to fetch consumer group config by id: foobar
diff --git a/t/config-center-json/consumer.t b/t/config-center-json/consumer.t
new file mode 100644
index 000000000..80b801982
--- /dev/null
+++ b/t/config-center-json/consumer.t
@@ -0,0 +1,93 @@
+#
+# 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();
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: validate consumer
+--- apisix_json
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ],
+ "consumers": [
+ {
+ "username": "jwt#auth"
+ }
+ ]
+}
+--- request
+GET /hello
+--- response_body
+hello world
+--- error_log
+property "username" validation failed
+
+
+
+=== TEST 2: consumer restriction
+--- apisix_json
+{
+ "consumers": [
+ {
+ "username": "jack",
+ "plugins": {
+ "key-auth": {
+ "key": "user-key"
+ }
+ }
+ }
+ ],
+ "routes": [
+ {
+ "id": "1",
+ "methods": ["POST"],
+ "uri": "/hello",
+ "plugins": {
+ "key-auth": {},
+ "consumer-restriction": {
+ "whitelist": ["jack"]
+ }
+ },
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:1980": 1
+ }
+ }
+ }
+ ]
+}
+--- more_headers
+apikey: user-key
+--- request
+POST /hello
diff --git a/t/config-center-json/global-rule.t
b/t/config-center-json/global-rule.t
new file mode 100644
index 000000000..54d589827
--- /dev/null
+++ b/t/config-center-json/global-rule.t
@@ -0,0 +1,160 @@
+#
+# 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 /hello");
+ }
+
+ if (!$block->error_log && !$block->no_error_log) {
+ $block->set_value("no_error_log", "[error]\n[alert]");
+ }
+});
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ],
+ "global_rules": [
+ {
+ "id": 1,
+ "plugins": {
+ "response-rewrite": {
+ "body": "hello\n"
+ }
+ }
+ }
+ ]
+}
+--- response_body
+hello
+
+
+
+=== TEST 2: global rule with bad plugin
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ],
+ "global_rules": [
+ {
+ "id": 1,
+ "plugins": {
+ "response-rewrite": {
+ "body": 4
+ }
+ }
+ }
+ ]
+}
+--- response_body
+hello world
+--- error_log
+property "body" validation failed
+
+
+
+=== TEST 3: fix global rule with default value
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ],
+ "global_rules": [
+ {
+ "id": 1,
+ "plugins": {
+ "uri-blocker": {
+ "block_rules": [
+ "/h*"
+ ]
+ }
+ }
+ }
+ ]
+}
+--- error_code: 403
+
+
+
+=== TEST 4: common phase without matched route
+--- apisix_json
+{
+ "routes": [
+ {
+ "uri": "/apisix/prometheus/metrics",
+ "plugins": {
+ "public-api": {}
+ }
+ }
+ ],
+ "global_rules": [
+ {
+ "id": 1,
+ "plugins": {
+ "cors": {
+ "allow_origins": "http://a.com,http://b.com"
+ }
+ }
+ }
+ ]
+}
+--- request
+GET /apisix/prometheus/metrics
+--- error_code: 200
diff --git a/t/config-center-json/plugin-configs.t
b/t/config-center-json/plugin-configs.t
new file mode 100644
index 000000000..79f8d0f03
--- /dev/null
+++ b/t/config-center-json/plugin-configs.t
@@ -0,0 +1,175 @@
+#
+# 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 /hello");
+ }
+});
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- apisix_json
+{
+ "plugin_configs": [
+ {
+ "id": 1,
+ "plugins": {
+ "response-rewrite": {
+ "body": "hello\n"
+ }
+ }
+ }
+ ],
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "plugin_config_id": 1,
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+--- response_body
+hello
+
+
+
+=== TEST 2: plugin_config not found
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "plugin_config_id": 1,
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+--- error_code: 503
+--- error_log
+failed to fetch plugin config by id: 1
+
+
+
+=== TEST 3: mix plugins & plugin_config_id
+--- apisix_json
+{
+ "plugin_configs": [
+ {
+ "id": 1,
+ "plugins": {
+ "example-plugin": {
+ "i": 1
+ },
+ "response-rewrite": {
+ "body": "hello\n"
+ }
+ }
+ }
+ ],
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/echo",
+ "plugin_config_id": 1,
+ "plugins": {
+ "proxy-rewrite": {
+ "headers": {
+ "in": "out"
+ }
+ },
+ "response-rewrite": {
+ "body": "world\n"
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+--- request
+GET /echo
+--- response_body
+world
+--- response_headers
+in: out
+--- error_log eval
+qr/conf_version: \d+#\d+,/
+
+
+
+=== TEST 4: invalid plugin
+--- apisix_json
+{
+ "plugin_configs": [
+ {
+ "id": 1,
+ "plugins": {
+ "example-plugin": {
+ "skey": "s"
+ },
+ "response-rewrite": {
+ "body": "hello\n"
+ }
+ }
+ }
+ ],
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "plugin_config_id": 1,
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+--- error_code: 503
+--- error_log
+failed to check the configuration of plugin example-plugin
+failed to fetch plugin config by id: 1
diff --git a/t/config-center-json/plugin-metadata.t
b/t/config-center-json/plugin-metadata.t
new file mode 100644
index 000000000..2eed9d418
--- /dev/null
+++ b/t/config-center-json/plugin-metadata.t
@@ -0,0 +1,100 @@
+#
+# 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();
+
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- apisix_json
+{
+ "upstreams": [
+ {
+ "id": 1,
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ ],
+ "routes": [
+ {
+ "uri": "/hello",
+ "upstream_id": 1,
+ "plugins": {
+ "http-logger": {
+ "batch_max_size": 1,
+ "uri": "http://127.0.0.1:1980/log"
+ }
+ }
+ }
+ ],
+ "plugin_metadata": [
+ {
+ "id": "http-logger",
+ "log_format": {
+ "host": "$host",
+ "remote_addr": "$remote_addr"
+ }
+ }
+ ]
+}
+--- request
+GET /hello
+--- error_log
+"remote_addr":"127.0.0.1"
+--- no_error_log
+failed to get schema for plugin:
+
+
+
+=== TEST 2: sanity
+--- apisix_json
+{
+ "upstreams": [
+ {
+ "id": 1,
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ ],
+ "routes": [
+ {
+ "uri": "/hello",
+ "upstream_id": 1
+ }
+ ],
+ "plugin_metadata": [
+ {
+ "id": "authz-casbin",
+ "model": 123
+ }
+ ]
+}
+--- request
+GET /hello
+--- error_log
+failed to check item data of [plugin_metadata]
diff --git a/t/config-center-json/plugin.t b/t/config-center-json/plugin.t
new file mode 100644
index 000000000..9dd143700
--- /dev/null
+++ b/t/config-center-json/plugin.t
@@ -0,0 +1,291 @@
+#
+# 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();
+
+our $debug_config = t::APISIX::read_file("conf/debug.yaml");
+$debug_config =~ s/basic:\n enable: false/basic:\n enable: true/;
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- apisix_json
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ],
+ "plugins": [
+ {
+ "name": "ip-restriction"
+ },
+ {
+ "name": "jwt-auth"
+ },
+ {
+ "name": "mqtt-proxy",
+ "stream": true
+ }
+ ]
+}
+--- debug_config eval: $::debug_config
+--- config
+ location /t {
+ content_by_lua_block {
+ ngx.sleep(0.3)
+ 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, {
+ method = "GET",
+ })
+ ngx.print(res.body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+hello world
+--- error_log
+use config_provider: yaml
+load(): loaded plugin and sort by priority: 3000 name: ip-restriction
+load(): loaded plugin and sort by priority: 2510 name: jwt-auth
+load_stream(): loaded stream plugin and sort by priority: 1000 name: mqtt-proxy
+--- grep_error_log eval
+qr/load\(\): new plugins/
+--- grep_error_log_out
+load(): new plugins
+load(): new plugins
+load(): new plugins
+load(): new plugins
+
+
+
+=== TEST 2: plugins not changed, but still need to reload
+--- yaml_config
+apisix:
+ node_listen: 1984
+ enable_admin: false
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+plugins:
+ - ip-restriction
+ - jwt-auth
+stream_plugins:
+ - mqtt-proxy
+--- apisix_json
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ],
+ "plugins": [
+ {
+ "name": "ip-restriction"
+ },
+ {
+ "name": "jwt-auth"
+ },
+ {
+ "name": "mqtt-proxy",
+ "stream": true
+ }
+ ]
+}
+--- debug_config eval: $::debug_config
+--- config
+ location /t {
+ content_by_lua_block {
+ ngx.sleep(0.3)
+ 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, {
+ method = "GET",
+ })
+ ngx.print(res.body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+hello world
+--- grep_error_log eval
+qr/loaded plugin and sort by priority: \d+ name: [^,]+/
+--- grep_error_log_out eval
+qr/(loaded plugin and sort by priority: (3000 name: ip-restriction|2510 name:
jwt-auth)
+){4}/
+
+
+
+=== TEST 3: disable plugin and its router
+--- apisix_json
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ],
+ "plugins": [
+ {
+ "name": "jwt-auth"
+ }
+ ]
+}
+--- request
+GET /apisix/prometheus/metrics
+--- error_code: 404
+
+
+
+=== TEST 4: enable plugin and its router
+--- apisix_json
+{
+ "routes": [
+ {
+ "uri": "/apisix/prometheus/metrics",
+ "plugins": {
+ "public-api": {}
+ }
+ }
+ ],
+ "plugins": [
+ {
+ "name": "public-api"
+ },
+ {
+ "name": "prometheus"
+ }
+ ]
+}
+--- request
+GET /apisix/prometheus/metrics
+
+
+
+=== TEST 5: invalid plugin config
+--- yaml_config
+apisix:
+ node_listen: 1984
+ enable_admin: false
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+plugins:
+ - ip-restriction
+ - jwt-auth
+stream_plugins:
+ - mqtt-proxy
+--- apisix_json
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ],
+ "plugins": [
+ {
+ "name": "xxx",
+ "stream": "ip-restriction"
+ }
+ ]
+}
+--- request
+GET /hello
+--- response_body
+hello world
+--- error_log
+property "stream" validation failed: wrong type: expected boolean, got string
+--- no_error_log
+load(): plugins not changed
+
+
+
+=== TEST 6: empty plugin list
+--- apisix_json
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ],
+ "plugins": [],
+ "stream_plugins": []
+}
+--- debug_config eval: $::debug_config
+--- config
+ location /t {
+ content_by_lua_block {
+ ngx.sleep(0.3)
+ 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, {
+ method = "GET",
+ })
+ ngx.print(res.body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+hello world
+--- error_log
+use config_provider: yaml
+load(): new plugins: {}
+load_stream(): new plugins: {}
diff --git a/t/config-center-json/route-service.t
b/t/config-center-json/route-service.t
new file mode 100644
index 000000000..b16e68056
--- /dev/null
+++ b/t/config-center-json/route-service.t
@@ -0,0 +1,379 @@
+#
+# 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();
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: hit route
+--- yaml_config eval: $::yaml_config
+--- apisix_json
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "service_id": 1,
+ "id": 1
+ }
+ ],
+ "services": [
+ {
+ "id": 1,
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+--- request
+GET /hello
+--- response_body
+hello world
+
+
+
+=== TEST 2: not found service
+--- yaml_config eval: $::yaml_config
+--- apisix_json
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "id": 1,
+ "service_id": 1111
+ }
+ ],
+ "services": [
+ {
+ "id": 1,
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+--- request
+GET /hello
+--- error_code: 404
+--- error_log
+failed to fetch service configuration by id: 1111
+
+
+
+=== TEST 3: service upstream priority
+--- yaml_config eval: $::yaml_config
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "service_id": 1
+ }
+ ],
+ "services": [
+ {
+ "id": 1,
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1977": 1
+ },
+ "type": "roundrobin"
+ },
+ "upstream_id": 1
+ }
+ ],
+ "upstreams": [
+ {
+ "id": 1,
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ ]
+}
+--- request
+GET /hello
+--- response_body
+hello world
+
+
+
+=== TEST 4: route service upstream priority
+--- yaml_config eval: $::yaml_config
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "service_id": 1,
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ],
+ "services": [
+ {
+ "id": 1,
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1977": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ],
+ "upstreams": [
+ {
+ "id": 1,
+ "nodes": {
+ "127.0.0.1:1977": 1
+ },
+ "type": "roundrobin"
+ }
+ ]
+}
+--- request
+GET /hello
+--- response_body
+hello world
+
+
+
+=== TEST 5: route service upstream by upstream_id priority
+--- yaml_config eval: $::yaml_config
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "service_id": 1,
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1977": 1
+ },
+ "type": "roundrobin"
+ },
+ "upstream_id": 1
+ }
+ ],
+ "services": [
+ {
+ "id": 1,
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1977": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ],
+ "upstreams": [
+ {
+ "id": 1,
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ ]
+}
+--- request
+GET /hello
+--- response_body
+hello world
+
+
+
+=== TEST 6: route service upstream priority
+--- yaml_config eval: $::yaml_config
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "service_id": 1,
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ],
+ "services": [
+ {
+ "id": 1,
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1977": 1
+ },
+ "type": "roundrobin"
+ },
+ "upstream_id": 1
+ }
+ ],
+ "upstreams": [
+ {
+ "id": 1,
+ "nodes": {
+ "127.0.0.1:1977": 1
+ },
+ "type": "roundrobin"
+ }
+ ]
+}
+--- request
+GET /hello
+--- response_body
+hello world
+
+
+
+=== TEST 7: two routes with the same service
+--- yaml_config eval: $::yaml_config
+--- apisix_json
+{
+ "routes": [
+ {
+ "uris": ["/hello"],
+ "service_id": 1,
+ "id": 1,
+ "plugins": {
+ "response-rewrite": {
+ "body": "hello\n"
+ }
+ }
+ },
+ {
+ "uris": ["/world"],
+ "service_id": 1,
+ "id": 2,
+ "plugins": {
+ "response-rewrite": {
+ "body": "world\n"
+ }
+ }
+ }
+ ],
+ "services": [
+ {
+ "id": 1,
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+--- request
+GET /hello
+--- response_body
+hello
+
+
+
+=== TEST 8: service with bad plugin
+--- yaml_config eval: $::yaml_config
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "service_id": 1
+ }
+ ],
+ "services": [
+ {
+ "id": 1,
+ "plugins": {
+ "proxy-rewrite": {
+ "uri": 1
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+--- request
+GET /hello
+--- error_code: 404
+--- error_log
+property "uri" validation failed
+
+
+
+=== TEST 9: fix service with default value
+--- yaml_config eval: $::yaml_config
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "service_id": 1
+ }
+ ],
+ "services": [
+ {
+ "id": 1,
+ "plugins": {
+ "uri-blocker": {
+ "block_rules": [
+ "/h*"
+ ]
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+--- request
+GET /hello
+--- error_code: 403
diff --git a/t/config-center-json/route-upstream.t
b/t/config-center-json/route-upstream.t
new file mode 100644
index 000000000..7082de27c
--- /dev/null
+++ b/t/config-center-json/route-upstream.t
@@ -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';
+
+repeat_each(1);
+log_level('info');
+no_root_location();
+no_shuffle();
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: hit route
+--- yaml_config eval: $::yaml_config
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "upstream_id": 1
+ }
+ ],
+ "upstreams": [
+ {
+ "id": 1,
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ ]
+}
+--- request
+GET /hello
+--- response_body
+hello world
+
+
+
+=== TEST 2: not found upstream
+--- yaml_config eval: $::yaml_config
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "upstream_id": 1111
+ }
+ ],
+ "upstreams": [
+ {
+ "id": 1,
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ ]
+}
+--- request
+GET /hello
+--- error_code_like: ^(?:50\d)$
+--- error_log
+failed to find upstream by id: 1111
+
+
+
+=== TEST 3: upstream_id priority upstream
+--- yaml_config eval: $::yaml_config
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "upstream_id": 1,
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1977": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ],
+ "upstreams": [
+ {
+ "id": 1,
+ "nodes": {
+ "127.0.0.1:1981": 1
+ },
+ "type": "roundrobin"
+ }
+ ]
+}
+--- request
+GET /hello
+--- response_body
+hello world
+
+
+
+=== TEST 4: enable healthcheck
+--- yaml_config eval: $::yaml_config
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "upstream_id": 1
+ }
+ ],
+ "upstreams": [
+ {
+ "id": 1,
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin",
+ "retries": 2,
+ "checks": {
+ "active": {
+ "http_path": "/status",
+ "healthy": {
+ "interval": 2,
+ "successes": 1
+ }
+ }
+ }
+ }
+ ]
+}
+--- request
+GET /hello
+--- response_body
+hello world
+
+
+
+=== TEST 5: upstream domain
+--- yaml_config eval: $::yaml_config
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "upstream_id": 1
+ }
+ ],
+ "upstreams": [
+ {
+ "id": 1,
+ "nodes": {
+ "test.com:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ ]
+}
+--- request
+GET /hello
+--- error_code: 200
+
+
+
+=== TEST 6: upstream hash_on (bad)
+--- yaml_config eval: $::yaml_config
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "upstream_id": 1
+ }
+ ],
+ "upstreams": [
+ {
+ "id": 1,
+ "nodes": {
+ "test.com:1980": 1
+ },
+ "type": "chash",
+ "hash_on": "header",
+ "key": "$aaa"
+ }
+ ]
+}
+--- request
+GET /hello
+--- error_code: 502
+--- error_log
+invalid configuration: failed to match pattern
+
+
+
+=== TEST 7: upstream hash_on (good)
+--- yaml_config eval: $::yaml_config
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "upstream_id": 1
+ }
+ ],
+ "upstreams": [
+ {
+ "id": 1,
+ "nodes": {
+ "127.0.0.1:1980": 1,
+ "127.0.0.2:1980": 1
+ },
+ "type": "chash",
+ "hash_on": "header",
+ "key": "test"
+ }
+ ]
+}
+--- request
+GET /hello
+--- more_headers
+test: one
+--- error_log
+proxy request to 127.0.0.1:1980
diff --git a/t/config-center-json/route.t b/t/config-center-json/route.t
new file mode 100644
index 000000000..eef7b5e8e
--- /dev/null
+++ b/t/config-center-json/route.t
@@ -0,0 +1,364 @@
+#
+# 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();
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- yaml_config eval: $::yaml_config
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+--- request
+GET /hello
+--- response_body
+hello world
+--- error_log
+use config_provider: yaml
+
+
+
+=== TEST 2: route:uri + host (missing host, not hit)
+--- yaml_config eval: $::yaml_config
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "host": "foo.com",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+--- request
+GET /hello
+--- error_code: 404
+--- error_log
+use config_provider: yaml
+
+
+
+=== TEST 3: route:uri + host
+--- yaml_config eval: $::yaml_config
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "host": "foo.com",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+--- more_headers
+host: foo.com
+--- request
+GET /hello
+--- response_body
+hello world
+
+
+
+=== TEST 4: route with bad plugin
+--- yaml_config eval: $::yaml_config
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "plugins": {
+ "proxy-rewrite": {
+ "uri": 1
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+--- request
+GET /hello
+--- error_code: 404
+--- error_log
+property "uri" validation failed
+
+
+
+=== TEST 5: ignore unknown plugin
+--- yaml_config eval: $::yaml_config
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "plugins": {
+ "x-rewrite": {
+ "uri": 1
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+--- request
+GET /hello
+--- response_body
+hello world
+
+
+
+=== TEST 6: route with bad plugin, radixtree_host_uri
+--- yaml_config
+apisix:
+ node_listen: 1984
+ router:
+ http: "radixtree_host_uri"
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "plugins": {
+ "proxy-rewrite": {
+ "uri": 1
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+--- request
+GET /hello
+--- error_code: 404
+--- error_log
+property "uri" validation failed
+
+
+
+=== TEST 7: fix route with default value
+--- yaml_config
+apisix:
+ node_listen: 1984
+ router:
+ http: "radixtree_host_uri"
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "plugins": {
+ "uri-blocker": {
+ "block_rules": [
+ "/h*"
+ ]
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+--- request
+GET /hello
+--- error_code: 403
+
+
+
+=== TEST 8: invalid route, bad vars operator
+--- yaml_config
+apisix:
+ node_listen: 1984
+ router:
+ http: "radixtree_host_uri"
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "vars": [
+ ["remote_addr", "=", "1"]
+ ],
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+--- request
+GET /hello
+--- error_code: 404
+--- error_log
+failed to validate the 'vars' expression
+
+
+
+=== TEST 9: script with id
+--- yaml_config
+apisix:
+ node_listen: 1984
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "script": "local ngx = ngx",
+ "script_id": "1",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+--- request
+GET /hello
+--- error_code: 200
+--- error_log
+missing loaded script object
+
+
+
+=== TEST 10: hosts with '_' is valid
+--- yaml_config eval: $::yaml_config
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "hosts": [
+ "foo.com",
+ "v1_test-api.com"
+ ],
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+--- more_headers
+host: v1_test-api.com
+--- request
+GET /hello
+--- response_body
+hello world
+
+
+
+=== TEST 11: script with plugin_config_id
+--- yaml_config eval: $::yaml_config
+--- apisix_json
+{
+ "routes": [
+ {
+ "id": 1,
+ "uri": "/hello",
+ "script": "local ngx = ngx",
+ "plugin_config_id": "1",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+--- request
+GET /hello
+--- error_code: 404
+--- error_log
+failed to check item data of [routes]
diff --git a/t/config-center-json/secret.t b/t/config-center-json/secret.t
new file mode 100644
index 000000000..178f19ef5
--- /dev/null
+++ b/t/config-center-json/secret.t
@@ -0,0 +1,458 @@
+#
+# 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->apisix_json) {
+ my $json_config = <<_EOC_;
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+_EOC_
+
+ $block->set_value("apisix_json", $json_config);
+ }
+});
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: validate secret/vault: wrong schema
+--- apisix_json
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ],
+ "secrets": [
+ {
+ "id": "vault/1",
+ "prefix": "kv/apisix",
+ "token": "root",
+ "uri": "127.0.0.1:8200"
+ }
+ ]
+}
+--- config
+ location /t {
+ content_by_lua_block {
+ local secret = require("apisix.secret")
+ local values = secret.secrets()
+ ngx.say(#values)
+ }
+ }
+--- request
+GET /t
+--- response_body
+0
+--- error_log
+property "uri" validation failed: failed to match pattern
"^[^\\/]+:\\/\\/([\\da-zA-Z.-]+|\\[[\\da-fA-F:]+\\])(:\\d+)?"
+
+
+
+=== TEST 2: validate secrets: manager not exits
+--- apisix_json
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ],
+ "secrets": [
+ {
+ "id": "hhh/1",
+ "prefix": "kv/apisix",
+ "token": "root",
+ "uri": "127.0.0.1:8200"
+ }
+ ]
+}
+--- config
+ location /t {
+ content_by_lua_block {
+ local secret = require("apisix.secret")
+ local values = secret.secrets()
+ ngx.say(#values)
+ }
+ }
+--- request
+GET /t
+--- response_body
+0
+--- error_log
+secret manager not exits
+
+
+
+=== TEST 3: load config normal
+--- apisix_json
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ],
+ "secrets": [
+ {
+ "id": "vault/1",
+ "prefix": "kv/apisix",
+ "token": "root",
+ "uri": "http://127.0.0.1:8200"
+ }
+ ]
+}
+--- config
+ location /t {
+ content_by_lua_block {
+ local secret = require("apisix.secret")
+ local values = secret.secrets()
+ ngx.say("len: ", #values)
+
+ ngx.say("id: ", values[1].value.id)
+ ngx.say("prefix: ", values[1].value.prefix)
+ ngx.say("token: ", values[1].value.token)
+ ngx.say("uri: ", values[1].value.uri)
+ }
+ }
+--- request
+GET /t
+--- response_body
+len: 1
+id: vault/1
+prefix: kv/apisix
+token: root
+uri: http://127.0.0.1:8200
+
+
+
+=== TEST 4: store secret into vault
+--- exec
+VAULT_TOKEN='root' VAULT_ADDR='http://0.0.0.0:8200' vault kv put
kv/apisix/apisix-key key=value
+--- response_body
+Success! Data written to: kv/apisix/apisix-key
+
+
+
+=== TEST 5: secret.fetch_by_uri: start with $secret://
+--- apisix_json
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ],
+ "secrets": [
+ {
+ "id": "vault/1",
+ "prefix": "kv/apisix",
+ "token": "root",
+ "uri": "http://127.0.0.1:8200"
+ }
+ ]
+}
+--- config
+ location /t {
+ content_by_lua_block {
+ local secret = require("apisix.secret")
+ local value =
secret.fetch_by_uri("$secret://vault/1/apisix-key/key")
+ ngx.say(value)
+ }
+ }
+--- request
+GET /t
+--- response_body
+value
+
+
+
+=== TEST 6: secret.fetch_by_uri, wrong ref format: wrong type
+--- config
+ location /t {
+ content_by_lua_block {
+ local secret = require("apisix.secret")
+ local _, err = secret.fetch_by_uri(1)
+ ngx.say(err)
+ }
+ }
+--- request
+GET /t
+--- response_body
+error secret_uri type: number
+
+
+
+=== TEST 7: secret.fetch_by_uri, wrong ref format: wrong prefix
+--- config
+ location /t {
+ content_by_lua_block {
+ local secret = require("apisix.secret")
+ local _, err = secret.fetch_by_uri("secret://")
+ ngx.say(err)
+ }
+ }
+--- request
+GET /t
+--- response_body
+error secret_uri prefix: secret://
+
+
+
+=== TEST 8: secret.fetch_by_uri, error format: no secret manager
+--- config
+ location /t {
+ content_by_lua_block {
+ local secret = require("apisix.secret")
+ local _, err = secret.fetch_by_uri("$secret://")
+ ngx.say(err)
+ }
+ }
+--- request
+GET /t
+--- response_body
+error format: no secret manager
+
+
+
+=== TEST 9: secret.fetch_by_uri, error format: no secret conf id
+--- config
+ location /t {
+ content_by_lua_block {
+ local secret = require("apisix.secret")
+ local _, err = secret.fetch_by_uri("$secret://vault/")
+ ngx.say(err)
+ }
+ }
+--- request
+GET /t
+--- response_body
+error format: no secret conf id
+
+
+
+=== TEST 10: secret.fetch_by_uri, error format: no secret key id
+--- config
+ location /t {
+ content_by_lua_block {
+ local secret = require("apisix.secret")
+ local _, err = secret.fetch_by_uri("$secret://vault/2/")
+ ngx.say(err)
+ }
+ }
+--- request
+GET /t
+--- response_body
+error format: no secret key id
+
+
+
+=== TEST 11: secret.fetch_by_uri, no config
+--- config
+ location /t {
+ content_by_lua_block {
+ local secret = require("apisix.secret")
+ local _, err = secret.fetch_by_uri("$secret://vault/2/bar")
+ ngx.say(err)
+ }
+ }
+--- request
+GET /t
+--- response_body
+no secret conf, secret_uri: $secret://vault/2/bar
+
+
+
+=== TEST 12: secret.fetch_by_uri, no sub key value
+--- apisix_json
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ],
+ "secrets": [
+ {
+ "id": "vault/1",
+ "prefix": "kv/apisix",
+ "token": "root",
+ "uri": "http://127.0.0.1:8200"
+ }
+ ]
+}
+--- config
+ location /t {
+ content_by_lua_block {
+ local secret = require("apisix.secret")
+ local value =
secret.fetch_by_uri("$secret://vault/1/apisix-key/bar")
+ ngx.say(value)
+ }
+ }
+--- request
+GET /t
+--- response_body
+nil
+
+
+
+=== TEST 13: fetch_secrets env: no cache
+--- main_config
+env secret=apisix;
+--- config
+ location /t {
+ content_by_lua_block {
+ local secret = require("apisix.secret")
+ local refs = {
+ key = "jack",
+ secret = "$env://secret"
+ }
+ local new_refs = secret.fetch_secrets(refs)
+ assert(new_refs ~= refs)
+ ngx.say(refs.secret)
+ ngx.say(new_refs.secret)
+ ngx.say(new_refs.key)
+ }
+ }
+--- request
+GET /t
+--- response_body
+$env://secret
+apisix
+jack
+--- error_log_like
+qr/retrieve secrets refs/
+
+
+
+=== TEST 14: fetch_secrets env: cache
+--- main_config
+env secret=apisix;
+--- config
+ location /t {
+ content_by_lua_block {
+ local secret = require("apisix.secret")
+ local refs = {
+ key = "jack",
+ secret = "$env://secret"
+ }
+ local refs_1 = secret.fetch_secrets(refs, true, "key", 1)
+ local refs_2 = secret.fetch_secrets(refs, true, "key", 1)
+ assert(refs_1 == refs_2)
+ ngx.say(refs_1.secret)
+ ngx.say(refs_2.secret)
+ }
+ }
+--- request
+GET /t
+--- response_body
+apisix
+apisix
+--- grep_error_log eval
+qr/retrieve secrets refs/
+--- grep_error_log_out
+retrieve secrets refs
+
+
+
+=== TEST 15: fetch_secrets env: table nesting
+--- main_config
+env secret=apisix;
+--- config
+ location /t {
+ content_by_lua_block {
+ local secret = require("apisix.secret")
+ local refs = {
+ key = "jack",
+ user = {
+ username = "apisix",
+ passsword = "$env://secret"
+ }
+ }
+ local new_refs = secret.fetch_secrets(refs)
+ ngx.say(new_refs.user.passsword)
+ }
+ }
+--- request
+GET /t
+--- response_body
+apisix
+
+
+
+=== TEST 16: fetch_secrets: wrong refs type
+--- main_config
+env secret=apisix;
+--- config
+ location /t {
+ content_by_lua_block {
+ local secret = require("apisix.secret")
+ local refs = "wrong"
+ local new_refs = secret.fetch_secrets(refs)
+ ngx.say(new_refs)
+ }
+ }
+--- request
+GET /t
+--- response_body
+nil
diff --git a/t/config-center-json/ssl.t b/t/config-center-json/ssl.t
new file mode 100644
index 000000000..f7637dec3
--- /dev/null
+++ b/t/config-center-json/ssl.t
@@ -0,0 +1,191 @@
+#
+# 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('debug');
+no_root_location();
+no_shuffle();
+
+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]");
+ }
+
+ if ($block->sslhandshake) {
+ my $sslhandshake = $block->sslhandshake;
+
+ $block->set_value("config", <<_EOC_)
+listen unix:\$TEST_NGINX_HTML_DIR/nginx.sock ssl;
+
+location /t {
+ content_by_lua_block {
+ -- sync
+ ngx.sleep(0.2)
+
+ do
+ local sock = ngx.socket.tcp()
+
+ sock:settimeout(2000)
+
+ local ok, err =
sock:connect("unix:\$TEST_NGINX_HTML_DIR/nginx.sock")
+ if not ok then
+ ngx.say("failed to connect: ", err)
+ return
+ end
+
+ $sslhandshake
+ local req = "GET /hello HTTP/1.0\\r\\nHost:
test.com\\r\\nConnection: close\\r\\n\\r\\n"
+ local bytes, err = sock:send(req)
+ if not bytes then
+ ngx.say("failed to send http request: ", err)
+ return
+ end
+
+ local line, err = sock:receive()
+ if not line then
+ ngx.say("failed to receive: ", err)
+ return
+ end
+
+ ngx.say("received: ", line)
+
+ local ok, err = sock:close()
+ ngx.say("close: ", ok, " ", err)
+ end -- do
+ -- collectgarbage()
+ }
+}
+_EOC_
+ }
+});
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- apisix_json
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ],
+ "ssls": [
+ {
+ "cert": "-----BEGIN
CERTIFICATE-----\nMIIDrzCCApegAwIBAgIJAI3Meu/gJVTLMA0GCSqGSIb3DQEBCwUAMG4xCzAJBgNV\nBAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA8GA1UEBwwISGFuZ3pob3UxDTAL\nBgNVBAoMBHRlc3QxDTALBgNVBAsMBHRlc3QxGzAZBgNVBAMMEmV0Y2QuY2x1c3Rl\nci5sb2NhbDAeFw0yMDEwMjgwMzMzMDJaFw0yMTEwMjgwMzMzMDJaMG4xCzAJBgNV\nBAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA8GA1UEBwwISGFuZ3pob3UxDTAL\nBgNVBAoMBHRlc3QxDTALBgNVBAsMBHRlc3QxGzAZBgNVBAMMEmV0Y2QuY2x1c3Rl\nci5sb2NhbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
[...]
+ "key": "-----BEGIN PRIVATE
KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCf6sMQke4OUrPf\nlZXqKZC4uaQLMxJB2DgacBf4Q3kXcVNCNHtc2U3U5cMD8tr3/Jt5MKKun5jQrbXV\nFF4eVr4Iv9jgPBwQc2kBUC9QL/alsMhEuXMeqdGQcCK3F0CLJdV3zUlKUDU0kg+O\nExnbl1CHXrIbpD7zLy1i3s8p39v1pYFYf4WlrQxvfa/xo97gXY5dJv8RryryLzRc\nuhHYBvX5MHCGpbrY61JxpfZqBo8CmLuHl1tmbeXpdHdQB11LKiuL6HtKflNjc6rg\n5r8bXl1nZbM/KOZEE+muA1LVoaTyHzY/aGXz0bNy4QRUO+De9JFcTDgnXnNZVG5x\ncyyDBpc9AgMBAAECggEAatcEtehZPJaCeClPPF/Cwbe9YoIfe4BCk
[...]
+ "snis": [
+ "t.com",
+ "test.com"
+ ]
+ }
+ ]
+}
+--- sslhandshake
+local sess, err = sock:sslhandshake(nil, "test.com", false)
+if not sess then
+ ngx.say("failed to do SSL handshake: ", err)
+ return
+end
+--- response_body
+received: HTTP/1.1 200 OK
+close: 1 nil
+--- error_log
+server name: "test.com"
+
+
+
+=== TEST 2: single sni
+--- apisix_json
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ],
+ "ssls": [
+ {
+ "cert": "-----BEGIN
CERTIFICATE-----\nMIIDrzCCApegAwIBAgIJAI3Meu/gJVTLMA0GCSqGSIb3DQEBCwUAMG4xCzAJBgNV\nBAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA8GA1UEBwwISGFuZ3pob3UxDTAL\nBgNVBAoMBHRlc3QxDTALBgNVBAsMBHRlc3QxGzAZBgNVBAMMEmV0Y2QuY2x1c3Rl\nci5sb2NhbDAeFw0yMDEwMjgwMzMzMDJaFw0yMTEwMjgwMzMzMDJaMG4xCzAJBgNV\nBAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA8GA1UEBwwISGFuZ3pob3UxDTAL\nBgNVBAoMBHRlc3QxDTALBgNVBAsMBHRlc3QxGzAZBgNVBAMMEmV0Y2QuY2x1c3Rl\nci5sb2NhbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
[...]
+ "key": "-----BEGIN PRIVATE
KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCf6sMQke4OUrPf\nlZXqKZC4uaQLMxJB2DgacBf4Q3kXcVNCNHtc2U3U5cMD8tr3/Jt5MKKun5jQrbXV\nFF4eVr4Iv9jgPBwQc2kBUC9QL/alsMhEuXMeqdGQcCK3F0CLJdV3zUlKUDU0kg+O\nExnbl1CHXrIbpD7zLy1i3s8p39v1pYFYf4WlrQxvfa/xo97gXY5dJv8RryryLzRc\nuhHYBvX5MHCGpbrY61JxpfZqBo8CmLuHl1tmbeXpdHdQB11LKiuL6HtKflNjc6rg\n5r8bXl1nZbM/KOZEE+muA1LVoaTyHzY/aGXz0bNy4QRUO+De9JFcTDgnXnNZVG5x\ncyyDBpc9AgMBAAECggEAatcEtehZPJaCeClPPF/Cwbe9YoIfe4BCk
[...]
+ "sni": "test.com"
+ }
+ ]
+}
+--- sslhandshake
+local sess, err = sock:sslhandshake(nil, "test.com", false)
+if not sess then
+ ngx.say("failed to do SSL handshake: ", err)
+ return
+end
+--- response_body
+received: HTTP/1.1 200 OK
+close: 1 nil
+--- error_log
+server name: "test.com"
+
+
+
+=== TEST 3: bad cert
+--- apisix_json
+{
+ "routes": [
+ {
+ "uri": "/hello",
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "httpbin.org:80": 1
+ }
+ },
+ "ssl_enable": true
+ }
+ ],
+ "ssls": [
+ {
+ "cert": "-----BEGIN
CERTIFICATE-----\nMIIDrzCCApegAwIBAgIJAI3Meu/gJVTLMA0GCSqGSIb3DQEBCwUAMG4xCzAJBgNV\nBAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA8GA1UEBwwISGFuZ3pob3UxDTAL\nBgNVBAoMBHRlc3QxDTALBgNVBAsMBHRlc3QxGzAZBgNVBAMMEmV0Y2QuY2x1c3Rl\nci5sb2NhbDAeFw0yMDEwMjgwMzMzMDJaFw0yMTEwMjgwMzMzMDJaMG4xCzAJBgNV\nBAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA8GA1UEBwwISGFuZ3pob3UxDTAL\nBgNVBAoMBHRlc3QxDTALBgNVBAsMBHRlc3QxGzAZBgNVBAMMEmV0Y2QuY2x1c3Rl\nci5sb2NhbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
[...]
+ "key": "-----BEGIN PRIVATE
KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCf6sMQke4OUrPf\nlZXqKZC4uaQLMxJB2DgacBf4Q3kXcVNCNHtc2U3U5cMD8tr3/Jt5MKKun5jQrbXV\nFF4eVr4Iv9jgPBwQc2kBUC9QL/alsMhEuXMeqdGQcCK3F0CLJdV3zUlKUDU0kg+O\nExnbl1CHXrIbpD7zLy1i3s8p39v1pYFYf4WlrQxvfa/xo97gXY5dJv8RryryLzRc\nuhHYBvX5MHCGpbrY61JxpfZqBo8CmLuHl1tmbeXpdHdQB11LKiuL6HtKflNjc6rg\n5r8bXl1nZbM/KOZEE+muA1LVoaTyHzY/aGXz0bNy4QRUO+De9JFcTDgnXnNZVG5x\ncyyDBpc9AgMBAAECggEAatcEtehZPJaCeClPPF/Cwbe9YoIfe4BCk
[...]
+ "snis": [
+ "t.com",
+ "test.com"
+ ]
+ }
+ ]
+}
+
+--- error_log
+failed to parse cert
+--- error_code: 404
diff --git a/t/config-center-json/stream-route.t
b/t/config-center-json/stream-route.t
new file mode 100644
index 000000000..47eb16eff
--- /dev/null
+++ b/t/config-center-json/stream-route.t
@@ -0,0 +1,148 @@
+#
+# 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) = @_;
+
+ $block->set_value("stream_enable", 1);
+
+ if (!$block->stream_request) {
+ $block->set_value("stream_request", "mmm");
+ }
+
+ if (!$block->error_log && !$block->no_error_log) {
+ $block->set_value("no_error_log", "[error]\n[alert]");
+ }
+});
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- apisix_json
+{
+ "stream_routes": [
+ {
+ "server_addr": "127.0.0.1",
+ "server_port": 1985,
+ "id": 1,
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1995": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+--- stream_response
+hello world
+
+
+
+=== TEST 2: rule with bad plugin
+--- apisix_json
+{
+ "stream_routes": [
+ {
+ "server_addr": "127.0.0.1",
+ "server_port": 1985,
+ "id": 1,
+ "plugins": {
+ "mqtt-proxy": {
+ "uri": 1
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1995": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+--- error_log eval
+qr/property "\w+" is required/
+
+
+
+=== TEST 3: ignore unknown plugin
+--- apisix_json
+{
+ "stream_routes": [
+ {
+ "server_addr": "127.0.0.1",
+ "server_port": 1985,
+ "id": 1,
+ "plugins": {
+ "x-rewrite": {
+ "uri": 1
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1995": 1
+ },
+ "type": "roundrobin"
+ }
+ }
+ ]
+}
+--- stream_response
+hello world
+
+
+
+=== TEST 4: sanity with plugin
+--- apisix_json
+{
+ "stream_routes": [
+ {
+ "server_addr": "127.0.0.1",
+ "server_port": 1985,
+ "id": 1,
+ "upstream_id": 1,
+ "plugins": {
+ "mqtt-proxy": {
+ "protocol_name": "MQTT",
+ "protocol_level": 4
+ }
+ }
+ }
+ ],
+ "upstreams": [
+ {
+ "nodes": {
+ "127.0.0.1:1995": 1
+ },
+ "type": "roundrobin",
+ "id": 1
+ }
+ ]
+}
+--- stream_request eval
+"\x10\x0f\x00\x04\x4d\x51\x54\x54\x04\x02\x00\x3c\x00\x03\x66\x6f\x6f"
+--- stream_response
+hello world