diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e4bcb4323a01..5d34c0330a5c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -106,6 +106,7 @@ jobs: - name: Start CI env (PLUGIN_TEST) if: steps.test_env.outputs.type == 'plugin' run: | + sh ci/pod/openfunction/build-function-image.sh make ci-env-up project_compose_ci=ci/pod/docker-compose.${{ steps.test_env.outputs.type }}.yml sudo ./ci/init-${{ steps.test_env.outputs.type }}-test-service.sh diff --git a/.github/workflows/centos7-ci.yml b/.github/workflows/centos7-ci.yml index b308c79fb95b..584ffac3bf20 100644 --- a/.github/workflows/centos7-ci.yml +++ b/.github/workflows/centos7-ci.yml @@ -101,6 +101,7 @@ jobs: - name: Start CI env (PLUGIN_TEST) if: steps.test_env.outputs.type == 'plugin' run: | + sh ci/pod/openfunction/build-function-image.sh make ci-env-up project_compose_ci=ci/pod/docker-compose.${{ steps.test_env.outputs.type }}.yml ./ci/init-${{ steps.test_env.outputs.type }}-test-service.sh diff --git a/LICENSE b/LICENSE index 0474b542fb65..5cadce448d62 100644 --- a/LICENSE +++ b/LICENSE @@ -216,3 +216,4 @@ The following components are provided under the Apache License. See project link The text of each license is the standard Apache 2.0 license. ewma.lua file from kubernetes/ingress-nginx: https://github.com/kubernetes/ingress-nginx Apache 2.0 + hello.go file from OpenFunction/samples: https://github.com/OpenFunction/samples Apache 2.0 diff --git a/apisix/plugins/openfunction.lua b/apisix/plugins/openfunction.lua new file mode 100644 index 000000000000..935d6ebbc540 --- /dev/null +++ b/apisix/plugins/openfunction.lua @@ -0,0 +1,35 @@ +-- +-- 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. +-- +local ngx_encode_base64 = ngx.encode_base64 +local plugin_name, plugin_version, priority = "openfunction", 0.1, -1902 + +local openfunction_authz_schema = { + service_token = {type = "string"} +} + +local function request_processor(conf, ctx, params) + local headers = params.headers or {} + -- setting authorization headers if authorization.service_token exists + if conf.authorization and conf.authorization.service_token then + headers["authorization"] = "Basic " .. ngx_encode_base64(conf.authorization.service_token) + end + + params.headers = headers +end + +return require("apisix.plugins.serverless.generic-upstream")(plugin_name, + plugin_version, priority, request_processor, openfunction_authz_schema) diff --git a/ci/pod/docker-compose.plugin.yml b/ci/pod/docker-compose.plugin.yml index 2367ccedb035..18d59a042433 100644 --- a/ci/pod/docker-compose.plugin.yml +++ b/ci/pod/docker-compose.plugin.yml @@ -225,6 +225,35 @@ services: xpack.security.enabled: 'true' + # The function services of OpenFunction + test-header: + image: test-header-image:latest + restart: unless-stopped + ports: + - "30583:8080" + environment: + CONTEXT_MODE: "self-host" + FUNC_CONTEXT: "{\"name\":\"HelloWorld\",\"version\":\"v1.0.0\",\"port\":\"8080\",\"runtime\":\"Knative\"}" + + test-uri: + image: test-uri-image:latest + restart: unless-stopped + ports: + - "30584:8080" + environment: + CONTEXT_MODE: "self-host" + FUNC_CONTEXT: "{\"name\":\"HelloWorld\",\"version\":\"v1.0.0\",\"port\":\"8080\",\"runtime\":\"Knative\"}" + + test-body: + image: test-body-image:latest + restart: unless-stopped + ports: + - "30585:8080" + environment: + CONTEXT_MODE: "self-host" + FUNC_CONTEXT: "{\"name\":\"HelloWorld\",\"version\":\"v1.0.0\",\"port\":\"8080\",\"runtime\":\"Knative\"}" + + networks: apisix_net: kafka_net: diff --git a/ci/pod/openfunction/build-function-image.sh b/ci/pod/openfunction/build-function-image.sh new file mode 100644 index 000000000000..3ad08447d090 --- /dev/null +++ b/ci/pod/openfunction/build-function-image.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# +# 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. +# + +wget https://github.com/buildpacks/pack/releases/download/v0.27.0/pack-v0.27.0-linux.tgz +tar -zxvf pack-v0.27.0-linux.tgz + +# please update function-example/*/hello.go if you want to update function +./pack build test-uri-image --path ./ci/pod/openfunction/function-example/test-uri --builder openfunction/builder-go:v2.4.0-1.17 --env FUNC_NAME="HelloWorld" --env FUNC_CLEAR_SOURCE=true --env FUNC_GOPROXY="https://goproxy.cn" +./pack build test-body-image --path ./ci/pod/openfunction/function-example/test-body --builder openfunction/builder-go:v2.4.0-1.17 --env FUNC_NAME="HelloWorld" --env FUNC_CLEAR_SOURCE=true --env FUNC_GOPROXY="https://goproxy.cn" +./pack build test-header-image --path ./ci/pod/openfunction/function-example/test-header --builder openfunction/builder-go:v2.4.0-1.17 --env FUNC_NAME="HelloWorld" --env FUNC_CLEAR_SOURCE=true --env FUNC_GOPROXY="https://goproxy.cn" diff --git a/ci/pod/openfunction/function-example/test-body/go.mod b/ci/pod/openfunction/function-example/test-body/go.mod new file mode 100644 index 000000000000..6242ced93022 --- /dev/null +++ b/ci/pod/openfunction/function-example/test-body/go.mod @@ -0,0 +1,5 @@ +module example.com/hello + +go 1.16 + +require github.com/OpenFunction/functions-framework-go v0.3.0 diff --git a/ci/pod/openfunction/function-example/test-body/hello.go b/ci/pod/openfunction/function-example/test-body/hello.go new file mode 100644 index 000000000000..ffa7fad6bd7e --- /dev/null +++ b/ci/pod/openfunction/function-example/test-body/hello.go @@ -0,0 +1,36 @@ +/* + * Copyright 2022 The OpenFunction Authors. + * + * 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. + */ + +package hello + +import ( + "fmt" + "net/http" + "io/ioutil" + "github.com/OpenFunction/functions-framework-go/functions" +) + +func init() { + functions.HTTP("HelloWorld", HelloWorld) +} + +func HelloWorld(w http.ResponseWriter, r *http.Request) { + body,_ := ioutil.ReadAll(r.Body) + fmt.Fprintf(w, "Hello, %s!\n", string(body)) +} diff --git a/ci/pod/openfunction/function-example/test-header/go.mod b/ci/pod/openfunction/function-example/test-header/go.mod new file mode 100644 index 000000000000..32c2cadc95a8 --- /dev/null +++ b/ci/pod/openfunction/function-example/test-header/go.mod @@ -0,0 +1,3 @@ +module example.com/hello + +go 1.16 diff --git a/ci/pod/openfunction/function-example/test-header/hello.go b/ci/pod/openfunction/function-example/test-header/hello.go new file mode 100644 index 000000000000..418f9fb80943 --- /dev/null +++ b/ci/pod/openfunction/function-example/test-header/hello.go @@ -0,0 +1,30 @@ +/* + * Copyright 2022 The OpenFunction Authors. + * + * 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. + */ + +package hello + +import ( + "fmt" + "net/http" +) + +func HelloWorld(w http.ResponseWriter, r *http.Request) { + header := r.Header + fmt.Fprintf(w, "%s", header["Authorization"]) +} diff --git a/ci/pod/openfunction/function-example/test-uri/go.mod b/ci/pod/openfunction/function-example/test-uri/go.mod new file mode 100644 index 000000000000..c259999831bd --- /dev/null +++ b/ci/pod/openfunction/function-example/test-uri/go.mod @@ -0,0 +1,5 @@ +module example.com/hello + +go 1.17 + +require github.com/OpenFunction/functions-framework-go v0.4.0 diff --git a/ci/pod/openfunction/function-example/test-uri/hello.go b/ci/pod/openfunction/function-example/test-uri/hello.go new file mode 100644 index 000000000000..d726b8e59457 --- /dev/null +++ b/ci/pod/openfunction/function-example/test-uri/hello.go @@ -0,0 +1,38 @@ +/* + * Copyright 2022 The OpenFunction Authors. + * + * 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. + */ + +package hello + +import ( + "fmt" + ofctx "github.com/OpenFunction/functions-framework-go/context" + "net/http" + + "github.com/OpenFunction/functions-framework-go/functions" +) + +func init() { + functions.HTTP("HelloWorld", HelloWorld, + functions.WithFunctionPath("/{greeting}")) +} + +func HelloWorld(w http.ResponseWriter, r *http.Request) { + vars := ofctx.VarsFromCtx(r.Context()) + fmt.Fprintf(w, "Hello, %s!\n", vars["greeting"]) +} diff --git a/conf/config-default.yaml b/conf/config-default.yaml index 7795d8172cc1..ef99fcfcd114 100755 --- a/conf/config-default.yaml +++ b/conf/config-default.yaml @@ -471,6 +471,7 @@ plugins: # plugin list (sorted by priority) - aws-lambda # priority: -1899 - azure-functions # priority: -1900 - openwhisk # priority: -1901 + - openfunction # priority: -1902 - serverless-post-function # priority: -2000 - ext-plugin-post-req # priority: -3000 - ext-plugin-post-resp # priority: -4000 diff --git a/docs/en/latest/config.json b/docs/en/latest/config.json index 05ee49c9e930..fcb04951f9f4 100644 --- a/docs/en/latest/config.json +++ b/docs/en/latest/config.json @@ -163,7 +163,8 @@ "plugins/azure-functions", "plugins/openwhisk", "plugins/aws-lambda", - "plugins/workflow" + "plugins/workflow", + "plugins/openfunction" ] }, { diff --git a/docs/en/latest/plugins/openfunction.md b/docs/en/latest/plugins/openfunction.md new file mode 100644 index 000000000000..7eee546bbf94 --- /dev/null +++ b/docs/en/latest/plugins/openfunction.md @@ -0,0 +1,161 @@ +--- +title: openfunction +keywords: + - APISIX + - Plugin + - OpenFunction + - openfunction +description: This document contains information about the Apache APISIX openfunction Plugin. +--- + + + +## Description + +The `openfunction` Plugin is used to integrate APISIX with [CNCF OpenFunction](https://openfunction.dev/) serverless platform. + +This Plugin can be configured on a Route and requests will be sent to the configured OpenFunction API endpoint as the upstream. + +## Attributes + +| Name | Type | Required | Default | Valid values | Description | +| --------------------------- | ------- | -------- | ------- | ------------ | ---------------------------------------------------------------------------------------------------------- | +| function_uri | string | True | | | function uri. For example, `https://localhost:30858/default/function-sample`. | +| ssl_verify | boolean | False | true | | When set to `true` verifies the SSL certificate. | +| authorization | object | False | | | Authorization credentials to access functions of OpenFunction. | +| authorization.service_token | string | False | | | The token format is 'xx:xx' which supports basic auth for function entry points. | +| timeout | integer | False | 3000 ms | [100, ...] ms| OpenFunction action and HTTP call timeout in ms. | +| keepalive | boolean | False | true | | When set to `true` keeps the connection alive for reuse. | +| keepalive_timeout | integer | False | 60000 ms| [1000,...] ms| Time is ms for connection to remain idle without closing. | +| keepalive_pool | integer | False | 5 | [1,...] | Maximum number of requests that can be sent on this connection before closing it. | + +:::note + +The `timeout` attribute sets the time taken by the OpenFunction to execute, and the timeout for the HTTP client in APISIX. OpenFunction calls may take time to pull the runtime image and start the container. So, if the value is set too small, it may cause a large number of requests to fail. + +::: + +## Prerequisites + +Before configuring the plugin, you need to have OpenFunction running. +Installation of OpenFunction requires a certain version Kubernetes cluster. +For details, please refer to [Installation](https://openfunction.dev/docs/getting-started/installation/). + +### Create and Push a Function + +You can then create a function following the [sample](https://github.com/OpenFunction/samples) + +You'll need to push your function container image to a container registry like Docker Hub or Quay.io when building a function. To do that, you'll need to generate a secret for your container registry first. + +```shell +REGISTRY_SERVER=https://index.docker.io/v1/ REGISTRY_USER= ${your_registry_user} REGISTRY_PASSWORD= ${your_registry_password} +kubectl create secret docker-registry push-secret \ + --docker-server=$REGISTRY_SERVER \ + --docker-username=$REGISTRY_USER \ + --docker-password=$REGISTRY_PASSWORD +``` + +## Enable the Plugin + +You can now configure the Plugin on a specific Route and point to this running OpenFunction service: + +```shell +curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "uri": "/hello", + "plugins": { + "openfunction": { + "function_uri": "http://localhost:3233/default/function-sample/test", + "authorization": { + "service_token": "test:test" + } + } + } +}' +``` + +## Example usage + +Once you have configured the plugin, you can send a request to the Route and it will invoke the configured function: + +```shell +curl -i http://127.0.0.1:9080/hello +``` + +This will give back the response from the function: + +``` +hello, test! +``` + +### Configure Path Transforming + +The `OpenFunction` Plugin also supports transforming the URL path while proxying requests to the OpenFunction API endpoints. Extensions to the base request path get appended to the `function_uri` specified in the Plugin configuration. + +:::info IMPORTANT + +The `uri` configured on a Route must end with `*` for this feature to work properly. APISIX Routes are matched strictly and the `*` implies that any subpath to this URI would be matched to the same Route. + +::: + +The example below configures this feature: + +```shell +curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "uri": "/hello/*", + "plugins": { + "openfunction": { + "function_uri": "http://localhost:3233/default/function-sample", + "authorization": { + "service_token": "test:test" + } + } + } +}' +``` + +Now, any requests to the path `hello/123` will invoke the OpenFunction, and the added path is forwarded: + +```shell +curl http://127.0.0.1:9080/hello/123 +``` + +```shell +Hello, 123! +``` + +## Disable Plugin + +To disable the `openfunction` Plugin, you can delete the corresponding JSON configuration from the Plugin configuration. APISIX will automatically reload and you do not have to restart for this to take effect. + +```shell +curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "methods": ["GET"], + "uri": "/index.html", + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } +}' +``` diff --git a/docs/zh/latest/config.json b/docs/zh/latest/config.json index 252788b40a51..5d68a50eed11 100644 --- a/docs/zh/latest/config.json +++ b/docs/zh/latest/config.json @@ -167,7 +167,8 @@ "plugins/azure-functions", "plugins/openwhisk", "plugins/aws-lambda", - "plugins/workflow" + "plugins/workflow", + "plugins/openfunction" ] }, { diff --git a/docs/zh/latest/plugins/openfunction.md b/docs/zh/latest/plugins/openfunction.md new file mode 100644 index 000000000000..7923db291c3f --- /dev/null +++ b/docs/zh/latest/plugins/openfunction.md @@ -0,0 +1,161 @@ +--- +title: openfunction +keywords: + - APISIX + - Plugin + - OpenFunction + - openfunction +description: 本文介绍了 API 网关 Apache APISIX 的 openfunction 插件的基本信息及使用方法。 +--- + + + +## 描述 + +`openfunction` 插件用于将开源的分布式无服务器平台 [CNCF OpenFunction](https://openfunction.dev/) 作为动态上游集成至 APISIX。 + +启用 `openfunction` 插件后,该插件会终止对已配置 URI 的请求,并代表客户端向 OpenFunction 的 function 发起一个新的请求,然后 `openfunction` 插件会将响应信息返回至客户端。 + +## 属性 + +| 名称 | 类型 | 必选项 | 默认值 | 有效值 | 描述 | +| --------------------------- | ------- | ------ | ------- | ------------ | ------------------------------------------------------------ | +| function_uri | string | 是 | | | OpenFunction function uri,例如 `https://localhost:30858/default/function-sample`。 | +| ssl_verify | boolean | 否 | true | | 当设置为 `true` 时执行 SSL 验证。 | +| authorization | object | 否 | | | 访问 OpenFunction 的函数的授权凭证。| +| authorization.service_token | string | 否 | | | OpenFunction service token,其格式为 `xxx:xxx`,支持函数入口的 basic auth 认证方式。 | +| timeout | integer | 否 | 3000 ms | [100,...] ms | OpenFunction action 和 HTTP 调用超时时间,以毫秒为单位。 | +| keepalive | boolean | 否 | true | | 当设置为 `true` 时,保持连接的活动状态以便重复使用。 | +| keepalive_timeout | integer | 否 | 60000 ms| [1000,...] ms| 当连接空闲时,保持该连接处于活动状态的时间,以毫秒为单位。 | +| keepalive_pool | integer | 否 | 5 | [1,...] | 连接断开之前,可接收的最大请求数。 | + +:::note 注意 + +`timeout` 字段规定了 OpenFunction function 的最大执行时间,以及 APISIX 中 HTTP 客户端的请求超时时间。 + +因为 OpenFunction function 调用可能会耗费很长时间来拉取容器镜像和启动容器,如果 `timeout` 字段的值设置太小,可能会导致大量请求失败。 + +::: + +## 前提条件 + +在使用 `openfunction` 插件之前,你需要通过以下命令运行 OpenFunction。详情参考 [OpenFunction 安装指南](https://openfunction.dev/docs/getting-started/installation/) 。 + +请确保当前环境中已经安装对应版本的 Kubernetes 集群。 + +### 创建并推送函数 + +你可以参考 [OpenFunction 官方示例](https://github.com/OpenFunction/samples) 创建函数。构建函数时,你需要使用以下命令为容器仓库生成一个密钥,才可以将函数容器镜像推送到容器仓库 ( 例如 Docker Hub 或 Quay.io)。 + +```shell +REGISTRY_SERVER=https://index.docker.io/v1/ REGISTRY_USER= REGISTRY_PASSWORD= +kubectl create secret docker-registry push-secret \ + --docker-server=$REGISTRY_SERVER \ + --docker-username=$REGISTRY_USER \ + --docker-password=$REGISTRY_PASSWORD +``` + +## 启用插件 + +你可以通过以下命令在指定路由中启用该插件: + +```shell +curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "uri": "/hello", + "plugins": { + "openfunction": { + "function_uri": "http://localhost:3233/default/function-sample/test", + "authorization": { + "service_token": "test:test" + } + } + } +}' +``` + +## 测试插件 + +使用 `curl` 命令测试: + +```shell +curl -i http://127.0.0.1:9080/hello -X POST -d'test' +``` + +正常返回结果: + +``` +hello, test! +``` + +### 配置路径转发 + +`OpenFunction` 插件还支持 URL 路径转发,同时将请求代理到上游的 OpenFunction API 端点。基本请求路径的扩展(如路由 `/hello/*` 中 `*` 的部分)会被添加到插件配置中指定的 `function_uri`。 + +:::info 重要 + +路由上配置的 `uri` 必须以 `*` 结尾,此功能才能正常工作。APISIX 路由是严格匹配的,`*` 表示此 URI 的任何子路径都将匹配到同一路由。 + +::: + +下面的示例配置了此功能: + +```shell +curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "uri": "/hello/*", + "plugins": { + "openfunction": { + "function_uri": "http://localhost:3233/default/function-sample", + "authorization": { + "service_token": "test:test" + } + } + } +}' +``` + +现在,对路径 `hello/123` 的任何请求都将调用 OpenFunction 插件设置的对应的函数,并转发添加的路径: + +```shell +curl http://127.0.0.1:9080/hello/123 +``` + +```shell +Hello, 123! +``` + +## 禁用插件 + +当你需要禁用 `openfunction` 插件时,可以通过以下命令删除相应的 JSON 配置,APISIX 将会自动重新加载相关配置,无需重启服务: + +```shell +curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "methods": ["GET"], + "uri": "/hello", + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } +}' +``` diff --git a/t/admin/plugins.t b/t/admin/plugins.t index ed9058c13be2..cfa7173f66b2 100644 --- a/t/admin/plugins.t +++ b/t/admin/plugins.t @@ -129,6 +129,7 @@ example-plugin aws-lambda azure-functions openwhisk +openfunction serverless-post-function ext-plugin-post-req ext-plugin-post-resp diff --git a/t/plugin/openfunction.t b/t/plugin/openfunction.t new file mode 100644 index 000000000000..8da960df4d40 --- /dev/null +++ b/t/plugin/openfunction.t @@ -0,0 +1,335 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +use t::APISIX 'no_plan'; + +repeat_each(1); +no_long_string(); +no_root_location(); + +add_block_preprocessor(sub { + my ($block) = @_; + + if ((!defined $block->error_log) && (!defined $block->no_error_log)) { + $block->set_value("no_error_log", "[error]"); + } + + if (!defined $block->request) { + $block->set_value("request", "GET /t"); + } +}); + +run_tests(); + +__DATA__ + +=== TEST 1: sanity +--- config + location /t { + content_by_lua_block { + local plugin = require("apisix.plugins.openfunction") + local ok, err = plugin.check_schema({function_uri = "http://127.0.0.1:30585/default/test-body"}) + if not ok then + ngx.say(err) + end + + ngx.say("done") + } + } +--- response_body +done + + + +=== TEST 2: missing `function_uri` +--- config + location /t { + content_by_lua_block { + local plugin = require("apisix.plugins.openfunction") + local ok, err = plugin.check_schema({timeout = 60000}) + if not ok then + ngx.say(err) + end + } + } +--- response_body +property "function_uri" is required + + + +=== TEST 3: wrong type for `function_uri` +--- config + location /t { + content_by_lua_block { + local plugin = require("apisix.plugins.openfunction") + local ok, err = plugin.check_schema({function_uri = 30858}) + if not ok then + ngx.say(err) + end + } + } +--- response_body +property "function_uri" validation failed: wrong type: expected string, got number + + + +=== TEST 4: setup route with plugin +--- 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": { + "openfunction": { + "function_uri": "http://127.0.0.1:30584/function-sample" + } + }, + "upstream": { + "nodes": {}, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- response_body +passed + + + +=== TEST 5: hit route (with GET request) +--- request +GET /hello +--- response_body +Hello, function-sample! + + + +=== TEST 6: reset route with test-body function +--- 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": { + "openfunction": { + "function_uri": "http://127.0.0.1:30585/default/test-body" + } + }, + "upstream": { + "nodes": {}, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- response_body +passed + + + +=== TEST 7: hit route with POST method +--- request +POST /hello +test +--- response_body +Hello, test! + + + +=== TEST 8: reset route with test-header function with service_token +--- 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": { + "openfunction": { + "function_uri": "http://127.0.0.1:30583/", + "authorization": { + "service_token": "test:test" + } + } + }, + "upstream": { + "nodes": {}, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- response_body +passed + + + +=== TEST 9: hit route with POST request with service_token +--- request +POST /hello +--- response_body chomp +[Basic dGVzdDp0ZXN0] + + + +=== TEST 10: reset route with test-header function without service_token +--- 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": { + "openfunction": { + "function_uri": "http://127.0.0.1:30583/" + } + }, + "upstream": { + "nodes": {}, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- response_body +passed + + + +=== TEST 11: hit route with user-specific Authorization header +--- request +POST /hello +--- more_headers +authorization: user-token-xxx +--- response_body chomp +[user-token-xxx] + + + +=== TEST 12: reset route to non-existent function_uri +--- 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": { + "openfunction": { + "function_uri": "http://127.0.0.1:30584/default/non-existent" + } + }, + "upstream": { + "nodes": {}, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- response_body +passed + + + +=== TEST 13: hit route (with non-existent function_uri) +--- request +POST /hello +test +--- more_headers +Content-Type: application/x-www-form-urlencoded +--- error_code: 404 +--- response_body_like eval +qr/not found/ + + + +=== TEST 14: reset route with test-uri function and path forwarding +--- 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": { + "openfunction": { + "function_uri": "http://127.0.0.1:30584" + } + }, + "upstream": { + "nodes": {}, + "type": "roundrobin" + }, + "uri": "/hello/*" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- response_body +passed + + + +=== TEST 15: hit route with GET method +--- request +GET /hello/openfunction +--- response_body +Hello, openfunction!