istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/model/envoyfilter_test.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package model
    16  
    17  import (
    18  	"reflect"
    19  	"testing"
    20  
    21  	"google.golang.org/protobuf/types/known/structpb"
    22  
    23  	networking "istio.io/api/networking/v1alpha3"
    24  	"istio.io/istio/pkg/config"
    25  	"istio.io/istio/pkg/util/protomarshal"
    26  )
    27  
    28  // TestEnvoyFilterMatch tests the matching logic for EnvoyFilter, in particular the regex -> prefix optimization
    29  func TestEnvoyFilterMatch(t *testing.T) {
    30  	cases := []struct {
    31  		name                  string
    32  		config                *networking.EnvoyFilter
    33  		expectedVersionPrefix string
    34  		matches               map[string]bool
    35  	}{
    36  		{
    37  			"version prefix match",
    38  			&networking.EnvoyFilter{
    39  				ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
    40  					{
    41  						Patch: &networking.EnvoyFilter_Patch{},
    42  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
    43  							Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `^1\.19.*`},
    44  						},
    45  					},
    46  				},
    47  			},
    48  			"1.19",
    49  			map[string]bool{
    50  				"1.19":         true,
    51  				"1.19.0":       true,
    52  				"1.19-dev.foo": true,
    53  				"1.5":          false,
    54  				"11.19":        false,
    55  				"foo1.19":      false,
    56  			},
    57  		},
    58  		{
    59  			"version prefix mismatch",
    60  			&networking.EnvoyFilter{
    61  				ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
    62  					{
    63  						Patch: &networking.EnvoyFilter_Patch{},
    64  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
    65  							Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `1\.19.*`},
    66  						},
    67  					},
    68  				},
    69  			},
    70  			"",
    71  			map[string]bool{
    72  				"1.19":         true,
    73  				"1.19.0":       true,
    74  				"1.19-dev.foo": true,
    75  				"1.5":          false,
    76  				"11.19":        true,
    77  				"foo1.19":      true,
    78  			},
    79  		},
    80  		{
    81  			"non-numeric",
    82  			&networking.EnvoyFilter{
    83  				ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
    84  					{
    85  						Patch: &networking.EnvoyFilter_Patch{},
    86  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
    87  							Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
    88  						},
    89  					},
    90  				},
    91  			},
    92  			"",
    93  			map[string]bool{
    94  				"1.19":         false,
    95  				"1.19.0":       false,
    96  				"1.19-dev.foo": false,
    97  				"foobar":       true,
    98  			},
    99  		},
   100  	}
   101  	for _, tt := range cases {
   102  		t.Run(tt.name, func(t *testing.T) {
   103  			got := convertToEnvoyFilterWrapper(&config.Config{
   104  				Meta: config.Meta{},
   105  				Spec: tt.config,
   106  			})
   107  			if len(got.Patches[networking.EnvoyFilter_INVALID]) != 1 {
   108  				t.Fatalf("unexpected patches: %v", got.Patches)
   109  			}
   110  			filter := got.Patches[networking.EnvoyFilter_INVALID][0]
   111  			if filter.ProxyPrefixMatch != tt.expectedVersionPrefix {
   112  				t.Errorf("unexpected prefix: got %v wanted %v", filter.ProxyPrefixMatch, tt.expectedVersionPrefix)
   113  			}
   114  			for ver, match := range tt.matches {
   115  				got := proxyMatch(&Proxy{Metadata: &NodeMetadata{IstioVersion: ver}}, filter)
   116  				if got != match {
   117  					t.Errorf("expected %v to match %v, got %v", ver, match, got)
   118  				}
   119  			}
   120  		})
   121  	}
   122  }
   123  
   124  func TestConvertEnvoyFilter(t *testing.T) {
   125  	buildPatchStruct := func(config string) *structpb.Struct {
   126  		val := &structpb.Struct{}
   127  		_ = protomarshal.UnmarshalString(config, val)
   128  		return val
   129  	}
   130  
   131  	cfilter := convertToEnvoyFilterWrapper(&config.Config{
   132  		Meta: config.Meta{Name: "test", Namespace: "testns"},
   133  		Spec: &networking.EnvoyFilter{
   134  			ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   135  				{
   136  					Patch: &networking.EnvoyFilter_Patch{},
   137  					Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   138  						Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
   139  					},
   140  				},
   141  				{ // valid http route patch
   142  					Patch: &networking.EnvoyFilter_Patch{
   143  						Operation: networking.EnvoyFilter_Patch_MERGE,
   144  						Value:     buildPatchStruct(`{"statPrefix": "bar"}`),
   145  					},
   146  					Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   147  						Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
   148  					},
   149  					ApplyTo: networking.EnvoyFilter_HTTP_ROUTE,
   150  				},
   151  				{ // invalid http route patch
   152  					Patch: &networking.EnvoyFilter_Patch{
   153  						Operation: networking.EnvoyFilter_Patch_MERGE,
   154  						Value: buildPatchStruct(`{
   155  							"typed_per_filter_config": {
   156  								"envoy.filters.http.ratelimit": {
   157  									"@type": "type.googleapis.com/thisisaninvalidtype"
   158  								}
   159  							}
   160  						}`),
   161  					},
   162  					Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   163  						Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
   164  					},
   165  					ApplyTo: networking.EnvoyFilter_HTTP_ROUTE,
   166  				},
   167  			},
   168  		},
   169  	})
   170  	if cfilter.Name != "test" && cfilter.Namespace != "testns" {
   171  		t.Errorf("expected name %s got %s and namespace %s got %s", "test", cfilter.Name, "testns", cfilter.Namespace)
   172  	}
   173  	if patches := cfilter.Patches[networking.EnvoyFilter_INVALID]; len(patches) != 1 {
   174  		t.Fatalf("unexpected patches of %v: %v", networking.EnvoyFilter_INVALID, cfilter.Patches)
   175  	}
   176  	if patches := cfilter.Patches[networking.EnvoyFilter_HTTP_ROUTE]; len(patches) != 1 { // check num of invalid http route patches
   177  		t.Fatalf("unexpected patches of %v: %v", networking.EnvoyFilter_HTTP_ROUTE, cfilter.Patches)
   178  	}
   179  }
   180  
   181  func TestKeysApplyingTo(t *testing.T) {
   182  	e := &EnvoyFilterWrapper{
   183  		Patches: map[networking.EnvoyFilter_ApplyTo][]*EnvoyFilterConfigPatchWrapper{
   184  			networking.EnvoyFilter_HTTP_FILTER: {
   185  				{
   186  					Name:      "http",
   187  					Namespace: "ns",
   188  					FullName:  "ns/http",
   189  				},
   190  			},
   191  			networking.EnvoyFilter_NETWORK_FILTER: {
   192  				{
   193  					Name:      "b",
   194  					Namespace: "ns",
   195  					FullName:  "ns/b",
   196  				},
   197  				{
   198  					Name:      "c",
   199  					Namespace: "ns",
   200  					FullName:  "ns/c",
   201  				},
   202  				{
   203  					Name:      "a",
   204  					Namespace: "ns",
   205  					FullName:  "ns/a",
   206  				},
   207  				{
   208  					Name:      "a",
   209  					Namespace: "ns",
   210  					FullName:  "ns/a",
   211  				},
   212  			},
   213  		},
   214  	}
   215  	tests := []struct {
   216  		name    string
   217  		efw     *EnvoyFilterWrapper
   218  		applyTo []networking.EnvoyFilter_ApplyTo
   219  		want    []string
   220  	}{
   221  		{
   222  			name:    "http filters",
   223  			efw:     e,
   224  			applyTo: []networking.EnvoyFilter_ApplyTo{networking.EnvoyFilter_HTTP_FILTER},
   225  			want:    []string{"ns/http"},
   226  		},
   227  		{
   228  			name:    "network filters",
   229  			efw:     e,
   230  			applyTo: []networking.EnvoyFilter_ApplyTo{networking.EnvoyFilter_NETWORK_FILTER},
   231  			want:    []string{"ns/a", "ns/b", "ns/c"},
   232  		},
   233  		{
   234  			name:    "cluster filters",
   235  			efw:     e,
   236  			applyTo: []networking.EnvoyFilter_ApplyTo{networking.EnvoyFilter_CLUSTER},
   237  			want:    []string{},
   238  		},
   239  		{
   240  			name: "route filters",
   241  			efw:  e,
   242  			applyTo: []networking.EnvoyFilter_ApplyTo{
   243  				networking.EnvoyFilter_ROUTE_CONFIGURATION,
   244  				networking.EnvoyFilter_VIRTUAL_HOST,
   245  				networking.EnvoyFilter_HTTP_ROUTE,
   246  			},
   247  			want: []string{},
   248  		},
   249  		{
   250  			name: "http and network filters",
   251  			efw:  e,
   252  			applyTo: []networking.EnvoyFilter_ApplyTo{
   253  				networking.EnvoyFilter_HTTP_FILTER,
   254  				networking.EnvoyFilter_NETWORK_FILTER,
   255  			},
   256  			want: []string{"ns/a", "ns/b", "ns/c", "ns/http"},
   257  		},
   258  	}
   259  	for _, tt := range tests {
   260  		t.Run(tt.name, func(t *testing.T) {
   261  			if got := tt.efw.KeysApplyingTo(tt.applyTo...); !reflect.DeepEqual(got, tt.want) {
   262  				t.Errorf("got %v, want %v", got, tt.want)
   263  			}
   264  		})
   265  	}
   266  }