istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/model/proxy_config_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 "testing" 19 "time" 20 21 "google.golang.org/protobuf/proto" 22 "google.golang.org/protobuf/types/known/durationpb" 23 wrappers "google.golang.org/protobuf/types/known/wrapperspb" 24 25 "istio.io/api/annotation" 26 meshconfig "istio.io/api/mesh/v1alpha1" 27 "istio.io/api/networking/v1beta1" 28 istioTypes "istio.io/api/type/v1beta1" 29 "istio.io/istio/pkg/config" 30 "istio.io/istio/pkg/config/mesh" 31 "istio.io/istio/pkg/config/schema/gvk" 32 "istio.io/istio/pkg/test/util/assert" 33 "istio.io/istio/pkg/util/protomarshal" 34 ) 35 36 var now = time.Now() 37 38 const istioRootNamespace = "istio-system" 39 40 func TestConvertToMeshConfigProxyConfig(t *testing.T) { 41 cases := []struct { 42 name string 43 pc *v1beta1.ProxyConfig 44 expected *meshconfig.ProxyConfig 45 }{ 46 { 47 name: "concurrency", 48 pc: &v1beta1.ProxyConfig{ 49 Concurrency: &wrappers.Int32Value{Value: 3}, 50 }, 51 expected: &meshconfig.ProxyConfig{ 52 Concurrency: &wrappers.Int32Value{Value: 3}, 53 }, 54 }, 55 { 56 name: "environment variables", 57 pc: &v1beta1.ProxyConfig{ 58 EnvironmentVariables: map[string]string{ 59 "a": "b", 60 "c": "d", 61 }, 62 }, 63 expected: &meshconfig.ProxyConfig{ 64 ProxyMetadata: map[string]string{ 65 "a": "b", 66 "c": "d", 67 }, 68 }, 69 }, 70 } 71 72 for _, tc := range cases { 73 converted := toMeshConfigProxyConfig(tc.pc) 74 assert.Equal(t, converted, tc.expected) 75 } 76 } 77 78 func TestMergeWithPrecedence(t *testing.T) { 79 cases := []struct { 80 name string 81 first *meshconfig.ProxyConfig 82 second *meshconfig.ProxyConfig 83 expected *meshconfig.ProxyConfig 84 }{ 85 { 86 name: "concurrency", 87 first: &meshconfig.ProxyConfig{ 88 Concurrency: v(1), 89 }, 90 second: &meshconfig.ProxyConfig{ 91 Concurrency: v(2), 92 }, 93 expected: &meshconfig.ProxyConfig{ 94 Concurrency: v(1), 95 }, 96 }, 97 { 98 name: "concurrency value 0", 99 first: &meshconfig.ProxyConfig{ 100 Concurrency: v(0), 101 }, 102 second: &meshconfig.ProxyConfig{ 103 Concurrency: v(2), 104 }, 105 expected: &meshconfig.ProxyConfig{ 106 Concurrency: v(0), 107 }, 108 }, 109 { 110 name: "source concurrency nil", 111 first: &meshconfig.ProxyConfig{ 112 Concurrency: nil, 113 }, 114 second: &meshconfig.ProxyConfig{ 115 Concurrency: v(2), 116 }, 117 expected: &meshconfig.ProxyConfig{ 118 Concurrency: v(2), 119 }, 120 }, 121 { 122 name: "dest concurrency nil", 123 first: &meshconfig.ProxyConfig{ 124 Concurrency: v(2), 125 }, 126 second: &meshconfig.ProxyConfig{ 127 Concurrency: nil, 128 }, 129 expected: &meshconfig.ProxyConfig{ 130 Concurrency: v(2), 131 }, 132 }, 133 { 134 name: "both concurrency nil", 135 first: &meshconfig.ProxyConfig{ 136 Concurrency: nil, 137 }, 138 second: &meshconfig.ProxyConfig{ 139 Concurrency: nil, 140 }, 141 expected: &meshconfig.ProxyConfig{ 142 Concurrency: nil, 143 }, 144 }, 145 { 146 name: "envvars", 147 first: &meshconfig.ProxyConfig{ 148 ProxyMetadata: map[string]string{ 149 "a": "x", 150 "b": "y", 151 }, 152 }, 153 second: &meshconfig.ProxyConfig{ 154 ProxyMetadata: map[string]string{ 155 "a": "z", 156 "b": "y", 157 "c": "d", 158 }, 159 }, 160 expected: &meshconfig.ProxyConfig{ 161 ProxyMetadata: map[string]string{ 162 "a": "x", 163 "b": "y", 164 "c": "d", 165 }, 166 }, 167 }, 168 { 169 name: "empty envars merge with populated", 170 first: &meshconfig.ProxyConfig{ 171 ProxyMetadata: map[string]string{}, 172 }, 173 second: &meshconfig.ProxyConfig{ 174 ProxyMetadata: map[string]string{ 175 "a": "z", 176 "b": "y", 177 "c": "d", 178 }, 179 }, 180 expected: &meshconfig.ProxyConfig{ 181 ProxyMetadata: map[string]string{ 182 "a": "z", 183 "b": "y", 184 "c": "d", 185 }, 186 }, 187 }, 188 { 189 name: "nil proxyconfig", 190 first: nil, 191 second: &meshconfig.ProxyConfig{ 192 ProxyMetadata: map[string]string{ 193 "a": "z", 194 "b": "y", 195 "c": "d", 196 }, 197 }, 198 expected: &meshconfig.ProxyConfig{ 199 ProxyMetadata: map[string]string{ 200 "a": "z", 201 "b": "y", 202 "c": "d", 203 }, 204 }, 205 }, 206 { 207 name: "terminationDrainDuration", 208 first: &meshconfig.ProxyConfig{ 209 TerminationDrainDuration: durationpb.New(500 * time.Millisecond), 210 }, 211 second: &meshconfig.ProxyConfig{ 212 TerminationDrainDuration: durationpb.New(5 * time.Second), 213 }, 214 expected: &meshconfig.ProxyConfig{ 215 TerminationDrainDuration: durationpb.New(500 * time.Millisecond), 216 }, 217 }, 218 { 219 name: "tracing is empty", 220 first: &meshconfig.ProxyConfig{ 221 Tracing: &meshconfig.Tracing{}, 222 }, 223 second: &meshconfig.ProxyConfig{ 224 Tracing: mesh.DefaultProxyConfig().GetTracing(), 225 }, 226 expected: &meshconfig.ProxyConfig{ 227 Tracing: &meshconfig.Tracing{}, 228 }, 229 }, 230 { 231 name: "tracing is not default", 232 first: &meshconfig.ProxyConfig{ 233 Tracing: &meshconfig.Tracing{ 234 Tracer: &meshconfig.Tracing_Datadog_{}, 235 }, 236 }, 237 second: &meshconfig.ProxyConfig{ 238 Tracing: mesh.DefaultProxyConfig().GetTracing(), 239 }, 240 expected: &meshconfig.ProxyConfig{ 241 Tracing: &meshconfig.Tracing{ 242 Tracer: &meshconfig.Tracing_Datadog_{}, 243 }, 244 }, 245 }, 246 } 247 248 for _, tc := range cases { 249 merged := mergeWithPrecedence(tc.first, tc.second) 250 assert.Equal(t, merged, tc.expected) 251 } 252 } 253 254 func TestEffectiveProxyConfig(t *testing.T) { 255 cases := []struct { 256 name string 257 configs []config.Config 258 defaultConfig *meshconfig.ProxyConfig 259 proxy *NodeMetadata 260 expected *meshconfig.ProxyConfig 261 }{ 262 { 263 name: "CR applies to matching namespace", 264 configs: []config.Config{ 265 newProxyConfig("ns", "test-ns", 266 &v1beta1.ProxyConfig{ 267 Concurrency: v(3), 268 Image: &v1beta1.ProxyImage{ 269 ImageType: "debug", 270 }, 271 }), 272 }, 273 proxy: newMeta("test-ns", nil, nil), 274 expected: &meshconfig.ProxyConfig{ 275 Concurrency: v(3), 276 Image: &v1beta1.ProxyImage{ 277 ImageType: "debug", 278 }, 279 }, 280 }, 281 { 282 name: "CR takes precedence over meshConfig.defaultConfig", 283 configs: []config.Config{ 284 newProxyConfig("ns", istioRootNamespace, 285 &v1beta1.ProxyConfig{ 286 Concurrency: v(3), 287 }), 288 }, 289 defaultConfig: &meshconfig.ProxyConfig{Concurrency: v(2)}, 290 proxy: newMeta("bar", nil, nil), 291 expected: &meshconfig.ProxyConfig{Concurrency: v(3)}, 292 }, 293 { 294 name: "workload matching CR takes precedence over namespace matching CR", 295 configs: []config.Config{ 296 newProxyConfig("workload", "test-ns", 297 &v1beta1.ProxyConfig{ 298 Selector: selector(map[string]string{ 299 "test": "selector", 300 }), 301 Concurrency: v(3), 302 }), 303 newProxyConfig("ns", "test-ns", 304 &v1beta1.ProxyConfig{ 305 Concurrency: v(2), 306 }), 307 }, 308 proxy: newMeta("test-ns", map[string]string{"test": "selector"}, nil), 309 expected: &meshconfig.ProxyConfig{Concurrency: v(3)}, 310 }, 311 { 312 name: "matching workload CR takes precedence over annotation", 313 configs: []config.Config{ 314 newProxyConfig("workload", "test-ns", 315 &v1beta1.ProxyConfig{ 316 Selector: selector(map[string]string{ 317 "test": "selector", 318 }), 319 Concurrency: v(3), 320 Image: &v1beta1.ProxyImage{ 321 ImageType: "debug", 322 }, 323 }), 324 }, 325 proxy: newMeta( 326 "test-ns", 327 map[string]string{ 328 "test": "selector", 329 }, map[string]string{ 330 annotation.ProxyConfig.Name: "{ \"concurrency\": 5 }", 331 }), 332 expected: &meshconfig.ProxyConfig{ 333 Concurrency: v(3), 334 Image: &v1beta1.ProxyImage{ 335 ImageType: "debug", 336 }, 337 }, 338 }, 339 { 340 name: "CR in other namespaces get ignored", 341 configs: []config.Config{ 342 newProxyConfig("ns", "wrong-ns", 343 &v1beta1.ProxyConfig{ 344 Concurrency: v(1), 345 }), 346 newProxyConfig("workload", "wrong-ns", 347 &v1beta1.ProxyConfig{ 348 Selector: selector(map[string]string{ 349 "test": "selector", 350 }), 351 Concurrency: v(2), 352 }), 353 newProxyConfig("global", istioRootNamespace, 354 &v1beta1.ProxyConfig{ 355 Concurrency: v(3), 356 }), 357 }, 358 proxy: newMeta("test-ns", map[string]string{"test": "selector"}, nil), 359 expected: &meshconfig.ProxyConfig{Concurrency: v(3)}, 360 }, 361 { 362 name: "multiple matching workload CRs, oldest applies", 363 configs: []config.Config{ 364 setCreationTimestamp(newProxyConfig("workload-a", "test-ns", 365 &v1beta1.ProxyConfig{ 366 Selector: selector(map[string]string{ 367 "test": "selector", 368 }), 369 EnvironmentVariables: map[string]string{ 370 "A": "1", 371 }, 372 }), now), 373 setCreationTimestamp(newProxyConfig("workload-b", "test-ns", 374 &v1beta1.ProxyConfig{ 375 Selector: selector(map[string]string{ 376 "test": "selector", 377 }), 378 EnvironmentVariables: map[string]string{ 379 "B": "2", 380 }, 381 }), now.Add(time.Hour)), 382 setCreationTimestamp(newProxyConfig("workload-c", "test-ns", 383 &v1beta1.ProxyConfig{ 384 Selector: selector(map[string]string{ 385 "test": "selector", 386 }), 387 EnvironmentVariables: map[string]string{ 388 "C": "3", 389 }, 390 }), now.Add(time.Hour)), 391 }, 392 proxy: newMeta( 393 "test-ns", 394 map[string]string{ 395 "test": "selector", 396 }, map[string]string{}), 397 expected: &meshconfig.ProxyConfig{ProxyMetadata: map[string]string{ 398 "A": "1", 399 }}, 400 }, 401 { 402 name: "multiple matching namespace CRs, oldest applies", 403 configs: []config.Config{ 404 setCreationTimestamp(newProxyConfig("workload-a", "test-ns", 405 &v1beta1.ProxyConfig{ 406 EnvironmentVariables: map[string]string{ 407 "A": "1", 408 }, 409 }), now), 410 setCreationTimestamp(newProxyConfig("workload-b", "test-ns", 411 &v1beta1.ProxyConfig{ 412 EnvironmentVariables: map[string]string{ 413 "B": "2", 414 }, 415 }), now.Add(time.Hour)), 416 setCreationTimestamp(newProxyConfig("workload-c", "test-ns", 417 &v1beta1.ProxyConfig{ 418 EnvironmentVariables: map[string]string{ 419 "C": "3", 420 }, 421 }), now.Add(time.Hour)), 422 }, 423 proxy: newMeta( 424 "test-ns", 425 map[string]string{}, map[string]string{}), 426 expected: &meshconfig.ProxyConfig{ProxyMetadata: map[string]string{ 427 "A": "1", 428 }}, 429 }, 430 { 431 name: "no configured CR or default config", 432 proxy: newMeta("ns", nil, nil), 433 }, 434 } 435 436 for _, tc := range cases { 437 t.Run(tc.name, func(t *testing.T) { 438 store := newProxyConfigStore(t, tc.configs) 439 m := &meshconfig.MeshConfig{ 440 RootNamespace: istioRootNamespace, 441 DefaultConfig: tc.defaultConfig, 442 } 443 original, _ := protomarshal.ToJSON(m) 444 pcs := GetProxyConfigs(store, m) 445 merged := pcs.EffectiveProxyConfig(tc.proxy, m) 446 pc := mesh.DefaultProxyConfig() 447 proto.Merge(pc, tc.expected) 448 449 assert.Equal(t, merged, pc) 450 after, _ := protomarshal.ToJSON(m) 451 assert.Equal(t, original, after, "mesh config should not be mutated") 452 }) 453 } 454 } 455 456 func newProxyConfig(name, ns string, spec config.Spec) config.Config { 457 return config.Config{ 458 Meta: config.Meta{ 459 GroupVersionKind: gvk.ProxyConfig, 460 Name: name, 461 Namespace: ns, 462 }, 463 Spec: spec, 464 } 465 } 466 467 func newProxyConfigStore(t *testing.T, configs []config.Config) ConfigStore { 468 t.Helper() 469 470 store := NewFakeStore() 471 for _, cfg := range configs { 472 store.Create(cfg) 473 } 474 475 return store 476 } 477 478 func setCreationTimestamp(c config.Config, t time.Time) config.Config { 479 c.Meta.CreationTimestamp = t 480 return c 481 } 482 483 func newMeta(ns string, labels, annotations map[string]string) *NodeMetadata { 484 return &NodeMetadata{ 485 Namespace: ns, 486 Labels: labels, 487 Annotations: annotations, 488 } 489 } 490 491 func v(x int32) *wrappers.Int32Value { 492 return &wrappers.Int32Value{Value: x} 493 } 494 495 func selector(l map[string]string) *istioTypes.WorkloadSelector { 496 return &istioTypes.WorkloadSelector{MatchLabels: l} 497 }