feat: add gzip global setting in configmap (#660)

This commit is contained in:
Jun
2023-12-18 19:05:29 +08:00
committed by GitHub
parent 8039b82699
commit 3cc1c7877f
8 changed files with 1102 additions and 1 deletions

View File

@@ -35,11 +35,13 @@ type ItemEventHandler = func(name string)
type HigressConfig struct {
Tracing *Tracing `json:"tracing,omitempty"`
Gzip *Gzip `json:"gzip,omitempty"`
}
func NewDefaultHigressConfig() *HigressConfig {
higressConfig := &HigressConfig{
Tracing: NewDefaultTracing(),
Gzip: NewDefaultGzip(),
}
return higressConfig
}

View File

@@ -73,7 +73,9 @@ func NewConfigmapMgr(XDSUpdater model.XDSUpdater, namespace string, higressConfi
configmapMgr.SetHigressConfig(NewDefaultHigressConfig())
tracingController := NewTracingController(namespace)
gzipController := NewGzipController(namespace)
configmapMgr.AddItemControllers(tracingController)
configmapMgr.AddItemControllers(gzipController)
configmapMgr.initEventHandlers()
return configmapMgr

View File

@@ -0,0 +1,336 @@
// 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"
"errors"
"fmt"
"reflect"
"strings"
"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 (
higressGzipEnvoyFilterName = "higress-config-gzip"
compressionStrategyValues = "DEFAULT_STRATEGY,FILTERED,HUFFMAN_ONLY,RLE,FIXED"
compressionLevelValues = "BEST_COMPRESSION,BEST_SPEED,COMPRESSION_LEVEL_1,COMPRESSION_LEVEL_2,COMPRESSION_LEVEL_3,COMPRESSION_LEVEL_4,COMPRESSION_LEVEL_5,COMPRESSION_LEVEL_6,COMPRESSION_LEVEL_7,COMPRESSION_LEVEL_8,COMPRESSION_LEVEL_9"
)
type Gzip struct {
// Flag to control gzip
Enable bool `json:"enable,omitempty"`
MinContentLength int32 `json:"minContentLength,omitempty"`
ContentType []string `json:"contentType,omitempty"`
DisableOnEtagHeader bool `json:"disableOnEtagHeader,omitempty"`
// Value from 1 to 9 that controls the amount of internal memory used by zlib.
// Higher values use more memory, but are faster and produce better compression results. The default value is 5.
MemoryLevel int32 `json:"memoryLevel,omitempty"`
// Value from 9 to 15 that represents the base two logarithmic of the compressors window size.
// Larger window results in better compression at the expense of memory usage.
// The default is 12 which will produce a 4096 bytes window
WindowBits int32 `json:"windowBits,omitempty"`
// Value for Zlibs next output buffer. If not set, defaults to 4096.
ChunkSize int32 `json:"chunkSize,omitempty"`
// A value used for selecting the zlib compression level.
// From COMPRESSION_LEVEL_1 to COMPRESSION_LEVEL_9
// BEST_COMPRESSION == COMPRESSION_LEVEL_9 , BEST_SPEED == COMPRESSION_LEVEL_1
CompressionLevel string `json:"compressionLevel,omitempty"`
// A value used for selecting the zlib compression strategy which is directly related to the characteristics of the content.
// Most of the time “DEFAULT_STRATEGY”
// Value is one of DEFAULT_STRATEGY, FILTERED, HUFFMAN_ONLY, RLE, FIXED
CompressionStrategy string `json:"compressionStrategy,omitempty"`
}
func validGzip(g *Gzip) error {
if g == nil {
return nil
}
if g.MinContentLength <= 0 {
return errors.New("minContentLength can not be less than zero")
}
if len(g.ContentType) == 0 {
return errors.New("content type can not be empty")
}
if !(g.MemoryLevel >= 1 && g.MemoryLevel <= 9) {
return errors.New("memory level need be between 1 and 9")
}
if !(g.WindowBits >= 9 && g.WindowBits <= 15) {
return errors.New("window bits need be between 9 and 15")
}
if g.ChunkSize <= 0 {
return errors.New("chunk size need be large than zero")
}
compressionLevels := strings.Split(compressionLevelValues, ",")
isFound := false
for _, v := range compressionLevels {
if g.CompressionLevel == v {
isFound = true
break
}
}
if !isFound {
return fmt.Errorf("compressionLevel need be one of %s", compressionLevelValues)
}
isFound = false
compressionStrategies := strings.Split(compressionStrategyValues, ",")
for _, v := range compressionStrategies {
if g.CompressionStrategy == v {
isFound = true
break
}
}
if !isFound {
return fmt.Errorf("compressionStrategy need be one of %s", compressionStrategyValues)
}
return nil
}
func compareGzip(old *Gzip, new *Gzip) (Result, error) {
if old == nil && new == nil {
return ResultNothing, nil
}
if new == nil {
return ResultDelete, nil
}
if !reflect.DeepEqual(old, new) {
return ResultReplace, nil
}
return ResultNothing, nil
}
func deepCopyGzip(gzip *Gzip) (*Gzip, error) {
newGzip := NewDefaultGzip()
bytes, err := json.Marshal(gzip)
if err != nil {
return nil, err
}
err = json.Unmarshal(bytes, newGzip)
return newGzip, err
}
func NewDefaultGzip() *Gzip {
gzip := &Gzip{
Enable: false,
MinContentLength: 1024,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
}
return gzip
}
type GzipController struct {
Namespace string
gzip atomic.Value
Name string
eventHandler ItemEventHandler
}
func NewGzipController(namespace string) *GzipController {
gzipController := &GzipController{
Namespace: namespace,
gzip: atomic.Value{},
Name: "gzip",
}
gzipController.SetGzip(NewDefaultGzip())
return gzipController
}
func (g *GzipController) GetName() string {
return g.Name
}
func (t *GzipController) SetGzip(gzip *Gzip) {
t.gzip.Store(gzip)
}
func (g *GzipController) GetGzip() *Gzip {
value := g.gzip.Load()
if value != nil {
if gzip, ok := value.(*Gzip); ok {
return gzip
}
}
return nil
}
func (g *GzipController) AddOrUpdateHigressConfig(name util.ClusterNamespacedName, old *HigressConfig, new *HigressConfig) error {
if err := validGzip(new.Gzip); err != nil {
IngressLog.Errorf("data:%+v convert to gzip , error: %+v", new.Gzip, err)
return nil
}
result, _ := compareGzip(old.Gzip, new.Gzip)
switch result {
case ResultReplace:
if newGzip, err := deepCopyGzip(new.Gzip); err != nil {
IngressLog.Infof("gzip deepcopy error:%v", err)
} else {
g.SetGzip(newGzip)
IngressLog.Infof("AddOrUpdate Higress config gzip")
g.eventHandler(higressGzipEnvoyFilterName)
IngressLog.Infof("send event with filter name:%s", higressGzipEnvoyFilterName)
}
case ResultDelete:
g.SetGzip(NewDefaultGzip())
IngressLog.Infof("Delete Higress config gzip")
g.eventHandler(higressGzipEnvoyFilterName)
IngressLog.Infof("send event with filter name:%s", higressGzipEnvoyFilterName)
}
return nil
}
func (g *GzipController) ValidHigressConfig(higressConfig *HigressConfig) error {
if higressConfig == nil {
return nil
}
if higressConfig.Gzip == nil {
return nil
}
return validGzip(higressConfig.Gzip)
}
func (g *GzipController) ConstructEnvoyFilters() ([]*config.Config, error) {
configs := make([]*config.Config, 0)
gzip := g.GetGzip()
namespace := g.Namespace
if gzip == nil {
return configs, nil
}
if gzip.Enable == false {
return configs, nil
}
gzipStruct := g.constructGzipStruct(gzip, namespace)
if len(gzipStruct) == 0 {
return configs, nil
}
config := &config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.EnvoyFilter,
Name: higressGzipEnvoyFilterName,
Namespace: namespace,
},
Spec: &networking.EnvoyFilter{
ConfigPatches: []*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_INSERT_BEFORE,
Value: util.BuildPatchStruct(gzipStruct),
},
},
},
},
}
configs = append(configs, config)
return configs, nil
}
func (g *GzipController) RegisterItemEventHandler(eventHandler ItemEventHandler) {
g.eventHandler = eventHandler
}
func (g *GzipController) constructGzipStruct(gzip *Gzip, namespace string) string {
gzipConfig := ""
contentType := ""
index := 0
for _, v := range gzip.ContentType {
contentType = contentType + fmt.Sprintf("\"%s\"", v)
if index < len(gzip.ContentType)-1 {
contentType = contentType + ","
}
index++
}
structFmt := `{
"name": "envoy.filters.http.compressor",
"typed_config": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor",
"response_direction_config": {
"common_config": {
"min_content_length": %d,
"content_type": [%s],
"disable_on_etag_header": %t
}
},
"request_direction_config": {
"common_config": {
"enabled": {
"default_value": false,
"runtime_key": "request_compressor_enabled"
}
}
},
"compressor_library": {
"name": "text_optimized",
"typed_config": {
"@type": "type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip",
"memory_level": %d,
"window_bits": %d,
"check_size": %d,
"compression_level": "%s",
"compression_strategy": "%s"
}
}
}
}`
gzipConfig = fmt.Sprintf(structFmt, gzip.MinContentLength, contentType, gzip.DisableOnEtagHeader,
gzip.MemoryLevel, gzip.WindowBits, gzip.ChunkSize, gzip.CompressionLevel, gzip.CompressionStrategy)
return gzipConfig
}

