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 }