mirror of
https://github.com/alibaba/higress.git
synced 2026-03-04 00:20:50 +08:00
feat: add e2e test for envoy filter (#710)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
3
go.mod
@@ -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
6
go.sum
@@ -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=
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
@@ -29,4 +29,5 @@ spec:
|
||||
service:
|
||||
name: infra-backend-v3
|
||||
port:
|
||||
number: 8080
|
||||
number: 8080
|
||||
|
||||
|
||||
287
test/e2e/conformance/utils/envoy/envoy.go
Normal file
287
test/e2e/conformance/utils/envoy/envoy.go
Normal 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
|
||||
}
|
||||
462
test/e2e/conformance/utils/envoy/envoy_test.go
Normal file
462
test/e2e/conformance/utils/envoy/envoy_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)).
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ func TestHigressConformanceTests(t *testing.T) {
|
||||
},
|
||||
GatewayAddress: "localhost",
|
||||
EnableAllSupportedFeatures: true,
|
||||
IsEnvoyConfigTest: *flags.IsEnvoyConfigTest,
|
||||
})
|
||||
|
||||
cSuite.Setup(t)
|
||||
|
||||
Reference in New Issue
Block a user