istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/framework/components/echo/common/deployment/echos.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 deployment 16 17 import ( 18 "context" 19 "fmt" 20 "strings" 21 22 "github.com/hashicorp/go-multierror" 23 "golang.org/x/sync/errgroup" 24 25 "istio.io/api/annotation" 26 "istio.io/api/label" 27 "istio.io/istio/pkg/config/constants" 28 "istio.io/istio/pkg/test" 29 "istio.io/istio/pkg/test/framework/components/ambient" 30 "istio.io/istio/pkg/test/framework/components/echo" 31 "istio.io/istio/pkg/test/framework/components/echo/common/ports" 32 "istio.io/istio/pkg/test/framework/components/echo/deployment" 33 "istio.io/istio/pkg/test/framework/components/namespace" 34 "istio.io/istio/pkg/test/framework/resource" 35 "istio.io/istio/pkg/test/scopes" 36 ) 37 38 // Config for new echo deployment. 39 type Config struct { 40 // Echos is the target Echos for the newly created echo apps. If nil, a new Echos 41 // instance will be created. 42 Echos *Echos 43 44 // NamespaceCount indicates the number of echo namespaces to be generated. 45 // Ignored if Namespaces is non-empty. Defaults to 1. 46 NamespaceCount int 47 48 // Namespaces is the user-provided list of echo namespaces. If empty, NamespaceCount 49 // namespaces will be generated. 50 Namespaces []namespace.Getter 51 52 // NoExternalNamespace if true, no external namespace will be generated and no external echo 53 // instance will be deployed. Ignored if ExternalNamespace is non-nil. 54 NoExternalNamespace bool 55 56 // ExternalNamespace the namespace to use for the external deployment. If nil, a namespace 57 // will be generated unless NoExternalNamespace is specified. 58 ExternalNamespace namespace.Getter 59 60 // IncludeExtAuthz if enabled, an additional ext-authz container will be included in the deployment. 61 // This is mainly used to test the CUSTOM authorization policy when the ext-authz server is deployed 62 // locally with the application container in the same pod. 63 IncludeExtAuthz bool 64 65 // Custom allows for configuring custom echo deployments. If a deployment's namespace 66 // is nil, it will be created in all namespaces. Otherwise, it must match one of the 67 // namespaces configured above. 68 // 69 // Custom echo instances will be accessible from the `All` field in the namespace(s) under which they 70 // were created. 71 Configs echo.ConfigGetter 72 } 73 74 // AddConfigs appends to the configs to be deployed 75 func (c *Config) AddConfigs(configs []echo.Config) *Config { 76 var existing echo.ConfigGetter 77 if c.Configs != nil { 78 existing = c.Configs 79 } 80 c.Configs = func() []echo.Config { 81 var out []echo.Config 82 if existing != nil { 83 out = append(out, existing()...) 84 } 85 return append(out, configs...) 86 } 87 return c 88 } 89 90 func (c *Config) fillDefaults(ctx resource.Context) error { 91 // Create the namespaces concurrently. 92 g, _ := errgroup.WithContext(context.TODO()) 93 94 if c.Echos == nil { 95 c.Echos = &Echos{} 96 } 97 98 if c.Configs == nil { 99 defaultConfigs := c.DefaultEchoConfigs(ctx) 100 c.Configs = echo.ConfigFuture(&defaultConfigs) 101 } 102 103 // Verify the namespace for any custom deployments. 104 for _, config := range c.Configs.Get() { 105 if config.Namespace != nil { 106 found := false 107 for _, ns := range c.Namespaces { 108 if config.Namespace.Name() == ns.Get().Name() { 109 found = true 110 break 111 } 112 } 113 if !found { 114 return fmt.Errorf("custom echo deployment %s uses unconfigured namespace %s", 115 config.NamespacedName().String(), config.NamespaceName()) 116 } 117 } 118 } 119 120 if len(c.Namespaces) > 0 { 121 c.NamespaceCount = len(c.Namespaces) 122 } else if c.NamespaceCount <= 0 { 123 c.NamespaceCount = 1 124 } 125 126 nsLabels := map[string]string{} 127 if ctx.Settings().Ambient { 128 nsLabels["istio.io/dataplane-mode"] = "ambient" 129 } 130 131 // Create the echo namespaces. 132 if len(c.Namespaces) == 0 { 133 c.Namespaces = make([]namespace.Getter, c.NamespaceCount) 134 if c.NamespaceCount == 1 { 135 // If only using a single namespace, preserve the "echo" prefix. 136 g.Go(func() error { 137 ns, err := namespace.New(ctx, namespace.Config{ 138 Inject: !ctx.Settings().AmbientEverywhere, 139 Prefix: "echo", 140 Labels: nsLabels, 141 }) 142 if err != nil { 143 return err 144 } 145 c.Namespaces[0] = namespace.Future(&ns) 146 return nil 147 }) 148 } else { 149 for i := 0; i < c.NamespaceCount; i++ { 150 i := i 151 g.Go(func() error { 152 ns, err := namespace.New(ctx, namespace.Config{ 153 Prefix: fmt.Sprintf("echo%d", i+1), 154 Inject: true, 155 }) 156 if err != nil { 157 return err 158 } 159 c.Namespaces[i] = namespace.Future(&ns) 160 return nil 161 }) 162 } 163 } 164 } 165 166 // Create the external namespace, if necessary. 167 if c.ExternalNamespace == nil && !c.NoExternalNamespace { 168 g.Go(func() error { 169 ns, err := namespace.New(ctx, namespace.Config{ 170 Prefix: "external", 171 Inject: false, 172 }) 173 if err != nil { 174 return err 175 } 176 c.ExternalNamespace = namespace.Future(&ns) 177 return nil 178 }) 179 } 180 181 // Wait for the namespaces to be created. 182 return g.Wait() 183 } 184 185 func (c *Config) DefaultEchoConfigs(t resource.Context) []echo.Config { 186 var defaultConfigs []echo.Config 187 188 disableAutomountSAToken := true 189 if t.Settings().Revisions.Minimum() < "1.16" { 190 disableAutomountSAToken = false 191 } 192 193 a := echo.Config{ 194 Service: ASvc, 195 ServiceAccount: true, 196 Ports: ports.All(), 197 Subsets: []echo.SubsetConfig{{}}, 198 Locality: "region.zone.subzone", 199 IncludeExtAuthz: c.IncludeExtAuthz, 200 DisableAutomountSAToken: disableAutomountSAToken, 201 } 202 203 b := echo.Config{ 204 Service: BSvc, 205 ServiceAccount: true, 206 Ports: ports.All(), 207 Subsets: []echo.SubsetConfig{{}}, 208 IncludeExtAuthz: c.IncludeExtAuthz, 209 } 210 211 cSvc := echo.Config{ 212 Service: CSvc, 213 ServiceAccount: true, 214 Ports: ports.All(), 215 Subsets: []echo.SubsetConfig{{}}, 216 IncludeExtAuthz: c.IncludeExtAuthz, 217 } 218 219 headless := echo.Config{ 220 Service: HeadlessSvc, 221 ServiceAccount: true, 222 Headless: true, 223 Ports: ports.Headless(), 224 Subsets: []echo.SubsetConfig{{}}, 225 IncludeExtAuthz: c.IncludeExtAuthz, 226 } 227 228 stateful := echo.Config{ 229 Service: StatefulSetSvc, 230 ServiceAccount: true, 231 Headless: true, 232 StatefulSet: true, 233 Ports: ports.Headless(), 234 Subsets: []echo.SubsetConfig{{}}, 235 IncludeExtAuthz: c.IncludeExtAuthz, 236 } 237 238 naked := echo.Config{ 239 Service: NakedSvc, 240 ServiceAccount: true, 241 Ports: ports.All(), 242 Subsets: []echo.SubsetConfig{ 243 { 244 Annotations: map[string]string{annotation.SidecarInject.Name: "false"}, 245 Labels: map[string]string{ 246 label.SidecarInject.Name: "false", 247 constants.DataplaneModeLabel: constants.DataplaneModeNone, 248 }, 249 }, 250 }, 251 } 252 253 tProxy := echo.Config{ 254 Service: TproxySvc, 255 ServiceAccount: true, 256 Ports: ports.All(), 257 Subsets: []echo.SubsetConfig{{ 258 Annotations: map[string]string{annotation.SidecarInterceptionMode.Name: "TPROXY"}, 259 Labels: map[string]string{ 260 constants.DataplaneModeLabel: constants.DataplaneModeNone, 261 }, 262 }}, 263 IncludeExtAuthz: c.IncludeExtAuthz, 264 } 265 266 vmSvc := echo.Config{ 267 Service: VMSvc, 268 ServiceAccount: true, 269 Ports: ports.All(), 270 DeployAsVM: true, 271 AutoRegisterVM: true, 272 Subsets: []echo.SubsetConfig{{}}, 273 IncludeExtAuthz: c.IncludeExtAuthz, 274 } 275 276 defaultConfigs = append(defaultConfigs, a, b, cSvc, headless, stateful, naked, tProxy, vmSvc) 277 278 if t.Settings().EnableDualStack { 279 dSvc := echo.Config{ 280 Service: DSvc, 281 ServiceAccount: true, 282 Ports: ports.All(), 283 Subsets: []echo.SubsetConfig{{}}, 284 IncludeExtAuthz: c.IncludeExtAuthz, 285 IPFamilies: "IPv6, IPv4", 286 IPFamilyPolicy: "RequireDualStack", 287 DualStack: true, 288 } 289 eSvc := echo.Config{ 290 Service: ESvc, 291 ServiceAccount: true, 292 Ports: ports.All(), 293 Subsets: []echo.SubsetConfig{{}}, 294 IncludeExtAuthz: c.IncludeExtAuthz, 295 IPFamilies: "IPv6", 296 IPFamilyPolicy: "SingleStack", 297 DualStack: true, 298 } 299 defaultConfigs = append(defaultConfigs, dSvc, eSvc) 300 } 301 302 sotw := `{"proxyMetadata": {"ISTIO_DELTA_XDS": "false"}}` 303 304 if !t.Settings().Skip(echo.Sotw) { 305 sotw := echo.Config{ 306 Service: SotwSvc, 307 ServiceAccount: true, 308 Ports: ports.All(), 309 Subsets: []echo.SubsetConfig{{ 310 Labels: map[string]string{label.SidecarInject.Name: "true"}, 311 Annotations: map[string]string{annotation.ProxyConfig.Name: sotw}, 312 }}, 313 } 314 defaultConfigs = append(defaultConfigs, sotw) 315 } 316 317 if !t.Clusters().IsMulticluster() { 318 // TODO when agent handles secure control-plane connection for grpc-less, deploy to "remote" clusters 319 proxylessGRPC := echo.Config{ 320 Service: ProxylessGRPCSvc, 321 ServiceAccount: true, 322 Ports: ports.All(), 323 Subsets: []echo.SubsetConfig{ 324 { 325 Labels: map[string]string{label.SidecarInject.Name: "true"}, 326 Annotations: map[string]string{annotation.InjectTemplates.Name: "grpc-agent"}, 327 }, 328 }, 329 } 330 defaultConfigs = append(defaultConfigs, proxylessGRPC) 331 } 332 333 if t.Settings().Ambient { 334 if t.Settings().AmbientEverywhere { 335 for i, config := range defaultConfigs { 336 if !config.HasSidecar() && !config.IsProxylessGRPC() { 337 scopes.Framework.Infof("adding waypoint to %s", config.NamespacedName()) 338 defaultConfigs[i].ServiceWaypointProxy = "shared" 339 defaultConfigs[i].WorkloadWaypointProxy = "shared" 340 } 341 } 342 } else { 343 waypointed := echo.Config{ 344 Service: WaypointSvc, 345 ServiceAccount: true, 346 Ports: ports.All(), 347 ServiceWaypointProxy: "shared", 348 WorkloadWaypointProxy: "shared", 349 Subsets: []echo.SubsetConfig{{ 350 Labels: map[string]string{label.SidecarInject.Name: "false"}, 351 }}, 352 } 353 defaultConfigs = append(defaultConfigs, waypointed) 354 } 355 356 captured := echo.Config{ 357 Service: CapturedSvc, 358 ServiceAccount: true, 359 Ports: ports.All(), 360 Subsets: []echo.SubsetConfig{{ 361 Labels: map[string]string{ 362 label.SidecarInject.Name: "false", 363 constants.AmbientRedirection: constants.AmbientRedirectionEnabled, 364 }, 365 }}, 366 } 367 defaultConfigs = append(defaultConfigs, captured) 368 } 369 370 return defaultConfigs 371 } 372 373 // View of an Echos deployment. 374 type View interface { 375 // Echos returns the underlying Echos deployment for this view. 376 Echos() *Echos 377 } 378 379 var ( 380 _ View = &SingleNamespaceView{} 381 _ View = &TwoNamespaceView{} 382 _ View = &Echos{} 383 ) 384 385 // SingleNamespaceView is a simplified view of Echos for tests that only require a single namespace. 386 type SingleNamespaceView struct { 387 // Include the echos at the top-level, so there is no need for accessing sub-structures. 388 EchoNamespace 389 390 // External (out-of-mesh) deployments 391 External External 392 393 // All echo instances 394 All echo.Services 395 396 echos *Echos 397 } 398 399 func (v *SingleNamespaceView) Echos() *Echos { 400 return v.echos 401 } 402 403 // TwoNamespaceView is a simplified view of Echos for tests that require 2 namespaces. 404 type TwoNamespaceView struct { 405 // Ns1 contains the echo deployments in the first namespace 406 Ns1 EchoNamespace 407 408 // Ns2 contains the echo deployments in the second namespace 409 Ns2 EchoNamespace 410 411 // Ns1AndNs2 contains just the echo services in Ns1 and Ns2 (excludes External). 412 Ns1AndNs2 echo.Services 413 414 // External (out-of-mesh) deployments 415 External External 416 417 // All echo instances 418 All echo.Services 419 420 echos *Echos 421 } 422 423 func (v *TwoNamespaceView) Echos() *Echos { 424 return v.echos 425 } 426 427 // Echos is a common set of echo deployments to support integration testing. 428 type Echos struct { 429 // NS is the list of echo namespaces. 430 NS []EchoNamespace 431 432 // External (out-of-mesh) deployments 433 External External 434 435 // All echo instances. 436 All echo.Services 437 } 438 439 func (e *Echos) Echos() *Echos { 440 return e 441 } 442 443 // New echo deployment with the given configuration. 444 func New(ctx resource.Context, cfg Config) (*Echos, error) { 445 if err := cfg.fillDefaults(ctx); err != nil { 446 return nil, err 447 } 448 449 apps := cfg.Echos 450 apps.NS = make([]EchoNamespace, len(cfg.Namespaces)) 451 for i, ns := range cfg.Namespaces { 452 apps.NS[i].Namespace = ns.Get() 453 } 454 if !cfg.NoExternalNamespace { 455 apps.External.Namespace = cfg.ExternalNamespace.Get() 456 } 457 458 builder := deployment.New(ctx).WithClusters(ctx.Clusters()...) 459 for _, n := range apps.NS { 460 builder = n.build(builder, cfg) 461 } 462 463 if !cfg.NoExternalNamespace { 464 builder = apps.External.Build(ctx, builder) 465 } 466 467 echos, err := builder.Build() 468 if err != nil { 469 return nil, err 470 } 471 472 if ctx.Settings().Ambient { 473 474 waypointProxies := make(map[string]ambient.WaypointProxy) 475 476 for _, echo := range echos { 477 svcwp := echo.Config().ServiceWaypointProxy 478 wlwp := echo.Config().WorkloadWaypointProxy 479 var err error 480 if svcwp != "" { 481 if _, found := waypointProxies[svcwp]; !found { 482 waypointProxies[svcwp], err = ambient.NewWaypointProxy(ctx, echo.Config().Namespace, svcwp) 483 if err != nil { 484 return nil, err 485 } 486 } 487 } 488 if wlwp != "" { 489 if _, found := waypointProxies[wlwp]; !found { 490 waypointProxies[wlwp], err = ambient.NewWaypointProxy(ctx, echo.Config().Namespace, wlwp) 491 if err != nil { 492 return nil, err 493 } 494 } 495 } 496 497 } 498 } 499 500 apps.All = echos.Services() 501 502 g := multierror.Group{} 503 for i := 0; i < len(apps.NS); i++ { 504 i := i 505 g.Go(func() error { 506 return apps.NS[i].loadValues(ctx, echos, apps) 507 }) 508 } 509 510 if !cfg.NoExternalNamespace { 511 apps.External.LoadValues(echos) 512 } 513 514 if err := g.Wait().ErrorOrNil(); err != nil { 515 return nil, err 516 } 517 518 return apps, nil 519 } 520 521 // NewOrFail calls New and fails if an error is returned. 522 func NewOrFail(t test.Failer, ctx resource.Context, cfg Config) *Echos { 523 t.Helper() 524 out, err := New(ctx, cfg) 525 if err != nil { 526 t.Fatal(err) 527 } 528 return out 529 } 530 531 // SingleNamespaceView converts this Echos into a SingleNamespaceView. 532 func (e *Echos) SingleNamespaceView() SingleNamespaceView { 533 return SingleNamespaceView{ 534 EchoNamespace: e.NS[0], 535 External: e.External, 536 All: e.NS[0].All.Append(e.External.All.Services()), 537 echos: e, 538 } 539 } 540 541 // TwoNamespaceView converts this Echos into a TwoNamespaceView. 542 func (e *Echos) TwoNamespaceView() TwoNamespaceView { 543 ns1AndNs2 := e.NS[0].All.Append(e.NS[1].All) 544 return TwoNamespaceView{ 545 Ns1: e.NS[0], 546 Ns2: e.NS[1], 547 Ns1AndNs2: ns1AndNs2, 548 External: e.External, 549 All: ns1AndNs2.Append(e.External.All.Services()), 550 echos: e, 551 } 552 } 553 554 func serviceEntryPorts() []echo.Port { 555 var res []echo.Port 556 for _, p := range ports.All().GetServicePorts() { 557 if strings.HasPrefix(p.Name, "auto") { 558 // The protocol needs to be set in common.EchoPorts to configure the echo deployment 559 // But for service entry, we want to ensure we set it to "" which will use sniffing 560 p.Protocol = "" 561 } 562 res = append(res, p) 563 } 564 return res 565 } 566 567 // SetupSingleNamespace calls Setup and returns a SingleNamespaceView. 568 func SetupSingleNamespace(view *SingleNamespaceView, cfg Config) resource.SetupFn { 569 cfg.NamespaceCount = 1 570 return func(ctx resource.Context) error { 571 // Perform a setup with 1 namespace. 572 var apps Echos 573 if err := Setup(&apps, cfg)(ctx); err != nil { 574 return err 575 } 576 577 // Store the view. 578 *view = apps.SingleNamespaceView() 579 return nil 580 } 581 } 582 583 // SetupTwoNamespaces calls Setup and returns a TwoNamespaceView. 584 func SetupTwoNamespaces(view *TwoNamespaceView, cfg Config) resource.SetupFn { 585 cfg.NamespaceCount = 2 586 return func(ctx resource.Context) error { 587 // Perform a setup with 2 namespaces. 588 var apps Echos 589 if err := Setup(&apps, cfg)(ctx); err != nil { 590 return err 591 } 592 593 // Store the view. 594 *view = apps.TwoNamespaceView() 595 return nil 596 } 597 } 598 599 // Setup function for writing to a global deployment variable. 600 func Setup(apps *Echos, cfg Config) resource.SetupFn { 601 return func(ctx resource.Context) error { 602 // Set the target for the deployments. 603 cfg.Echos = apps 604 605 _, err := New(ctx, cfg) 606 if err != nil { 607 return err 608 } 609 610 return nil 611 } 612 }