View File

@@ -0,0 +1,495 @@
// 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 (
"errors"
"fmt"
"github.com/alibaba/higress/pkg/ingress/kube/util"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_validGzip(t *testing.T) {
tests := []struct {
name string
gzip *Gzip
wantErr error
}{
{
name: "default",
gzip: &Gzip{
Enable: false,
MinContentLength: 1024,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
},
wantErr: nil,
},
{
name: "nil",
gzip: nil,
wantErr: nil,
},
{
name: "no content type",
gzip: &Gzip{
Enable: false,
MinContentLength: 1024,
ContentType: []string{},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
},
wantErr: errors.New("content type can not be empty"),
},
{
name: "MinContentLength less than zero",
gzip: &Gzip{
Enable: false,
MinContentLength: 0,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
},
wantErr: errors.New("minContentLength can not be less than zero"),
},
{
name: "MemoryLevel less than 1",
gzip: &Gzip{
Enable: false,
MinContentLength: 1024,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
},
wantErr: errors.New("memory level need be between 1 and 9"),
},
{
name: "WindowBits less than 9",
gzip: &Gzip{
Enable: false,
MinContentLength: 1024,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 8,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
},
wantErr: errors.New("window bits need be between 9 and 15"),
},
{
name: "ChunkSize less than zero",
gzip: &Gzip{
Enable: false,
MinContentLength: 1024,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
},
wantErr: errors.New("chunk size need be large than zero"),
},
{
name: "CompressionLevel is not right",
gzip: &Gzip{
Enable: false,
MinContentLength: 1024,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSIONA",
CompressionStrategy: "DEFAULT_STRATEGY",
},
wantErr: fmt.Errorf("compressionLevel need be one of %s", compressionLevelValues),
},
{
name: "CompressionStrategy is not right",
gzip: &Gzip{
Enable: false,
MinContentLength: 1024,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGYA",
},
wantErr: fmt.Errorf("compressionStrategy need be one of %s", compressionStrategyValues),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := validGzip(tt.gzip); err != nil {
assert.Equal(t, tt.wantErr, err)
}
})
}
}
func Test_compareGzip(t *testing.T) {
tests := []struct {
name string
old *Gzip
new *Gzip
wantResult Result
wantErr error
}{
{
name: "compare both nil",
old: nil,
new: nil,
wantResult: ResultNothing,
wantErr: nil,
},
{
name: "compare result delete",
old: &Gzip{
Enable: false,
MinContentLength: 1024,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
},
new: nil,
wantResult: ResultDelete,
wantErr: nil,
},
{
name: "compare result equal",
old: &Gzip{
Enable: false,
MinContentLength: 1024,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
},
new: &Gzip{
Enable: false,
MinContentLength: 1024,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
},
wantResult: ResultNothing,
wantErr: nil,
},
{
name: "compare result replace",
old: &Gzip{
Enable: false,
MinContentLength: 1024,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
},
new: &Gzip{
Enable: true,
MinContentLength: 1024,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
},
wantResult: ResultReplace,
wantErr: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := compareGzip(tt.old, tt.new)
assert.Equal(t, tt.wantResult, result)
assert.Equal(t, tt.wantErr, err)
})
}
}
func Test_deepCopyGzip(t *testing.T) {
tests := []struct {
name string
gzip *Gzip
wantGzip *Gzip
wantErr error
}{
{
name: "deep copy",
gzip: &Gzip{
Enable: false,
MinContentLength: 1024,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
},
wantGzip: &Gzip{
Enable: false,
MinContentLength: 1024,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
},
wantErr: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gzip, err := deepCopyGzip(tt.gzip)
assert.Equal(t, tt.wantGzip, gzip)
assert.Equal(t, tt.wantErr, err)
})
}
}
func TestGzipController_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
wantGzip *Gzip
}{
{
name: "default",
old: &HigressConfig{
Gzip: NewDefaultGzip(),
},
new: &HigressConfig{
Gzip: NewDefaultGzip(),
},
wantErr: nil,
wantEventPush: "default",
wantGzip: NewDefaultGzip(),
},
{
name: "replace and push 1",
old: &HigressConfig{
Gzip: NewDefaultGzip(),
},
new: &HigressConfig{
Gzip: &Gzip{
Enable: true,
MinContentLength: 1024,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
},
},
wantErr: nil,
wantEventPush: "push",
wantGzip: &Gzip{
Enable: true,
MinContentLength: 1024,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
},
},
{
name: "replace and push 2",
old: &HigressConfig{
Gzip: &Gzip{
Enable: true,
MinContentLength: 1024,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
},
},
new: &HigressConfig{
Gzip: &Gzip{
Enable: true,
MinContentLength: 2048,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
},
},
wantErr: nil,
wantEventPush: "push",
wantGzip: &Gzip{
Enable: true,
MinContentLength: 2048,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
},
},
{
name: "replace and push 3",
old: &HigressConfig{
Gzip: &Gzip{
Enable: true,
MinContentLength: 1024,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
},
},
new: &HigressConfig{
Gzip: &Gzip{
Enable: false,
MinContentLength: 2048,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
},
},
wantErr: nil,
wantEventPush: "push",
wantGzip: &Gzip{
Enable: false,
MinContentLength: 2048,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
},
},
{
name: "delete and push",
old: &HigressConfig{
Gzip: &Gzip{
Enable: true,
MinContentLength: 1024,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
},
},
new: &HigressConfig{
Gzip: nil,
},
wantErr: nil,
wantEventPush: "push",
wantGzip: NewDefaultGzip(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewGzipController("higress-system")
g.eventHandler = defaultHandler
eventPush = "default"
err := g.AddOrUpdateHigressConfig(defaultName, tt.old, tt.new)
assert.Equal(t, tt.wantEventPush, eventPush)
assert.Equal(t, tt.wantErr, err)
assert.Equal(t, tt.wantGzip, g.GetGzip())
})
}
}

