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