diff --git a/pkg/ingress/kube/configmap/config.go b/pkg/ingress/kube/configmap/config.go index cba582d59..caf350bba 100644 --- a/pkg/ingress/kube/configmap/config.go +++ b/pkg/ingress/kube/configmap/config.go @@ -34,14 +34,21 @@ const ( type ItemEventHandler = func(name string) type HigressConfig struct { - Tracing *Tracing `json:"tracing,omitempty"` - Gzip *Gzip `json:"gzip,omitempty"` + Tracing *Tracing `json:"tracing,omitempty"` + Gzip *Gzip `json:"gzip,omitempty"` + Downstream *Downstream `json:"downstream,omitempty"` + DisableXEnvoyHeaders bool `json:"disableXEnvoyHeaders,omitempty"` + AddXRealIpHeader bool `json:"addXRealIpHeader,omitempty"` } func NewDefaultHigressConfig() *HigressConfig { + globalOption := NewDefaultGlobalOption() higressConfig := &HigressConfig{ - Tracing: NewDefaultTracing(), - Gzip: NewDefaultGzip(), + Tracing: NewDefaultTracing(), + Gzip: NewDefaultGzip(), + Downstream: globalOption.Downstream, + DisableXEnvoyHeaders: globalOption.DisableXEnvoyHeaders, + AddXRealIpHeader: globalOption.AddXRealIpHeader, } return higressConfig } diff --git a/pkg/ingress/kube/configmap/controller.go b/pkg/ingress/kube/configmap/controller.go index 594a61a5c..4390f9a48 100644 --- a/pkg/ingress/kube/configmap/controller.go +++ b/pkg/ingress/kube/configmap/controller.go @@ -73,9 +73,14 @@ func NewConfigmapMgr(XDSUpdater model.XDSUpdater, namespace string, higressConfi configmapMgr.SetHigressConfig(NewDefaultHigressConfig()) tracingController := NewTracingController(namespace) - gzipController := NewGzipController(namespace) configmapMgr.AddItemControllers(tracingController) + + gzipController := NewGzipController(namespace) configmapMgr.AddItemControllers(gzipController) + + globalOptionController := NewGlobalOptionController(namespace) + configmapMgr.AddItemControllers(globalOptionController) + configmapMgr.initEventHandlers() return configmapMgr diff --git a/pkg/ingress/kube/configmap/global.go b/pkg/ingress/kube/configmap/global.go new file mode 100644 index 000000000..29221b42e --- /dev/null +++ b/pkg/ingress/kube/configmap/global.go @@ -0,0 +1,502 @@ +// Copyright (c) 2022 Alibaba Group Holding Ltd. +// +// Licensed 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 configmap + +import ( + "encoding/json" + "fmt" + "reflect" + "sync/atomic" + + "github.com/alibaba/higress/pkg/ingress/kube/util" + . "github.com/alibaba/higress/pkg/ingress/log" + networking "istio.io/api/networking/v1alpha3" + "istio.io/istio/pkg/config" + "istio.io/istio/pkg/config/schema/gvk" +) + +const ( + higressGlobalEnvoyFilterName = "global-option" + + maxMaxRequestHeadersKb = 8192 + minMaxConcurrentStreams = 1 + maxMaxConcurrentStreams = 2147483647 + minInitialStreamWindowSize = 65535 + maxInitialStreamWindowSize = 2147483647 + minInitialConnectionWindowSize = 65535 + maxInitialConnectionWindowSize = 2147483647 + + defaultIdleTimeout = 180 + defaultMaxRequestHeadersKb = 60 + defaultConnectionBufferLimits = 32768 + defaultMaxConcurrentStreams = 100 + defaultInitialStreamWindowSize = 65535 + defaultInitialConnectionWindowSize = 1048576 + defaultAddXRealIpHeader = false + defaultDisableXEnvoyHeaders = false +) + +// Global configures the behavior of the downstream connection, x-real-ip header and x-envoy headers. +type Global struct { + Downstream *Downstream `json:"downstream,omitempty"` + AddXRealIpHeader bool `json:"addXRealIpHeader,omitempty"` + DisableXEnvoyHeaders bool `json:"disableXEnvoyHeaders,omitempty"` +} + +// Downstream configures the behavior of the downstream connection. +type Downstream struct { + // IdleTimeout limits the time that a connection may be idle and stream idle. + IdleTimeout uint32 `json:"idleTimeout,omitempty"` + // MaxRequestHeadersKb limits the size of request headers allowed. + MaxRequestHeadersKb uint32 `json:"maxRequestHeadersKb,omitempty"` + // ConnectionBufferLimits configures the buffer size limits for connections. + ConnectionBufferLimits uint32 `json:"connectionBufferLimits,omitempty"` + // Http2 configures HTTP/2 specific options. + Http2 *Http2 `json:"http2,omitempty"` +} + +// Http2 configures HTTP/2 specific options. +type Http2 struct { + // MaxConcurrentStreams limits the number of concurrent streams allowed. + MaxConcurrentStreams uint32 `json:"maxConcurrentStreams,omitempty"` + // InitialStreamWindowSize limits the initial window size of stream. + InitialStreamWindowSize uint32 `json:"initialStreamWindowSize,omitempty"` + // InitialConnectionWindowSize limits the initial window size of connection. + InitialConnectionWindowSize uint32 `json:"initialConnectionWindowSize,omitempty"` +} + +// validGlobal validates the global config. +func validGlobal(global *Global) error { + if global == nil { + return nil + } + + if global.Downstream == nil { + return nil + } + + downStream := global.Downstream + + // check maxRequestHeadersKb + if downStream.MaxRequestHeadersKb > maxMaxRequestHeadersKb { + return fmt.Errorf("maxRequestHeadersKb must be less than or equal to 8192") + } + // check http2 + if downStream.Http2 != nil { + // check maxConcurrentStreams + if downStream.Http2.MaxConcurrentStreams < minMaxConcurrentStreams || + downStream.Http2.MaxConcurrentStreams > maxMaxConcurrentStreams { + return fmt.Errorf("http2.maxConcurrentStreams must be between 1 and 2147483647") + } + // check initialStreamWindowSize + if downStream.Http2.InitialStreamWindowSize < minInitialStreamWindowSize || + downStream.Http2.InitialStreamWindowSize > maxInitialStreamWindowSize { + return fmt.Errorf("http2.initialStreamWindowSize must be between 65535 and 2147483647") + } + // check initialConnectionWindowSize + if downStream.Http2.InitialConnectionWindowSize < minInitialConnectionWindowSize || + downStream.Http2.InitialConnectionWindowSize > maxInitialConnectionWindowSize { + return fmt.Errorf("http2.initialConnectionWindowSize must be between 65535 and 2147483647") + } + } + + return nil +} + +// compareGlobal compares the old and new global option. +func compareGlobal(old *Global, new *Global) (Result, error) { + if old == nil && new == nil { + return ResultNothing, nil + } + + if new == nil { + return ResultDelete, nil + } + + if new.Downstream == nil && !new.AddXRealIpHeader && !new.DisableXEnvoyHeaders { + return ResultDelete, nil + } + + if !reflect.DeepEqual(old, new) { + return ResultReplace, nil + } + + return ResultNothing, nil +} + +// deepCopyGlobal deep copies the global option. +func deepCopyGlobal(global *Global) (*Global, error) { + newGlobal := NewDefaultGlobalOption() + bytes, err := json.Marshal(global) + if err != nil { + return nil, err + } + err = json.Unmarshal(bytes, newGlobal) + return newGlobal, err +} + +// NewDefaultGlobalOption returns a default global config. +func NewDefaultGlobalOption() *Global { + return &Global{ + Downstream: NewDefaultDownstream(), + AddXRealIpHeader: defaultAddXRealIpHeader, + DisableXEnvoyHeaders: defaultDisableXEnvoyHeaders, + } +} + +// NewDefaultDownstream returns a default downstream config. +func NewDefaultDownstream() *Downstream { + return &Downstream{ + IdleTimeout: defaultIdleTimeout, + MaxRequestHeadersKb: defaultMaxRequestHeadersKb, + ConnectionBufferLimits: defaultConnectionBufferLimits, + Http2: NewDefaultHttp2(), + } +} + +// NewDefaultHttp2 returns a default http2 config. +func NewDefaultHttp2() *Http2 { + return &Http2{ + MaxConcurrentStreams: defaultMaxConcurrentStreams, + InitialStreamWindowSize: defaultInitialStreamWindowSize, + InitialConnectionWindowSize: defaultInitialConnectionWindowSize, + } +} + +// GlobalOptionController is the controller of downstream config. +type GlobalOptionController struct { + Namespace string + global atomic.Value + Name string + eventHandler ItemEventHandler +} + +// NewGlobalOptionController returns a GlobalOptionController. +func NewGlobalOptionController(namespace string) *GlobalOptionController { + globalOptionController := &GlobalOptionController{ + Namespace: namespace, + global: atomic.Value{}, + Name: "global-option", + } + globalOptionController.SetGlobal(NewDefaultGlobalOption()) + return globalOptionController +} + +func (g *GlobalOptionController) SetGlobal(global *Global) { + g.global.Store(global) +} + +func (g *GlobalOptionController) GetGlobal() *Global { + value := g.global.Load() + if value != nil { + if global, ok := value.(*Global); ok { + return global + } + } + return nil +} + +func (g *GlobalOptionController) GetName() string { + return g.Name +} + +func (g *GlobalOptionController) AddOrUpdateHigressConfig(name util.ClusterNamespacedName, old *HigressConfig, new *HigressConfig) error { + newGlobal := &Global{ + Downstream: new.Downstream, + AddXRealIpHeader: new.AddXRealIpHeader, + DisableXEnvoyHeaders: new.DisableXEnvoyHeaders, + } + + oldGlobal := &Global{ + Downstream: old.Downstream, + AddXRealIpHeader: old.AddXRealIpHeader, + DisableXEnvoyHeaders: old.DisableXEnvoyHeaders, + } + + err := validGlobal(newGlobal) + if err != nil { + IngressLog.Errorf("data:%+v convert to global-option config error, error: %+v", newGlobal, err) + return nil + } + + result, _ := compareGlobal(oldGlobal, newGlobal) + + switch result { + case ResultReplace: + if newGlobalCopy, err := deepCopyGlobal(newGlobal); err != nil { + IngressLog.Infof("global-option config deepcopy error:%v", err) + } else { + g.SetGlobal(newGlobalCopy) + IngressLog.Infof("AddOrUpdate Higress config global-option") + g.eventHandler(higressGlobalEnvoyFilterName) + IngressLog.Infof("send event with filter name:%s", higressGlobalEnvoyFilterName) + } + case ResultDelete: + g.SetGlobal(NewDefaultGlobalOption()) + IngressLog.Infof("Delete Higress config global-option") + g.eventHandler(higressGlobalEnvoyFilterName) + IngressLog.Infof("send event with filter name:%s", higressGlobalEnvoyFilterName) + } + + return nil +} + +func (g *GlobalOptionController) ValidHigressConfig(higressConfig *HigressConfig) error { + if higressConfig == nil { + return nil + } + + if higressConfig.Downstream == nil { + return nil + } + + global := &Global{ + Downstream: higressConfig.Downstream, + AddXRealIpHeader: higressConfig.AddXRealIpHeader, + DisableXEnvoyHeaders: higressConfig.DisableXEnvoyHeaders, + } + + return validGlobal(global) +} + +func (g *GlobalOptionController) ConstructEnvoyFilters() ([]*config.Config, error) { + configPatch := make([]*networking.EnvoyFilter_EnvoyConfigObjectPatch, 0) + global := g.GetGlobal() + if global == nil { + configs := make([]*config.Config, 0) + return configs, nil + } + + namespace := g.Namespace + + if global.AddXRealIpHeader { + addXRealIpStruct := g.constructAddXRealIpHeader() + addXRealIpHeaderConfig := g.generateAddXRealIpHeaderEnvoyFilter(addXRealIpStruct, namespace) + configPatch = append(configPatch, addXRealIpHeaderConfig...) + } + + if global.DisableXEnvoyHeaders { + disableXEnvoyHeadersStruct := g.constructDisableXEnvoyHeaders() + disableXEnvoyHeadersConfig := g.generateDisableXEnvoyHeadersEnvoyFilter(disableXEnvoyHeadersStruct, namespace) + configPatch = append(configPatch, disableXEnvoyHeadersConfig...) + } + + if global.Downstream == nil { + return generateEnvoyFilter(namespace, configPatch), nil + } + + downstreamStruct := g.constructDownstream(global.Downstream) + bufferLimitStruct := g.constructBufferLimit(global.Downstream) + if len(downstreamStruct) == 0 && len(bufferLimitStruct) == 0 { + return generateEnvoyFilter(namespace, configPatch), nil + } + + downstreamConfig := g.generateDownstreamEnvoyFilter(downstreamStruct, bufferLimitStruct, namespace) + configPatch = append(configPatch, downstreamConfig...) + + return generateEnvoyFilter(namespace, configPatch), nil +} + +func generateEnvoyFilter(namespace string, configPatch []*networking.EnvoyFilter_EnvoyConfigObjectPatch) []*config.Config { + configs := make([]*config.Config, 0) + envoyConfig := &config.Config{ + Meta: config.Meta{ + GroupVersionKind: gvk.EnvoyFilter, + Name: higressGlobalEnvoyFilterName, + Namespace: namespace, + }, + Spec: &networking.EnvoyFilter{ + ConfigPatches: configPatch, + }, + } + configs = append(configs, envoyConfig) + return configs +} + +func (g *GlobalOptionController) RegisterItemEventHandler(eventHandler ItemEventHandler) { + g.eventHandler = eventHandler +} + +// generateDownstreamEnvoyFilter generates the downstream envoy filter. +func (g *GlobalOptionController) generateDownstreamEnvoyFilter(downstreamValueStruct string, bufferLimitStruct string, namespace string) []*networking.EnvoyFilter_EnvoyConfigObjectPatch { + downstreamConfig := []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ + { + ApplyTo: networking.EnvoyFilter_NETWORK_FILTER, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_GATEWAY, + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{ + Listener: &networking.EnvoyFilter_ListenerMatch{ + FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{ + Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{ + Name: "envoy.filters.network.http_connection_manager", + }, + }, + }, + }, + }, + Patch: &networking.EnvoyFilter_Patch{ + Operation: networking.EnvoyFilter_Patch_MERGE, + Value: util.BuildPatchStruct(downstreamValueStruct), + }, + }, + { + ApplyTo: networking.EnvoyFilter_LISTENER, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_GATEWAY, + }, + Patch: &networking.EnvoyFilter_Patch{ + Operation: networking.EnvoyFilter_Patch_MERGE, + Value: util.BuildPatchStruct(bufferLimitStruct), + }, + }, + } + return downstreamConfig +} + +// generateAddXRealIpHeaderEnvoyFilter generates the add x-real-ip header envoy filter. +func (g *GlobalOptionController) generateAddXRealIpHeaderEnvoyFilter(addXRealIpHeaderStruct string, namespace string) []*networking.EnvoyFilter_EnvoyConfigObjectPatch { + addXRealIpHeaderConfig := []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ + { + ApplyTo: networking.EnvoyFilter_ROUTE_CONFIGURATION, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_GATEWAY, + }, + Patch: &networking.EnvoyFilter_Patch{ + Operation: networking.EnvoyFilter_Patch_MERGE, + Value: util.BuildPatchStruct(addXRealIpHeaderStruct), + }, + }, + } + return addXRealIpHeaderConfig +} + +// generateDisableXEnvoyHeadersEnvoyFilter generates the disable x-envoy headers envoy filter. +func (g *GlobalOptionController) generateDisableXEnvoyHeadersEnvoyFilter(disableXEnvoyStruct string, namespace string) []*networking.EnvoyFilter_EnvoyConfigObjectPatch { + disableXEnvoyHeadersConfig := []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ + { + ApplyTo: networking.EnvoyFilter_HTTP_FILTER, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_GATEWAY, + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{ + Listener: &networking.EnvoyFilter_ListenerMatch{ + FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{ + Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{ + Name: "envoy.filters.network.http_connection_manager", + SubFilter: &networking.EnvoyFilter_ListenerMatch_SubFilterMatch{ + Name: "envoy.filters.http.router", + }, + }, + }, + }, + }, + }, + Patch: &networking.EnvoyFilter_Patch{ + Operation: networking.EnvoyFilter_Patch_REPLACE, + Value: util.BuildPatchStruct(disableXEnvoyStruct), + }, + }, + } + return disableXEnvoyHeadersConfig +} + +// constructDownstream constructs the downstream config. +func (g *GlobalOptionController) constructDownstream(downstream *Downstream) string { + downstreamConfig := "" + idleTimeout := downstream.IdleTimeout + maxRequestHeadersKb := downstream.MaxRequestHeadersKb + + if downstream.Http2 != nil { + maxConcurrentStreams := downstream.Http2.MaxConcurrentStreams + initialStreamWindowSize := downstream.Http2.InitialStreamWindowSize + initialConnectionWindowSize := downstream.Http2.InitialConnectionWindowSize + + downstreamConfig = fmt.Sprintf(` + { + "name": "envoy.filters.network.http_connection_manager", + "typed_config": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "common_http_protocol_options": { + "idleTimeout": "%ds" + }, + "http2_protocol_options": { + "maxConcurrentStreams": %d, + "initialStreamWindowSize": %d, + "initialConnectionWindowSize": %d + }, + "maxRequestHeadersKb": %d, + "streamIdleTimeout": "%ds" + } + } +`, idleTimeout, maxConcurrentStreams, initialStreamWindowSize, initialConnectionWindowSize, maxRequestHeadersKb, idleTimeout) + return downstreamConfig + } + + downstreamConfig = fmt.Sprintf(` + { + "name": "envoy.filters.network.http_connection_manager", + "typed_config": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "common_http_protocol_options": { + "idleTimeout": "%ds" + }, + "maxRequestHeadersKb": %d, + "streamIdleTimeout": "%ds" + } + } +`, idleTimeout, maxRequestHeadersKb, idleTimeout) + + return downstreamConfig +} + +// constructAddXRealIpHeader constructs the add x-real-ip header config. +func (g *GlobalOptionController) constructAddXRealIpHeader() string { + addXRealIpHeaderStruct := fmt.Sprintf(` + { + "request_headers_to_add": [ + { + "append": false, + "header": { + "key": "x-real-ip", + "value": "%%REQ(X-ENVOY-EXTERNAL-ADDRESS)%%" + } + } + ] + } +`) + return addXRealIpHeaderStruct +} + +// constructDisableXEnvoyHeaders constructs the disable x-envoy headers config. +func (g *GlobalOptionController) constructDisableXEnvoyHeaders() string { + disableXEnvoyHeadersStruct := fmt.Sprintf(` + { + "name": "envoy.filters.http.router", + "typed_config": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router", + "suppress_envoy_headers": true + } + } +`) + return disableXEnvoyHeadersStruct +} + +// constructBufferLimit constructs the buffer limit config. +func (g *GlobalOptionController) constructBufferLimit(downstream *Downstream) string { + return fmt.Sprintf(` + { + "per_connection_buffer_limit_bytes": %d + } + `, downstream.ConnectionBufferLimits) +} diff --git a/pkg/ingress/kube/configmap/global_test.go b/pkg/ingress/kube/configmap/global_test.go new file mode 100644 index 000000000..b2f0cd9ce --- /dev/null +++ b/pkg/ingress/kube/configmap/global_test.go @@ -0,0 +1,252 @@ +// Copyright (c) 2022 Alibaba Group Holding Ltd. +// +// Licensed 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 configmap + +import ( + "testing" + + "github.com/alibaba/higress/pkg/ingress/kube/util" + "github.com/stretchr/testify/assert" +) + +func Test_validGlobal(t *testing.T) { + tests := []struct { + name string + global *Global + wantErr error + }{ + { + name: "default", + global: NewDefaultGlobalOption(), + wantErr: nil, + }, + { + name: "nil", + global: nil, + wantErr: nil, + }, + { + name: "downstream nil", + global: &Global{ + Downstream: nil, + AddXRealIpHeader: true, + DisableXEnvoyHeaders: true, + }, + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validGlobal(tt.global) + assert.Equal(t, tt.wantErr, err) + }) + } +} + +func Test_compareGlobal(t *testing.T) { + tests := []struct { + name string + old *Global + new *Global + want Result + wantErr error + }{ + { + name: "compare both nil", + old: nil, + new: nil, + want: ResultNothing, + wantErr: nil, + }, + { + name: "compare new nil 1", + old: NewDefaultGlobalOption(), + new: nil, + want: ResultDelete, + wantErr: nil, + }, + { + name: "compare new nil 2", + old: NewDefaultGlobalOption(), + new: &Global{}, + want: ResultDelete, + wantErr: nil, + }, + { + name: "compare result equal", + old: NewDefaultGlobalOption(), + new: NewDefaultGlobalOption(), + want: ResultNothing, + wantErr: nil, + }, + { + name: "compare result not equal", + old: NewDefaultGlobalOption(), + new: &Global{ + Downstream: &Downstream{ + IdleTimeout: 1, + }, + AddXRealIpHeader: true, + DisableXEnvoyHeaders: true, + }, + want: ResultReplace, + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := compareGlobal(tt.old, tt.new) + assert.Equal(t, tt.wantErr, err) + assert.Equal(t, tt.want, result) + }) + } +} + +func Test_deepCopyGlobal(t *testing.T) { + tests := []struct { + name string + global *Global + want *Global + wantErr error + }{ + { + name: "deep copy 1", + global: NewDefaultGlobalOption(), + want: NewDefaultGlobalOption(), + wantErr: nil, + }, + { + name: "deep copy 2", + global: &Global{ + Downstream: &Downstream{ + IdleTimeout: 1, + MaxRequestHeadersKb: 9600, + ConnectionBufferLimits: 4096, + Http2: NewDefaultHttp2(), + }, + AddXRealIpHeader: true, + DisableXEnvoyHeaders: true, + }, + want: &Global{ + Downstream: &Downstream{ + IdleTimeout: 1, + MaxRequestHeadersKb: 9600, + ConnectionBufferLimits: 4096, + Http2: NewDefaultHttp2(), + }, + AddXRealIpHeader: true, + DisableXEnvoyHeaders: true, + }, + wantErr: nil, + }, + { + name: "deep copy 3", + global: &Global{ + Downstream: &Downstream{}, + AddXRealIpHeader: true, + DisableXEnvoyHeaders: true, + }, + want: &Global{ + Downstream: NewDefaultDownstream(), + AddXRealIpHeader: true, + DisableXEnvoyHeaders: true, + }, + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + global, err := deepCopyGlobal(tt.global) + assert.Equal(t, tt.wantErr, err) + assert.Equal(t, tt.want, global) + }) + } +} + +func Test_AddOrUpdateHigressConfig(t *testing.T) { + eventPush := "default" + defaultHandler := func(name string) { + eventPush = "push" + } + defaultName := util.ClusterNamespacedName{} + + tests := []struct { + name string + old *HigressConfig + new *HigressConfig + wantErr error + wantEventPush string + wantGlobal *Global + }{ + { + name: "default", + new: NewDefaultHigressConfig(), + old: NewDefaultHigressConfig(), + wantErr: nil, + wantEventPush: "default", + wantGlobal: NewDefaultGlobalOption(), + }, + { + name: "replace and push", + old: NewDefaultHigressConfig(), + new: &HigressConfig{ + Downstream: &Downstream{ + IdleTimeout: 1, + }, + AddXRealIpHeader: true, + DisableXEnvoyHeaders: true, + }, + wantErr: nil, + wantEventPush: "push", + wantGlobal: &Global{ + Downstream: &Downstream{ + IdleTimeout: 1, + MaxRequestHeadersKb: defaultMaxRequestHeadersKb, + ConnectionBufferLimits: defaultConnectionBufferLimits, + Http2: NewDefaultHttp2(), + }, + AddXRealIpHeader: true, + DisableXEnvoyHeaders: true, + }, + }, + { + name: "delete and push", + old: &HigressConfig{ + Downstream: NewDefaultDownstream(), + AddXRealIpHeader: defaultAddXRealIpHeader, + DisableXEnvoyHeaders: defaultDisableXEnvoyHeaders, + }, + new: &HigressConfig{}, + wantErr: nil, + wantEventPush: "push", + wantGlobal: &Global{ + Downstream: NewDefaultDownstream(), + AddXRealIpHeader: defaultAddXRealIpHeader, + DisableXEnvoyHeaders: defaultDisableXEnvoyHeaders, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewGlobalOptionController("higress-namespace") + g.eventHandler = defaultHandler + eventPush = "default" + err := g.AddOrUpdateHigressConfig(defaultName, tt.old, tt.new) + assert.Equal(t, tt.wantErr, err) + assert.Equal(t, tt.wantEventPush, eventPush) + assert.Equal(t, tt.wantGlobal, g.GetGlobal()) + }) + } +} diff --git a/test/e2e/conformance/tests/configmap-global.go b/test/e2e/conformance/tests/configmap-global.go new file mode 100644 index 000000000..c1eef1ecd --- /dev/null +++ b/test/e2e/conformance/tests/configmap-global.go @@ -0,0 +1,562 @@ +// Copyright (c) 2022 Alibaba Group Holding Ltd. +// +// Licensed 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 tests + +import ( + "testing" + + "github.com/alibaba/higress/pkg/ingress/kube/configmap" + "github.com/alibaba/higress/test/e2e/conformance/utils/envoy" + "github.com/alibaba/higress/test/e2e/conformance/utils/kubernetes" + "github.com/alibaba/higress/test/e2e/conformance/utils/suite" +) + +func init() { + Register(ConfigMapGlobalEnvoy) +} + +var ConfigMapGlobalEnvoy = suite.ConformanceTest{ + ShortName: "ConfigMapGlobalEnvoy", + Description: "The Envoy config should contain global config", + Manifests: []string{"tests/configmap-global.yaml"}, + Features: []suite.SupportedFeature{suite.EnvoyConfigConformanceFeature}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + testCases := []struct { + name string + higressConfig *configmap.HigressConfig + envoyAssertion []envoy.Assertion + }{ + { + name: "set config all", + higressConfig: &configmap.HigressConfig{ + Downstream: &configmap.Downstream{ + IdleTimeout: 180, + MaxRequestHeadersKb: 60, + ConnectionBufferLimits: 32768, + Http2: &configmap.Http2{ + MaxConcurrentStreams: 100, + InitialStreamWindowSize: 65535, + InitialConnectionWindowSize: 1048576, + }, + }, + DisableXEnvoyHeaders: true, + AddXRealIpHeader: true, + }, + envoyAssertion: []envoy.Assertion{ + { + Path: "configs.#.dynamic_route_configs.#.route_config", + CheckType: envoy.CheckTypeExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "request_headers_to_add": []interface{}{ + map[string]interface{}{ + "append": false, + "header": map[string]interface{}{ + "key": "x-real-ip", + "value": "%REQ(X-ENVOY-EXTERNAL-ADDRESS)%", + }, + }, + }, + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains.#.filters.#.typed_config", + CheckType: envoy.CheckTypeExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "stat_prefix": "outbound_0.0.0.0_80", + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains.#.filters.#.typed_config.http_filters", + CheckType: envoy.CheckTypeMatch, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "name": "envoy.filters.http.router", + "typed_config": map[string]interface{}{ + "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router", + "suppress_envoy_headers": true, + }, + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener", + CheckType: envoy.CheckTypeExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "per_connection_buffer_limit_bytes": 32768, + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains.#.filters.#.typed_config", + CheckType: envoy.CheckTypeExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "http2_protocol_options": map[string]interface{}{ + "max_concurrent_streams": 100, + "initial_stream_window_size": 65535, + "initial_connection_window_size": 1048576, + }, + "stream_idle_timeout": "180s", + "max_request_headers_kb": 60, + "common_http_protocol_options": map[string]interface{}{ + "idle_timeout": "180s", + }, + }, + }, + }, + }, + { + name: "did not set AddXRealIpHeader", + higressConfig: &configmap.HigressConfig{ + Downstream: &configmap.Downstream{ + IdleTimeout: 180, + MaxRequestHeadersKb: 60, + ConnectionBufferLimits: 32768, + Http2: &configmap.Http2{ + MaxConcurrentStreams: 100, + InitialStreamWindowSize: 65535, + InitialConnectionWindowSize: 1048576, + }, + }, + DisableXEnvoyHeaders: true, + }, + envoyAssertion: []envoy.Assertion{ + { + Path: "configs.#.dynamic_route_configs.#.route_config.request_headers_to_add.#.header", + CheckType: envoy.CheckTypeNotExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "key": "x-real-ip", + "value": "%REQ(X-ENVOY-EXTERNAL-ADDRESS)%", + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains.#.filters.#.typed_config", + CheckType: envoy.CheckTypeExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "stat_prefix": "outbound_0.0.0.0_80", + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains.#.filters.#.typed_config.http_filters", + CheckType: envoy.CheckTypeMatch, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "name": "envoy.filters.http.router", + "typed_config": map[string]interface{}{ + "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router", + "suppress_envoy_headers": true, + }, + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener", + CheckType: envoy.CheckTypeExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "per_connection_buffer_limit_bytes": 32768, + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains.#.filters.#.typed_config", + CheckType: envoy.CheckTypeExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "http2_protocol_options": map[string]interface{}{ + "max_concurrent_streams": 100, + "initial_stream_window_size": 65535, + "initial_connection_window_size": 1048576, + }, + "stream_idle_timeout": "180s", + "max_request_headers_kb": 60, + "common_http_protocol_options": map[string]interface{}{ + "idle_timeout": "180s", + }, + }, + }, + }, + }, + { + name: "did not set DisableXEnvoyHeaders", + higressConfig: &configmap.HigressConfig{ + Downstream: &configmap.Downstream{ + IdleTimeout: 180, + MaxRequestHeadersKb: 60, + ConnectionBufferLimits: 32768, + Http2: &configmap.Http2{ + MaxConcurrentStreams: 100, + InitialStreamWindowSize: 65535, + InitialConnectionWindowSize: 1048576, + }, + }, + AddXRealIpHeader: true, + }, + envoyAssertion: []envoy.Assertion{ + { + Path: "configs.#.dynamic_route_configs.#.route_config", + CheckType: envoy.CheckTypeExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "request_headers_to_add": []interface{}{ + map[string]interface{}{ + "append": false, + "header": map[string]interface{}{ + "key": "x-real-ip", + "value": "%REQ(X-ENVOY-EXTERNAL-ADDRESS)%", + }, + }, + }, + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains.#.filters.#.typed_config", + CheckType: envoy.CheckTypeExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "stat_prefix": "outbound_0.0.0.0_80", + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains.#.filters.#.typed_config.http_filters", + CheckType: envoy.CheckTypeNotExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "typed_config": map[string]interface{}{ + "suppress_envoy_headers": true, + }, + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener", + CheckType: envoy.CheckTypeExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "per_connection_buffer_limit_bytes": 32768, + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains.#.filters.#.typed_config", + CheckType: envoy.CheckTypeExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "http2_protocol_options": map[string]interface{}{ + "max_concurrent_streams": 100, + "initial_stream_window_size": 65535, + "initial_connection_window_size": 1048576, + }, + "stream_idle_timeout": "180s", + "max_request_headers_kb": 60, + "common_http_protocol_options": map[string]interface{}{ + "idle_timeout": "180s", + }, + }, + }, + }, + }, + { + name: "did not set AddXRealIpHeader and DisableXEnvoyHeaders", + higressConfig: &configmap.HigressConfig{ + Downstream: &configmap.Downstream{ + IdleTimeout: 180, + MaxRequestHeadersKb: 60, + ConnectionBufferLimits: 32768, + Http2: &configmap.Http2{ + MaxConcurrentStreams: 100, + InitialStreamWindowSize: 65535, + InitialConnectionWindowSize: 1048576, + }, + }, + }, + envoyAssertion: []envoy.Assertion{ + { + Path: "configs.#.dynamic_route_configs.#.route_config.request_headers_to_add.#.header", + CheckType: envoy.CheckTypeNotExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "key": "x-real-ip", + "value": "%REQ(X-ENVOY-EXTERNAL-ADDRESS)%", + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains.#.filters.#.typed_config", + CheckType: envoy.CheckTypeExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "stat_prefix": "outbound_0.0.0.0_80", + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains.#.filters.#.typed_config.http_filters", + CheckType: envoy.CheckTypeNotExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "typed_config": map[string]interface{}{ + "suppress_envoy_headers": true, + }, + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener", + CheckType: envoy.CheckTypeExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "per_connection_buffer_limit_bytes": 32768, + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains.#.filters.#.typed_config", + CheckType: envoy.CheckTypeExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "http2_protocol_options": map[string]interface{}{ + "max_concurrent_streams": 100, + "initial_stream_window_size": 65535, + "initial_connection_window_size": 1048576, + }, + "stream_idle_timeout": "180s", + "max_request_headers_kb": 60, + "common_http_protocol_options": map[string]interface{}{ + "idle_timeout": "180s", + }, + }, + }, + }, + }, + { + name: "did not set Downstream, will use default value", + higressConfig: &configmap.HigressConfig{ + DisableXEnvoyHeaders: true, + AddXRealIpHeader: true, + }, + envoyAssertion: []envoy.Assertion{ + { + Path: "configs.#.dynamic_route_configs.#.route_config", + CheckType: envoy.CheckTypeExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "request_headers_to_add": []interface{}{ + map[string]interface{}{ + "append": false, + "header": map[string]interface{}{ + "key": "x-real-ip", + "value": "%REQ(X-ENVOY-EXTERNAL-ADDRESS)%", + }, + }, + }, + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains.#.filters.#.typed_config", + CheckType: envoy.CheckTypeExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "stat_prefix": "outbound_0.0.0.0_80", + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains.#.filters.#.typed_config.http_filters", + CheckType: envoy.CheckTypeMatch, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "name": "envoy.filters.http.router", + "typed_config": map[string]interface{}{ + "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router", + "suppress_envoy_headers": true, + }, + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener", + CheckType: envoy.CheckTypeExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "per_connection_buffer_limit_bytes": 32768, + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains.#.filters.#.typed_config", + CheckType: envoy.CheckTypeExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "max_concurrent_streams": 100, + "initial_stream_window_size": 65535, + "initial_connection_window_size": 1048576, + "stream_idle_timeout": "180s", + "max_request_headers_kb": 60, + "idle_timeout": "180s", + }, + }, + }, + }, + { + name: "modify Downstream", + higressConfig: &configmap.HigressConfig{ + Downstream: &configmap.Downstream{ + IdleTimeout: 200, + MaxRequestHeadersKb: 60, + ConnectionBufferLimits: 32768, + Http2: &configmap.Http2{ + MaxConcurrentStreams: 200, + InitialStreamWindowSize: 65535, + InitialConnectionWindowSize: 1048576, + }, + }, + DisableXEnvoyHeaders: true, + AddXRealIpHeader: true, + }, + envoyAssertion: []envoy.Assertion{ + { + Path: "configs.#.dynamic_route_configs.#.route_config", + CheckType: envoy.CheckTypeExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "request_headers_to_add": []interface{}{ + map[string]interface{}{ + "append": false, + "header": map[string]interface{}{ + "key": "x-real-ip", + "value": "%REQ(X-ENVOY-EXTERNAL-ADDRESS)%", + }, + }, + }, + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains.#.filters.#.typed_config", + CheckType: envoy.CheckTypeExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "stat_prefix": "outbound_0.0.0.0_80", + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains.#.filters.#.typed_config.http_filters", + CheckType: envoy.CheckTypeMatch, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "name": "envoy.filters.http.router", + "typed_config": map[string]interface{}{ + "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router", + "suppress_envoy_headers": true, + }, + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener", + CheckType: envoy.CheckTypeExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "per_connection_buffer_limit_bytes": 32768, + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains.#.filters.#.typed_config", + CheckType: envoy.CheckTypeExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "http2_protocol_options": map[string]interface{}{ + "max_concurrent_streams": 200, + "initial_stream_window_size": 65535, + "initial_connection_window_size": 1048576, + }, + "stream_idle_timeout": "200s", + "max_request_headers_kb": 60, + "common_http_protocol_options": map[string]interface{}{ + "idle_timeout": "200s", + }, + }, + }, + }, + }, + { + name: "did not set global config, downstream will use default value", + higressConfig: &configmap.HigressConfig{}, + envoyAssertion: []envoy.Assertion{ + { + Path: "configs.#.dynamic_route_configs.#.route_config.request_headers_to_add.#.header", + CheckType: envoy.CheckTypeNotExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "key": "x-real-ip", + "value": "%REQ(X-ENVOY-EXTERNAL-ADDRESS)%", + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains.#.filters.#.typed_config", + CheckType: envoy.CheckTypeExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "stat_prefix": "outbound_0.0.0.0_80", + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains.#.filters.#.typed_config.http_filters", + CheckType: envoy.CheckTypeNotExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "typed_config": map[string]interface{}{ + "suppress_envoy_headers": true, + }, + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener", + CheckType: envoy.CheckTypeExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "per_connection_buffer_limit_bytes": 32768, + }, + }, + { + Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains.#.filters.#.typed_config", + CheckType: envoy.CheckTypeExist, + TargetNamespace: "higress-system", + ExpectEnvoyConfig: map[string]interface{}{ + "max_concurrent_streams": 100, + "initial_stream_window_size": 65535, + "initial_connection_window_size": 1048576, + "stream_idle_timeout": "180s", + "max_request_headers_kb": 60, + "idle_timeout": "180s", + }, + }, + }, + }, + } + + t.Run("ConfigMap Global Envoy", func(t *testing.T) { + for _, testcase := range testCases { + // apply config + err := kubernetes.ApplyConfigmapDataWithYaml(t, suite.Client, "higress-system", "higress-config", "higress", testcase.higressConfig) + if err != nil { + t.Fatalf("can't apply conifgmap %s in namespace %s for data key %s", "higress-config", "higress-system", "higress") + } + t.Logf("Checking Envoy config for test case %s", testcase.name) + for _, assertion := range testcase.envoyAssertion { + envoy.AssertEnvoyConfig(t, suite.TimeoutConfig, assertion) + } + } + }) + }, +} diff --git a/test/e2e/conformance/tests/configmap-global.yaml b/test/e2e/conformance/tests/configmap-global.yaml new file mode 100644 index 000000000..d73a6187e --- /dev/null +++ b/test/e2e/conformance/tests/configmap-global.yaml @@ -0,0 +1,32 @@ +# Copyright (c) 2022 Alibaba Group Holding Ltd. +# +# Licensed 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. + +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: higress-conformance-infra-configmap-global-test + namespace: higress-conformance-infra +spec: + ingressClassName: higress + rules: + - host: "foo.com" + http: + paths: + - pathType: Prefix + path: "/foo" + backend: + service: + name: infra-backend-v3 + port: + number: 8080 diff --git a/test/e2e/conformance/utils/envoy/envoy.go b/test/e2e/conformance/utils/envoy/envoy.go index 3c219f33b..cbbd662d0 100644 --- a/test/e2e/conformance/utils/envoy/envoy.go +++ b/test/e2e/conformance/utils/envoy/envoy.go @@ -57,9 +57,42 @@ type Assertion struct { func AssertEnvoyConfig(t *testing.T, timeoutConfig cfg.TimeoutConfig, expected Assertion) { options := config.NewDefaultGetEnvoyConfigOptions() options.PodNamespace = expected.TargetNamespace + convertEnvoyConfig := convertNumbersToFloat64(expected.ExpectEnvoyConfig) + if _, ok := convertEnvoyConfig.(map[string]interface{}); !ok { + t.Errorf("failed to convert envoy config number to float64") + return + } + expected.ExpectEnvoyConfig = convertEnvoyConfig.(map[string]interface{}) waitForEnvoyConfig(t, timeoutConfig, options, expected) } +func convertNumbersToFloat64(data interface{}) interface{} { + switch val := data.(type) { + case map[string]interface{}: + result := make(map[string]interface{}) + for key, v := range val { + result[key] = convertNumbersToFloat64(v) + } + return result + case []interface{}: + result := make([]interface{}, len(val)) + for i, v := range val { + result[i] = convertNumbersToFloat64(v) + } + return result + case float64: + return val + case int: + return float64(val) + case int64: + return float64(val) + case float32: + return float64(val) + default: + return data + } +} + // waitForEnvoyConfig waits for the Envoy config to be ready and asserts it. func waitForEnvoyConfig(t *testing.T, timeoutConfig cfg.TimeoutConfig, options *config.GetEnvoyConfigOptions, expected Assertion) { awaitConvergence(t, defaultSuccessThreshold, timeoutConfig.MaxTimeToConsistency, func(elapsed time.Duration) bool { @@ -245,7 +278,7 @@ func findKey(actual interface{}, key string, expectValue interface{}) bool { case reflect.Map: actualValueMap := actual.(map[string]interface{}) for actualKey, actualValue := range actualValueMap { - if actualKey == key && reflect.DeepEqual(convertType(actualValue, expectValue), expectValue) { + if actualKey == key && reflect.DeepEqual(actualValue, expectValue) { return true } if findKey(actualValue, key, expectValue) { @@ -254,34 +287,9 @@ func findKey(actual interface{}, key string, expectValue interface{}) bool { } return false default: - if reflectValue.String() == key && reflect.DeepEqual(convertType(actual, expectValue), expectValue) { + if reflectValue.String() == key && reflect.DeepEqual(actual, expectValue) { return true } return false } } - -// convertType converts the type of the given value to the type of the given target value. -func convertType(value interface{}, targetType interface{}) interface{} { - targetTypeValue := reflect.ValueOf(targetType) - targetTypeKind := targetTypeValue.Kind() - - switch targetTypeKind { - case reflect.Int: - switch value.(type) { - case int: - return value - case float64: - return int(value.(float64)) - } - case reflect.Float64: - switch value.(type) { - case int: - return float64(value.(int)) - case float64: - return value - } - } - - return value -}