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 }