diff --git a/CHANGELOG.md b/CHANGELOG.md index 2aafe95b5128..ce5f1d01726b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,30 +77,29 @@ title: Changelog ### Change -- :warning: Remove the `etcd.use_grpc` and no longer support communication with ETCD using protocols with gRPC: [#10015](https://github.com/apache/apisix/pull/10015) -- :warning: Removing conf server, the data plane no longer supports communication with the data plane, and needs to be adjusted from `config_provider: control_plane` to `config_provider: etcd`: [#10012](https://github.com/apache/apisix/pull/10012) +- :warning: Remove gRPC support between APISIX and etcd and remove `etcd.use_grpc` configuration option: [#10015](https://github.com/apache/apisix/pull/10015) +- :warning: Remove conf server. The data plane no longer supports direct communication with the control plane, and the configuration should be adjusted from `config_provider: control_plane` to `config_provider: etcd`: [#10012](https://github.com/apache/apisix/pull/10012) +- :warning: Enforce strict schema validation on the properties of the core APISIX resources: [#10233](https://github.com/apache/apisix/pull/10233) ### Core -- :sunrise: support configuring the buffer size of the access log: [#10225](https://github.com/apache/apisix/pull/10225) -- :sunrise: Support for passing resolv.conf in dns discovery: [#9770](https://github.com/apache/apisix/pull/9770) -- :sunrise: No longer relying on trust: [#10121](https://github.com/apache/apisix/pull/10065) -- :sunrise: Strictly validate the input of core resources: [#10233](https://github.com/apache/apisix/pull/10233) -- :sunrise: Add dubbo protocols Support in the xrpc [#9660](https://github.com/apache/apisix/pull/9660) +- :sunrise: Support configuring the buffer size of the access log: [#10225](https://github.com/apache/apisix/pull/10225) +- :sunrise: Support the use of local DNS resolvers in service discovery by configuring `resolv_conf`: [#9770](https://github.com/apache/apisix/pull/9770) +- :sunrise: Remove Rust dependency for installation: [#10121](https://github.com/apache/apisix/pull/10121) +- :sunrise: Support Dubbo protocol in xRPC [#9660](https://github.com/apache/apisix/pull/9660) ### Plugins - :sunrise: Support https in traffic-split plugin: [#9115](https://github.com/apache/apisix/pull/9115) -- :sunrise: Support for passing resolv.conf in dns discovery: [#9770](https://github.com/apache/apisix/pull/9770) - :sunrise: Support rewrite request body in external plugin:[#9990](https://github.com/apache/apisix/pull/9990) - :sunrise: Support set nginx variables in opentelemetry plugin: [#8871](https://github.com/apache/apisix/pull/8871) - :sunrise: Support unix sock host pattern in the chaitin-waf plugin: [#10161](https://github.com/apache/apisix/pull/10161) ### Bugfixes -- Fix graphql post request route matching exception: [#10198](https://github.com/apache/apisix/pull/10198) +- Fix GraphQL POST request route matching exception: [#10198](https://github.com/apache/apisix/pull/10198) - Fix error on array of multiline string in `apisix.yaml`: [#10193](https://github.com/apache/apisix/pull/10193) -- Fix provide error instead of nil panic when cache_zone is missing in proxy-cache plugin: [#10138](https://github.com/apache/apisix/pull/10138) +- Add error handlers for invalid `cache_zone` configuration in the `proxy-cache` plugin: [#10138](https://github.com/apache/apisix/pull/10138) ## 3.5.0 diff --git a/apisix/admin/services.lua b/apisix/admin/services.lua index dc14bda44ec6..4218b77f22dd 100644 --- a/apisix/admin/services.lua +++ b/apisix/admin/services.lua @@ -16,6 +16,7 @@ -- local core = require("apisix.core") local get_routes = require("apisix.router").http_routes +local get_stream_routes = require("apisix.router").stream_routes local apisix_upstream = require("apisix.upstream") local resource = require("apisix.admin.resource") local schema_plugin = require("apisix.admin.plugins").check_schema @@ -99,6 +100,21 @@ local function delete_checker(id) end end + local stream_routes, stream_routes_ver = get_stream_routes() + core.log.info("stream_routes: ", core.json.delay_encode(stream_routes, true)) + core.log.info("stream_routes_ver: ", stream_routes_ver) + if stream_routes_ver and stream_routes then + for _, route in ipairs(stream_routes) do + if type(route) == "table" and route.value + and route.value.service_id + and tostring(route.value.service_id) == id then + return 400, {error_msg = "can not delete this service directly," + .. " stream_route [" .. route.value.id + .. "] is still using it now"} + end + end + end + return nil, nil end diff --git a/apisix/admin/stream_routes.lua b/apisix/admin/stream_routes.lua index c16a9a7938c3..6e1c6e6385c3 100644 --- a/apisix/admin/stream_routes.lua +++ b/apisix/admin/stream_routes.lua @@ -42,6 +42,23 @@ local function check_conf(id, conf, need_id, schema) end end + local service_id = conf.service_id + if service_id then + local key = "/services/" .. service_id + local res, err = core.etcd.get(key) + if not res then + return nil, {error_msg = "failed to fetch service info by " + .. "service id [" .. service_id .. "]: " + .. err} + end + + if res.status ~= 200 then + return nil, {error_msg = "failed to fetch service info by " + .. "service id [" .. service_id .. "], " + .. "response code: " .. res.status} + end + end + local ok, err = stream_route_checker(conf, true) if not ok then return nil, {error_msg = err} diff --git a/apisix/balancer.lua b/apisix/balancer.lua index f836533171e7..0fe2e6539922 100644 --- a/apisix/balancer.lua +++ b/apisix/balancer.lua @@ -79,7 +79,7 @@ local function fetch_health_nodes(upstream, checker) if ok then up_nodes = transform_node(up_nodes, node) elseif err then - core.log.error("failed to get health check target status, addr: ", + core.log.warn("failed to get health check target status, addr: ", node.host, ":", port or node.port, ", host: ", host, ", err: ", err) end end diff --git a/apisix/constants.lua b/apisix/constants.lua index 72209aa4d905..0b3ec160b53d 100644 --- a/apisix/constants.lua +++ b/apisix/constants.lua @@ -37,6 +37,7 @@ return { }, STREAM_ETCD_DIRECTORY = { ["/upstreams"] = true, + ["/services"] = true, ["/plugins"] = true, ["/ssls"] = true, ["/stream_routes"] = true, diff --git a/apisix/core/config_util.lua b/apisix/core/config_util.lua index 7e57ed402fd8..7313e0116ad2 100644 --- a/apisix/core/config_util.lua +++ b/apisix/core/config_util.lua @@ -114,7 +114,7 @@ function _M.fire_all_clean_handlers(item) clean_handler.f(item) end - item.clean_handlers = nil + item.clean_handlers = {} end diff --git a/apisix/discovery/consul/init.lua b/apisix/discovery/consul/init.lua index ae1e4c64cc9c..253d0cb8cebd 100644 --- a/apisix/discovery/consul/init.lua +++ b/apisix/discovery/consul/init.lua @@ -197,21 +197,20 @@ local function get_opts(consul_server, is_catalog) port = consul_server.port, connect_timeout = consul_server.connect_timeout, read_timeout = consul_server.read_timeout, + default_args = { + token = consul_server.token, + } } if not consul_server.keepalive then return opts end + opts.default_args.wait = consul_server.wait_timeout --blocked wait!=0; unblocked by wait=0 + if is_catalog then - opts.default_args = { - wait = consul_server.wait_timeout, --blocked wait!=0; unblocked by wait=0 - index = consul_server.catalog_index, - } + opts.default_args.index = consul_server.catalog_index else - opts.default_args = { - wait = consul_server.wait_timeout, --blocked wait!=0; unblocked by wait=0 - index = consul_server.health_index, - } + opts.default_args.index = consul_server.health_index end return opts @@ -396,6 +395,9 @@ function _M.connect(premature, consul_server, retry_delay) port = consul_server.port, connect_timeout = consul_server.connect_timeout, read_timeout = consul_server.read_timeout, + default_args = { + token = consul_server.token + } }) local catalog_success, catalog_res, catalog_err = pcall(function() return consul_client:get(consul_server.consul_watch_catalog_url) @@ -545,6 +547,7 @@ local function format_consul_params(consul_conf) core.table.insert(consul_server_list, { host = host, port = port, + token = consul_conf.token, connect_timeout = consul_conf.timeout.connect, read_timeout = consul_conf.timeout.read, wait_timeout = consul_conf.timeout.wait, diff --git a/apisix/discovery/consul/schema.lua b/apisix/discovery/consul/schema.lua index 3e998b015ce1..d7cf2954abf3 100644 --- a/apisix/discovery/consul/schema.lua +++ b/apisix/discovery/consul/schema.lua @@ -24,6 +24,7 @@ return { type = "string", } }, + token = {type = "string", default = ""}, fetch_interval = {type = "integer", minimum = 1, default = 3}, keepalive = { type = "boolean", diff --git a/apisix/discovery/consul_kv/init.lua b/apisix/discovery/consul_kv/init.lua index 2dad772ace75..6d616e059190 100644 --- a/apisix/discovery/consul_kv/init.lua +++ b/apisix/discovery/consul_kv/init.lua @@ -320,18 +320,14 @@ end local function format_consul_params(consul_conf) local consul_server_list = core.table.new(0, #consul_conf.servers) - local args + local args = { + token = consul_conf.token, + recurse = true + } - if consul_conf.keepalive == false then - args = { - recurse = true, - } - elseif consul_conf.keepalive then - args = { - recurse = true, - wait = consul_conf.timeout.wait, --blocked wait!=0; unblocked by wait=0 - index = 0, - } + if consul_conf.keepalive then + args.wait = consul_conf.timeout.wait --blocked wait!=0; unblocked by wait=0 + args.index = 0 end for _, v in pairs(consul_conf.servers) do diff --git a/apisix/discovery/consul_kv/schema.lua b/apisix/discovery/consul_kv/schema.lua index a2ebb5d07919..4c02b2c80dd0 100644 --- a/apisix/discovery/consul_kv/schema.lua +++ b/apisix/discovery/consul_kv/schema.lua @@ -24,6 +24,7 @@ return { type = "string", } }, + token = {type = "string", default = ""}, fetch_interval = {type = "integer", minimum = 1, default = 3}, keepalive = { type = "boolean", diff --git a/apisix/http/service.lua b/apisix/http/service.lua index 83bcb9b9d341..97b224d622c8 100644 --- a/apisix/http/service.lua +++ b/apisix/http/service.lua @@ -61,7 +61,7 @@ function _M.init_worker() filter = filter, }) if not services then - error("failed to create etcd instance for fetching upstream: " .. err) + error("failed to create etcd instance for fetching /services: " .. err) return end end diff --git a/apisix/init.lua b/apisix/init.lua index 86b68cf62208..da9fe915a665 100644 --- a/apisix/init.lua +++ b/apisix/init.lua @@ -1021,6 +1021,7 @@ function _M.stream_init_worker() plugin.init_worker() xrpc.init_worker() router.stream_init_worker() + require("apisix.http.service").init_worker() apisix_upstream.init_worker() local we = require("resty.worker.events") @@ -1078,6 +1079,34 @@ function _M.stream_preread_phase() api_ctx.matched_upstream = upstream + elseif matched_route.value.service_id then + local service = service_fetch(matched_route.value.service_id) + if not service then + core.log.error("failed to fetch service configuration by ", + "id: ", matched_route.value.service_id) + return core.response.exit(404) + end + + matched_route = plugin.merge_service_stream_route(service, matched_route) + api_ctx.matched_route = matched_route + api_ctx.conf_type = "stream_route&service" + api_ctx.conf_version = matched_route.modifiedIndex .. "&" .. service.modifiedIndex + api_ctx.conf_id = matched_route.value.id .. "&" .. service.value.id + api_ctx.service_id = service.value.id + api_ctx.service_name = service.value.name + api_ctx.matched_upstream = matched_route.value.upstream + if matched_route.value.upstream_id and not matched_route.value.upstream then + local upstream = apisix_upstream.get_by_id(matched_route.value.upstream_id) + if not upstream then + if is_http then + return core.response.exit(502) + end + + return ngx_exit(1) + end + + api_ctx.matched_upstream = upstream + end else if matched_route.has_domain then local err diff --git a/apisix/plugin.lua b/apisix/plugin.lua index bde2b89a5393..fa1d814b290a 100644 --- a/apisix/plugin.lua +++ b/apisix/plugin.lua @@ -43,6 +43,9 @@ local stream_local_plugins_hash = core.table.new(0, 32) local merged_route = core.lrucache.new({ ttl = 300, count = 512 }) +local merged_stream_route = core.lrucache.new({ + ttl = 300, count = 512 +}) local expr_lrucache = core.lrucache.new({ ttl = 300, count = 512 }) @@ -637,6 +640,49 @@ function _M.merge_service_route(service_conf, route_conf) end +local function merge_service_stream_route(service_conf, route_conf) + -- because many fields in Service are not supported by stream route, + -- so we copy the stream route as base object + local new_conf = core.table.deepcopy(route_conf) + if service_conf.value.plugins then + for name, conf in pairs(service_conf.value.plugins) do + if not new_conf.value.plugins then + new_conf.value.plugins = {} + end + + if not new_conf.value.plugins[name] then + new_conf.value.plugins[name] = conf + end + end + end + + new_conf.value.service_id = nil + + if not new_conf.value.upstream and service_conf.value.upstream then + new_conf.value.upstream = service_conf.value.upstream + end + + if not new_conf.value.upstream_id and service_conf.value.upstream_id then + new_conf.value.upstream_id = service_conf.value.upstream_id + end + + return new_conf +end + + +function _M.merge_service_stream_route(service_conf, route_conf) + core.log.info("service conf: ", core.json.delay_encode(service_conf, true)) + core.log.info(" stream route conf: ", core.json.delay_encode(route_conf, true)) + + local version = route_conf.modifiedIndex .. "#" .. service_conf.modifiedIndex + local route_service_key = route_conf.value.id .. "#" + .. version + return merged_stream_route(route_service_key, version, + merge_service_stream_route, + service_conf, route_conf) +end + + local function merge_consumer_route(route_conf, consumer_conf, consumer_group_conf) if not consumer_conf.plugins or core.table.nkeys(consumer_conf.plugins) == 0 diff --git a/apisix/plugins/cors.lua b/apisix/plugins/cors.lua index 4f0bfa5d37aa..f4a59ce5e301 100644 --- a/apisix/plugins/cors.lua +++ b/apisix/plugins/cors.lua @@ -98,7 +98,7 @@ local schema = { type = "array", description = "you can use regex to allow specific origins when no credentials," .. - "for example use [.*\\.test.com] to allow a.test.com and b.test.com", + "for example use [.*\\.test.com$] to allow a.test.com and b.test.com", items = { type = "string", minLength = 1, diff --git a/apisix/schema_def.lua b/apisix/schema_def.lua index cec1c53b3438..e3e9a05aca26 100644 --- a/apisix/schema_def.lua +++ b/apisix/schema_def.lua @@ -402,16 +402,10 @@ local upstream_schema = { }, }, dependencies = { - client_cert = { - required = {"client_key"}, - ["not"] = {required = {"client_cert_id"}} - }, - client_key = { - required = {"client_cert"}, - ["not"] = {required = {"client_cert_id"}} - }, + client_cert = {required = {"client_key"}}, + client_key = {required = {"client_cert"}}, client_cert_id = { - ["not"] = {required = {"client_client", "client_key"}} + ["not"] = {required = {"client_cert", "client_key"}} } } }, @@ -784,10 +778,6 @@ _M.ssl = { }, required = {"ca"}, }, - exptime = { - type = "integer", - minimum = 1588262400, -- 2020/5/1 0:0:0 - }, labels = labels_def, status = { description = "ssl status, 1 to enable, 0 to disable", @@ -804,8 +794,6 @@ _M.ssl = { enum = {"TLSv1.1", "TLSv1.2", "TLSv1.3"} }, }, - validity_end = timestamp_def, - validity_start = timestamp_def, create_time = timestamp_def, update_time = timestamp_def }, @@ -920,6 +908,7 @@ _M.stream_route = { }, upstream = upstream_schema, upstream_id = id_schema, + service_id = id_schema, plugins = plugins_schema, protocol = xrpc_protocol_schema, }, @@ -949,6 +938,9 @@ _M.plugins = { _M.plugin_config = { type = "object", properties = { + name = { + type = "string", + }, id = id_schema, desc = desc_def, plugins = plugins_schema, diff --git a/apisix/stream/router/ip_port.lua b/apisix/stream/router/ip_port.lua index 977bcb2d3a4e..284cc456edbc 100644 --- a/apisix/stream/router/ip_port.lua +++ b/apisix/stream/router/ip_port.lua @@ -110,6 +110,8 @@ do for _, route in ipairs(items) do local hit = match_addrs(route, vars) if hit then + route.value.remote_addr_matcher = nil + route.value.server_addr_matcher = nil ctx.matched_route = route return true end @@ -175,6 +177,8 @@ do for _, route in ipairs(other_routes) do local hit = match_addrs(route, api_ctx.var) if hit then + route.value.remote_addr_matcher = nil + route.value.server_addr_matcher = nil api_ctx.matched_route = route return true end diff --git a/apisix/upstream.lua b/apisix/upstream.lua index 0ff82fea856a..d8e3f3a98750 100644 --- a/apisix/upstream.lua +++ b/apisix/upstream.lua @@ -83,7 +83,7 @@ _M.set = set_directly local function release_checker(healthcheck_parent) local checker = healthcheck_parent.checker core.log.info("try to release checker: ", tostring(checker)) - checker:clear() + checker:delayed_clear(3) checker:stop() end diff --git a/ci/pod/docker-compose.first.yml b/ci/pod/docker-compose.first.yml index 62ef7a328c16..aee79a8387c7 100644 --- a/ci/pod/docker-compose.first.yml +++ b/ci/pod/docker-compose.first.yml @@ -46,6 +46,15 @@ services: networks: consul_net: + consul_3: + image: hashicorp/consul:1.16.2 + restart: unless-stopped + ports: + - "8502:8500" + command: [ "consul", "agent", "-server", "-bootstrap-expect=1", "-client", "0.0.0.0", "-log-level", "info", "-data-dir=/consul/data", "-enable-script-checks", "-ui", "-hcl", "acl = {\nenabled = true\ndefault_policy = \"deny\"\nenable_token_persistence = true\ntokens = {\nagent = \"2b778dd9-f5f1-6f29-b4b4-9a5fa948757a\"\n}}" ] + networks: + consul_net: + ## Nacos cluster nacos_auth: hostname: nacos1 diff --git a/docs/en/latest/admin-api.md b/docs/en/latest/admin-api.md index 396365f45e0f..77c2141336c8 100644 --- a/docs/en/latest/admin-api.md +++ b/docs/en/latest/admin-api.md @@ -887,30 +887,30 @@ For notes on ID syntax please refer to: [ID Syntax](#quick-note-on-id-syntax) In addition to the equalization algorithm selections, Upstream also supports passive health check and retry for the upstream. See the table below for more details: -| Name | Optional | Description | Example | -| --------------------------- | ------------------------------------------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| ------------------------------------------------------------------------------------------------------------------------------------------ | -| type | optional | Load balancing algorithm to be used, and the default value is `roundrobin`. | | -| nodes | required, can't be used with `service_name` | IP addresses (with optional ports) of the Upstream nodes represented as a hash table or an array. In the hash table, the key is the IP address and the value is the weight of the node for the load balancing algorithm. For hash table case, if the key is IPv6 address with port, then the IPv6 address must be quoted with square brackets. In the array, each item is a hash table with keys `host`, `weight`, and the optional `port` and `priority`. Empty nodes are treated as placeholders and clients trying to access this Upstream will receive a 502 response. | `192.168.1.100:80`, `[::1]:80` | -| service_name | required, can't be used with `nodes` | Service name used for [service discovery](discovery.md). | `a-bootiful-client` | -| discovery_type | required, if `service_name` is used | The type of service [discovery](discovery.md). | `eureka` | -| hash_on | optional | Only valid if the `type` is `chash`. Supports Nginx variables (`vars`), custom headers (`header`), `cookie` and `consumer`. Defaults to `vars`. | | -| key | optional | Only valid if the `type` is `chash`. Finds the corresponding node `id` according to `hash_on` and `key` values. When `hash_on` is set to `vars`, `key` is a required parameter and it supports [Nginx variables](http://nginx.org/en/docs/varindex.html). When `hash_on` is set as `header`, `key` is a required parameter, and `header name` can be customized. When `hash_on` is set to `cookie`, `key` is also a required parameter, and `cookie name` can be customized. When `hash_on` is set to `consumer`, `key` need not be set and the `key` used by the hash algorithm would be the authenticated `consumer_name`. | `uri`, `server_name`, `server_addr`, `request_uri`, `remote_port`, `remote_addr`, `query_string`, `host`, `hostname`, `arg_***`, `arg_***` | -| checks | optional | Configures the parameters for the [health check](./tutorials/health-check.md). | | -| retries | optional | Sets the number of retries while passing the request to Upstream using the underlying Nginx mechanism. Set according to the number of available backend nodes by default. Setting this to `0` disables retry. | | -| retry_timeout | optional | Timeout to continue with retries. Setting this to `0` disables the retry timeout. | | -| timeout | optional | Sets the timeout (in seconds) for connecting to, and sending and receiving messages to and from the Upstream. | `{"connect": 0.5,"send": 0.5,"read": 0.5}` | -| name | optional | Identifier for the Upstream. | | -| desc | optional | Description of usage scenarios. | | -| pass_host | optional | Configures the `host` when the request is forwarded to the upstream. Can be one of `pass`, `node` or `rewrite`. Defaults to `pass` if not specified. `pass`- transparently passes the client's host to the Upstream. `node`- uses the host configured in the node of the Upstream. `rewrite`- Uses the value configured in `upstream_host`. | | -| upstream_host | optional | Specifies the host of the Upstream request. This is only valid if the `pass_host` is set to `rewrite`. | | -| scheme | optional | The scheme used when communicating with the Upstream. For an L7 proxy, this value can be one of `http`, `https`, `grpc`, `grpcs`. For an L4 proxy, this value could be one of `tcp`, `udp`, `tls`. Defaults to `http`. | | -| labels | optional | Attributes of the Upstream specified as `key-value` pairs. | {"version":"v2","build":"16","env":"production"} | -| tls.client_cert | optional, can't be used with `tls.client_cert_id` | Sets the client certificate while connecting to a TLS Upstream. | | -| tls.client_key | optional, can't be used with `tls.client_cert_id` | Sets the client private key while connecting to a TLS Upstream. | | -| tls.client_cert_id | optional, can't be used with `tls.client_cert` and `tls.client_key` | Set the referenced [SSL](#ssl) id. | | -| keepalive_pool.size | optional | Sets `keepalive` directive dynamically. | | -| keepalive_pool.idle_timeout | optional | Sets `keepalive_timeout` directive dynamically. | | -| keepalive_pool.requests | optional | Sets `keepalive_requests` directive dynamically. | | +| Parameter | Required | Type | Description | Example | +|-----------------------------|------------------------------------------------------------------|-------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------| +| type | False | Enumeration | Load balancing algorithm to be used, and the default value is `roundrobin`. | | +| nodes | True, can't be used with `service_name` | Node | IP addresses (with optional ports) of the Upstream nodes represented as a hash table or an array. In the hash table, the key is the IP address and the value is the weight of the node for the load balancing algorithm. For hash table case, if the key is IPv6 address with port, then the IPv6 address must be quoted with square brackets. In the array, each item is a hash table with keys `host`, `weight`, and the optional `port` and `priority`. Empty nodes are treated as placeholders and clients trying to access this Upstream will receive a 502 response. | `192.168.1.100:80`, `[::1]:80` | +| service_name | True, can't be used with `nodes` | String | Service name used for [service discovery](discovery.md). | `a-bootiful-client` | +| discovery_type | True, if `service_name` is used | String | The type of service [discovery](discovery.md). | `eureka` | +| hash_on | False | Auxiliary | Only valid if the `type` is `chash`. Supports Nginx variables (`vars`), custom headers (`header`), `cookie` and `consumer`. Defaults to `vars`. | | +| key | False | Match Rules | Only valid if the `type` is `chash`. Finds the corresponding node `id` according to `hash_on` and `key` values. When `hash_on` is set to `vars`, `key` is a required parameter and it supports [Nginx variables](http://nginx.org/en/docs/varindex.html). When `hash_on` is set as `header`, `key` is a required parameter, and `header name` can be customized. When `hash_on` is set to `cookie`, `key` is also a required parameter, and `cookie name` can be customized. When `hash_on` is set to `consumer`, `key` need not be set and the `key` used by the hash algorithm would be the authenticated `consumer_name`. | `uri`, `server_name`, `server_addr`, `request_uri`, `remote_port`, `remote_addr`, `query_string`, `host`, `hostname`, `arg_***`, `arg_***` | +| checks | False | Health Checker | Configures the parameters for the [health check](./tutorials/health-check.md). | | +| retries | False | Integer | Sets the number of retries while passing the request to Upstream using the underlying Nginx mechanism. Set according to the number of available backend nodes by default. Setting this to `0` disables retry. | | +| retry_timeout | False | Integer | Timeout to continue with retries. Setting this to `0` disables the retry timeout. | | +| timeout | False | Timeout | Sets the timeout (in seconds) for connecting to, and sending and receiving messages to and from the Upstream. | `{"connect": 0.5,"send": 0.5,"read": 0.5}` | +| name | False | Auxiliary | Identifier for the Upstream. | | +| desc | False | Auxiliary | Description of usage scenarios. | | +| pass_host | False | Enumeration | Configures the `host` when the request is forwarded to the upstream. Can be one of `pass`, `node` or `rewrite`. Defaults to `pass` if not specified. `pass`- transparently passes the client's host to the Upstream. `node`- uses the host configured in the node of the Upstream. `rewrite`- Uses the value configured in `upstream_host`. | | +| upstream_host | False | Auxiliary | Specifies the host of the Upstream request. This is only valid if the `pass_host` is set to `rewrite`. | | +| scheme | False | Auxiliary | The scheme used when communicating with the Upstream. For an L7 proxy, this value can be one of `http`, `https`, `grpc`, `grpcs`. For an L4 proxy, this value could be one of `tcp`, `udp`, `tls`. Defaults to `http`. | | +| labels | False | Match Rules | Attributes of the Upstream specified as `key-value` pairs. | {"version":"v2","build":"16","env":"production"} | +| tls.client_cert | False, can't be used with `tls.client_cert_id` | HTTPS certificate | Sets the client certificate while connecting to a TLS Upstream. | | +| tls.client_key | False, can't be used with `tls.client_cert_id` | HTTPS certificate private key | Sets the client private key while connecting to a TLS Upstream. | | +| tls.client_cert_id | False, can't be used with `tls.client_cert` and `tls.client_key` | SSL | Set the referenced [SSL](#ssl) id. | | +| keepalive_pool.size | False | Auxiliary | Sets `keepalive` directive dynamically. | | +| keepalive_pool.idle_timeout | False | Auxiliary | Sets `keepalive_timeout` directive dynamically. | | +| keepalive_pool.requests | False | Auxiliary | Sets `keepalive_requests` directive dynamically. | | An Upstream can be one of the following `types`: @@ -1419,6 +1419,7 @@ Stream Route resource request address: /apisix/admin/stream_routes/{id} | ----------- | -------- | -------- | ------------------------------------------------------------------- | ----------------------------- | | upstream | False | Upstream | Configuration of the [Upstream](./terminology/upstream.md). | | | upstream_id | False | Upstream | Id of the [Upstream](terminology/upstream.md) service. | | +| service_id | False | String | Id of the [Service](terminology/service.md) service. | | | remote_addr | False | IPv4, IPv4 CIDR, IPv6 | Filters Upstream forwards by matching with client IP. | "127.0.0.1" or "127.0.0.1/32" or "::1" | | server_addr | False | IPv4, IPv4 CIDR, IPv6 | Filters Upstream forwards by matching with APISIX Server IP. | "127.0.0.1" or "127.0.0.1/32" or "::1" | | server_port | False | Integer | Filters Upstream forwards by matching with APISIX Server port. | 9090 | diff --git a/docs/en/latest/discovery/consul.md b/docs/en/latest/discovery/consul.md index b4eab61e982d..85e6b9ba2c8b 100644 --- a/docs/en/latest/discovery/consul.md +++ b/docs/en/latest/discovery/consul.md @@ -37,6 +37,7 @@ discovery: servers: # make sure service name is unique in these consul servers - "http://127.0.0.1:8500" # `http://127.0.0.1:8500` and `http://127.0.0.1:8600` are different clusters - "http://127.0.0.1:8600" # `consul` service is default skip service + token: "..." # if your consul cluster has enabled acl access control, you need to specify the token skip_services: # if you need to skip special services - "service_a" timeout: diff --git a/docs/en/latest/discovery/consul_kv.md b/docs/en/latest/discovery/consul_kv.md index bfb434417033..e0a2602c074b 100644 --- a/docs/en/latest/discovery/consul_kv.md +++ b/docs/en/latest/discovery/consul_kv.md @@ -40,6 +40,7 @@ discovery: servers: - "http://127.0.0.1:8500" - "http://127.0.0.1:8600" + token: "..." # if your consul cluster has enabled acl access control, you need to specify the token prefix: "upstreams" skip_keys: # if you need to skip special keys - "upstreams/unused_api/" diff --git a/docs/en/latest/plugins/cors.md b/docs/en/latest/plugins/cors.md index 7d46c7a5a675..dad8279656aa 100644 --- a/docs/en/latest/plugins/cors.md +++ b/docs/en/latest/plugins/cors.md @@ -40,7 +40,7 @@ The `cors` Plugins lets you enable [CORS](https://developer.mozilla.org/en-US/do | expose_headers | string | False | "*" | Headers in the response allowed when accessing a cross-origin resource. Use `,` to add multiple headers. If `allow_credential` is set to `false`, you can enable CORS for all response headers by using `*`. If `allow_credential` is set to `true`, you can forcefully allow CORS on all response headers by using `**` but it will pose some security issues. | | max_age | integer | False | 5 | Maximum time in seconds the result is cached. If the time is within this limit, the browser will check the cached result. Set to `-1` to disable caching. Note that the maximum value is browser dependent. See [Access-Control-Max-Age](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age#Directives) for more details. | | allow_credential | boolean | False | false | When set to `true`, allows requests to include credentials like cookies. According to CORS specification, if you set this to `true`, you cannot use '*' to allow all for the other attributes. | -| allow_origins_by_regex | array | False | nil | Regex to match with origin for enabling CORS. For example, `[".*\.test.com"]` can match all subdomain of `test.com`. When set to specified range, only domains in this range will be allowed, no matter what `allow_origins` is. | +| allow_origins_by_regex | array | False | nil | Regex to match origins that allow CORS. For example, `[".*\.test.com$"]` can match all subdomains of `test.com`. When set to specified range, only domains in this range will be allowed, no matter what `allow_origins` is. | | allow_origins_by_metadata | array | False | nil | Origins to enable CORS referenced from `allow_origins` set in the Plugin metadata. For example, if `"allow_origins": {"EXAMPLE": "https://example.com"}` is set in the Plugin metadata, then `["EXAMPLE"]` can be used to allow CORS on the origin `https://example.com`. | :::info IMPORTANT diff --git a/docs/en/latest/terminology/plugin-config.md b/docs/en/latest/terminology/plugin-config.md index 59b46b08933e..4ed78e6d5776 100644 --- a/docs/en/latest/terminology/plugin-config.md +++ b/docs/en/latest/terminology/plugin-config.md @@ -30,7 +30,7 @@ description: Plugin Config in Apache APISIX. Plugin Configs are used to extract commonly used [Plugin](./plugin.md) configurations and can be bound directly to a [Route](./route.md). -While configuring the same plugin, only one copy of the configuration is valid. The order of precedence is always `Consumer` > `Consumer Group` > `Route` > `Plugin Config` > `Service`. +While configuring the same plugin, only one copy of the configuration is valid. Please read the [plugin execution order](../terminology/plugin.md#plugins-execution-order) and [plugin merging order](../terminology/plugin.md#plugins-merging-precedence). ## Example diff --git a/docs/en/latest/tutorials/protect-api.md b/docs/en/latest/tutorials/protect-api.md index 22caa1b16080..38c0bd6240f6 100644 --- a/docs/en/latest/tutorials/protect-api.md +++ b/docs/en/latest/tutorials/protect-api.md @@ -37,7 +37,7 @@ This represents the configuration of the plugins that are executed during the HT :::note -If [Route](../terminology/route.md), [Service](../terminology/service.md), [Plugin Config](../terminology/plugin-config.md) or Consumer are all bound to the same for plugins, only one plugin configuration will take effect. The priority of plugin configurations is: Consumer > Route > Plugin Config > Service. At the same time, there are 6 stages involved in the plugin execution process, namely `rewrite`, `access`, `before_proxy`, `header_filter`, `body_filter` and `log`. +If [Route](../terminology/route.md), [Service](../terminology/service.md), [Plugin Config](../terminology/plugin-config.md) or [Consumer](../terminology/consumer.md) are all bound to the same for plugins, only one plugin configuration will take effect. The priority of plugin configurations is described in [plugin execution order](../terminology/plugin.md#plugins-execution-order). At the same time, there are various stages involved in the plugin execution process. See [plugin execution lifecycle](../terminology/plugin.md#plugins-execution-order). ::: diff --git a/docs/zh/latest/CHANGELOG.md b/docs/zh/latest/CHANGELOG.md index ffee1ebb977a..a3ce74cca888 100644 --- a/docs/zh/latest/CHANGELOG.md +++ b/docs/zh/latest/CHANGELOG.md @@ -78,23 +78,22 @@ title: CHANGELOG ### Change - :warning: 移除 `etcd.use_grpc`,不再支持使用 gRPC 协议与 etcd 进行通信:[#10015](https://github.com/apache/apisix/pull/10015) -- :warning: 移除 conf server,数据平面不再支持与数据平面进行通信,需要从 `config_provider: control_plane` 调整为 `config_provider: etcd`:[#10012](https://github.com/apache/apisix/pull/10012) +- :warning: 移除 conf server,数据平面不再支持与控制平面进行通信,需要从 `config_provider: control_plane` 调整为 `config_provider: etcd`:[#10012](https://github.com/apache/apisix/pull/10012) +- :warning: 严格验证核心资源的输入:[#10233](https://github.com/apache/apisix/pull/10233) ### Core - :sunrise: 支持配置访问日志的缓冲区大小:[#10225](https://github.com/apache/apisix/pull/10225) -- :sunrise: 支持在 DNS 发现中传递 resolv.conf:[#9770](https://github.com/apache/apisix/pull/9770) -- :sunrise: 不再依赖 Rust:[#10121](https://github.com/apache/apisix/pull/10065) -- :sunrise: 严格验证核心资源的输入:[#10233](https://github.com/apache/apisix/pull/10233) -- :sunrise: 在 xrpc 中添加 dubbo 协议支持:[#9660](https://github.com/apache/apisix/pull/9660) +- :sunrise: 支持在 DNS 发现服务中允许配置 `resolv_conf` 来使用本地 DNS 解析器:[#9770](https://github.com/apache/apisix/pull/9770) +- :sunrise: 安装不再依赖 Rust:[#10121](https://github.com/apache/apisix/pull/10121) +- :sunrise: 在 xRPC 中添加 Dubbo 协议支持:[#9660](https://github.com/apache/apisix/pull/9660) ### Plugins -- :sunrise: 在 traffic-split 插件中支持 https:[#9115](https://github.com/apache/apisix/pull/9115) -- :sunrise: 支持在 DNS 发现中传递 resolv.conf:[#9770](https://github.com/apache/apisix/pull/9770) -- :sunrise: 在 ext-plugin 插件中支持重写请求体:[#9990](https://github.com/apache/apisix/pull/9990) -- :sunrise: 在 opentelemetry 插件中支持设置 nginx 变量:[#8871](https://github.com/apache/apisix/pull/8871) -- :sunrise: 在 chaitin-waf 插件中支持 unix sock 主机模式:[#10161](https://github.com/apache/apisix/pull/10161) +- :sunrise: 在 `traffic-split` 插件中支持 HTTPS:[#9115](https://github.com/apache/apisix/pull/9115) +- :sunrise: 在 `ext-plugin` 插件中支持重写请求体:[#9990](https://github.com/apache/apisix/pull/9990) +- :sunrise: 在 `opentelemetry` 插件中支持设置 NGINX 变量:[#8871](https://github.com/apache/apisix/pull/8871) +- :sunrise: 在 `chaitin-waf` 插件中支持 UNIX sock 主机模式:[#10161](https://github.com/apache/apisix/pull/10161) ### Bugfixes diff --git a/docs/zh/latest/admin-api.md b/docs/zh/latest/admin-api.md index dd2d257a4043..899fb4c44deb 100644 --- a/docs/zh/latest/admin-api.md +++ b/docs/zh/latest/admin-api.md @@ -1428,6 +1428,7 @@ Plugin 资源请求地址:/apisix/admin/stream_routes/{id} | ---------------- | ------| -------- | ------------------------------------------------------------------------------| ------ | | upstream | 否 | Upstream | Upstream 配置,详细信息请参考 [Upstream](terminology/upstream.md)。 | | | upstream_id | 否 | Upstream | 需要使用的 Upstream id,详细信息请 [Upstream](terminology/upstream.md)。 | | +| service_id | 否 | String | 需要使用的 [Service](terminology/service.md) id. | | | remote_addr | 否 | IPv4, IPv4 CIDR, IPv6 | 过滤选项:如果客户端 IP 匹配,则转发到上游 | "127.0.0.1" 或 "127.0.0.1/32" 或 "::1" | | server_addr | 否 | IPv4, IPv4 CIDR, IPv6 | 过滤选项:如果 APISIX 服务器的 IP 与 `server_addr` 匹配,则转发到上游。 | "127.0.0.1" 或 "127.0.0.1/32" 或 "::1" | | server_port | 否 | 整数 | 过滤选项:如果 APISIX 服务器的端口 与 `server_port` 匹配,则转发到上游。 | 9090 | diff --git a/docs/zh/latest/plugins/cors.md b/docs/zh/latest/plugins/cors.md index e9c60d465a38..0ced5a57dd45 100644 --- a/docs/zh/latest/plugins/cors.md +++ b/docs/zh/latest/plugins/cors.md @@ -40,7 +40,7 @@ description: 本文介绍了 Apache APISIX cors 插件的基本信息及使用 | expose_headers | string | 否 | "*" | 允许跨域访问时响应方携带哪些非 `CORS 规范` 以外的 Header。如果你有多个 Header,请使用 `,` 分割。当 `allow_credential` 为 `false` 时,可以使用 `*` 来表示允许任意 Header。你也可以在启用了 `allow_credential` 后使用 `**` 强制允许任意 Header,但请注意这样存在安全隐患。 | | max_age | integer | 否 | 5 | 浏览器缓存 CORS 结果的最大时间,单位为秒。在这个时间范围内,浏览器会复用上一次的检查结果,`-1` 表示不缓存。请注意各个浏览器允许的最大时间不同,详情请参考 [Access-Control-Max-Age - MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age#directives)。 | | allow_credential | boolean | 否 | false | 是否允许跨域访问的请求方携带凭据(如 Cookie 等)。根据 CORS 规范,如果设置该选项为 `true`,那么将不能在其他属性中使用 `*`。 | -| allow_origins_by_regex | array | 否 | nil | 使用正则表达式数组来匹配允许跨域访问的 Origin,如 `[".*\.test.com"]` 可以匹配任何 `test.com` 的子域名 `*`。如果 `allow_origins_by_regex` 属性已经指定,则会忽略 `allow_origins` 属性。 | +| allow_origins_by_regex | array | 否 | nil | 使用正则表达式数组来匹配允许跨域访问的 Origin,如 `[".*\.test.com$"]` 可以匹配任何 `test.com` 的子域名。如果 `allow_origins_by_regex` 属性已经指定,则会忽略 `allow_origins` 属性。 | | allow_origins_by_metadata | array | 否 | nil | 通过引用插件元数据的 `allow_origins` 配置允许跨域访问的 Origin。比如当插件元数据为 `"allow_origins": {"EXAMPLE": "https://example.com"}` 时,配置 `["EXAMPLE"]` 将允许 Origin `https://example.com` 的访问。 | :::info IMPORTANT diff --git a/rockspec/apisix-master-0.rockspec b/rockspec/apisix-master-0.rockspec index ec99128f30f3..d4e4b71a0db5 100644 --- a/rockspec/apisix-master-0.rockspec +++ b/rockspec/apisix-master-0.rockspec @@ -39,7 +39,8 @@ dependencies = { "lua-resty-balancer = 0.04", "lua-resty-ngxvar = 0.5.2", "lua-resty-jit-uuid = 0.0.7", - "lua-resty-healthcheck-api7 = 3.0.0", + "lua-resty-worker-events = 1.0.0", + "lua-resty-healthcheck-api7 = 3.2.0", "api7-lua-resty-jwt = 0.2.5", "lua-resty-hmac-ffi = 0.06-1", "lua-resty-cookie = 0.2.0-1", diff --git a/t/admin/ssl.t b/t/admin/ssl.t index 6b259b25ee4d..b03eb494f854 100644 --- a/t/admin/ssl.t +++ b/t/admin/ssl.t @@ -268,45 +268,7 @@ passed -=== TEST 9: store exptime ---- config - location /t { - content_by_lua_block { - local core = require("apisix.core") - local t = require("lib.test_admin") - - local ssl_cert = t.read_file("t/certs/apisix.crt") - local ssl_key = t.read_file("t/certs/apisix.key") - local data = { - cert = ssl_cert, key = ssl_key, - sni = "bar.com", - exptime = 1588262400 + 60 * 60 * 24 * 365, - } - - local code, body = t.test('/apisix/admin/ssls/1', - ngx.HTTP_PUT, - core.json.encode(data), - [[{ - "value": { - "sni": "bar.com", - "exptime": 1619798400 - }, - "key": "/apisix/ssls/1" - }]] - ) - - ngx.status = code - ngx.say(body) - } - } ---- request -GET /t ---- response_body -passed - - - -=== TEST 10: string id +=== TEST 9: string id --- config location /t { content_by_lua_block { @@ -334,7 +296,7 @@ passed -=== TEST 11: string id(delete) +=== TEST 10: string id(delete) --- config location /t { content_by_lua_block { @@ -361,7 +323,7 @@ passed -=== TEST 12: invalid id +=== TEST 11: invalid id --- config location /t { content_by_lua_block { @@ -388,7 +350,7 @@ GET /t -=== TEST 13: set ssl with multicerts(id: 1) +=== TEST 12: set ssl with multicerts(id: 1) --- config location /t { content_by_lua_block { @@ -429,7 +391,7 @@ passed -=== TEST 14: mismatched certs and keys +=== TEST 13: mismatched certs and keys --- config location /t { content_by_lua_block { @@ -467,7 +429,7 @@ GET /t -=== TEST 15: set ssl(with labels) +=== TEST 14: set ssl(with labels) --- config location /t { content_by_lua_block { @@ -505,7 +467,7 @@ passed -=== TEST 16: invalid format of label value: set ssl +=== TEST 15: invalid format of label value: set ssl --- config location /t { content_by_lua_block { @@ -542,7 +504,7 @@ GET /t -=== TEST 17: create ssl with manage fields(id: 1) +=== TEST 16: create ssl with manage fields(id: 1) --- config location /t { content_by_lua_block { @@ -554,9 +516,7 @@ GET /t local data = { cert = ssl_cert, key = ssl_key, - sni = "test.com", - validity_start = 1602873670, - validity_end = 1603893670 + sni = "test.com" } local code, body = t.test('/apisix/admin/ssls/1', @@ -564,9 +524,7 @@ GET /t core.json.encode(data), [[{ "value": { - "sni": "test.com", - "validity_start": 1602873670, - "validity_end": 1603893670 + "sni": "test.com" }, "key": "/apisix/ssls/1" }]] @@ -583,7 +541,7 @@ passed -=== TEST 18: delete test ssl(id: 1) +=== TEST 17: delete test ssl(id: 1) --- config location /t { content_by_lua_block { @@ -599,7 +557,7 @@ GET /t -=== TEST 19: create/patch ssl +=== TEST 18: create/patch ssl --- config location /t { content_by_lua_block { @@ -664,7 +622,7 @@ passed -=== TEST 20: missing sni information +=== TEST 19: missing sni information --- config location /t { content_by_lua_block { @@ -695,7 +653,7 @@ GET /t -=== TEST 21: type client, missing sni information +=== TEST 20: type client, missing sni information --- config location /t { content_by_lua_block { diff --git a/t/cli/test_cmd.sh b/t/cli/test_cmd.sh index 953b6dc4a119..81864aeb9a18 100755 --- a/t/cli/test_cmd.sh +++ b/t/cli/test_cmd.sh @@ -87,7 +87,7 @@ deployment: # check if .customized_config_path has been created if [ ! -e conf/.customized_config_path ]; then rm conf/customized_config.yaml - echo ".config_path file should exits" + echo ".customized_config_path should exits" exit 1 fi @@ -102,9 +102,9 @@ fi make stop # check if .customized_config_path has been removed -if [ -e conf/.config_path ]; then +if [ -e conf/.customized_config_path ]; then rm conf/customized_config_path.yaml - echo ".config_path file should be removed" + echo ".customized_config_path should be removed" exit 1 fi diff --git a/t/control/discovery.t b/t/control/discovery.t index c548b3697563..7bf81b15ee94 100644 --- a/t/control/discovery.t +++ b/t/control/discovery.t @@ -77,7 +77,7 @@ 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},"weight":1} +{"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 diff --git a/t/control/healthcheck.t b/t/control/healthcheck.t index 5d40e970739b..9673ab917ba5 100644 --- a/t/control/healthcheck.t +++ b/t/control/healthcheck.t @@ -51,7 +51,7 @@ routes: upstreams: - nodes: "127.0.0.1:1980": 1 - "127.0.0.2:1988": 1 + "127.0.0.2:1988": 0 type: roundrobin id: 1 checks: @@ -120,11 +120,11 @@ upstreams: --- grep_error_log eval qr/unhealthy TCP increment \(.+\) for '[^']+'/ --- grep_error_log_out -unhealthy TCP increment (1/2) for '(127.0.0.2:1988)' -unhealthy TCP increment (2/2) for '(127.0.0.2:1988)' +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},"ip":"127.0.0.1","port":1980,"status":"healthy"},{"counter":{"http_failure":0,"success":0,"tcp_failure":2,"timeout_failure":0},"ip":"127.0.0.2","port":1988,"status":"unhealthy"}] -[{"counter":{"http_failure":0,"success":0,"tcp_failure":0,"timeout_failure":0},"ip":"127.0.0.1","port":1980,"status":"healthy"},{"counter":{"http_failure":0,"success":0,"tcp_failure":2,"timeout_failure":0},"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"}] +[{"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"}] diff --git a/t/core/schema_def.t b/t/core/schema_def.t index b6a7bba05b0c..da3bb51f8b26 100644 --- a/t/core/schema_def.t +++ b/t/core/schema_def.t @@ -139,3 +139,101 @@ qr/ok: false err: property "(id|plugins)" is required/ GET /t --- response_body passed + + + +=== TEST 4: sanity check upstream_schema +--- config + location /t { + content_by_lua_block { + local schema_def = require("apisix.schema_def") + local core = require("apisix.core") + local t = require("lib.test_admin") + local ssl_cert = t.read_file("t/certs/apisix.crt") + local ssl_key = t.read_file("t/certs/apisix.key") + local upstream = { + nodes = { + ["127.0.0.1:8080"] = 1 + }, + type = "roundrobin", + tls = { + client_cert_id = 1, + client_cert = ssl_cert, + client_key = ssl_key + } + } + local ok, err = core.schema.check(schema_def.upstream, upstream) + assert(not ok) + assert(err ~= nil) + + upstream = { + nodes = { + ["127.0.0.1:8080"] = 1 + }, + type = "roundrobin", + tls = { + client_cert_id = 1 + } + } + local ok, err = core.schema.check(schema_def.upstream, upstream) + assert(ok) + assert(err == nil, err) + + upstream = { + nodes = { + ["127.0.0.1:8080"] = 1 + }, + type = "roundrobin", + tls = { + client_cert = ssl_cert, + client_key = ssl_key + } + } + local ok, err = core.schema.check(schema_def.upstream, upstream) + assert(ok) + assert(err == nil, err) + + upstream = { + nodes = { + ["127.0.0.1:8080"] = 1 + }, + type = "roundrobin", + tls = { + } + } + local ok, err = core.schema.check(schema_def.upstream, upstream) + assert(ok) + assert(err == nil, err) + + upstream = { + nodes = { + ["127.0.0.1:8080"] = 1 + }, + type = "roundrobin", + tls = { + client_cert = ssl_cert + } + } + local ok, err = core.schema.check(schema_def.upstream, upstream) + assert(not ok) + assert(err ~= nil) + + upstream = { + nodes = { + ["127.0.0.1:8080"] = 1 + }, + type = "roundrobin", + tls = { + client_cert_id = 1, + client_key = ssl_key + } + } + local ok, err = core.schema.check(schema_def.upstream, upstream) + assert(not ok) + assert(err ~= nil) + + ngx.say("passed") + } + } +--- response_body +passed diff --git a/t/discovery/consul.t b/t/discovery/consul.t index 57a6ab5b4fbd..9ec87202118e 100644 --- a/t/discovery/consul.t +++ b/t/discovery/consul.t @@ -112,6 +112,40 @@ discovery: max_fails: 1 _EOC_ +our $yaml_config_with_acl = <<_EOC_; +apisix: + node_listen: 1984 + enable_control: true + control: + ip: 127.0.0.1 + port: 9090 +deployment: + role: data_plane + role_data_plane: + config_provider: yaml +discovery: + consul: + servers: + - "http://127.0.0.1:8502" + token: "2b778dd9-f5f1-6f29-b4b4-9a5fa948757a" + skip_services: + - "service_c" + timeout: + connect: 1000 + read: 1000 + wait: 60 + weight: 1 + fetch_interval: 1 + keepalive: true + default_service: + host: "127.0.0.1" + port: 20999 + metadata: + fail_timeout: 1 + weight: 1 + max_fails: 1 +_EOC_ + run_tests(); @@ -579,8 +613,8 @@ upstreams: --- request GET /thc --- response_body -[{"ip":"127.0.0.1","port":30513,"status":"healthy"},{"ip":"127.0.0.1","port":30514,"status":"healthy"}] -[{"ip":"127.0.0.1","port":30513,"status":"healthy"},{"ip":"127.0.0.1","port":30514,"status":"healthy"}] +[{"hostname":"127.0.0.1","ip":"127.0.0.1","port":30513,"status":"healthy"},{"hostname":"127.0.0.1","ip":"127.0.0.1","port":30514,"status":"healthy"}] +[{"hostname":"127.0.0.1","ip":"127.0.0.1","port":30513,"status":"healthy"},{"hostname":"127.0.0.1","ip":"127.0.0.1","port":30514,"status":"healthy"}] --- ignore_error_log @@ -657,3 +691,93 @@ location /sleep { qr/server 1\n/, ] --- ignore_error_log + + + +=== TEST 14: bootstrap acl +--- config +location /v1/acl { + proxy_pass http://127.0.0.1:8502; +} +--- request eval +"PUT /v1/acl/bootstrap\n" . "{\"BootstrapSecret\": \"2b778dd9-f5f1-6f29-b4b4-9a5fa948757a\"}" +--- error_code_like: ^(?:200|403)$ + + + +=== TEST 15: test register and unregister nodes with acl +--- yaml_config eval: $::yaml_config_with_acl +--- apisix_yaml +routes: + - + uri: /* + upstream: + service_name: service-a + discovery_type: consul + type: roundrobin +#END +--- config +location /v1/agent { + proxy_pass http://127.0.0.1:8502; + proxy_set_header X-Consul-Token "2b778dd9-f5f1-6f29-b4b4-9a5fa948757a"; +} +location /sleep { + content_by_lua_block { + local args = ngx.req.get_uri_args() + local sec = args.sec or "2" + ngx.sleep(tonumber(sec)) + ngx.say("ok") + } +} +--- timeout: 6 +--- pipelined_requests eval +[ + "PUT /v1/agent/service/register\n" . "{\"ID\":\"service-a1\",\"Name\":\"service-a\",\"Tags\":[\"primary\",\"v1\"],\"Address\":\"127.0.0.1\",\"Port\":30513,\"Meta\":{\"service_b_version\":\"4.1\"},\"EnableTagOverride\":false,\"Weights\":{\"Passing\":10,\"Warning\":1}}", + "PUT /v1/agent/service/register\n" . "{\"ID\":\"service-a2\",\"Name\":\"service-a\",\"Tags\":[\"primary\",\"v1\"],\"Address\":\"127.0.0.1\",\"Port\":30514,\"Meta\":{\"service_b_version\":\"4.1\"},\"EnableTagOverride\":false,\"Weights\":{\"Passing\":10,\"Warning\":1}}", + "GET /sleep", + + "GET /hello?random1", + "GET /hello?random2", + "GET /hello?random3", + "GET /hello?random4", + + "PUT /v1/agent/service/deregister/service-a1", + "PUT /v1/agent/service/deregister/service-a2", + "PUT /v1/agent/service/register\n" . "{\"ID\":\"service-a1\",\"Name\":\"service-a\",\"Tags\":[\"primary\",\"v1\"],\"Address\":\"127.0.0.1\",\"Port\":30511,\"Meta\":{\"service_b_version\":\"4.1\"},\"EnableTagOverride\":false,\"Weights\":{\"Passing\":10,\"Warning\":1}}", + "PUT /v1/agent/service/register\n" . "{\"ID\":\"service-a2\",\"Name\":\"service-a\",\"Tags\":[\"primary\",\"v1\"],\"Address\":\"127.0.0.1\",\"Port\":30512,\"Meta\":{\"service_b_version\":\"4.1\"},\"EnableTagOverride\":false,\"Weights\":{\"Passing\":10,\"Warning\":1}}", + "GET /sleep?sec=5", + + "GET /hello?random1", + "GET /hello?random2", + "GET /hello?random3", + "GET /hello?random4", + + "PUT /v1/agent/service/deregister/service-a1", + "PUT /v1/agent/service/deregister/service-a2", +] +--- response_body_like eval +[ + qr//, + qr//, + qr/ok\n/, + + qr/server [3-4]\n/, + qr/server [3-4]\n/, + qr/server [3-4]\n/, + qr/server [3-4]\n/, + + qr//, + qr//, + qr//, + qr//, + qr/ok\n/, + + qr/server [1-2]\n/, + qr/server [1-2]\n/, + qr/server [1-2]\n/, + qr/server [1-2]\n/, + + qr//, + qr// +] +--- ignore_error_log diff --git a/t/discovery/consul_kv.t b/t/discovery/consul_kv.t index 9363f768d209..0034997c5a39 100644 --- a/t/discovery/consul_kv.t +++ b/t/discovery/consul_kv.t @@ -109,6 +109,37 @@ discovery: max_fails: 1 _EOC_ +our $yaml_config_with_acl = <<_EOC_; +apisix: + node_listen: 1984 +deployment: + role: data_plane + role_data_plane: + config_provider: yaml +discovery: + consul_kv: + servers: + - "http://127.0.0.1:8502" + token: "2b778dd9-f5f1-6f29-b4b4-9a5fa948757a" + prefix: "upstreams" + skip_keys: + - "upstreams/unused_api/" + timeout: + connect: 1000 + read: 1000 + wait: 60 + weight: 1 + fetch_interval: 1 + keepalive: true + default_service: + host: "127.0.0.1" + port: 20999 + metadata: + fail_timeout: 1 + weight: 1 + max_fails: 1 +_EOC_ + run_tests(); @@ -450,8 +481,8 @@ upstreams: --- request GET /thc --- response_body -[{"ip":"127.0.0.1","port":30511,"status":"healthy"},{"ip":"127.0.0.2","port":1988,"status":"unhealthy"}] -[{"ip":"127.0.0.1","port":30511,"status":"healthy"},{"ip":"127.0.0.2","port":1988,"status":"unhealthy"}] +[{"hostname":"127.0.0.1","ip":"127.0.0.1","port":30511,"status":"healthy"},{"hostname":"127.0.0.2","ip":"127.0.0.2","port":1988,"status":"unhealthy"}] +[{"hostname":"127.0.0.1","ip":"127.0.0.1","port":30511,"status":"healthy"},{"hostname":"127.0.0.2","ip":"127.0.0.2","port":1988,"status":"unhealthy"}] --- ignore_error_log @@ -576,3 +607,92 @@ qr/retry connecting consul after \d seconds/ --- grep_error_log_out retry connecting consul after 1 seconds retry connecting consul after 4 seconds + + + +=== TEST 13: bootstrap acl +--- config +location /v1/acl { + proxy_pass http://127.0.0.1:8502; +} +--- request eval +"PUT /v1/acl/bootstrap\n" . "{\"BootstrapSecret\": \"2b778dd9-f5f1-6f29-b4b4-9a5fa948757a\"}" +--- error_code_like: ^(?:200|403)$ + + + +=== TEST 14: test register and unregister nodes +--- yaml_config eval: $::yaml_config_with_acl +--- apisix_yaml +routes: + - + uri: /* + upstream: + service_name: http://127.0.0.1:8502/v1/kv/upstreams/webpages/ + discovery_type: consul_kv + type: roundrobin +#END +--- config +location /v1/kv { + proxy_pass http://127.0.0.1:8502; + proxy_set_header X-Consul-Token "2b778dd9-f5f1-6f29-b4b4-9a5fa948757a"; +} +location /sleep { + content_by_lua_block { + local args = ngx.req.get_uri_args() + local sec = args.sec or "2" + ngx.sleep(tonumber(sec)) + ngx.say("ok") + } +} +--- timeout: 6 +--- request eval +[ + "DELETE /v1/kv/upstreams/webpages/127.0.0.1:30511", + "DELETE /v1/kv/upstreams/webpages/127.0.0.1:30512", + "PUT /v1/kv/upstreams/webpages/127.0.0.1:30513\n" . "{\"weight\": 1, \"max_fails\": 2, \"fail_timeout\": 1}", + "PUT /v1/kv/upstreams/webpages/127.0.0.1:30514\n" . "{\"weight\": 1, \"max_fails\": 2, \"fail_timeout\": 1}", + "GET /sleep", + + "GET /hello?random1", + "GET /hello?random2", + "GET /hello?random3", + "GET /hello?random4", + + "DELETE /v1/kv/upstreams/webpages/127.0.0.1:30513", + "DELETE /v1/kv/upstreams/webpages/127.0.0.1:30514", + "PUT /v1/kv/upstreams/webpages/127.0.0.1:30511\n" . "{\"weight\": 1, \"max_fails\": 2, \"fail_timeout\": 1}", + "PUT /v1/kv/upstreams/webpages/127.0.0.1:30512\n" . "{\"weight\": 1, \"max_fails\": 2, \"fail_timeout\": 1}", + "GET /sleep?sec=5", + + "GET /hello?random1", + "GET /hello?random2", + "GET /hello?random3", + "GET /hello?random4", + +] +--- response_body_like eval +[ + qr/true/, + qr/true/, + qr/true/, + qr/true/, + qr/ok\n/, + + qr/server [3-4]\n/, + qr/server [3-4]\n/, + qr/server [3-4]\n/, + qr/server [3-4]\n/, + + qr/true/, + qr/true/, + qr/true/, + qr/true/, + qr/ok\n/, + + qr/server [1-2]\n/, + qr/server [1-2]\n/, + qr/server [1-2]\n/, + qr/server [1-2]\n/ +] +--- ignore_error_log diff --git a/t/node/healthcheck-discovery.t b/t/node/healthcheck-discovery.t index c8cf99b6c1dc..e8e21b04240f 100644 --- a/t/node/healthcheck-discovery.t +++ b/t/node/healthcheck-discovery.t @@ -94,7 +94,7 @@ routes: local httpc = http.new() local res, err = httpc:request_uri(uri, {method = "GET", keepalive = false}) - ngx.sleep(0.5) + ngx.sleep(1.5) ngx.say(res.status) } diff --git a/t/node/healthcheck-leak-bugfix.t b/t/node/healthcheck-leak-bugfix.t index d3ada8c171f7..1caf5d348abf 100644 --- a/t/node/healthcheck-leak-bugfix.t +++ b/t/node/healthcheck-leak-bugfix.t @@ -31,8 +31,8 @@ __DATA__ local new = healthcheck.new healthcheck.new = function(...) local obj = new(...) - local clear = obj.clear - obj.clear = function(...) + local clear = obj.delayed_clear + obj.delayed_clear = function(...) ngx.log(ngx.WARN, "clear checker") return clear(...) end diff --git a/t/node/healthcheck.t b/t/node/healthcheck.t index 0ae4fbdb9d85..3053a0bb713f 100644 --- a/t/node/healthcheck.t +++ b/t/node/healthcheck.t @@ -486,6 +486,7 @@ qr{\[error\].*while connecting to upstream.*} qr{.*http://127.0.0.1:1960/server_port.* .*http://127.0.0.1:1961/server_port.* .*http://127.0.0.1:1961/server_port.* +.*http://127.0.0.1:1960/server_port.* .*http://127.0.0.1:1961/server_port.* .*http://127.0.0.1:1961/server_port.*} --- timeout: 10 diff --git a/t/node/priority-balancer/health-checker.t b/t/node/priority-balancer/health-checker.t index fb65e3fd2355..7ad685ac86bb 100644 --- a/t/node/priority-balancer/health-checker.t +++ b/t/node/priority-balancer/health-checker.t @@ -102,8 +102,8 @@ upstreams: GET /t --- error_log connect() failed -unhealthy TCP increment (2/2) for '(127.0.0.1:1979) -unhealthy TCP increment (2/2) for '(127.0.0.2:1979) +unhealthy TCP increment (2/2) for '127.0.0.1(127.0.0.1:1979) +unhealthy TCP increment (2/2) for '127.0.0.2(127.0.0.2:1979) --- grep_error_log eval qr/proxy request to \S+/ --- grep_error_log_out @@ -177,7 +177,7 @@ passed GET /t --- error_log connect() failed -unhealthy TCP increment (2/2) for '(127.0.0.1:1979) +unhealthy TCP increment (2/2) for '127.0.0.1(127.0.0.1:1979) --- grep_error_log eval qr/proxy request to \S+/ --- grep_error_log_out diff --git a/t/plugin/cors.t b/t/plugin/cors.t index f05385ddd05f..157832b8737e 100644 --- a/t/plugin/cors.t +++ b/t/plugin/cors.t @@ -723,7 +723,7 @@ qr/failed to check the configuration of plugin cors err: you can not/ "expose_headers": "ex-headr1,ex-headr2", "max_age": 50, "allow_credential": true, - "allow_origins_by_regex":[".*\\.test.com"] + "allow_origins_by_regex":[".*\\.test.com$"] } }, "upstream": { @@ -802,7 +802,7 @@ Access-Control-Allow-Credentials: "expose_headers": "ex-headr1,ex-headr2", "max_age": 50, "allow_credential": true, - "allow_origins_by_regex":[".*\\.test.com",".*\\.example.org"] + "allow_origins_by_regex":[".*\\.test.com$",".*\\.example.org$"] } }, "upstream": { diff --git a/t/plugin/cors2.t b/t/plugin/cors2.t index 1bc223bd3e8b..a2b06305bd97 100644 --- a/t/plugin/cors2.t +++ b/t/plugin/cors2.t @@ -107,7 +107,7 @@ done "allow_headers": "request-h", "expose_headers": "expose-h", "max_age": 10, - "allow_origins_by_regex":[".*\\.domain.com"] + "allow_origins_by_regex":[".*\\.domain.com$"] } }, "upstream": { diff --git a/t/stream-node/sanity-with-service.t b/t/stream-node/sanity-with-service.t new file mode 100644 index 000000000000..799a96aa8434 --- /dev/null +++ b/t/stream-node/sanity-with-service.t @@ -0,0 +1,294 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +use t::APISIX 'no_plan'; + +log_level('info'); +no_root_location(); + +run_tests(); + +__DATA__ + +=== TEST 1: set stream route(id: 1) -> service(id: 1) +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/services/1', + ngx.HTTP_PUT, + [[{ + "upstream": { + "nodes": { + "127.0.0.1:1995": 1 + }, + "type": "roundrobin" + } + }]] + ) + + if code >= 300 then + ngx.status = code + end + + code, body = t('/apisix/admin/stream_routes/1', + ngx.HTTP_PUT, + [[{ + "remote_addr": "127.0.0.1", + "service_id": 1 + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed + + + +=== TEST 2: hit route +--- stream_request eval +mmm +--- stream_response +hello world + + + +=== TEST 3: set stream route(id: 1) +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/stream_routes/1', + ngx.HTTP_PUT, + [[{ + "remote_addr": "127.0.0.2", + "service_id": 1 + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed + + + +=== TEST 4: not hit route +--- stream_enable +--- stream_response + + + +=== TEST 5: delete route(id: 1) +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/stream_routes/1', + ngx.HTTP_DELETE + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed + + + +=== TEST 6: set service upstream (id: 1) +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/upstreams/1', + ngx.HTTP_PUT, + [[{ + "nodes": { + "127.0.0.1:1995": 1 + }, + "type": "roundrobin" + }]] + ) + if code >= 300 then + ngx.status = code + end + + code, body = t('/apisix/admin/services/1', + ngx.HTTP_PUT, + [[{ + "upstream_id": 1 + }]] + ) + + if code >= 300 then + ngx.status = code + end + + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed + + + +=== TEST 7: set stream route (id: 1) with service (id: 1) which uses upstream_id +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/stream_routes/1', + ngx.HTTP_PUT, + [[{ + "remote_addr": "127.0.0.1", + "service_id": 1 + }]] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed + + + +=== TEST 8: hit route +--- stream_request eval +mmm +--- stream_response +hello world + + + +=== TEST 9: set stream route (id: 1) which uses upstream_id and remote address with IP CIDR +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/stream_routes/1', + ngx.HTTP_PUT, + [[{ + "remote_addr": "127.0.0.1/26", + "service_id": "1" + }]] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed + + + +=== TEST 10: hit route +--- stream_request eval +mmm +--- stream_response +hello world + + + +=== TEST 11: reject bad CIDR +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/stream_routes/1', + ngx.HTTP_PUT, + [[{ + "remote_addr": ":/8", + "service_id": "1" + }]] + ) + if code >= 300 then + ngx.status = code + end + ngx.print(body) + } + } +--- request +GET /t +--- error_code: 400 +--- response_body +{"error_msg":"invalid remote_addr: :/8"} + + + +=== TEST 12: skip upstream http host check in stream subsystem +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/upstreams/1', + ngx.HTTP_PUT, + [[{ + "nodes": { + "127.0.0.1:1995": 1, + "127.0.0.2:1995": 1 + }, + "pass_host": "node", + "type": "roundrobin" + }]] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed + + + +=== TEST 13: hit route +--- stream_request eval +mmm +--- stream_response +hello world