View File

@@ -117,7 +117,7 @@ func validTracing(t *Tracing) error {
}
}
if tracerNum != 1 {
if tracerNum != 1 && t.Enable == true {
return errors.New("only one of skywalkingzipkin and opentelemetry configuration can be set")
}
return nil

View File

@@ -0,0 +1,208 @@
// 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/http"
"github.com/alibaba/higress/test/e2e/conformance/utils/kubernetes"
"github.com/alibaba/higress/test/e2e/conformance/utils/suite"
)
func init() {
Register(ConfigmapGzip)
}
var ConfigmapGzip = suite.ConformanceTest{
ShortName: "ConfigmapGzip",
Description: "The Ingress in the higress-conformance-infra namespace uses the configmap gzip.",
Manifests: []string{"tests/configmap-gzip.yaml"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []struct {
higressConfig *configmap.HigressConfig
httpAssert http.Assertion
}{
{
higressConfig: &configmap.HigressConfig{
Gzip: &configmap.Gzip{
Enable: false,
MinContentLength: 1024,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
},
},
httpAssert: http.Assertion{
Meta: http.AssertionMeta{
TestCaseName: "case1: disable gzip output",
TargetBackend: "web-backend",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/foo",
Method: "GET",
Headers: map[string]string{
"Accept-Encoding": "*",
},
},
},
Response: http.AssertionResponse{
ExpectedResponseNoRequest: true,
ExpectedResponse: http.Response{
StatusCode: 200,
AbsentHeaders: []string{"content-encoding"},
},
},
},
},
{
higressConfig: &configmap.HigressConfig{
Gzip: &configmap.Gzip{
Enable: true,
MinContentLength: 100,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
},
},
httpAssert: http.Assertion{
Meta: http.AssertionMeta{
TestCaseName: "case2: enable gzip output",
TargetBackend: "web-backend",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/foo",
Method: "GET",
Headers: map[string]string{
"Accept-Encoding": "*",
},
},
},
Response: http.AssertionResponse{
ExpectedResponseNoRequest: true,
ExpectedResponse: http.Response{
StatusCode: 200,
},
AdditionalResponseHeaders: map[string]string{"content-encoding": "gzip"},
},
},
},
{
higressConfig: &configmap.HigressConfig{
Gzip: &configmap.Gzip{
Enable: true,
MinContentLength: 4096,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/json", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
},
},
httpAssert: http.Assertion{
Meta: http.AssertionMeta{
TestCaseName: "case3: disable gzip output because content length less hhan 4096 ",
TargetBackend: "web-backend",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/foo",
Method: "GET",
Headers: map[string]string{
"Accept-Encoding": "*",
},
},
},
Response: http.AssertionResponse{
ExpectedResponseNoRequest: true,
ExpectedResponse: http.Response{
StatusCode: 200,
AbsentHeaders: []string{"content-encoding"},
},
},
},
},
{
higressConfig: &configmap.HigressConfig{
Gzip: &configmap.Gzip{
Enable: true,
MinContentLength: 100,
ContentType: []string{"text/html", "text/css", "text/plain", "text/xml", "application/javascript", "application/xhtml+xml", "image/svg+xml"},
DisableOnEtagHeader: true,
MemoryLevel: 5,
WindowBits: 12,
ChunkSize: 4096,
CompressionLevel: "BEST_COMPRESSION",
CompressionStrategy: "DEFAULT_STRATEGY",
},
},
httpAssert: http.Assertion{
Meta: http.AssertionMeta{
TestCaseName: "case4: disable gzip output because application/json missed in content types ",
TargetBackend: "web-backend",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/foo",
Method: "GET",
Headers: map[string]string{
"Accept-Encoding": "*",
},
},
},
Response: http.AssertionResponse{
ExpectedResponseNoRequest: true,
ExpectedResponse: http.Response{
StatusCode: 200,
AbsentHeaders: []string{"content-encoding"},
},
},
},
},
}
t.Run("Configmap Gzip", func(t *testing.T) {
for _, testcase := range testcases {
err := kubernetes.ApplyConfigmapDataWithYaml(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")
}
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase.httpAssert)
}
})
},
}

View File

@@ -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-gzip-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

View File

@@ -15,6 +15,7 @@ package kubernetes
import (
"context"
"sigs.k8s.io/yaml"
"strings"
"testing"
"time"
@@ -119,3 +120,28 @@ func FindPodConditionInList(t *testing.T, conditions []v1.PodCondition, condName
t.Logf("⌛️ %s was not in conditions list", condName)
return false
}
func ApplyConfigmapDataWithYaml(c client.Client, namespace string, name string, key string, val any) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cm := &v1.ConfigMap{}
if err := c.Get(ctx, client.ObjectKey{Namespace: namespace, Name: name}, cm); err != nil {
return err
}
y, err := yaml.Marshal(val)
if err != nil {
return err
}
data := string(y)
if cm.Data == nil {
cm.Data = make(map[string]string, 0)
}
cm.Data[key] = data
if err := c.Update(ctx, cm); err != nil {
return err
}
return nil
}