blob: 70821457e1407eded89c2ada2c55bb904a2abe6e [file] [log] [blame]
// Copyright Istio 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 deployment
import (
"context"
"fmt"
"sort"
"strings"
)
import (
"github.com/hashicorp/go-multierror"
"golang.org/x/sync/errgroup"
)
import (
"github.com/apache/dubbo-go-pixiu/pkg/test"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/components/echo"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/components/echo/common/ports"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/components/echo/deployment"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/components/namespace"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/resource"
)
// Config for new echo deployment.
type Config struct {
// Echos is the target Echos for the newly created echo apps. If nil, a new Echos
// instance will be created.
Echos *Echos
// NamespaceCount indicates the number of echo namespaces to be generated.
// Ignored if Namespaces is non-empty. Defaults to 1.
NamespaceCount int
// Namespaces is the user-provided list of echo namespaces. If empty, NamespaceCount
// namespaces will be generated.
Namespaces []namespace.Instance
// NoExternalNamespace if true, no external namespace will be generated and no external echo
// instance will be deployed. Ignored if ExternalNamespace is non-nil.
NoExternalNamespace bool
// ExternalNamespace the namespace to use for the external deployment. If nil, a namespace
// will be generated unless NoExternalNamespace is specified.
ExternalNamespace namespace.Instance
}
func (c *Config) fillDefaults(ctx resource.Context) error {
// Create the namespaces concurrently.
g, _ := errgroup.WithContext(context.TODO())
if c.Echos == nil {
c.Echos = &Echos{}
}
if len(c.Namespaces) > 0 {
c.NamespaceCount = len(c.Namespaces)
} else if c.NamespaceCount <= 0 {
c.NamespaceCount = 1
}
// Create the echo namespaces.
if len(c.Namespaces) == 0 {
c.Namespaces = make([]namespace.Instance, c.NamespaceCount)
if c.NamespaceCount == 1 {
// If only using a single namespace, preserve the "echo" prefix.
g.Go(func() (err error) {
c.Namespaces[0], err = namespace.New(ctx, namespace.Config{
Prefix: "echo",
Inject: true,
})
return
})
} else {
for i := 0; i < c.NamespaceCount; i++ {
i := i
g.Go(func() (err error) {
c.Namespaces[i], err = namespace.New(ctx, namespace.Config{
Prefix: fmt.Sprintf("echo%d", i+1),
Inject: true,
})
return
})
}
}
}
// Create the external namespace, if necessary.
if c.ExternalNamespace == nil && !c.NoExternalNamespace {
g.Go(func() (err error) {
c.ExternalNamespace, err = namespace.New(ctx, namespace.Config{
Prefix: "external",
Inject: false,
})
return
})
}
// Wait for the namespaces to be created.
return g.Wait()
}
// SingleNamespaceView is a simplified view of Echos for tests that only require a single namespace.
type SingleNamespaceView struct {
// Include the echos at the top-level, so there is no need for accessing sub-structures.
EchoNamespace
// External (out-of-mesh) deployments
External External
// All echo instances
All echo.Services
}
// TwoNamespaceView is a simplified view of Echos for tests that require 2 namespaces.
type TwoNamespaceView struct {
// Ns1 contains the echo deployments in the first namespace
Ns1 EchoNamespace
// Ns2 contains the echo deployments in the second namespace
Ns2 EchoNamespace
// Ns1AndNs2 contains just the echo services in Ns1 and Ns2 (excludes External).
Ns1AndNs2 echo.Services
// External (out-of-mesh) deployments
External External
// All echo instances
All echo.Services
}
// Echos is a common set of echo deployments to support integration testing.
type Echos struct {
// NS is the list of echo namespaces.
NS []EchoNamespace
// External (out-of-mesh) deployments
External External
// All echo instances.
All echo.Services
}
// New echo deployment with the given configuration.
func New(ctx resource.Context, cfg Config) (*Echos, error) {
if err := cfg.fillDefaults(ctx); err != nil {
return nil, err
}
apps := cfg.Echos
apps.NS = make([]EchoNamespace, len(cfg.Namespaces))
for i, ns := range cfg.Namespaces {
apps.NS[i].Namespace = ns
}
apps.External.Namespace = cfg.ExternalNamespace
builder := deployment.New(ctx).WithClusters(ctx.Clusters()...)
for _, n := range apps.NS {
builder = n.build(ctx, builder)
}
if !cfg.NoExternalNamespace {
builder = apps.External.build(builder)
}
echos, err := builder.Build()
if err != nil {
return nil, err
}
apps.All = echos.Services()
g := multierror.Group{}
for i := 0; i < len(apps.NS); i++ {
i := i
g.Go(func() error {
return apps.NS[i].loadValues(ctx, echos, apps)
})
}
if !cfg.NoExternalNamespace {
g.Go(func() error {
return apps.External.loadValues(echos)
})
}
if err := g.Wait().ErrorOrNil(); err != nil {
return nil, err
}
return apps, nil
}
// NewOrFail calls New and fails if an error is returned.
func NewOrFail(t test.Failer, ctx resource.Context, cfg Config) *Echos {
t.Helper()
out, err := New(ctx, cfg)
if err != nil {
t.Fatal(err)
}
return out
}
// SingleNamespaceView converts this Echos into a SingleNamespaceView.
func (d Echos) SingleNamespaceView() SingleNamespaceView {
return SingleNamespaceView{
EchoNamespace: d.NS[0],
External: d.External,
All: d.NS[0].All.Append(d.External.All.Services()),
}
}
// TwoNamespaceView converts this Echos into a TwoNamespaceView.
func (d Echos) TwoNamespaceView() TwoNamespaceView {
ns1AndNs2 := d.NS[0].All.Append(d.NS[1].All)
return TwoNamespaceView{
Ns1: d.NS[0],
Ns2: d.NS[1],
Ns1AndNs2: ns1AndNs2,
External: d.External,
All: ns1AndNs2.Append(d.External.All.Services()),
}
}
func (d Echos) namespaces(excludes ...namespace.Instance) []string {
var out []string
for _, n := range d.NS {
include := true
for _, e := range excludes {
if n.Namespace.Name() == e.Name() {
include = false
break
}
}
if include {
out = append(out, n.Namespace.Name())
}
}
sort.Strings(out)
return out
}
func serviceEntryPorts() []echo.Port {
var res []echo.Port
for _, p := range ports.All().GetServicePorts() {
if strings.HasPrefix(p.Name, "auto") {
// The protocol needs to be set in common.EchoPorts to configure the echo deployment
// But for service entry, we want to ensure we set it to "" which will use sniffing
p.Protocol = ""
}
res = append(res, p)
}
return res
}
// SetupSingleNamespace calls Setup and returns a SingleNamespaceView.
func SetupSingleNamespace(view *SingleNamespaceView) resource.SetupFn {
return func(ctx resource.Context) error {
// Perform a setup with 1 namespace.
var apps Echos
if err := Setup(&apps, Config{NamespaceCount: 1})(ctx); err != nil {
return err
}
// Store the view.
*view = apps.SingleNamespaceView()
return nil
}
}
// SetupTwoNamespaces calls Setup and returns a TwoNamespaceView.
func SetupTwoNamespaces(view *TwoNamespaceView) resource.SetupFn {
return func(ctx resource.Context) error {
// Perform a setup with 2 namespaces.
var apps Echos
if err := Setup(&apps, Config{NamespaceCount: 2})(ctx); err != nil {
return err
}
// Store the view.
*view = apps.TwoNamespaceView()
return nil
}
}
// Setup function for writing to a global deployment variable.
func Setup(apps *Echos, cfg Config) resource.SetupFn {
return func(ctx resource.Context) error {
// Set the target for the deployments.
cfg.Echos = apps
_, err := New(ctx, cfg)
if err != nil {
return err
}
return nil
}
}
// TODO(nmittler): should ctx.Settings().Skip(echo.Delta) do all of this?
func skipDeltaXDS(ctx resource.Context) bool {
return ctx.Settings().Skip(echo.Delta) || !ctx.Settings().Revisions.AtLeast("1.12")
}