istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/networking/core/fake.go (about)

     1  //go:build !agent
     2  // +build !agent
     3  
     4  // Copyright Istio Authors
     5  //
     6  // Licensed under the Apache License, Version 2.0 (the "License");
     7  // you may not use this file except in compliance with the License.
     8  // You may obtain a copy of the License at
     9  //
    10  //     http://www.apache.org/licenses/LICENSE-2.0
    11  //
    12  // Unless required by applicable law or agreed to in writing, software
    13  // distributed under the License is distributed on an "AS IS" BASIS,
    14  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15  // See the License for the specific language governing permissions and
    16  // limitations under the License.
    17  
    18  package core
    19  
    20  import (
    21  	"bytes"
    22  	"text/template"
    23  	"time"
    24  
    25  	"github.com/Masterminds/sprig/v3"
    26  	cluster "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
    27  	listener "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
    28  	route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
    29  	hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
    30  
    31  	meshconfig "istio.io/api/mesh/v1alpha1"
    32  	configaggregate "istio.io/istio/pilot/pkg/config/aggregate"
    33  	"istio.io/istio/pilot/pkg/config/kube/crd"
    34  	"istio.io/istio/pilot/pkg/config/memory"
    35  	"istio.io/istio/pilot/pkg/model"
    36  	"istio.io/istio/pilot/pkg/serviceregistry"
    37  	"istio.io/istio/pilot/pkg/serviceregistry/aggregate"
    38  	memregistry "istio.io/istio/pilot/pkg/serviceregistry/memory"
    39  	"istio.io/istio/pilot/pkg/serviceregistry/provider"
    40  	"istio.io/istio/pilot/pkg/serviceregistry/serviceentry"
    41  	cluster2 "istio.io/istio/pkg/cluster"
    42  	"istio.io/istio/pkg/config"
    43  	"istio.io/istio/pkg/config/mesh"
    44  	"istio.io/istio/pkg/config/schema/collections"
    45  	"istio.io/istio/pkg/test"
    46  	"istio.io/istio/pkg/test/util/retry"
    47  	"istio.io/istio/pkg/util/sets"
    48  	"istio.io/istio/pkg/wellknown"
    49  )
    50  
    51  type TestOptions struct {
    52  	// If provided, these configs will be used directly
    53  	Configs        []config.Config
    54  	ConfigPointers []*config.Config
    55  
    56  	// If provided, the yaml string will be parsed and used as configs
    57  	ConfigString string
    58  	// If provided, the ConfigString will be treated as a go template, with this as input params
    59  	ConfigTemplateInput any
    60  
    61  	// Services to pre-populate as part of the service discovery
    62  	Services  []*model.Service
    63  	Instances []*model.ServiceInstance
    64  	Gateways  []model.NetworkGateway
    65  
    66  	// If provided, this mesh config will be used
    67  	MeshConfig      *meshconfig.MeshConfig
    68  	NetworksWatcher mesh.NetworksWatcher
    69  
    70  	// Additional service registries to use. A ServiceEntry and memory registry will always be created.
    71  	ServiceRegistries []serviceregistry.Instance
    72  
    73  	// Base ConfigController to use. If not set, a in-memory store will be used
    74  	ConfigController model.ConfigStoreController
    75  
    76  	// Additional ConfigStoreController to use
    77  	ConfigStoreCaches []model.ConfigStoreController
    78  
    79  	// CreateConfigStore defines a function that, given a ConfigStoreController, returns another ConfigStoreController to use
    80  	CreateConfigStore func(c model.ConfigStoreController) model.ConfigStoreController
    81  
    82  	// If set, we will not run immediately, allowing adding event handlers, etc prior to start.
    83  	SkipRun bool
    84  
    85  	// Used to set the serviceentry registry's cluster id
    86  	ClusterID cluster2.ID
    87  
    88  	// XDSUpdater to use. Otherwise, our own will be used
    89  	XDSUpdater model.XDSUpdater
    90  }
    91  
    92  func (to TestOptions) FuzzValidate() bool {
    93  	for _, csc := range to.ConfigStoreCaches {
    94  		if csc == nil {
    95  			return false
    96  		}
    97  	}
    98  	for _, sr := range to.ServiceRegistries {
    99  		if sr == nil {
   100  			return false
   101  		}
   102  	}
   103  	return true
   104  }
   105  
   106  type ConfigGenTest struct {
   107  	t                    test.Failer
   108  	store                model.ConfigStoreController
   109  	env                  *model.Environment
   110  	ConfigGen            *ConfigGeneratorImpl
   111  	MemRegistry          *memregistry.ServiceDiscovery
   112  	ServiceEntryRegistry *serviceentry.Controller
   113  	Registry             model.Controller
   114  	initialConfigs       []config.Config
   115  	stop                 chan struct{}
   116  	MemServiceRegistry   serviceregistry.Simple
   117  }
   118  
   119  func NewConfigGenTest(t test.Failer, opts TestOptions) *ConfigGenTest {
   120  	t.Helper()
   121  	configs := getConfigs(t, opts)
   122  	cc := opts.ConfigController
   123  	if cc == nil {
   124  		cc = memory.NewSyncController(memory.MakeSkipValidation(collections.PilotGatewayAPI()))
   125  	}
   126  	controllers := []model.ConfigStoreController{cc}
   127  	if opts.CreateConfigStore != nil {
   128  		controllers = append(controllers, opts.CreateConfigStore(cc))
   129  	}
   130  	controllers = append(controllers, opts.ConfigStoreCaches...)
   131  	configController, _ := configaggregate.MakeWriteableCache(controllers, cc)
   132  
   133  	m := opts.MeshConfig
   134  	if m == nil {
   135  		m = mesh.DefaultMeshConfig()
   136  	}
   137  
   138  	env := model.NewEnvironment()
   139  
   140  	xdsUpdater := opts.XDSUpdater
   141  	if xdsUpdater == nil {
   142  		xdsUpdater = model.NewEndpointIndexUpdater(env.EndpointIndex)
   143  	}
   144  
   145  	serviceDiscovery := aggregate.NewController(aggregate.Options{})
   146  	se := serviceentry.NewController(
   147  		configController,
   148  		xdsUpdater,
   149  		serviceentry.WithClusterID(opts.ClusterID))
   150  	// TODO allow passing in registry, for k8s, mem reigstry
   151  	serviceDiscovery.AddRegistry(se)
   152  	msd := memregistry.NewServiceDiscovery(opts.Services...)
   153  	msd.XdsUpdater = xdsUpdater
   154  	for _, instance := range opts.Instances {
   155  		msd.AddInstance(instance)
   156  	}
   157  	msd.AddGateways(opts.Gateways...)
   158  	msd.ClusterID = cluster2.ID(provider.Mock)
   159  	memserviceRegistry := serviceregistry.Simple{
   160  		ClusterID:           cluster2.ID(provider.Mock),
   161  		ProviderID:          provider.Mock,
   162  		DiscoveryController: msd,
   163  	}
   164  	serviceDiscovery.AddRegistry(memserviceRegistry)
   165  	for _, reg := range opts.ServiceRegistries {
   166  		serviceDiscovery.AddRegistry(reg)
   167  	}
   168  	env.Watcher = mesh.NewFixedWatcher(m)
   169  	if opts.NetworksWatcher == nil {
   170  		opts.NetworksWatcher = mesh.NewFixedNetworksWatcher(nil)
   171  	}
   172  	env.ServiceDiscovery = serviceDiscovery
   173  	env.ConfigStore = configController
   174  	env.NetworksWatcher = opts.NetworksWatcher
   175  	env.Init()
   176  
   177  	fake := &ConfigGenTest{
   178  		t:                    t,
   179  		store:                configController,
   180  		env:                  env,
   181  		initialConfigs:       configs,
   182  		stop:                 test.NewStop(t),
   183  		ConfigGen:            NewConfigGenerator(&model.DisabledCache{}),
   184  		MemRegistry:          msd,
   185  		MemServiceRegistry:   memserviceRegistry,
   186  		Registry:             serviceDiscovery,
   187  		ServiceEntryRegistry: se,
   188  	}
   189  	if !opts.SkipRun {
   190  		fake.Run()
   191  		if err := env.InitNetworksManager(xdsUpdater); err != nil {
   192  			t.Fatal(err)
   193  		}
   194  		if err := env.PushContext().InitContext(env, nil, nil); err != nil {
   195  			t.Fatalf("Failed to initialize push context: %v", err)
   196  		}
   197  	}
   198  	return fake
   199  }
   200  
   201  func (f *ConfigGenTest) Run() {
   202  	go f.Registry.Run(f.stop)
   203  	go f.store.Run(f.stop)
   204  	// Setup configuration. This should be done after registries are added so they can process events.
   205  	for _, cfg := range f.initialConfigs {
   206  		if _, err := f.store.Create(cfg); err != nil {
   207  			f.t.Fatalf("failed to create config %v: %v", cfg.Name, err)
   208  		}
   209  	}
   210  
   211  	// TODO allow passing event handlers for controller
   212  
   213  	retry.UntilOrFail(f.t, f.store.HasSynced, retry.Delay(time.Millisecond))
   214  	retry.UntilOrFail(f.t, f.Registry.HasSynced, retry.Delay(time.Millisecond))
   215  
   216  	f.ServiceEntryRegistry.ResyncEDS()
   217  }
   218  
   219  // SetupProxy initializes a proxy for the current environment. This should generally be used when creating
   220  // any proxy. For example, `p := SetupProxy(&model.Proxy{...})`.
   221  func (f *ConfigGenTest) SetupProxy(p *model.Proxy) *model.Proxy {
   222  	// Setup defaults
   223  	if p == nil {
   224  		p = &model.Proxy{}
   225  	}
   226  	if p.Metadata == nil {
   227  		p.Metadata = &model.NodeMetadata{}
   228  	}
   229  	if p.Metadata.IstioVersion == "" {
   230  		p.Metadata.IstioVersion = "1.23.0"
   231  	}
   232  	if p.IstioVersion == nil {
   233  		p.IstioVersion = model.ParseIstioVersion(p.Metadata.IstioVersion)
   234  	}
   235  	if p.Type == "" {
   236  		p.Type = model.SidecarProxy
   237  	}
   238  	if p.ConfigNamespace == "" {
   239  		p.ConfigNamespace = "default"
   240  	}
   241  	if p.Metadata.Namespace == "" {
   242  		p.Metadata.Namespace = p.ConfigNamespace
   243  	}
   244  	if p.ID == "" {
   245  		p.ID = "app.test"
   246  	}
   247  	if p.DNSDomain == "" {
   248  		p.DNSDomain = p.ConfigNamespace + ".svc.cluster.local"
   249  	}
   250  	if len(p.IPAddresses) == 0 {
   251  		p.IPAddresses = []string{"1.1.1.1"}
   252  	}
   253  
   254  	// Initialize data structures
   255  	pc := f.PushContext()
   256  	p.SetSidecarScope(pc)
   257  	p.SetServiceTargets(f.env.ServiceDiscovery)
   258  	p.SetGatewaysForProxy(pc)
   259  	p.DiscoverIPMode()
   260  	return p
   261  }
   262  
   263  func (f *ConfigGenTest) Listeners(p *model.Proxy) []*listener.Listener {
   264  	return f.ConfigGen.BuildListeners(p, f.PushContext())
   265  }
   266  
   267  func (f *ConfigGenTest) Clusters(p *model.Proxy) []*cluster.Cluster {
   268  	raw, _ := f.ConfigGen.BuildClusters(p, &model.PushRequest{Push: f.PushContext()})
   269  	res := make([]*cluster.Cluster, 0, len(raw))
   270  	for _, r := range raw {
   271  		c := &cluster.Cluster{}
   272  		if err := r.Resource.UnmarshalTo(c); err != nil {
   273  			f.t.Fatal(err)
   274  		}
   275  		res = append(res, c)
   276  	}
   277  	return res
   278  }
   279  
   280  func (f *ConfigGenTest) DeltaClusters(
   281  	p *model.Proxy,
   282  	configUpdated sets.Set[model.ConfigKey],
   283  	watched *model.WatchedResource,
   284  ) ([]*cluster.Cluster, []string, bool) {
   285  	raw, removed, _, delta := f.ConfigGen.BuildDeltaClusters(p,
   286  		&model.PushRequest{
   287  			Push: f.PushContext(), ConfigsUpdated: configUpdated,
   288  		}, watched)
   289  	res := make([]*cluster.Cluster, 0, len(raw))
   290  	for _, r := range raw {
   291  		c := &cluster.Cluster{}
   292  		if err := r.Resource.UnmarshalTo(c); err != nil {
   293  			f.t.Fatal(err)
   294  		}
   295  		res = append(res, c)
   296  	}
   297  	return res, removed, delta
   298  }
   299  
   300  func (f *ConfigGenTest) RoutesFromListeners(p *model.Proxy, l []*listener.Listener) []*route.RouteConfiguration {
   301  	resources, _ := f.ConfigGen.BuildHTTPRoutes(p, &model.PushRequest{Push: f.PushContext()}, ExtractRoutesFromListeners(l))
   302  	out := make([]*route.RouteConfiguration, 0, len(resources))
   303  	for _, resource := range resources {
   304  		routeConfig := &route.RouteConfiguration{}
   305  		_ = resource.Resource.UnmarshalTo(routeConfig)
   306  		out = append(out, routeConfig)
   307  	}
   308  	return out
   309  }
   310  
   311  func (f *ConfigGenTest) Routes(p *model.Proxy) []*route.RouteConfiguration {
   312  	return f.RoutesFromListeners(p, f.Listeners(p))
   313  }
   314  
   315  func (f *ConfigGenTest) PushContext() *model.PushContext {
   316  	return f.env.PushContext()
   317  }
   318  
   319  func (f *ConfigGenTest) Env() *model.Environment {
   320  	return f.env
   321  }
   322  
   323  func (f *ConfigGenTest) Store() model.ConfigStoreController {
   324  	return f.store
   325  }
   326  
   327  func getConfigs(t test.Failer, opts TestOptions) []config.Config {
   328  	for _, p := range opts.ConfigPointers {
   329  		if p != nil {
   330  			opts.Configs = append(opts.Configs, *p)
   331  		}
   332  	}
   333  	configStr := opts.ConfigString
   334  	if opts.ConfigTemplateInput != nil {
   335  		tmpl := template.Must(template.New("").Funcs(sprig.TxtFuncMap()).Parse(opts.ConfigString))
   336  		var buf bytes.Buffer
   337  		if err := tmpl.Execute(&buf, opts.ConfigTemplateInput); err != nil {
   338  			t.Fatalf("failed to execute template: %v", err)
   339  		}
   340  		configStr = buf.String()
   341  	}
   342  	cfgs := opts.Configs
   343  	if configStr != "" {
   344  		t0 := time.Now()
   345  		configs, _, err := crd.ParseInputs(configStr)
   346  		if err != nil {
   347  			t.Fatalf("failed to read config: %v: %v", err, configStr)
   348  		}
   349  		// setup default namespace if not defined
   350  		for _, c := range configs {
   351  			if c.Namespace == "" {
   352  				c.Namespace = "default"
   353  			}
   354  			// Set creation timestamp to same time for all of them for consistency.
   355  			// If explicit setting is needed it can be set in the yaml
   356  			if c.CreationTimestamp.IsZero() {
   357  				c.CreationTimestamp = t0
   358  			}
   359  			cfgs = append(cfgs, c)
   360  		}
   361  	}
   362  	return cfgs
   363  }
   364  
   365  // copied from xdstest to avoid import issues
   366  func ExtractRoutesFromListeners(ll []*listener.Listener) []string {
   367  	routes := []string{}
   368  	for _, l := range ll {
   369  		for _, fc := range l.FilterChains {
   370  			for _, filter := range fc.Filters {
   371  				if filter.Name == wellknown.HTTPConnectionManager {
   372  					h := &hcm.HttpConnectionManager{}
   373  					_ = filter.GetTypedConfig().UnmarshalTo(h)
   374  					switch r := h.GetRouteSpecifier().(type) {
   375  					case *hcm.HttpConnectionManager_Rds:
   376  						routes = append(routes, r.Rds.RouteConfigName)
   377  					}
   378  				}
   379  			}
   380  		}
   381  	}
   382  	return routes
   383  }