feat: add e2e test for envoy filter (#710)

This commit is contained in:
SJC
2023-12-29 21:39:28 +08:00
committed by GitHub
parent 85df257f4e
commit 89c72777e1
33 changed files with 1250 additions and 252 deletions

View File

@@ -26,7 +26,7 @@ header:
- 'VERSION'
- 'tools/'
- 'test/README.md'
- 'pkg/cmd/hgctl/testdata/config'
- 'cmd/hgctl/config/testdata/config'
- 'pkg/cmd/hgctl/manifests'
comment: on-failure

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package hgctl
package config
import (
"encoding/json"
@@ -27,26 +27,61 @@ import (
)
var (
output string
podName string
podNamespace string
BootstrapEnvoyConfigType EnvoyConfigType = "bootstrap"
ClusterEnvoyConfigType EnvoyConfigType = "cluster"
EndpointEnvoyConfigType EnvoyConfigType = "endpoint"
ListenerEnvoyConfigType EnvoyConfigType = "listener"
RouteEnvoyConfigType EnvoyConfigType = "route"
AllEnvoyConfigType EnvoyConfigType = "all"
)
const (
defaultProxyAdminPort = 15000
containerName = "envoy"
)
func retrieveConfigDump(args []string, includeEds bool) ([]byte, error) {
if len(args) != 0 {
podName = args[0]
}
type EnvoyConfigType string
type GetEnvoyConfigOptions struct {
IncludeEds bool
PodName string
PodNamespace string
BindAddress string
Output string
EnvoyConfigType EnvoyConfigType
}
func NewDefaultGetEnvoyConfigOptions() *GetEnvoyConfigOptions {
return &GetEnvoyConfigOptions{
IncludeEds: true,
PodName: "",
PodNamespace: "higress-system",
BindAddress: "localhost",
Output: "json",
EnvoyConfigType: AllEnvoyConfigType,
}
}
func GetEnvoyConfig(config *GetEnvoyConfigOptions) ([]byte, error) {
configDump, err := retrieveConfigDump(config.PodName, config.PodNamespace, config.BindAddress, config.IncludeEds)
if err != nil {
return nil, err
}
if config.EnvoyConfigType == AllEnvoyConfigType {
return configDump, nil
}
resource, err := getXDSResource(config.EnvoyConfigType, configDump)
if err != nil {
return nil, err
}
return formatGatewayConfig(resource, config.Output)
}
func retrieveConfigDump(podName, podNamespace, bindAddress string, includeEds bool) ([]byte, error) {
if podNamespace == "" {
return nil, fmt.Errorf("pod namespace is required")
}
if podName == "" || len(args) == 0 {
if podName == "" {
c, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
if err != nil {
return nil, fmt.Errorf("failed to build kubernetes client: %w", err)
@@ -65,7 +100,7 @@ func retrieveConfigDump(args []string, includeEds bool) ([]byte, error) {
fw, err := portForwarder(types.NamespacedName{
Namespace: podNamespace,
Name: podName,
})
}, bindAddress)
if err != nil {
return nil, err
}
@@ -82,7 +117,7 @@ func retrieveConfigDump(args []string, includeEds bool) ([]byte, error) {
return configDump, nil
}
func portForwarder(nn types.NamespacedName) (kubernetes.PortForwarder, error) {
func portForwarder(nn types.NamespacedName, bindAddress string) (kubernetes.PortForwarder, error) {
c, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
if err != nil {
return nil, fmt.Errorf("build CLI client fail: %w", err)
@@ -149,3 +184,53 @@ func configDumpRequest(address string, includeEds bool) ([]byte, error) {
return io.ReadAll(resp.Body)
}
func getXDSResource(resourceType EnvoyConfigType, configDump []byte) (any, error) {
cd := map[string]any{}
if err := json.Unmarshal(configDump, &cd); err != nil {
return nil, err
}
if resourceType == AllEnvoyConfigType {
return cd, nil
}
configs := cd["configs"]
globalConfigs := configs.([]any)
switch resourceType {
case BootstrapEnvoyConfigType:
for _, config := range globalConfigs {
if config.(map[string]interface{})["@type"] == "type.googleapis.com/envoy.admin.v3.BootstrapConfigDump" {
return config, nil
}
}
case EndpointEnvoyConfigType:
for _, config := range globalConfigs {
if config.(map[string]interface{})["@type"] == "type.googleapis.com/envoy.admin.v3.EndpointsConfigDump" {
return config, nil
}
}
case ClusterEnvoyConfigType:
for _, config := range globalConfigs {
if config.(map[string]interface{})["@type"] == "type.googleapis.com/envoy.admin.v3.ClustersConfigDump" {
return config, nil
}
}
case ListenerEnvoyConfigType:
for _, config := range globalConfigs {
if config.(map[string]interface{})["@type"] == "type.googleapis.com/envoy.admin.v3.ListenersConfigDump" {
return config, nil
}
}
case RouteEnvoyConfigType:
for _, config := range globalConfigs {
if config.(map[string]interface{})["@type"] == "type.googleapis.com/envoy.admin.v3.RoutesConfigDump" {
return config, nil
}
}
default:
return nil, fmt.Errorf("unknown resourceType %s", resourceType)
}
return nil, fmt.Errorf("unknown resourceType %s", resourceType)
}

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package hgctl
package config
import (
"fmt"
@@ -109,7 +109,7 @@ func TestExtractAllConfigDump(t *testing.T) {
t.Run(tc.output, func(t *testing.T) {
configDump, err := fetchGatewayConfig(fw, true)
assert.NoError(t, err)
data, err := GetXDSResource(AllEnvoyConfigType, configDump)
data, err := getXDSResource(AllEnvoyConfigType, configDump)
assert.NoError(t, err)
got, err := formatGatewayConfig(data, tc.output)
assert.NoError(t, err)
@@ -137,7 +137,7 @@ func TestExtractSubResourcesConfigDump(t *testing.T) {
cases := []struct {
output string
expected string
resourceType envoyConfigType
resourceType EnvoyConfigType
}{
{
output: "json",
@@ -192,7 +192,7 @@ func TestExtractSubResourcesConfigDump(t *testing.T) {
t.Run(tc.output, func(t *testing.T) {
configDump, err := fetchGatewayConfig(fw, false)
assert.NoError(t, err)
resource, err := GetXDSResource(tc.resourceType, configDump)
resource, err := getXDSResource(tc.resourceType, configDump)
assert.NoError(t, err)
got, err := formatGatewayConfig(resource, tc.output)
assert.NoError(t, err)

3
go.mod
View File

@@ -238,6 +238,8 @@ require (
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/theupdateframework/notary v0.7.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tonistiigi/fsutil v0.0.0-20220930225714-4638ad635be5 // indirect
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 // indirect
@@ -302,6 +304,7 @@ require (
github.com/evanphx/json-patch/v5 v5.6.0
github.com/google/yamlfmt v0.10.0
github.com/kylelemons/godebug v1.1.0
github.com/tidwall/gjson v1.17.0
helm.sh/helm/v3 v3.7.1
k8s.io/apiextensions-apiserver v0.25.4
knative.dev/networking v0.0.0-20220302134042-e8b2eb995165

6
go.sum
View File

@@ -1582,6 +1582,12 @@ github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIU
github.com/theupdateframework/notary v0.6.1/go.mod h1:MOfgIfmox8s7/7fduvB2xyPPMJCrjRLRizA8OFwpnKY=
github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4DzbAiAiEL3c=
github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnHqbFDGRSlXPO0AhYWw=
github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=

View File

@@ -17,6 +17,7 @@ package hgctl
import (
"fmt"
"github.com/alibaba/higress/cmd/hgctl/config"
"github.com/spf13/cobra"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
)
@@ -45,21 +46,20 @@ func bootstrapConfigCmd() *cobra.Command {
}
func runBootstrapConfig(c *cobra.Command, args []string) error {
configDump, err := retrieveConfigDump(args, false)
if len(args) != 0 {
podName = args[0]
}
envoyConfig, err := config.GetEnvoyConfig(&config.GetEnvoyConfigOptions{
PodName: podName,
PodNamespace: podNamespace,
BindAddress: bindAddress,
Output: output,
EnvoyConfigType: config.BootstrapEnvoyConfigType,
IncludeEds: true,
})
if err != nil {
return err
}
bootstrap, err := GetXDSResource(BootstrapEnvoyConfigType, configDump)
if err != nil {
return err
}
out, err := formatGatewayConfig(bootstrap, output)
if err != nil {
return err
}
_, err = fmt.Fprintln(c.OutOrStdout(), string(out))
_, err = fmt.Fprintln(c.OutOrStdout(), string(envoyConfig))
return err
}

View File

@@ -11,11 +11,13 @@
// 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 hgctl
import (
"fmt"
"github.com/alibaba/higress/cmd/hgctl/config"
"github.com/spf13/cobra"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
)
@@ -44,21 +46,20 @@ func clusterConfigCmd() *cobra.Command {
}
func runClusterConfig(c *cobra.Command, args []string) error {
configDump, err := retrieveConfigDump(args, false)
if len(args) != 0 {
podName = args[0]
}
envoyConfig, err := config.GetEnvoyConfig(&config.GetEnvoyConfigOptions{
PodName: podName,
PodNamespace: podNamespace,
BindAddress: bindAddress,
Output: output,
EnvoyConfigType: config.ClusterEnvoyConfigType,
IncludeEds: true,
})
if err != nil {
return err
}
cluster, err := GetXDSResource(ClusterEnvoyConfigType, configDump)
if err != nil {
return err
}
out, err := formatGatewayConfig(cluster, output)
if err != nil {
return err
}
_, err = fmt.Fprintln(c.OutOrStdout(), string(out))
_, err = fmt.Fprintln(c.OutOrStdout(), string(envoyConfig))
return err
}

View File

@@ -17,11 +17,23 @@ package hgctl
import (
"fmt"
"github.com/alibaba/higress/cmd/hgctl/config"
"github.com/alibaba/higress/pkg/cmd/options"
"github.com/spf13/cobra"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
)
var (
output string
podName string
podNamespace string
)
const (
defaultProxyAdminPort = 15000
containerName = "envoy"
)
func newConfigCommand() *cobra.Command {
cfgCommand := &cobra.Command{
Use: "gateway-config",
@@ -69,11 +81,20 @@ func allConfigCmd() *cobra.Command {
}
func runAllConfig(c *cobra.Command, args []string) error {
configDump, err := retrieveConfigDump(args, true)
if len(args) != 0 {
podName = args[0]
}
envoyConfig, err := config.GetEnvoyConfig(&config.GetEnvoyConfigOptions{
PodName: podName,
PodNamespace: podNamespace,
BindAddress: bindAddress,
Output: output,
EnvoyConfigType: config.AllEnvoyConfigType,
IncludeEds: true,
})
if err != nil {
return err
}
_, err = fmt.Fprintln(c.OutOrStdout(), string(configDump))
_, err = fmt.Fprintln(c.OutOrStdout(), string(envoyConfig))
return err
}

View File

@@ -17,6 +17,7 @@ package hgctl
import (
"fmt"
"github.com/alibaba/higress/cmd/hgctl/config"
"github.com/spf13/cobra"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
)
@@ -45,21 +46,20 @@ func endpointConfigCmd() *cobra.Command {
}
func runEndpointConfig(c *cobra.Command, args []string) error {
configDump, err := retrieveConfigDump(args, true)
if len(args) != 0 {
podName = args[0]
}
envoyConfig, err := config.GetEnvoyConfig(&config.GetEnvoyConfigOptions{
PodName: podName,
PodNamespace: podNamespace,
BindAddress: bindAddress,
Output: output,
EnvoyConfigType: config.EndpointEnvoyConfigType,
IncludeEds: true,
})
if err != nil {
return err
}
endpoint, err := GetXDSResource(EndpointEnvoyConfigType, configDump)
if err != nil {
return err
}
out, err := formatGatewayConfig(endpoint, output)
if err != nil {
return err
}
_, err = fmt.Fprintln(c.OutOrStdout(), string(out))
_, err = fmt.Fprintln(c.OutOrStdout(), string(envoyConfig))
return err
}

View File

@@ -17,6 +17,7 @@ package hgctl
import (
"fmt"
"github.com/alibaba/higress/cmd/hgctl/config"
"github.com/spf13/cobra"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
)
@@ -45,21 +46,20 @@ func listenerConfigCmd() *cobra.Command {
}
func runListenerConfig(c *cobra.Command, args []string) error {
configDump, err := retrieveConfigDump(args, false)
if len(args) != 0 {
podName = args[0]
}
envoyConfig, err := config.GetEnvoyConfig(&config.GetEnvoyConfigOptions{
PodName: podName,
PodNamespace: podNamespace,
BindAddress: bindAddress,
Output: output,
EnvoyConfigType: config.ListenerEnvoyConfigType,
IncludeEds: true,
})
if err != nil {
return err
}
listener, err := GetXDSResource(ListenerEnvoyConfigType, configDump)
if err != nil {
return err
}
out, err := formatGatewayConfig(listener, output)
if err != nil {
return err
}
_, err = fmt.Fprintln(c.OutOrStdout(), string(out))
_, err = fmt.Fprintln(c.OutOrStdout(), string(envoyConfig))
return err
}

View File

@@ -17,6 +17,7 @@ package hgctl
import (
"fmt"
"github.com/alibaba/higress/cmd/hgctl/config"
"github.com/spf13/cobra"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
)
@@ -45,21 +46,20 @@ func routeConfigCmd() *cobra.Command {
}
func runRouteConfig(c *cobra.Command, args []string) error {
configDump, err := retrieveConfigDump(args, false)
if len(args) != 0 {
podName = args[0]
}
envoyConfig, err := config.GetEnvoyConfig(&config.GetEnvoyConfigOptions{
PodName: podName,
PodNamespace: podNamespace,
BindAddress: bindAddress,
Output: output,
EnvoyConfigType: config.RouteEnvoyConfigType,
IncludeEds: true,
})
if err != nil {
return err
}
route, err := GetXDSResource(RouteEnvoyConfigType, configDump)
if err != nil {
return err
}
out, err := formatGatewayConfig(route, output)
if err != nil {
return err
}
_, err = fmt.Fprintln(c.OutOrStdout(), string(out))
_, err = fmt.Fprintln(c.OutOrStdout(), string(envoyConfig))
return err
}

View File

@@ -18,6 +18,7 @@ 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/http"
"github.com/alibaba/higress/test/e2e/conformance/utils/kubernetes"
"github.com/alibaba/higress/test/e2e/conformance/utils/suite"
@@ -25,6 +26,266 @@ import (
func init() {
Register(ConfigmapGzip)
Register(ConfigMapGzipEnvoy)
}
var testCases = []struct {
higressConfig *configmap.HigressConfig
envoyAssertion envoy.Assertion
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"},
},
},
},
envoyAssertion: envoy.Assertion{
Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains",
TargetNamespace: "higress-system",
CheckType: envoy.CheckTypeNotExist,
ExpectEnvoyConfig: map[string]interface{}{
"memory_level": 5,
"compression_level": "COMPRESSION_LEVEL_9",
"window_bits": 12,
"min_content_length": 1024,
"disable_on_etag_header": true,
"content_type": []interface{}{
"text/html",
"text/css",
"text/plain",
"text/xml",
"application/json",
"application/javascript",
"application/xhtml+xml",
"image/svg+xml",
},
},
},
},
{
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"},
},
},
envoyAssertion: envoy.Assertion{
Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains",
TargetNamespace: "higress-system",
CheckType: envoy.CheckTypeExist,
ExpectEnvoyConfig: map[string]interface{}{
"name": "envoy.filters.network.http_connection_manager",
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
"stat_prefix": "outbound_0.0.0.0_80",
"memory_level": 5,
"compression_level": "COMPRESSION_LEVEL_9",
"window_bits": 12,
"min_content_length": 100,
"disable_on_etag_header": true,
"content_type": []interface{}{
"text/html",
"text/css",
"text/plain",
"text/xml",
"application/json",
"application/javascript",
"application/xhtml+xml",
"image/svg+xml",
},
},
},
},
{
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"},
},
},
},
envoyAssertion: envoy.Assertion{
Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains",
TargetNamespace: "higress-system",
CheckType: envoy.CheckTypeExist,
ExpectEnvoyConfig: map[string]interface{}{
"name": "envoy.filters.network.http_connection_manager",
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
"stat_prefix": "outbound_0.0.0.0_80",
"memory_level": 5,
"compression_level": "COMPRESSION_LEVEL_9",
"window_bits": 12,
"min_content_length": 4096,
"disable_on_etag_header": true,
"content_type": []interface{}{
"text/html",
"text/css",
"text/plain",
"text/xml",
"application/json",
"application/javascript",
"application/xhtml+xml",
"image/svg+xml",
},
},
},
},
{
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"},
},
},
},
envoyAssertion: envoy.Assertion{
Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains",
TargetNamespace: "higress-system",
CheckType: envoy.CheckTypeExist,
ExpectEnvoyConfig: map[string]interface{}{
"name": "envoy.filters.network.http_connection_manager",
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
"stat_prefix": "outbound_0.0.0.0_80",
"memory_level": 5,
"compression_level": "COMPRESSION_LEVEL_9",
"window_bits": 12,
"min_content_length": 100,
"disable_on_etag_header": true,
"content_type": []interface{}{
"text/html",
"text/css",
"text/plain",
"text/xml",
"application/javascript",
"application/xhtml+xml",
"image/svg+xml",
},
},
},
},
}
var ConfigmapGzip = suite.ConformanceTest{
@@ -33,171 +294,9 @@ var ConfigmapGzip = suite.ConformanceTest{
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)
for _, testcase := range testCases {
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")
}
@@ -206,3 +305,22 @@ var ConfigmapGzip = suite.ConformanceTest{
})
},
}
var ConfigMapGzipEnvoy = suite.ConformanceTest{
ShortName: "ConfigMapGzipEnvoy",
Description: "The Envoy config should contain gzip config",
Manifests: []string{"tests/configmap-gzip.yaml"},
Features: []suite.SupportedFeature{suite.EnvoyConfigConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
t.Run("ConfigMap Gzip 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")
}
envoy.AssertEnvoyConfig(t, suite.TimeoutConfig, testcase.envoyAssertion)
}
})
},
}

View File

@@ -29,4 +29,5 @@ spec:
service:
name: infra-backend-v3
port:
number: 8080
number: 8080

View File

@@ -0,0 +1,287 @@
/*
Copyright 2022 The Kubernetes Authors.
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 envoy
import (
"fmt"
"reflect"
"testing"
"time"
"github.com/alibaba/higress/cmd/hgctl/config"
cfg "github.com/alibaba/higress/test/e2e/conformance/utils/config"
"github.com/tidwall/gjson"
"k8s.io/apimachinery/pkg/util/wait"
)
type CheckType string
const (
// CheckTypeMatch checks if the actual value matches the expected value.
CheckTypeMatch CheckType = "match"
// CheckTypeExist checks if the actual value exists.
CheckTypeExist CheckType = "exist"
// CheckTypeNotExist checks if the actual value does not exist.
CheckTypeNotExist CheckType = "notexist"
// defaultSuccessThreshold is the default number of times the assertion must succeed in a row.
defaultSuccessThreshold = 3
)
// Assertion defines the assertion to be made on the Envoy config.
// TODO: It can support localization judgment so that this configuration check function will be more universal.
// TODO: Can be used for general e2e tests, rather than just envoy filter scenarios.
type Assertion struct {
// Path is the path of gjson to the value to be asserted.
Path string
// CheckType is the type of assertion to be made.
CheckType CheckType
// ExpectEnvoyConfig is the expected value of the Envoy config.
ExpectEnvoyConfig map[string]interface{}
// TargetNamespace is the namespace of the Envoy pod.
TargetNamespace string
}
// AssertEnvoyConfig asserts the Envoy config.
func AssertEnvoyConfig(t *testing.T, timeoutConfig cfg.TimeoutConfig, expected Assertion) {
options := config.NewDefaultGetEnvoyConfigOptions()
options.PodNamespace = expected.TargetNamespace
waitForEnvoyConfig(t, timeoutConfig, options, expected)
}
// 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 {
allEnvoyConfig := ""
err := wait.Poll(1*time.Second, 10*time.Second, func() (bool, error) {
out, err := config.GetEnvoyConfig(options)
if err != nil {
return false, err
}
allEnvoyConfig = string(out)
return true, nil
})
if err != nil {
return false
}
switch expected.CheckType {
case CheckTypeMatch:
err = assertEnvoyConfigMatch(t, allEnvoyConfig, expected)
case CheckTypeExist:
err = assertEnvoyConfigExist(t, allEnvoyConfig, expected)
case CheckTypeNotExist:
err = assertEnvoyConfigNotExist(t, allEnvoyConfig, expected)
default:
err = fmt.Errorf("unsupported check type %s", expected.CheckType)
}
if err != nil {
return false
}
return true
})
t.Logf("✅ Envoy config checked")
}
// assertEnvoyConfigNotExist asserts the Envoy config does not exist.
func assertEnvoyConfigNotExist(t *testing.T, envoyConfig string, expected Assertion) error {
result := gjson.Get(envoyConfig, expected.Path).Value()
if result == nil {
return nil
}
if !findMustNotExist(t, result, expected.ExpectEnvoyConfig) {
return fmt.Errorf("the expected value %s exists in path '%s'", expected.ExpectEnvoyConfig, expected.Path)
}
return nil
}
// assertEnvoyConfigExist asserts the Envoy config exists.
func assertEnvoyConfigExist(t *testing.T, envoyConfig string, expected Assertion) error {
result := gjson.Get(envoyConfig, expected.Path).Value()
if result == nil {
return fmt.Errorf("failed to get value from path '%s'", expected.Path)
}
if !findMustExist(t, result, expected.ExpectEnvoyConfig) {
return fmt.Errorf("the expected value %s does not exist in path '%s'", expected.ExpectEnvoyConfig, expected.Path)
}
return nil
}
// assertEnvoyConfigMatch asserts the Envoy config matches the expected value.
func assertEnvoyConfigMatch(t *testing.T, envoyConfig string, expected Assertion) error {
result := gjson.Get(envoyConfig, expected.Path).Value()
if result == nil {
return fmt.Errorf("failed to get value from path '%s'", expected.Path)
}
if !match(t, result, expected.ExpectEnvoyConfig) {
return fmt.Errorf("failed to match value from path '%s'", expected.Path)
}
t.Logf("✅ Matched value %s in path '%s'", expected.ExpectEnvoyConfig, expected.Path)
return nil
}
// awaitConvergence runs the given function until it returns 'true' `threshold` times in a row.
// Each failed attempt has a 1s delay; successful attempts have no delay.
func awaitConvergence(t *testing.T, threshold int, maxTimeToConsistency time.Duration, fn func(elapsed time.Duration) bool) {
successes := 0
attempts := 0
start := time.Now()
to := time.After(maxTimeToConsistency)
delay := time.Second
for {
select {
case <-to:
t.Fatalf("timeout while waiting after %d attempts", attempts)
default:
}
completed := fn(time.Now().Sub(start))
attempts++
if completed {
successes++
if successes >= threshold {
return
}
// Skip delay if we have a success
continue
}
successes = 0
select {
// Capture the overall timeout
case <-to:
t.Fatalf("timeout while waiting after %d attempts, %d/%d sucessess", attempts, successes, threshold)
// And the per-try delay
case <-time.After(delay):
}
}
}
// match
// 1. interface{} is a slice: if one of the slice elements matches, the assertion passes
// Notice: can recursively find slices
// 2. interface{} is a map: if all the map elements match, the assertion passes
// 3. interface{} is a field: if the field matches, the assertion passes
func match(t *testing.T, actual interface{}, expected map[string]interface{}) bool {
reflectValue := reflect.ValueOf(actual)
kind := reflectValue.Kind()
switch kind {
case reflect.Slice:
actualValueSlice := actual.([]interface{})
for _, v := range actualValueSlice {
if match(t, v, expected) {
return true
}
}
return false
case reflect.Map:
actualValueMap := actual.(map[string]interface{})
for key, expectValue := range expected {
actualValue, ok := actualValueMap[key]
if !ok {
return false
}
if !reflect.DeepEqual(actualValue, expectValue) {
return false
}
}
return true
default:
return reflect.DeepEqual(actual, expected)
}
}
// findMustExist finds the value of the given path in the given Envoy config.
func findMustExist(t *testing.T, actual interface{}, expected map[string]interface{}) bool {
for key, expectValue := range expected {
// If the key does not exist, the assertion fails.
t.Logf("🔍 Finding key %s", key)
if !findKey(actual, key, expectValue) {
t.Logf("❌ Not found key %s", key)
return false
}
t.Logf("✅ Found key %s", key)
}
return true
}
// findMustNotExist finds the value of the given path in the given Envoy config.
func findMustNotExist(t *testing.T, actual interface{}, expected map[string]interface{}) bool {
for key, expectValue := range expected {
// If the key exists, the assertion fails.
t.Logf("🔍 Finding key %s", key)
if findKey(actual, key, expectValue) {
t.Logf("❌ Found key %s", key)
return false
}
t.Logf("✅ Not found key %s", key)
}
return true
}
// findKey finds the value of the given key in the given Envoy config.
func findKey(actual interface{}, key string, expectValue interface{}) bool {
reflectValue := reflect.ValueOf(actual)
kind := reflectValue.Kind()
switch kind {
case reflect.Slice:
actualValueSlice := actual.([]interface{})
for _, v := range actualValueSlice {
if findKey(v, key, expectValue) {
return true
}
}
return false
case reflect.Map:
actualValueMap := actual.(map[string]interface{})
for actualKey, actualValue := range actualValueMap {
if actualKey == key && reflect.DeepEqual(convertType(actualValue, expectValue), expectValue) {
return true
}
if findKey(actualValue, key, expectValue) {
return true
}
}
return false
default:
if reflectValue.String() == key && reflect.DeepEqual(convertType(actual, expectValue), 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
}

View File

@@ -0,0 +1,462 @@
/*
Copyright 2022 The Kubernetes Authors.
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 envoy
import (
"testing"
)
func Test_match(t *testing.T) {
testCases := []struct {
name string
actual interface{}
expected map[string]interface{}
expectResult bool
}{
{
name: "case 1",
actual: []interface{}{
map[string]interface{}{
"foo": "bar",
},
map[string]interface{}{
"foo": "baz",
},
},
expected: map[string]interface{}{
"foo": "bar",
},
expectResult: true,
},
{
name: "case 2",
actual: []interface{}{
map[string]interface{}{
"foo": "bar",
},
map[string]interface{}{
"foo": "baz",
},
},
expected: map[string]interface{}{
"foo": "bay",
},
expectResult: false,
},
{
name: "case 3",
actual: map[string]interface{}{
"foo": "bar",
"bar": "baz",
"baz": "bay",
},
expected: map[string]interface{}{
"foo": "bar",
},
expectResult: true,
},
{
name: "case 4",
actual: map[string]interface{}{
"foo": "bar",
},
expected: map[string]interface{}{
"foo": "bar",
},
expectResult: true,
},
{
name: "case 5",
actual: map[string]interface{}{
"foo": "bar",
},
expected: map[string]interface{}{
"foo": "baz",
},
expectResult: false,
},
{
name: "case 6",
actual: []interface{}{
[]interface{}{
map[string]interface{}{
"foo": "bar",
},
},
},
expected: map[string]interface{}{
"foo": "bar",
},
expectResult: true,
},
{
name: "case 7",
actual: []interface{}{
map[string]interface{}{
"foo": "bar",
},
[]interface{}{
map[string]interface{}{
"foo": "baz",
},
},
},
expected: map[string]interface{}{
"foo": "baz",
},
expectResult: true,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
result := match(t, testCase.actual, testCase.expected)
if result != testCase.expectResult {
t.Errorf("expected %v, got %v", testCase.expectResult, result)
}
})
}
}
func Test_findMustExist(t *testing.T) {
testCases := []struct {
name string
actual interface{}
expected map[string]interface{}
expectResult bool
}{
{
name: "case 1",
actual: []interface{}{
map[string]interface{}{
"foo": "bar",
},
},
expected: map[string]interface{}{
"foo": "bar",
},
expectResult: true,
},
{
name: "case 2",
actual: []interface{}{
map[string]interface{}{
"foo": "bar",
},
},
expected: map[string]interface{}{
"foo": "baz",
},
expectResult: false,
},
{
name: "case 3",
actual: map[string]interface{}{
"foo": "bar",
"bar": "baz",
"baz": "bay",
},
expected: map[string]interface{}{
"foo": "bar",
},
expectResult: true,
},
{
name: "case 4",
actual: map[string]interface{}{
"foo": "bar",
},
expected: map[string]interface{}{
"foo": "baz",
},
expectResult: false,
},
{
name: "case 5",
actual: []interface{}{
[]interface{}{
map[string]interface{}{
"foo": "bar",
},
},
},
expected: map[string]interface{}{
"foo": "bar",
},
expectResult: true,
},
{
name: "case 6",
actual: []interface{}{
map[string]interface{}{
"foo": "bar",
},
[]interface{}{
map[string]interface{}{
"foo": "baz",
},
},
},
expected: map[string]interface{}{
"foo": "baz",
},
expectResult: true,
},
{
name: "case 7",
actual: []interface{}{
map[string]interface{}{
"foo": "bar",
},
[]interface{}{
map[string]interface{}{
"test": "baz",
},
},
},
expected: map[string]interface{}{
"foo": "bar",
"test": "baz",
},
expectResult: true,
},
{
name: "case 8",
actual: []interface{}{
map[string]interface{}{
"foo": "bar",
"test": "baz",
},
[]interface{}{
map[string]interface{}{
"foo": "baz",
},
},
},
expected: map[string]interface{}{
"foo": "baz",
"test": "baz",
},
expectResult: true,
},
{
name: "case 9",
actual: []interface{}{
map[string]interface{}{
"foo": "bar",
},
[]interface{}{
[]interface{}{
map[string]interface{}{
"foo": "baz",
},
},
},
},
expected: map[string]interface{}{
"foo": "baz",
},
expectResult: true,
},
{
name: "case 9",
actual: []interface{}{
map[string]interface{}{
"foo": "bar",
},
[]interface{}{
[]interface{}{
map[string]interface{}{
"foo": "baz",
},
},
},
map[string]interface{}{
"content": []interface{}{
"one",
"two",
},
},
},
expected: map[string]interface{}{
"foo": "baz",
"content": []interface{}{
"one",
"two",
},
},
expectResult: true,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
result := findMustExist(t, testCase.actual, testCase.expected)
if result != testCase.expectResult {
t.Errorf("expected %v, got %v", testCase.expectResult, result)
}
})
}
}
func Test_findMustNotExist(t *testing.T) {
testCases := []struct {
name string
actual interface{}
expected map[string]interface{}
expectResult bool
}{
{
name: "case 1",
actual: []interface{}{
map[string]interface{}{
"foo": "bar",
},
},
expected: map[string]interface{}{
"foo": "bar",
},
expectResult: false,
},
{
name: "case 2",
actual: []interface{}{
map[string]interface{}{
"foo": "bar",
},
},
expected: map[string]interface{}{
"foo": "baz",
},
expectResult: true,
},
{
name: "case 3",
actual: map[string]interface{}{
"foo": "bar",
"bar": "baz",
"baz": "bay",
},
expected: map[string]interface{}{
"foo": "bar",
},
expectResult: false,
},
{
name: "case 4",
actual: map[string]interface{}{
"foo": "bar",
},
expected: map[string]interface{}{
"foo": "baz",
},
expectResult: true,
},
{
name: "case 5",
actual: []interface{}{
[]interface{}{
map[string]interface{}{
"foo": "bar",
},
},
},
expected: map[string]interface{}{
"foo": "bar",
},
expectResult: false,
},
{
name: "case 6",
actual: []interface{}{
map[string]interface{}{
"foo": "bar",
},
[]interface{}{
map[string]interface{}{
"foo": "baz",
},
},
},
expected: map[string]interface{}{
"foo": "baz",
},
expectResult: false,
},
{
name: "case 7",
actual: []interface{}{
map[string]interface{}{
"foo": "bar",
},
[]interface{}{
map[string]interface{}{
"test": "baz",
},
},
},
expected: map[string]interface{}{
"foo": "bar",
},
expectResult: false,
},
{
name: "case 8",
actual: []interface{}{
map[string]interface{}{
"foo": "bar",
"test": "baz",
},
[]interface{}{
map[string]interface{}{
"foo": "baz",
},
},
},
expected: map[string]interface{}{
"foo": "baz",
"test": "baz",
},
expectResult: false,
},
{
name: "case 9",
actual: []interface{}{
map[string]interface{}{
"foo": "bar",
},
[]interface{}{
[]interface{}{
map[string]interface{}{
"foo": "baz",
},
},
},
},
expected: map[string]interface{}{
"foo": "baz",
},
expectResult: false,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
result := findMustNotExist(t, testCase.actual, testCase.expected)
if result != testCase.expectResult {
t.Errorf("expected %v, got %v", testCase.expectResult, result)
}
})
}
}

View File

@@ -26,4 +26,5 @@ var (
IsWasmPluginTest = flag.Bool("isWasmPluginTest", false, "Determine if run wasm plugin conformance test")
WasmPluginType = flag.String("wasmPluginType", "GO", "Define wasm plugin type, currently supports GO, CPP")
WasmPluginName = flag.String("wasmPluginName", "", "Define wasm plugin name")
IsEnvoyConfigTest = flag.Bool("isEnvoyConfigTest", false, "Determine if run envoy config conformance test")
)

View File

@@ -121,7 +121,7 @@ func FindPodConditionInList(t *testing.T, conditions []v1.PodCondition, condName
return false
}
func ApplyConfigmapDataWithYaml(c client.Client, namespace string, name string, key string, val any) error {
func ApplyConfigmapDataWithYaml(t *testing.T, c client.Client, namespace string, name string, key string, val any) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
@@ -140,8 +140,11 @@ func ApplyConfigmapDataWithYaml(c client.Client, namespace string, name string,
}
cm.Data[key] = data
t.Logf("🏗 Updating %s %s", name, namespace)
if err := c.Update(ctx, cm); err != nil {
return err
}
return nil
}

View File

@@ -31,6 +31,9 @@ const (
EurekaConformanceFeature SupportedFeature = "eureka"
ConsulConformanceFeature SupportedFeature = "consul"
NacosConformanceFeature SupportedFeature = "nacos"
// extended: envoy config
EnvoyConfigConformanceFeature SupportedFeature = "envoy-config"
)
var AllFeatures = sets.Set{}.
@@ -38,7 +41,8 @@ var AllFeatures = sets.Set{}.
Insert(string(DubboConformanceFeature)).
Insert(string(EurekaConformanceFeature)).
Insert(string(ConsulConformanceFeature)).
Insert(string(NacosConformanceFeature))
Insert(string(NacosConformanceFeature)).
Insert(string(EnvoyConfigConformanceFeature))
var ExperimentFeatures = sets.Set{}.
Insert(string(WASMGoConformanceFeature)).

View File

@@ -60,6 +60,9 @@ type Options struct {
// resources such as Gateways should be cleaned up after the run.
CleanupBaseResources bool
TimeoutConfig config.TimeoutConfig
// IsEnvoyConfigTest indicates whether or not the test is for envoy config
IsEnvoyConfigTest bool
}
type WASMOptions struct {
@@ -87,6 +90,8 @@ func New(s Options) *ConformanceTestSuite {
} else {
s.SupportedFeatures.Insert(string(WASMGoConformanceFeature))
}
} else if s.IsEnvoyConfigTest {
s.SupportedFeatures.Insert(string(EnvoyConfigConformanceFeature))
} else if s.EnableAllSupportedFeatures {
s.SupportedFeatures = AllFeatures
}

View File

@@ -51,6 +51,7 @@ func TestHigressConformanceTests(t *testing.T) {
},
GatewayAddress: "localhost",
EnableAllSupportedFeatures: true,
IsEnvoyConfigTest: *flags.IsEnvoyConfigTest,
})
cSuite.Setup(t)