blob: 0f723af03ace67fdc1acedb9870b761e039b3dbf [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 framework
import (
"context"
"fmt"
"strings"
)
import (
"github.com/hashicorp/go-multierror"
"go.uber.org/atomic"
"golang.org/x/sync/errgroup"
)
import (
"github.com/apache/dubbo-go-pixiu/pkg/test"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/components/cluster"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/components/istioctl"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/resource"
"github.com/apache/dubbo-go-pixiu/pkg/test/scopes"
"github.com/apache/dubbo-go-pixiu/pkg/test/util/file"
"github.com/apache/dubbo-go-pixiu/pkg/test/util/tmpl"
"github.com/apache/dubbo-go-pixiu/pkg/test/util/yml"
)
var _ resource.ConfigManager = &configManager{}
type configManager struct {
ctx resource.Context
clusters []cluster.Cluster
prefix string
}
func newConfigManager(ctx resource.Context, clusters cluster.Clusters) resource.ConfigManager {
if len(clusters) == 0 {
clusters = ctx.Clusters()
}
return &configManager{
ctx: ctx,
clusters: clusters.Kube(),
}
}
// GlobalYAMLWrites records how many YAMLs we have applied from all sources.
// Note: go tests are distinct binaries per test suite, so this is the suite level number of calls
var GlobalYAMLWrites = atomic.NewUint64(0)
func (c *configManager) New() resource.Config {
return &configImpl{
configManager: c,
yamlText: make(map[string][]string),
}
}
func (c *configManager) YAML(ns string, yamlText ...string) resource.Config {
return c.New().YAML(ns, yamlText...)
}
func (c *configManager) Eval(ns string, args interface{}, yamlTemplates ...string) resource.Config {
return c.New().Eval(ns, args, yamlTemplates...)
}
func (c *configManager) File(ns string, filePaths ...string) resource.Config {
return c.New().File(ns, filePaths...)
}
func (c *configManager) EvalFile(ns string, args interface{}, filePaths ...string) resource.Config {
return c.New().EvalFile(ns, args, filePaths...)
}
func (c *configManager) applyYAML(cleanup bool, ns string, yamlText ...string) error {
if len(c.prefix) == 0 {
return c.WithFilePrefix("apply").(*configManager).applyYAML(cleanup, ns, yamlText...)
}
GlobalYAMLWrites.Add(uint64(len(yamlText)))
// Convert the content to files.
yamlFiles, err := c.ctx.WriteYAML(c.prefix, yamlText...)
if err != nil {
return err
}
g, _ := errgroup.WithContext(context.TODO())
for _, cl := range c.clusters {
cl := cl
g.Go(func() error {
scopes.Framework.Debugf("Applying to %s to namespace %v: %s", cl.StableName(), ns, strings.Join(yamlFiles, ", "))
if err := cl.ApplyYAMLFiles(ns, yamlFiles...); err != nil {
return fmt.Errorf("failed applying YAML files %v to ns %s in cluster %s: %v", yamlFiles, ns, cl.Name(), err)
}
if cleanup {
c.ctx.Cleanup(func() {
scopes.Framework.Debugf("Deleting from %s: %s", cl.StableName(), strings.Join(yamlFiles, ", "))
if err := cl.DeleteYAMLFiles(ns, yamlFiles...); err != nil {
scopes.Framework.Errorf("failed deleting YAML files %v from ns %s in cluster %s: %v", yamlFiles, ns, cl.Name(), err)
}
})
}
return nil
})
}
return g.Wait()
}
func (c *configManager) deleteYAML(ns string, yamlText ...string) error {
if len(c.prefix) == 0 {
return c.WithFilePrefix("delete").(*configManager).deleteYAML(ns, yamlText...)
}
// Convert the content to files.
yamlFiles, err := c.ctx.WriteYAML(c.prefix, yamlText...)
if err != nil {
return err
}
g, _ := errgroup.WithContext(context.TODO())
for _, c := range c.clusters {
c := c
g.Go(func() error {
if err := c.DeleteYAMLFiles(ns, yamlFiles...); err != nil {
return fmt.Errorf("failed deleting YAML from cluster %s: %v", c.Name(), err)
}
return nil
})
}
return g.Wait()
}
func (c *configManager) WaitForConfig(ctx resource.Context, ns string, yamlText ...string) error {
var outErr error
for _, c := range c.ctx.Clusters() {
ik, err := istioctl.New(ctx, istioctl.Config{Cluster: c})
if err != nil {
return err
}
for _, config := range yamlText {
config := config
// TODO(https://github.com/istio/istio/issues/37324): It's currently unsafe
// to call istioctl concurrently since it relies on the istioctl library
// (rather than calling the binary from the command line) which uses a number
// of global variables, which will be overwritten for each call.
if err := ik.WaitForConfig(ns, config); err != nil {
// Get proxy status for additional debugging
s, _, _ := ik.Invoke([]string{"ps"})
outErr = multierror.Append(err, fmt.Errorf("failed waiting for config for cluster %s: err=%v. Proxy status: %v",
c.StableName(), err, s))
}
}
}
return outErr
}
func (c *configManager) WaitForConfigOrFail(ctx resource.Context, t test.Failer, ns string, yamlText ...string) {
err := c.WaitForConfig(ctx, ns, yamlText...)
if err != nil {
// TODO(https://github.com/istio/istio/issues/37148) fail hard in this case
t.Log(err)
}
}
func (c *configManager) WithFilePrefix(prefix string) resource.ConfigManager {
return &configManager{
ctx: c.ctx,
prefix: prefix,
clusters: c.clusters,
}
}
var _ resource.Config = &configImpl{}
type configImpl struct {
*configManager
yamlText map[string][]string
}
func (c *configImpl) Copy() resource.Config {
yamlText := make(map[string][]string, len(c.yamlText))
for k, v := range c.yamlText {
yamlText[k] = append([]string{}, v...)
}
return &configImpl{
configManager: c.configManager,
yamlText: yamlText,
}
}
func (c *configImpl) YAML(ns string, yamlText ...string) resource.Config {
c.yamlText[ns] = append(c.yamlText[ns], splitYAML(yamlText...)...)
return c
}
func splitYAML(yamlText ...string) []string {
var out []string
for _, doc := range yamlText {
out = append(out, yml.SplitString(doc)...)
}
return out
}
func (c *configImpl) File(ns string, paths ...string) resource.Config {
yamlText, err := file.AsStringArray(paths...)
if err != nil {
panic(err)
}
return c.YAML(ns, yamlText...)
}
func (c *configImpl) Eval(ns string, args interface{}, templates ...string) resource.Config {
return c.YAML(ns, tmpl.MustEvaluateAll(args, templates...)...)
}
func (c *configImpl) EvalFile(ns string, args interface{}, paths ...string) resource.Config {
templates, err := file.AsStringArray(paths...)
if err != nil {
panic(err)
}
return c.Eval(ns, args, templates...)
}
func (c *configImpl) Apply(opts ...resource.ConfigOption) error {
// Apply the options.
options := resource.ConfigOptions{}
for _, o := range opts {
o(&options)
}
// Apply for each namespace concurrently.
g, _ := errgroup.WithContext(context.TODO())
for ns, y := range c.yamlText {
ns, y := ns, y
g.Go(func() error {
return c.applyYAML(!options.NoCleanup, ns, y...)
})
}
// Wait for all each apply to complete.
if err := g.Wait(); err != nil {
return err
}
if options.Wait {
// TODO: wait for each namespace concurrently once WaitForConfig supports concurrency.
for ns, y := range c.yamlText {
if err := c.WaitForConfig(c.ctx, ns, y...); err != nil {
// TODO(https://github.com/istio/istio/issues/37148) fail hard in this case
scopes.Framework.Warnf("(Ignored until https://github.com/istio/istio/issues/37148 is fixed) "+
"failed waiting for YAML %v: %v", y, err)
}
}
}
return nil
}
func (c *configImpl) ApplyOrFail(t test.Failer, opts ...resource.ConfigOption) {
t.Helper()
if err := c.Apply(opts...); err != nil {
t.Fatal(err)
}
}
func (c *configImpl) Delete() error {
// Delete for each namespace concurrently.
g, _ := errgroup.WithContext(context.TODO())
for ns, y := range c.yamlText {
ns, y := ns, y
g.Go(func() error {
return c.deleteYAML(ns, y...)
})
}
// Wait for all each delete to complete.
return g.Wait()
}
func (c *configImpl) DeleteOrFail(t test.Failer) {
t.Helper()
if err := c.Delete(); err != nil {
t.Fatal(err)
}
}