istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/framework/components/echo/config/builder.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 config
    16  
    17  import (
    18  	"fmt"
    19  	"reflect"
    20  	"time"
    21  
    22  	"istio.io/istio/pkg/test/framework"
    23  	"istio.io/istio/pkg/test/framework/components/echo"
    24  	"istio.io/istio/pkg/test/framework/components/echo/config/param"
    25  	"istio.io/istio/pkg/test/framework/components/istio"
    26  	"istio.io/istio/pkg/test/framework/components/namespace"
    27  	"istio.io/istio/pkg/test/framework/resource/config"
    28  	"istio.io/istio/pkg/test/framework/resource/config/apply"
    29  	"istio.io/istio/pkg/test/scopes"
    30  	"istio.io/istio/pkg/util/sets"
    31  )
    32  
    33  // Builder of configuration.
    34  type Builder struct {
    35  	t             framework.TestContext
    36  	out           config.Plan
    37  	needFrom      []Source
    38  	needTo        []Source
    39  	needFromAndTo []Source
    40  	complete      []Source
    41  	yamlCount     int
    42  }
    43  
    44  func New(t framework.TestContext) *Builder {
    45  	return NewWithOutput(t, t.ConfigIstio().New())
    46  }
    47  
    48  // NewWithOutput creates a new Builder that targets the given config as output.
    49  func NewWithOutput(t framework.TestContext, out config.Plan) *Builder {
    50  	t.Helper()
    51  	checkNotNil("t", t)
    52  	checkNotNil("out", out)
    53  
    54  	return &Builder{
    55  		t:   t,
    56  		out: out,
    57  	}
    58  }
    59  
    60  func checkNotNil(name string, value any) {
    61  	if value == nil {
    62  		panic(fmt.Sprintf("%s must not be nil", name))
    63  	}
    64  }
    65  
    66  // Output returns a copy of this Builder with the given output set.
    67  func (b *Builder) Output(out config.Plan) *Builder {
    68  	b.t.Helper()
    69  
    70  	checkNotNil("out", out)
    71  
    72  	ret := b.Copy()
    73  	ret.out = out
    74  	return ret
    75  }
    76  
    77  // Context returns a copy of this Builder with the given context set.
    78  func (b *Builder) Context(t framework.TestContext) *Builder {
    79  	checkNotNil("t", t)
    80  	out := b.Copy()
    81  	out.t = t
    82  	return out
    83  }
    84  
    85  // Source returns a copy of this Builder with the given Source objects added.
    86  func (b *Builder) Source(sources ...Source) *Builder {
    87  	b.t.Helper()
    88  	out := b.Copy()
    89  
    90  	for _, s := range sources {
    91  		// Split the source so that we process a single CRD at a time.
    92  		for _, s := range s.SplitOrFail(b.t) {
    93  			// If the caller set namespaces with literal strings, replace with namespace.Instance objects.
    94  			s = replaceNamespaceStrings(s)
    95  
    96  			tpl := s.TemplateOrFail(out.t)
    97  			need := tpl.MissingParams(s.Params())
    98  			needFrom := need.Contains(param.From.String())
    99  			needTo := need.Contains(param.To.String())
   100  
   101  			if needFrom && needTo {
   102  				out.needFromAndTo = append(out.needFromAndTo, s)
   103  			} else if needFrom {
   104  				out.needFrom = append(out.needFrom, s)
   105  			} else if needTo {
   106  				out.needTo = append(out.needTo, s)
   107  			} else {
   108  				// No well-known parameters are missing.
   109  				out.complete = append(out.complete, s)
   110  			}
   111  
   112  			// Delete all the wellknown parameters.
   113  			need.DeleteAll(param.AllWellKnown().ToStringArray()...)
   114  			if len(need) > 0 {
   115  				panic(fmt.Sprintf("config source missing parameters: %v", need))
   116  			}
   117  		}
   118  	}
   119  
   120  	return out
   121  }
   122  
   123  // BuildCompleteSources builds only those sources that already contain all parameters
   124  // needed by their templates. Specifically, they are not missing any of the well-known
   125  // parameters: "From", "To", or "Namespace".
   126  func (b *Builder) BuildCompleteSources() *Builder {
   127  	b.t.Helper()
   128  	out := b.Copy()
   129  
   130  	systemNS := istio.ClaimSystemNamespaceOrFail(out.t, out.t)
   131  
   132  	// Build all the complete config that doesn't require well-known parameters.
   133  	for _, s := range out.complete {
   134  		out.addYAML(withParams(s, param.Params{
   135  			param.SystemNamespace.String(): systemNS,
   136  		}))
   137  	}
   138  
   139  	return out
   140  }
   141  
   142  // BuildFrom builds only those sources that require only the "From" parameter.
   143  func (b *Builder) BuildFrom(fromAll ...echo.Caller) *Builder {
   144  	b.t.Helper()
   145  	out := b.Copy()
   146  
   147  	systemNS := istio.ClaimSystemNamespaceOrFail(out.t, out.t)
   148  
   149  	for _, from := range fromAll {
   150  		for _, s := range out.needFrom {
   151  			out.addYAML(withParams(s, param.Params{
   152  				param.From.String():            from,
   153  				param.SystemNamespace.String(): systemNS,
   154  			}))
   155  		}
   156  	}
   157  
   158  	return out
   159  }
   160  
   161  // BuildFromAndTo builds only those sources that require both the "From" and "To" parameters.
   162  func (b *Builder) BuildFromAndTo(fromAll echo.Callers, toAll echo.Services) *Builder {
   163  	b.t.Helper()
   164  	out := b.Copy()
   165  
   166  	systemNS := istio.ClaimSystemNamespaceOrFail(out.t, out.t)
   167  
   168  	for _, from := range fromAll {
   169  		for _, to := range toAll {
   170  			for _, s := range out.needFromAndTo {
   171  				out.addYAML(withParams(s, param.Params{
   172  					param.From.String():            from,
   173  					param.To.String():              to,
   174  					param.Namespace.String():       to.Config().Namespace,
   175  					param.SystemNamespace.String(): systemNS,
   176  				}))
   177  			}
   178  		}
   179  	}
   180  
   181  	for _, to := range toAll {
   182  		for _, s := range out.needTo {
   183  			out.addYAML(withParams(s, param.Params{
   184  				param.To.String():              to,
   185  				param.Namespace.String():       to.Config().Namespace,
   186  				param.SystemNamespace.String(): systemNS,
   187  			}))
   188  		}
   189  	}
   190  
   191  	return out
   192  }
   193  
   194  // BuildAll builds the config for all sources.
   195  func (b *Builder) BuildAll(fromAll echo.Callers, toAll echo.Services) *Builder {
   196  	b.t.Helper()
   197  
   198  	return b.BuildCompleteSources().
   199  		BuildFrom(fromAll...).
   200  		BuildFromAndTo(fromAll, toAll)
   201  }
   202  
   203  func (b *Builder) Apply(opts ...apply.Option) {
   204  	if b.yamlCount == 0 {
   205  		// Nothing to do.
   206  		return
   207  	}
   208  
   209  	start := time.Now()
   210  	scopes.Framework.Info("=== BEGIN: Deploy config ===")
   211  
   212  	b.out.ApplyOrFail(b.t, opts...)
   213  
   214  	scopes.Framework.Infof("=== SUCCEEDED: Deploy config in %v ===", time.Since(start))
   215  }
   216  
   217  // Replace any namespace strings with namespace.Instance objects.
   218  func replaceNamespaceStrings(s Source) Source {
   219  	params := s.Params()
   220  	newParams := param.NewParams()
   221  	for _, nsKey := range []param.WellKnown{param.Namespace, param.SystemNamespace} {
   222  		if params.ContainsWellKnown(nsKey) {
   223  			val := params.GetWellKnown(nsKey)
   224  			if strVal, ok := val.(string); ok {
   225  				newParams.SetWellKnown(nsKey, namespace.Static(strVal))
   226  			}
   227  		}
   228  	}
   229  	return s.WithParams(newParams)
   230  }
   231  
   232  func (b *Builder) addYAML(s Source) {
   233  	b.t.Helper()
   234  
   235  	// Ensure all parameters have been set.
   236  	b.checkMissing(s)
   237  
   238  	// Get the namespace where the config should be applied.
   239  	ns := b.getNamespace(s)
   240  
   241  	// Generate the YAML and add it to the configuration.
   242  	b.out.YAML(ns.Name(), s.YAMLOrFail(b.t))
   243  	b.yamlCount++
   244  }
   245  
   246  func withParams(s Source, params param.Params) Source {
   247  	return s.WithParams(s.Params().SetAllNoOverwrite(params))
   248  }
   249  
   250  func (b *Builder) checkMissing(s Source) {
   251  	b.t.Helper()
   252  	tpl := s.TemplateOrFail(b.t)
   253  	missing := tpl.MissingParams(s.Params())
   254  	if missing.Len() > 0 {
   255  		b.t.Fatalf("config template requires missing params: %v", sets.SortedList(missing))
   256  	}
   257  }
   258  
   259  func (b *Builder) getNamespace(s Source) namespace.Instance {
   260  	b.t.Helper()
   261  	ns := s.Params().GetWellKnown(param.Namespace)
   262  	if ns == nil {
   263  		b.t.Fatalf("no %s specified in config params", param.Namespace)
   264  	}
   265  	inst, ok := ns.(namespace.Instance)
   266  	if !ok {
   267  		b.t.Fatalf("%s was of unexpected type: %v", param.Namespace, reflect.TypeOf(ns))
   268  	}
   269  	return inst
   270  }
   271  
   272  func (b *Builder) Copy() *Builder {
   273  	return &Builder{
   274  		t:             b.t,
   275  		needFrom:      copySources(b.needFrom),
   276  		needTo:        copySources(b.needTo),
   277  		needFromAndTo: copySources(b.needFromAndTo),
   278  		complete:      copySources(b.complete),
   279  		out:           b.out.Copy(),
   280  		yamlCount:     b.yamlCount,
   281  	}
   282  }
   283  
   284  func copySources(sources []Source) []Source {
   285  	return append([]Source{}, sources...)
   286  }