This is an automated email from the ASF dual-hosted git repository.

leslie 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 2494b1829 feat: proxy-mirror add grpc and grpcs support (#9388)
2494b1829 is described below

commit 2494b1829e5957840c14e02fb4960191f2163dd3
Author: Sn0rt <[email protected]>
AuthorDate: Thu May 11 13:53:23 2023 +0800

    feat: proxy-mirror add grpc and grpcs support (#9388)
---
 apisix/cli/ngx_tpl.lua                 | 31 ++++++++++++++
 apisix/core/ctx.lua                    |  1 +
 apisix/init.lua                        |  5 +++
 apisix/plugins/proxy-mirror.lua        | 14 ++++---
 docs/en/latest/plugins/proxy-mirror.md |  6 +--
 docs/zh/latest/plugins/proxy-mirror.md |  6 +--
 t/APISIX.pm                            | 18 ++++++++
 t/plugin/proxy-mirror.t                | 72 ++++++++++++++++++++++++++++++++
 t/plugin/proxy-mirror3.t               | 76 ++++++++++++++++++++++++++++++++++
 9 files changed, 218 insertions(+), 11 deletions(-)

diff --git a/apisix/cli/ngx_tpl.lua b/apisix/cli/ngx_tpl.lua
index b1580e1e8..74b14302b 100644
--- a/apisix/cli/ngx_tpl.lua
+++ b/apisix/cli/ngx_tpl.lua
@@ -664,6 +664,7 @@ http {
         {% end %}
 
         location / {
+            set $upstream_mirror_host        '';
             set $upstream_mirror_uri         '';
             set $upstream_upgrade            '';
             set $upstream_connection         '';
@@ -770,6 +771,10 @@ http {
             grpc_socket_keepalive on;
             grpc_pass         $upstream_scheme://apisix_backend;
 
+            {% if enabled_plugins["proxy-mirror"] then %}
+            mirror           /proxy_mirror_grpc;
+            {% end %}
+
             header_filter_by_lua_block {
                 apisix.http_header_filter_phase()
             }
@@ -834,6 +839,32 @@ http {
             proxy_pass $upstream_mirror_uri;
         }
         {% end %}
+
+        {% if enabled_plugins["proxy-mirror"] then %}
+        location = /proxy_mirror_grpc {
+            internal;
+
+            {% if not use_apisix_base then %}
+            if ($upstream_mirror_uri = "") {
+                return 200;
+            }
+            {% end %}
+
+
+            {% if proxy_mirror_timeouts then %}
+                {% if proxy_mirror_timeouts.connect then %}
+            grpc_connect_timeout {* proxy_mirror_timeouts.connect *};
+                {% end %}
+                {% if proxy_mirror_timeouts.read then %}
+            grpc_read_timeout {* proxy_mirror_timeouts.read *};
+                {% end %}
+                {% if proxy_mirror_timeouts.send then %}
+            grpc_send_timeout {* proxy_mirror_timeouts.send *};
+                {% end %}
+            {% end %}
+            grpc_pass $upstream_mirror_host;
+        }
+        {% end %}
     }
     {% end %}
 
diff --git a/apisix/core/ctx.lua b/apisix/core/ctx.lua
index d723f17cb..5128061d5 100644
--- a/apisix/core/ctx.lua
+++ b/apisix/core/ctx.lua
@@ -190,6 +190,7 @@ do
         upstream_connection        = true,
         upstream_uri               = true,
 
+        upstream_mirror_host       = true,
         upstream_mirror_uri        = true,
 
         upstream_cache_zone        = true,
diff --git a/apisix/init.lua b/apisix/init.lua
index bf5644521..bff68dd5b 100644
--- a/apisix/init.lua
+++ b/apisix/init.lua
@@ -77,6 +77,7 @@ local load_balancer
 local local_conf
 local ver_header = "APISIX/" .. core.version.VERSION
 
+local has_mod, apisix_ngx_client = pcall(require, "resty.apisix.client")
 
 local _M = {version = 0.4}
 
@@ -716,6 +717,10 @@ function _M.grpc_access_phase()
         core.log.error("failed to set grpcs upstream param: ", err)
         core.response.exit(code)
     end
+
+    if api_ctx.enable_mirror == true and has_mod then
+        apisix_ngx_client.enable_mirror()
+    end
 end
 
 
diff --git a/apisix/plugins/proxy-mirror.lua b/apisix/plugins/proxy-mirror.lua
index 312d3ec37..d6cede6e9 100644
--- a/apisix/plugins/proxy-mirror.lua
+++ b/apisix/plugins/proxy-mirror.lua
@@ -27,7 +27,7 @@ local schema = {
     properties = {
         host = {
             type = "string",
-            pattern = 
[=[^http(s)?:\/\/([\da-zA-Z.-]+|\[[\da-fA-F:]+\])(:\d+)?$]=],
+            pattern = 
[=[^(http(s)?|grpc(s)?):\/\/([\da-zA-Z.-]+|\[[\da-fA-F:]+\])(:\d+)?$]=],
         },
         path = {
             type = "string",
@@ -76,15 +76,15 @@ local function resolver_host(prop_host)
         if not ip then
             core.log.error("dns resolver resolves domain: ", decoded_host," 
error: ", err,
                             " will continue to use the host: ", decoded_host)
-            return prop_host
+            return url_decoded.scheme, prop_host
         end
 
         local host = url_decoded.scheme .. '://' .. ip ..
             (url_decoded.port and ':' .. url_decoded.port or '')
         core.log.info(prop_host, " is resolved to: ", host)
-        return host
+        return url_decoded.scheme, host
     end
-    return prop_host
+    return url_decoded.scheme, prop_host
 end
 
 
@@ -101,7 +101,9 @@ local function enable_mirror(ctx, conf)
         end
     end
 
-    ctx.var.upstream_mirror_uri = resolver_host(conf.host) .. uri
+    local _, mirror_host = resolver_host(conf.host)
+    ctx.var.upstream_mirror_host = mirror_host
+    ctx.var.upstream_mirror_uri = mirror_host .. uri
 
     if has_mod then
         apisix_ngx_client.enable_mirror()
@@ -114,12 +116,14 @@ function _M.rewrite(conf, ctx)
 
     if conf.sample_ratio == 1 then
         enable_mirror(ctx, conf)
+        ctx.enable_mirror = true
     else
         local val = math_random()
         core.log.info("mirror request sample_ratio conf: ", conf.sample_ratio,
                                 ", random value: ", val)
         if val < conf.sample_ratio then
             enable_mirror(ctx, conf)
+            ctx.enable_mirror = true
         end
     end
 
diff --git a/docs/en/latest/plugins/proxy-mirror.md 
b/docs/en/latest/plugins/proxy-mirror.md
index 1b97daa5b..50b11e710 100644
--- a/docs/en/latest/plugins/proxy-mirror.md
+++ b/docs/en/latest/plugins/proxy-mirror.md
@@ -39,9 +39,9 @@ The response returned by the mirror request is ignored.
 
 | Name         | Type   | Required | Default | Valid values | Description      
                                                                                
                         |
 
|--------------|--------|----------|---------|--------------|---------------------------------------------------------------------------------------------------------------------------|
-| host         | string | True     |         |              | Address of the 
mirror service. It needs to contain the scheme but without the path. For 
example, `http://127.0.0.1:9797`. |
-| path         | string | False    |         |              | Path of the 
mirror request. If unspecified, current path will be used.                      
                              |
-| path_concat_mode | string | False   |   replace     | ["replace", "prefix"]  
     | If the path of a mirror request is specified, set the concatenation mode 
of request paths. The `replace` mode will directly use `path` as the path of 
the mirror request. The `prefix` mode will use the `path` + `source request 
URI` as the path to the mirror request.  |
+| host         | string | True     |         |              | Address of the 
mirror service. It needs to contain the scheme (`http(s)` or `grpc(s)`) but 
without the path. For example, `http://127.0.0.1:9797`. |
+| path         | string | False    |         |              | Path of the 
mirror request. If unspecified, current path will be used. If it is for 
mirroring grpc traffic, this option is no longer applicable.                    
                               |
+| path_concat_mode | string | False   |   replace     | ["replace", "prefix"]  
     | If the path of a mirror request is specified, set the concatenation mode 
of request paths. The `replace` mode will directly use `path` as the path of 
the mirror request. The `prefix` mode will use the `path` + `source request 
URI` as the path to the mirror request. If it is for mirroring grpc traffic, 
this option is no longer applicable too. |
 | sample_ratio | number | False    | 1       | [0.00001, 1] | Ratio of the 
requests that will be mirrored.                                                 
                             |
 
 You can customize the proxy timeouts for the mirrored sub-requests by 
configuring the `plugin_attr` key in your configuration file 
(`conf/config.yaml`). This can be used for mirroring traffic to a slow backend.
diff --git a/docs/zh/latest/plugins/proxy-mirror.md 
b/docs/zh/latest/plugins/proxy-mirror.md
index 365368d8b..e1c7af5e5 100644
--- a/docs/zh/latest/plugins/proxy-mirror.md
+++ b/docs/zh/latest/plugins/proxy-mirror.md
@@ -40,9 +40,9 @@ description: 本文介绍了 Apache APISIX proxy-mirror 插件的相关操作,
 
 | 名称 | 类型   | 必选项 | 默认值 | 有效值 | 描述                                             
                                                       |
 | ---- | ------ | ------ | ------ | ------ | 
-------------------------------------------------------------------------------------------------------
 |
-| host | string | 是   |        |        | 指定镜像服务的地址,地址中需要包含 `schema`(`http` 或 
`https`),但不能包含 `path` 部分。例如 `http://127.0.0.1:9797`。 |
-| path | string | 否   |        |        | 指定镜像请求的路径。如果不指定,则默认会使用当前路径。 |
-| path_concat_mode | string | 否   |   replace     | ["replace", "prefix"]      
 | 当指定镜像请求的路径时,设置请求路径的拼接模式。`replace` 模式将会直接使用 `path` 作为镜像请求的路径。`prefix` 模式将会使用 
`path` + `来源请求 URI` 作为镜像请求的路径。  |
+| host | string | 是   |        |        | 指定镜像服务的地址,地址中需要包含 `schema`(`http(s)` 
或 `grpc(s)`),但不能包含 `path` 部分。例如 `http://127.0.0.1:9797`。 |
+| path | string | 否   |        |        | 指定镜像请求的路径。如果不指定,则默认会使用当前路径。如果是为了镜像 
grpc 流量,这个选项不再适用。|
+| path_concat_mode | string | 否   |   replace     | ["replace", "prefix"]      
 | 当指定镜像请求的路径时,设置请求路径的拼接模式。`replace` 模式将会直接使用 `path` 作为镜像请求的路径。`prefix` 模式将会使用 
`path` + `来源请求 URI` 作为镜像请求的路径。当然如果是为了镜像 grpc 流量,这个选项也不再适用。|
 | sample_ratio | number | 否    | 1       |  [0.00001, 1]     | 镜像请求的采样率。当设置为 
`1` 时为全采样。 |
 
 ## 启用插件
diff --git a/t/APISIX.pm b/t/APISIX.pm
index 5ab486dc4..0c057b538 100644
--- a/t/APISIX.pm
+++ b/t/APISIX.pm
@@ -206,6 +206,7 @@ $grpc_location .= <<_EOC_;
             grpc_set_header   TE trailers;
             grpc_socket_keepalive on;
             grpc_pass         \$upstream_scheme://apisix_backend;
+            mirror              /proxy_mirror_grpc;
 
             header_filter_by_lua_block {
                 apisix.http_header_filter_phase()
@@ -743,6 +744,7 @@ _EOC_
         }
 
         location / {
+            set \$upstream_mirror_host        '';
             set \$upstream_mirror_uri         '';
             set \$upstream_upgrade            '';
             set \$upstream_connection         '';
@@ -828,6 +830,22 @@ _EOC_
             proxy_set_header Host \$upstream_host;
             proxy_pass \$upstream_mirror_uri;
         }
+
+        location = /proxy_mirror_grpc {
+            internal;
+_EOC_
+
+    if ($version !~ m/\/apisix-nginx-module/) {
+        $config .= <<_EOC_;
+            if (\$upstream_mirror_uri = "") {
+                return 200;
+            }
+_EOC_
+    }
+
+    $config .= <<_EOC_;
+            grpc_pass \$upstream_mirror_host;
+        }
 _EOC_
 
     $block->set_value("config", $config);
diff --git a/t/plugin/proxy-mirror.t b/t/plugin/proxy-mirror.t
index 031737d50..458bcf342 100644
--- a/t/plugin/proxy-mirror.t
+++ b/t/plugin/proxy-mirror.t
@@ -838,3 +838,75 @@ GET /hello?a=1
 hello world
 --- error_log
 uri: /a/hello?a=1
+
+
+
+=== TEST 30: (grpc) sanity check (normal case grpc)
+--- config
+       location /t {
+           content_by_lua_block {
+               local t = require("lib.test_admin").test
+               local code, body = t('/apisix/admin/routes/1',
+                    ngx.HTTP_PUT,
+                    [[{
+                        "plugins": {
+                            "proxy-mirror": {
+                               "host": "grpc://127.0.0.1:1986"
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "scheme": "grpc",
+                            "type": "roundrobin"
+                        },
+                        "uri": "/hello"
+                   }]]
+                   )
+
+               if code >= 300 then
+                   ngx.status = code
+               end
+               ngx.say(body)
+           }
+       }
+--- error_code: 200
+--- response_body
+passed
+
+
+
+=== TEST 31: (grpcs) sanity check (normal case for grpcs)
+--- config
+       location /t {
+           content_by_lua_block {
+               local t = require("lib.test_admin").test
+               local code, body = t('/apisix/admin/routes/1',
+                    ngx.HTTP_PUT,
+                    [[{
+                        "plugins": {
+                            "proxy-mirror": {
+                               "host": "grpcs://127.0.0.1:1986"
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "scheme": "grpc",
+                            "type": "roundrobin"
+                        },
+                        "uri": "/hello"
+                   }]]
+                   )
+
+               if code >= 300 then
+                   ngx.status = code
+               end
+               ngx.say(body)
+           }
+       }
+--- error_code: 200
+--- response_body
+passed
diff --git a/t/plugin/proxy-mirror3.t b/t/plugin/proxy-mirror3.t
new file mode 100644
index 000000000..65a23dc82
--- /dev/null
+++ b/t/plugin/proxy-mirror3.t
@@ -0,0 +1,76 @@
+#
+# 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) = @_;
+
+    my $yaml_config = $block->yaml_config // <<_EOC_;
+apisix:
+    node_listen: 1984
+deployment:
+    role: data_plane
+    role_data_plane:
+        config_provider: yaml
+_EOC_
+
+    $block->set_value("yaml_config", $yaml_config);
+
+    if (!$block->request) {
+        $block->set_value("request", "POST /hello");
+    }
+});
+
+run_tests;
+
+__DATA__
+
+=== TEST 1: grpc mirror
+--- log_level: debug
+--- http2
+--- apisix_yaml
+routes:
+  -
+    id: 1
+    uris:
+        - /helloworld.Greeter/SayHello
+    methods: [
+        POST
+    ]
+    plugins:
+        proxy-mirror:
+            host: grpc://127.0.0.1:19797
+            sample_ratio: 1
+    upstream:
+      scheme: grpc
+      nodes:
+        "127.0.0.1:50051": 1
+      type: roundrobin
+#END
+--- exec
+grpcurl -import-path ./t/grpc_server_example/proto -proto helloworld.proto 
-plaintext -d '{"name":"apisix"}' 127.0.0.1:1984 helloworld.Greeter.SayHello
+--- response_body
+{
+  "message": "Hello apisix"
+}
+--- error_log eval
+qr/Connection refused\) while connecting to upstream.*proxy_mirror_grpc/

Reply via email to