Compare commits

..

8 Commits

Author SHA1 Message Date
Jun
6a40d83ec0 enable automatichttps (#1026) 2024-06-04 09:35:30 +08:00
Jun
2807ddfbb7 Feat https fallback (#1020) 2024-05-31 14:04:02 +08:00
Kent Dong
6e4ade05a8 doc: Add instructions of how to build ai-proxy plugin (#1005) 2024-05-31 13:59:43 +08:00
Kent Dong
bdd050b926 fix: Fix tool_calls compatibility issues with LobeChat (#1009) 2024-05-30 19:20:48 +08:00
澄潭
38ddc49360 Optimize the method for judging streaming responses (#1017) 2024-05-30 17:50:28 +08:00
澄潭
26ec0d3d55 Update manifests.yaml 2024-05-30 10:42:54 +08:00
澄潭
909f8bc719 Update main.go 2024-05-30 10:26:52 +08:00
澄潭
863d0e5872 Update main.go 2024-05-30 09:53:48 +08:00
15 changed files with 285 additions and 79 deletions

6
go.mod
View File

@@ -259,7 +259,6 @@ require (
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/oauth2 v0.6.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.15.0 // indirect
@@ -281,6 +280,8 @@ require (
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
k8s.io/apiserver v0.22.5 // indirect
k8s.io/component-base v0.22.5 // indirect
k8s.io/klog/v2 v2.60.1 // indirect
k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c // indirect
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
oras.land/oras-go v0.4.0 // indirect
@@ -312,10 +313,9 @@ require (
github.com/kylelemons/godebug v1.1.0
github.com/mholt/acmez v1.2.0
github.com/tidwall/gjson v1.17.0
golang.org/x/net v0.17.0
helm.sh/helm/v3 v3.7.1
k8s.io/apiextensions-apiserver v0.25.4
k8s.io/component-base v0.22.5
k8s.io/klog/v2 v2.60.1
knative.dev/networking v0.0.0-20220302134042-e8b2eb995165
knative.dev/pkg v0.0.0-20220301181942-2fdd5f232e77
)

View File

@@ -589,7 +589,7 @@ controller:
maxReplicas: 5
targetCPUUtilizationPercentage: 80
automaticHttps:
enabled: false
enabled: true
email: ""
## Discovery Settings

View File

@@ -45,11 +45,12 @@ const (
// Config is the configuration of automatic https.
type Config struct {
AutomaticHttps bool `json:"automaticHttps"`
RenewBeforeDays int `json:"renewBeforeDays"`
CredentialConfig []CredentialEntry `json:"credentialConfig"`
ACMEIssuer []ACMEIssuerEntry `json:"acmeIssuer"`
Version string `json:"version"`
AutomaticHttps bool `json:"automaticHttps"`
FallbackForInvalidSecret bool `json:"fallbackForInvalidSecret"`
RenewBeforeDays int `json:"renewBeforeDays"`
CredentialConfig []CredentialEntry `json:"credentialConfig"`
ACMEIssuer []ACMEIssuerEntry `json:"acmeIssuer"`
Version string `json:"version"`
}
func (c *Config) GetIssuer(issuerName IssuerName) *ACMEIssuerEntry {
@@ -274,11 +275,12 @@ func newDefaultConfig(email string) *Config {
}
defaultCredentialConfig := make([]CredentialEntry, 0)
config := &Config{
AutomaticHttps: true,
RenewBeforeDays: DefaultRenewBeforeDays,
ACMEIssuer: defaultIssuer,
CredentialConfig: defaultCredentialConfig,
Version: time.Now().Format("20060102030405"),
AutomaticHttps: true,
FallbackForInvalidSecret: false,
RenewBeforeDays: DefaultRenewBeforeDays,
ACMEIssuer: defaultIssuer,
CredentialConfig: defaultCredentialConfig,
Version: time.Now().Format("20060102030405"),
}
return config
}

View File

@@ -51,6 +51,7 @@ import (
higressv1 "github.com/alibaba/higress/api/networking/v1"
extlisterv1 "github.com/alibaba/higress/client/pkg/listers/extensions/v1alpha1"
netlisterv1 "github.com/alibaba/higress/client/pkg/listers/networking/v1"
"github.com/alibaba/higress/pkg/cert"
"github.com/alibaba/higress/pkg/ingress/kube/annotations"
"github.com/alibaba/higress/pkg/ingress/kube/common"
"github.com/alibaba/higress/pkg/ingress/kube/configmap"
@@ -144,6 +145,8 @@ type IngressConfig struct {
namespace string
clusterId string
httpsConfigMgr *cert.ConfigMgr
}
func NewIngressConfig(localKubeClient kube.Client, XDSUpdater model.XDSUpdater, namespace, clusterId string) *IngressConfig {
@@ -180,6 +183,9 @@ func NewIngressConfig(localKubeClient kube.Client, XDSUpdater model.XDSUpdater,
higressConfigController := configmap.NewController(localKubeClient, clusterId, namespace)
config.configmapMgr = configmap.NewConfigmapMgr(XDSUpdater, namespace, higressConfigController, higressConfigController.Lister())
httpsConfigMgr, _ := cert.NewConfigMgr(namespace, localKubeClient)
config.httpsConfigMgr = httpsConfigMgr
return config
}
@@ -347,6 +353,10 @@ func (m *IngressConfig) convertGateways(configs []common.WrapperConfig) []config
Gateways: map[string]*common.WrapperGateway{},
}
httpsCredentialConfig, err := m.httpsConfigMgr.GetConfigFromConfigmap()
if err != nil {
IngressLog.Errorf("Get higress https configmap err %v", err)
}
for idx := range configs {
cfg := configs[idx]
clusterId := common.GetClusterId(cfg.Config.Annotations)
@@ -356,7 +366,7 @@ func (m *IngressConfig) convertGateways(configs []common.WrapperConfig) []config
if ingressController == nil {
continue
}
if err := ingressController.ConvertGateway(&convertOptions, &cfg); err != nil {
if err := ingressController.ConvertGateway(&convertOptions, &cfg, httpsCredentialConfig); err != nil {
IngressLog.Errorf("Convert ingress %s/%s to gateway fail in cluster %s, err %v", cfg.Config.Namespace, cfg.Config.Name, clusterId, err)
}
}

View File

@@ -17,14 +17,14 @@ package common
import (
"strings"
"github.com/alibaba/higress/pkg/cert"
"github.com/alibaba/higress/pkg/ingress/kube/annotations"
networking "istio.io/api/networking/v1alpha3"
"istio.io/istio/pilot/pkg/model"
"istio.io/istio/pkg/config"
gatewaytool "istio.io/istio/pkg/config/gateway"
listerv1 "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
"github.com/alibaba/higress/pkg/ingress/kube/annotations"
)
type ServiceKey struct {
@@ -121,7 +121,7 @@ type IngressController interface {
SecretLister() listerv1.SecretLister
ConvertGateway(convertOptions *ConvertOptions, wrapper *WrapperConfig) error
ConvertGateway(convertOptions *ConvertOptions, wrapper *WrapperConfig, httpsCredentialConfig *cert.Config) error
ConvertHTTPRoute(convertOptions *ConvertOptions, wrapper *WrapperConfig) error

View File

@@ -55,6 +55,7 @@ import (
"github.com/alibaba/higress/pkg/ingress/kube/secret"
"github.com/alibaba/higress/pkg/ingress/kube/util"
. "github.com/alibaba/higress/pkg/ingress/log"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
)
var (
@@ -87,8 +88,6 @@ type controller struct {
secretController secret.SecretController
statusSyncer *statusSyncer
configMgr *cert.ConfigMgr
}
// NewController creates a new Kubernetes controller
@@ -107,7 +106,6 @@ func NewController(localKubeClient, client kubeclient.Client, options common.Opt
IngressLog.Infof("Skipping IngressClass, resource not supported for cluster %s", options.ClusterId)
}
configMgr, _ := cert.NewConfigMgr(options.SystemNamespace, client.Kube())
c := &controller{
options: options,
queue: q,
@@ -118,7 +116,6 @@ func NewController(localKubeClient, client kubeclient.Client, options common.Opt
serviceInformer: serviceInformer.Informer(),
serviceLister: serviceInformer.Lister(),
secretController: secretController,
configMgr: configMgr,
}
handler := controllers.LatestVersionHandlerFuncs(controllers.EnqueueForSelf(q))
@@ -354,7 +351,7 @@ func extractTLSSecretName(host string, tls []ingress.IngressTLS) string {
return ""
}
func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapper *common.WrapperConfig) error {
func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapper *common.WrapperConfig, httpsCredentialConfig *cert.Config) error {
if convertOptions == nil {
return fmt.Errorf("convertOptions is nil")
}
@@ -377,7 +374,6 @@ func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapp
common.IncrementInvalidIngress(c.options.ClusterId, common.EmptyRule)
return fmt.Errorf("invalid ingress rule %s:%s in cluster %s, either `defaultBackend` or `rules` must be specified", cfg.Namespace, cfg.Name, c.options.ClusterId)
}
httpsCredentialConfig, _ := c.configMgr.GetConfigFromConfigmap()
for _, rule := range ingressV1Beta.Rules {
// Need create builder for every rule.
domainBuilder := &common.IngressDomainBuilder{
@@ -429,10 +425,23 @@ func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapp
// Get tls secret matching the rule host
secretName := extractTLSSecretName(rule.Host, ingressV1Beta.TLS)
secretNamespace := cfg.Namespace
// If there is no matching secret, try to get it from configmap.
if secretName == "" && httpsCredentialConfig != nil {
secretName = httpsCredentialConfig.MatchSecretNameByDomain(rule.Host)
secretNamespace = c.options.SystemNamespace
if secretName != "" {
if httpsCredentialConfig != nil && httpsCredentialConfig.FallbackForInvalidSecret {
_, err := c.secretController.Lister().Secrets(secretNamespace).Get(secretName)
if err != nil {
if k8serrors.IsNotFound(err) {
// If there is no matching secret, try to get it from configmap.
secretName = httpsCredentialConfig.MatchSecretNameByDomain(rule.Host)
secretNamespace = c.options.SystemNamespace
}
}
}
} else {
// If there is no matching secret, try to get it from configmap.
if httpsCredentialConfig != nil {
secretName = httpsCredentialConfig.MatchSecretNameByDomain(rule.Host)
secretNamespace = c.options.SystemNamespace
}
}
if secretName == "" {
// There no matching secret, so just skip.

View File

@@ -334,7 +334,7 @@ func testConvertGateway(t *testing.T, c common.IngressController) {
}
for _, testcase := range testcases {
err := c.ConvertGateway(testcase.input.options, testcase.input.wrapperConfig)
err := c.ConvertGateway(testcase.input.options, testcase.input.wrapperConfig, nil)
if err != nil {
require.Equal(t, testcase.expectNoError, false)
} else {

View File

@@ -54,6 +54,7 @@ import (
"github.com/alibaba/higress/pkg/ingress/kube/secret"
"github.com/alibaba/higress/pkg/ingress/kube/util"
. "github.com/alibaba/higress/pkg/ingress/log"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
)
var (
@@ -85,8 +86,6 @@ type controller struct {
secretController secret.SecretController
statusSyncer *statusSyncer
configMgr *cert.ConfigMgr
}
// NewController creates a new Kubernetes controller
@@ -99,7 +98,6 @@ func NewController(localKubeClient, client kubeclient.Client, options common.Opt
classes := client.KubeInformer().Networking().V1().IngressClasses()
classes.Informer()
configMgr, _ := cert.NewConfigMgr(options.SystemNamespace, client.Kube())
c := &controller{
options: options,
queue: q,
@@ -110,7 +108,6 @@ func NewController(localKubeClient, client kubeclient.Client, options common.Opt
serviceInformer: serviceInformer.Informer(),
serviceLister: serviceInformer.Lister(),
secretController: secretController,
configMgr: configMgr,
}
handler := controllers.LatestVersionHandlerFuncs(controllers.EnqueueForSelf(q))
@@ -346,7 +343,7 @@ func extractTLSSecretName(host string, tls []ingress.IngressTLS) string {
return ""
}
func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapper *common.WrapperConfig) error {
func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapper *common.WrapperConfig, httpsCredentialConfig *cert.Config) error {
// Ignore canary config.
if wrapper.AnnotationsConfig.IsCanary() {
return nil
@@ -363,7 +360,6 @@ func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapp
return fmt.Errorf("invalid ingress rule %s:%s in cluster %s, either `defaultBackend` or `rules` must be specified", cfg.Namespace, cfg.Name, c.options.ClusterId)
}
httpsCredentialConfig, _ := c.configMgr.GetConfigFromConfigmap()
for _, rule := range ingressV1.Rules {
// Need create builder for every rule.
domainBuilder := &common.IngressDomainBuilder{
@@ -415,11 +411,25 @@ func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapp
// Get tls secret matching the rule host
secretName := extractTLSSecretName(rule.Host, ingressV1.TLS)
secretNamespace := cfg.Namespace
// If there is no matching secret, try to get it from configmap.
if secretName == "" && httpsCredentialConfig != nil {
secretName = httpsCredentialConfig.MatchSecretNameByDomain(rule.Host)
secretNamespace = c.options.SystemNamespace
if secretName != "" {
if httpsCredentialConfig != nil && httpsCredentialConfig.FallbackForInvalidSecret {
_, err := c.secretController.Lister().Secrets(secretNamespace).Get(secretName)
if err != nil {
if k8serrors.IsNotFound(err) {
// If there is no matching secret, try to get it from configmap.
secretName = httpsCredentialConfig.MatchSecretNameByDomain(rule.Host)
secretNamespace = c.options.SystemNamespace
}
}
}
} else {
// If there is no matching secret, try to get it from configmap.
if httpsCredentialConfig != nil {
secretName = httpsCredentialConfig.MatchSecretNameByDomain(rule.Host)
secretNamespace = c.options.SystemNamespace
}
}
if secretName == "" {
// There no matching secret, so just skip.
continue

View File

@@ -30,6 +30,7 @@ func main() {
wrapper.ParseConfigBy(parseConfig),
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
wrapper.ProcessRequestBodyBy(onHttpRequestBody),
wrapper.ProcessResponseHeadersBy(onHttpResponseHeaders),
wrapper.ProcessStreamingResponseBodyBy(onHttpResponseBody),
)
}
@@ -54,9 +55,16 @@ func main() {
// cacheKeyFrom:
// requestBody: "messages.@reverse.0.content"
// cacheValueFrom:
// responseBody: "choices.0"
// responseBody: "choices.0.message.content"
// cacheStreamValueFrom:
// responseBody: "choices.0.delta.content"
// returnResponseTemplate: |
// {"id":"from-cache","choices":[%s],"model":"gpt-4o","object":"chat.completion","usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
// {"id":"from-cache","choices":[{"index":0,"message":{"role":"assistant","content":"%s"},"finish_reason":"stop"}],"model":"gpt-4o","object":"chat.completion","usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
// returnStreamResponseTemplate: |
// data:{"id":"from-cache","choices":[{"index":0,"delta":{"role":"assistant","content":"%s"},"finish_reason":"stop"}],"model":"gpt-4o","object":"chat.completion","usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
//
// data:[DONE]
//
// @End
type RedisInfo struct {
@@ -174,12 +182,6 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, config PluginConfig, log wrap
ctx.DontReadRequestBody()
return types.ActionContinue
}
// compatiable with qwen
x_dashscope_sse, _ := proxywasm.GetHttpRequestHeader("X-DashScope-SSE")
accept, _ := proxywasm.GetHttpRequestHeader("Accept")
if x_dashscope_sse == "enable" || strings.Contains(accept, "text/event-stream") {
ctx.SetContext(StreamContextKey, struct{}{})
}
proxywasm.RemoveHttpRequestHeader("Accept-Encoding")
// The request has a body and requires delaying the header transmission until a cache miss occurs,
// at which point the header should be sent.
@@ -267,6 +269,14 @@ func processSSEMessage(ctx wrapper.HttpContext, config PluginConfig, sseMessage
return ""
}
func onHttpResponseHeaders(ctx wrapper.HttpContext, config PluginConfig, log wrapper.Log) types.Action {
contentType, _ := proxywasm.GetHttpResponseHeader("content-type")
if strings.Contains(contentType, "text/event-stream") {
ctx.SetContext(StreamContextKey, struct{}{})
}
return types.ActionContinue
}
func onHttpResponseBody(ctx wrapper.HttpContext, config PluginConfig, chunk []byte, isLastChunk bool, log wrapper.Log) []byte {
if ctx.GetContext(ToolCallsContextKey) != nil {
// we should not cache tool call result

View File

@@ -0,0 +1,18 @@
## 构建方法
确认本机已安装 Docker然后根据操作系统选择对应的构建命令并在 `ai-proxy` 目录下执行。构建产物将输出至 `out` 目录。
***Linux/macOS:***
```shell
DOCKER_BUILDKIT=1; docker build --build-arg PLUGIN_NAME=ai-proxy --build-arg EXTRA_TAGS=proxy_wasm_version_0_2_100 --build-arg BUILDER=higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/wasm-go-builder:go1.19-tinygo0.28.1-oras1.0.0 -t ai-proxy:0.0.1 --output ./out ../..
```
***Windows:***
```powershell
$env:DOCKER_BUILDKIT=1; docker build --build-arg PLUGIN_NAME=ai-proxy --build-arg EXTRA_TAGS=proxy_wasm_version_0_2_100 --build-arg BUILDER=higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/wasm-go-builder:go1.19-tinygo0.28.1-oras1.0.0 -t ai-proxy:0.0.1 --output .\out ..\..
```
## 测试须知
由于 `ai-proxy` 插件使用了 Higress 对数据面定制的特殊功能,因此在测试时需要使用版本不低于 1.4.0-rc.1 的 Higress Gateway 镜像。

View File

@@ -54,7 +54,7 @@ type toolChoice struct {
type chatCompletionResponse struct {
Id string `json:"id,omitempty"`
Choices []chatCompletionChoice `json:"choices,omitempty"`
Choices []chatCompletionChoice `json:"choices"`
Created int64 `json:"created,omitempty"`
Model string `json:"model,omitempty"`
SystemFingerprint string `json:"system_fingerprint,omitempty"`
@@ -102,14 +102,15 @@ func (m *chatMessage) IsEmpty() bool {
}
type toolCall struct {
Index int `json:"index"`
Id string `json:"id"`
Type string `json:"type"`
Function functionCall `json:"function"`
}
type functionCall struct {
Id string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Id string `json:"id"`
Name string `json:"name"`
Arguments string `json:"arguments"`
}

View File

@@ -341,6 +341,7 @@ func (m *qwenProvider) buildChatCompletionStreamingResponse(ctx wrapper.HttpCont
Id: qwenResponse.RequestId,
Created: time.Now().UnixMilli() / 1000,
Model: ctx.GetContext(ctxKeyFinalRequestModel).(string),
Choices: make([]chatCompletionChoice, 0),
SystemFingerprint: "",
Object: objectChatCompletionChunk,
}
@@ -350,12 +351,16 @@ func (m *qwenProvider) buildChatCompletionStreamingResponse(ctx wrapper.HttpCont
qwenChoice := qwenResponse.Output.Choices[0]
message := qwenChoice.Message
deltaMessage := &chatMessage{Role: message.Role, Content: message.Content, ToolCalls: append([]toolCall{}, message.ToolCalls...)}
deltaContentMessage := &chatMessage{Role: message.Role, Content: message.Content}
deltaToolCallsMessage := &chatMessage{Role: message.Role, ToolCalls: append([]toolCall{}, message.ToolCalls...)}
if !incrementalStreaming {
if pushedMessage, ok := ctx.GetContext(ctxKeyPushedMessage).(qwenMessage); ok {
deltaMessage.Content = util.StripPrefix(deltaMessage.Content, pushedMessage.Content)
if len(deltaMessage.ToolCalls) > 0 && pushedMessage.ToolCalls != nil {
for i, tc := range deltaMessage.ToolCalls {
if message.Content == "" {
message.Content = pushedMessage.Content
}
deltaContentMessage.Content = util.StripPrefix(deltaContentMessage.Content, pushedMessage.Content)
if len(deltaToolCallsMessage.ToolCalls) > 0 && pushedMessage.ToolCalls != nil {
for i, tc := range deltaToolCallsMessage.ToolCalls {
if i >= len(pushedMessage.ToolCalls) {
break
}
@@ -363,24 +368,37 @@ func (m *qwenProvider) buildChatCompletionStreamingResponse(ctx wrapper.HttpCont
tc.Function.Id = util.StripPrefix(tc.Function.Id, pushedFunction.Id)
tc.Function.Name = util.StripPrefix(tc.Function.Name, pushedFunction.Name)
tc.Function.Arguments = util.StripPrefix(tc.Function.Arguments, pushedFunction.Arguments)
deltaMessage.ToolCalls[i] = tc
deltaToolCallsMessage.ToolCalls[i] = tc
}
}
}
ctx.SetContext(ctxKeyPushedMessage, message)
}
if !deltaMessage.IsEmpty() {
deltaResponse := *&baseMessage
deltaResponse.Choices = append(deltaResponse.Choices, chatCompletionChoice{Delta: deltaMessage})
responses = append(responses, &deltaResponse)
if !deltaContentMessage.IsEmpty() {
response := *&baseMessage
response.Choices = append(response.Choices, chatCompletionChoice{Delta: deltaContentMessage})
responses = append(responses, &response)
}
if !deltaToolCallsMessage.IsEmpty() {
response := *&baseMessage
response.Choices = append(response.Choices, chatCompletionChoice{Delta: deltaToolCallsMessage})
responses = append(responses, &response)
}
// Yes, Qwen uses a string "null" as null.
if qwenChoice.FinishReason != "" && qwenChoice.FinishReason != "null" {
finishResponse := *&baseMessage
finishResponse.Choices = append(finishResponse.Choices, chatCompletionChoice{FinishReason: qwenChoice.FinishReason})
responses = append(responses, &finishResponse)
usageResponse := *&baseMessage
usageResponse.Usage = chatCompletionUsage{
PromptTokens: qwenResponse.Usage.InputTokens,
CompletionTokens: qwenResponse.Usage.OutputTokens,
TotalTokens: qwenResponse.Usage.TotalTokens,
}
responses = append(responses, &finishResponse, &usageResponse)
}
return responses

View File

@@ -209,7 +209,7 @@ spec:
containers:
- name: infra-backend-echo-body-v1
# FROM https://github.com/higress-group/echo-body
image: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/echo-server:1.3.0
image: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/echo-body:1.0.0
env:
- name: POD_NAME
valueFrom:

View File

@@ -17,11 +17,8 @@ package tests
import (
"testing"
"github.com/alibaba/higress/test/e2e/conformance/utils/cert"
"github.com/alibaba/higress/test/e2e/conformance/utils/http"
"github.com/alibaba/higress/test/e2e/conformance/utils/kubernetes"
"github.com/alibaba/higress/test/e2e/conformance/utils/suite"
"sigs.k8s.io/controller-runtime/pkg/client"
)
func init() {
@@ -34,11 +31,6 @@ var ConfigmapHttps = suite.ConformanceTest{
Manifests: []string{"tests/configmap-https.yaml"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
// Prepare secrets for testcases
_, _, caCert, caKey := cert.MustGenerateCaCert(t)
svcCertOut, svcKeyOut := cert.MustGenerateCertWithCA(t, cert.ServerCertType, caCert, caKey, []string{"foo.com"})
fooSecret := kubernetes.ConstructTLSSecret("higress-system", "foo-com-secret", svcCertOut.Bytes(), svcKeyOut.Bytes())
suite.Applier.MustApplyObjectsWithCleanup(t, suite.Client, suite.TimeoutConfig, []client.Object{fooSecret}, suite.Cleanup)
testCases := []struct {
httpAssert http.Assertion
@@ -46,22 +38,80 @@ var ConfigmapHttps = suite.ConformanceTest{
{
httpAssert: http.Assertion{
Meta: http.AssertionMeta{
TestCaseName: "test configmap https",
TestCaseName: "test configmap bar-com https",
TargetBackend: "infra-backend-v2",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Path: "/foohttps",
Host: "foo.com",
Path: "/barhttps",
Host: "bar.com",
TLSConfig: &http.TLSConfig{
SNI: "foo.com",
SNI: "bar.com",
},
},
ExpectedRequest: &http.ExpectedRequest{
Request: http.Request{
Path: "/foohttps",
Host: "foo.com",
Path: "/barhttps",
Host: "bar.com",
},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
},
},
},
{
httpAssert: http.Assertion{
Meta: http.AssertionMeta{
TestCaseName: "test configmap a-foo-com https",
TargetBackend: "infra-backend-v2",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Path: "/afoohttps",
Host: "a.foo.com",
TLSConfig: &http.TLSConfig{
SNI: "a.foo.com",
},
},
ExpectedRequest: &http.ExpectedRequest{
Request: http.Request{
Path: "/afoohttps",
Host: "a.foo.com",
},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
},
},
},
{
httpAssert: http.Assertion{
Meta: http.AssertionMeta{
TestCaseName: "test configmap b-foo-com https",
TargetBackend: "infra-backend-v2",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Path: "/bfoohttps",
Host: "b.foo.com",
TLSConfig: &http.TLSConfig{
SNI: "b.foo.com",
},
},
ExpectedRequest: &http.ExpectedRequest{
Request: http.Request{
Path: "/bfoohttps",
Host: "b.foo.com",
},
},
},

View File

@@ -20,6 +20,7 @@ data:
cert: |
automaticHttps: true
renewBeforeDays: 30
fallbackForInvalidSecret: true
acmeIssuer:
- ak: test
sk: test
@@ -34,22 +35,98 @@ data:
version: test
---
apiVersion: v1
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURMRENDQWhTZ0F3SUJBZ0lSQUw4TGhJL1F1N3lJYUg3Ny8rdCtsSDR3RFFZSktvWklodmNOQVFFTEJRQXcKTFRFWk1CY0dBMVVFQ2hNUVNHbG5jbVZ6Y3lCRk1rVWdWR1Z6ZERFUU1BNEdBMVVFQXhNSFpHVm1ZWFZzZERBZQpGdzB5TkRBMU16RXdOVEV5TkRKYUZ3MHlOVEExTXpFd05URXlOREphTUMweEdUQVhCZ05WQkFvVEVFaHBaM0psCmMzTWdSVEpGSUZSbGMzUXhFREFPQmdOVkJBTVRCMlJsWm1GMWJIUXdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUEKQTRJQkR3QXdnZ0VLQW9JQkFRQ2xZOWZlUGNxK0FhMnNBcWJFSlFyb1R5OVdOTndpejg5YlVQSExHSlE3QVpLNApiTm9nYzB4NGVNT0tZVjlXVnExS0NZZkdlQWJEYys5elV1Z3VtRFEyNUlwWWo1MGlNVnIwek81TFZzTllBdEFwCnFvNExNaVh1UStCZ3hIamlYQldXOFFPbVZxbVBKZnZ0RTNsZWdROFl2bVRSMGZIK254eHlRbVlqbkdJcHVlMmEKeGREc005cHJQYk56U0V4Um9QL2FDWElxWExvS0NaYzArUlVmK2JEQzE1QzJEamJoOXVyT1JQU1ZhKzR0TDlteQpwdGY4RTJIS0xuSUFuNWdBMUhORXR3STYxb0c0dmtUcDVwdDAyRzlpSlZPTGN5NnhjRDFUU0lBUktVUW5VcXQzClIycGxHTDREdEY5M2gyWjZ1YWt5TDh5VHAvUzFjRU8wWnEzTUdZYVBBZ01CQUFHalJ6QkZNQTRHQTFVZER3RUIKL3dRRUF3SUZvREFkQmdOVkhTVUVGakFVQmdnckJnRUZCUWNEQVFZSUt3WUJCUVVIQXdJd0ZBWURWUjBSQkEwdwpDNElKS2k1bWIyOHVZMjl0TUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFBcGtHTU1KWFU0OXJ6Ym1VM3BZVWVoCnk5dDNsVmpzU1lYUEhZVzBvclA1NWdSRGNnRTZ0SDY2WjlUa05BYlZ0RUczWFkwUWhrd0Q2MWNwN0VJSXBBOHIKcnVVS3lnR29vT1g1Zy9WSEhNWmI5Qmtud1MwQlpwaFhQaWs1K213L3U2QnJnRCtEUUgzTUU0MkdFOVppZmUzcwp2blBvQTVQVURXNmZPR3FPWENSdFlCeGQ3MXdmYy83MCszcVZiaDVEayt3TnlxM1U4S0xQdDBHOXpsUnZhclVsCmJ3eFFEeDAxNGtvUVo4ZStEUXU2QzFRWTBPZjZIVkp2Yk1tVUVWeENLUWNjTEFIdFl6dnZKdWdLeHdONWtsZEkKUS9UMWJZc24zNXZEZUpYL1NUQ2Radjk0eWhhVVl3TUt1OHkxNnExaHFGcmdpNWRNcWZ0RDZ6RVlKYUwyRFh1LwotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBcFdQWDNqM0t2Z0d0ckFLbXhDVUs2RTh2VmpUY0lzL1BXMUR4eXhpVU93R1N1R3phCklITk1lSGpEaW1GZlZsYXRTZ21IeG5nR3czUHZjMUxvTHBnME51U0tXSStkSWpGYTlNenVTMWJEV0FMUUthcU8KQ3pJbDdrUGdZTVI0NGx3Vmx2RURwbGFwanlYNzdSTjVYb0VQR0w1azBkSHgvcDhjY2tKbUk1eGlLYm50bXNYUQo3RFBhYXoyemMwaE1VYUQvMmdseUtseTZDZ21YTlBrVkgvbXd3dGVRdGc0MjRmYnF6a1QwbFd2dUxTL1pzcWJYCi9CTmh5aTV5QUorWUFOUnpSTGNDT3RhQnVMNUU2ZWFiZE5odllpVlRpM011c1hBOVUwaUFFU2xFSjFLcmQwZHEKWlJpK0E3UmZkNGRtZXJtcE1pL01rNmYwdFhCRHRHYXR6Qm1HandJREFRQUJBb0lCQUhrVjRSeGZwd2gzR0J5QQpFSEk0SUlVMlBCVGtQR3JzTkFiSisweFRJV3NWMnNKVlIxbE1zS2JlMjJKN3FaMy9kWDFuL3RUS1dVRk5wdmlLCnNWd3pxTDZya2JJRzZ1YjJ0WDNXYjN3TytKTjk4OE1ka0VNWUl2Y1BFTDRuK2N6WDJDS2JMNjNmY3VKUHorS2gKU0ZGdE1ZMVBEMmNpU3dhOG5NbjJYT3NqZWliTFFKcWFNbGZnb2sraHBaUTlBcXV1STMzdmk3R1J4cWg0bEhvUgptU05uc21DZHpsQ3ovRTdvWHZ1Q2lwcnNPNjVkT21wNG0vUVZ4dktFbjlFQmRaekdONC9MWHZNdnA3N2lBRjc2CkpjTjBQVjk3TkZpdk9BMTY0c2QxaHg2TTJkd2lVWmZVdmdFbklBMWdpQThFYmx6M2pFM3FuS054KzRJZml6di8KNk5SWmxta0NnWUVBME5kU2g2NGFMc2UvY3lFMUZPRUh5bjNzNTlaN3Zia2hnYWU5WHUrcmdrWWdidGFQNVczWApLUHBlWThSL1QwZkVDMklxb29uZWhBUmxIUzNMTzV6VmN5OStERkJWSjArWHUxdG5TZVptOFI5bnpaQURmQXN5ClV3VUFteUZpa1g2dFRwaENPa085S2NkaWl4OXovZ1kwMVdtVng2V1o3TEI2VXM5V05vYTdKQU1DZ1lFQXlyeXoKdWxmTHdDaElMa1FkU1BmcXc5L2V2MGdGWDBRUmQxZHptVytiSXNqdEJxalhRbzYvNkEwdlBUcUNIem40NzFrRgprcWh4VitqNGZWT3ZOUGM3NUZnSEtUS0dkYVV6eTJna3RVWE9ldkxwNU5OZE5rRk5CM2tLOUgvc2psY3BmYjlVCnplMDVjbldtdHMzWkNxUFFEang3alFnT2MveWtlT0JhTStJR200VUNnWUVBaEh5Vk5zNFVmaWpxSTdlbFhTR0YKTjhpN1NqaWZON1VDdEtZZFZPVG5BVFpMelFVQk5LT0NJOVR4bklsRDJwL0VseFFueUFWK3pIR2RVKzJCU01ndQpBV3pYb2lnMFhVUDVGanJlUTl1TzR0anhtVThMWnQ0VGh1ZGRnd3lpNDNwaHA4S2dBU2FJRXNFU212L1JMZzN4CjVwR2RHNUxMRzRTNWxWOURha1ArNU5FQ2dZQXFkdEh5WXZkVFhWeVpERDFTRGxPSENYb2ZlSmRmZCtOc3FzMlUKd3RLc3U0Y2lFUFZkaEliZnRQdERDT0UrWnlja0F2SnU0SWMxRWFBU3FCZVhzWDFDKzhrc01PQUcvajVXQ1k4Kwp4TXRWNTFGa1UzMC9vdmZlYTlVR2wxRFdFNTJtTUJBMFBjNzlrWFVFN3lMWjNxdnlmMnFsaEoxNlg5MlhUKzYwCjFVL3EvUUtCZ0RQVUxrQ1V3ZElRYXZpTXczUXZjYUZoVGR3dTBJT2hQYWhlWm9kRmdYSXpWUVdZbmovT2lpSTIKK0U2eHhNVGNyY0czTGgwWkFyNTBXaUZKYWpMeUswd042WUVGVWNlcHV0c0ZOQjdEOXFCeStlZWoyVGRCS2kwVwozRTk3cmVjVXdZR3QvdnpPaURiU29zajdWbitCQVFaQnRGOXBCdTJwYzZDaW1Vb1JsOGZjCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==
kind: Secret
metadata:
name: foo-com-secret
namespace: higress-system
type: kubernetes.io/tls
---
apiVersion: v1
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURLakNDQWhLZ0F3SUJBZ0lSQVBObk9PUTBvQVJvMHhTN2xKVDJXWHN3RFFZSktvWklodmNOQVFFTEJRQXcKTFRFWk1CY0dBMVVFQ2hNUVNHbG5jbVZ6Y3lCRk1rVWdWR1Z6ZERFUU1BNEdBMVVFQXhNSFpHVm1ZWFZzZERBZQpGdzB5TkRBMU16RXdOVEExTURKYUZ3MHlOVEExTXpFd05UQTFNREphTUMweEdUQVhCZ05WQkFvVEVFaHBaM0psCmMzTWdSVEpGSUZSbGMzUXhFREFPQmdOVkJBTVRCMlJsWm1GMWJIUXdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUEKQTRJQkR3QXdnZ0VLQW9JQkFRRDBVTnlaNjlRVXQvbFhOclBGcCtPVjhBT2k4bVQvTkJ2UGFpQnRBZzkxc05keApSSXRZUWJ4WVpxNnJTMTJMQmQvVjdpK1VkZjNPbW5Bc053QXo3UnFLaVVtTTN2cXdvdTQxVzVyMExGbkJxVEkwCjJrZDdlelplUUlWS2lHMThGVzl5VmxMVE5ralN1YTRsZmg5K2ZwUEJZT0dMY1lXVm9ETDhkb09OKzUwY0RPNmMKeURwUjFzRGZQNFUyT2YrMm8wU1pYQmxodkYzdEMxMlAwR1Y5VWNwTTRENDFUR1dYTlVtV2N5bmJBaEF3RGZwQQprZmZXLytDU0RQa3F1TlB6UEtteklBcDVxbFdrajBXbDBkM1RzbVpZTzFnTjdwRFI4Wk9KMThWa1dCRCtJbEM5CktQanBqK00yK1liRjgzZHJ1ODlya3NzbXVUYWN4WGttMzZjbVR0YjlBZ01CQUFHalJUQkRNQTRHQTFVZER3RUIKL3dRRUF3SUZvREFkQmdOVkhTVUVGakFVQmdnckJnRUZCUWNEQVFZSUt3WUJCUVVIQXdJd0VnWURWUjBSQkFzdwpDWUlIWW1GeUxtTnZiVEFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBVFdHd3N2bEpPM1NzVHpsYVRoRnQ4QjRKCk5JTDhYbWpWbytzMkFra2k4T0hGT3dXVnVEOEM2Q3d3cDRiODBzc2ZEbkdpTXl0Y0NnMFlvMFJzQmRGaFh6Y0cKVlRkSnpQY3hoblhEZkhTTXd1SnlYd3oybVd6SDN4UjhlVWUvSEcvaXYyWnRkQ29YMlNVNTBIQ2RiOVVaTE1jWApzVWpNUnJhcEpCMWNPNERJcTIvYTM4NWRobnRXTXp2VVBGLzBQaEZnaER1WGFjamg3bU5Xb2lWVE9HdWpiYTBlCnIvZk9pdUt5L1IwZ0xFUmlCN0tieHJRU1paM3hyMlNZdjJvM1JaOUhGdFRkdFdmdDlFcXVVWFhwWHRLK01VMmsKS0ZnK0NWTnd6emg3REtKa2czWlJRWTJXTUU1RjBRYUdMelJKeHdQRVMzSmZwK3RpQSs0RHNDMGJVUUo0eHc9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBOUZEY21ldlVGTGY1VnphenhhZmpsZkFEb3ZKay96UWJ6Mm9nYlFJUGRiRFhjVVNMCldFRzhXR2F1cTB0ZGl3WGYxZTR2bEhYOXpwcHdMRGNBTSswYWlvbEpqTjc2c0tMdU5WdWE5Q3had2FreU5OcEgKZTNzMlhrQ0ZTb2h0ZkJWdmNsWlMwelpJMHJtdUpYNGZmbjZUd1dEaGkzR0ZsYUF5L0hhRGpmdWRIQXp1bk1nNgpVZGJBM3orRk5qbi90cU5FbVZ3WllieGQ3UXRkajlCbGZWSEtUT0ErTlV4bGx6Vkpsbk1wMndJUU1BMzZRSkgzCjF2L2drZ3o1S3JqVDh6eXBzeUFLZWFwVnBJOUZwZEhkMDdKbVdEdFlEZTZRMGZHVGlkZkZaRmdRL2lKUXZTajQKNlkvak52bUd4Zk4zYTd2UGE1TExKcmsybk1WNUp0K25KazdXL1FJREFRQUJBb0lCQUdiTUN6WDhZenpnZDlvNQpXd1RFY2w3cElTNlRuT2xBVEo5R0FTUzhwREtaMk55QXdieTkwL2pDSTZaUlRLZXRMaFErWnVpcGlNUkFlUWd4CmtEVkpBMHpkSFFSWDRkVW1pT0lNakRORzRmRTVOclhFVGlWbm4yV2k4ako5R3N3RjNPR1g3cnVOOExBeGpsT2EKTUxneG5BdldycS9VY1NlV3d6MDB4SCtlS2VuZHVLZGx0amtoTUNXUmhaMmFGeGI3UnNPalpHMENVdC9nU0c0QgptenBvREVnQmZyV1dTVGJVUS9UU1o2VEFkdHFOQjBycjhwZmV4Qk9QSndyVmxMS1o5bkZKMFlVclYvb3JsV3U1Cjk1NUFCb0RlU2N3Zy9oVis5UlhTajdtb09sdGp4Uk1TL3dKdkR4K3p4T2s0cWRRRlZ1QXYyVjZML0tsUEhhLzQKbGpQTmc4VUNnWUVBK3RRbDZiN3V5a0J3U21pVlZiYk9EWVdjNE5IREVmaGJxc0RRdWdMNDZ5OVFKLy9sSDd0bgpObElFcWFPRCtKRVFVOThLR2src0FOQzh0ekNybTZ6aGd5YVZ5T0RyQWNBVzcyZ3JkSm9pMDY5Szg0aE5uU0tkClNQM1M5UmlLcitwSlg4WlJpVGV4bitzdzZYcHJTbm9nZ2FOSzFKcWtPNndtS25oRHdvWmFlVGNDZ1lFQStWcFcKNVp1U3hkMCs5VXRuWnorVXhRSWJPUGVYb2V2eTVwNWV0d1I4czFVc3VhWU5nQ3M5NUJoOCtxMXpmbTN0VzcxcgpWTitKNS9WKzFvM3E4cy9jZEZ6OGRTc0sxTTFaS0E5NmJqeHV6YS9uWHplU25hTnE5TzhkOTdRa25BRlRPMm1JCldOcE1aQjNLOTRGclBOSnJ3Tm1OTFg2QmVrMGVXeFdadnpVbHUyc0NnWUF1WS9oVEgvNFlLQXpjcGpVZ2NqdnYKNGt0ZWhVMDMwS0JibDJmRFQzTnNSQWJtTHZ6WWZwZWJRMVliYmVPbG9HYk5yRTI1Q2cwODVWNVIzMDJOOEU2UgpMQnk5MTJOL29tQmJqUCtraERGMngwL3NkTVF1RU0zWVJ5R3lOUVRKZm1KdHRVYzFRcmkyWkJCYXprcHpydHkrClBVNUV2Z2tzQkMzVzR3RmRRKzROeHdLQmdRRFpXdFVhYW1ValVyczVpTlFHM1JacU1HN1lWb0ozbzd2bEtURjQKcVZHbDVONEtxZU5rME15dlVtVkhBZ0VGdVA3SkZERkdGMkVYc0JneklCd29NZWFDRERnSVRrK3Z0Wnc4M2xragpWRXhsd1NxWEJsTW9WRFc4Y2Q4V2Q1SGQ1dzNOWVMxMy9qbk9uMlc0SDdrQm1JNVMyWkJGa3R0OFoxTEpwT2VUCkU5bmpKd0tCZ1FEYi81VzlhN2Q5Qzc4LzBHWjN6OVg1dnZKYU1YMkF6WDN5d3oydWl6U0sydkZsUnJ2MFZzTUcKZzArcmxGSnlYMGZDZE1wSTFwZUJYZUh4QlZDOXY3VXk3VkN4c3EwVEJVaHdaMTJobUtUVkNRSzBQR0F4dURzOQpLbEcxQXpDOFp6cXI4TWxUY1djaXNWNkhRSG5sc3NOUTBHZVd1NHNuRWxzK3Z6ZUFBLzMyb3c9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
kind: Secret
metadata:
name: bar-com-secret
namespace: higress-conformance-infra
type: kubernetes.io/tls
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: httproute-configmap-https
name: httproute-bar-configmap-https
namespace: higress-conformance-infra
spec:
ingressClassName: higress
tls:
- hosts:
- "foo.com"
- "bar.com"
secretName: bar-com-secret
rules:
- host: "foo.com"
- host: "bar.com"
http:
paths:
- pathType: Exact
path: "/foohttps"
path: "/barhttps"
backend:
service:
name: infra-backend-v2
port:
number: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: httproute-afoo-configmap-https
namespace: higress-conformance-infra
spec:
ingressClassName: higress
tls:
- hosts:
- "a.foo.com"
rules:
- host: "a.foo.com"
http:
paths:
- pathType: Exact
path: "/afoohttps"
backend:
service:
name: infra-backend-v2
port:
number: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: httproute-foo2-configmap-https
namespace: higress-conformance-infra
spec:
ingressClassName: higress
tls:
- hosts:
- "b.foo.com"
secretName: b-foo-com-secret
rules:
- host: "b.foo.com"
http:
paths:
- pathType: Exact
path: "/bfoohttps"
backend:
service:
name: infra-backend-v2
@@ -59,3 +136,4 @@ spec: