istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/analysis/analyzers/virtualservice/conflictingmeshgatewayhosts.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 virtualservice
    16  
    17  import (
    18  	"fmt"
    19  	"sort"
    20  	"strings"
    21  
    22  	"istio.io/api/networking/v1alpha3"
    23  	"istio.io/istio/pkg/config"
    24  	"istio.io/istio/pkg/config/analysis"
    25  	"istio.io/istio/pkg/config/analysis/analyzers/util"
    26  	"istio.io/istio/pkg/config/analysis/msg"
    27  	"istio.io/istio/pkg/config/host"
    28  	"istio.io/istio/pkg/config/resource"
    29  	"istio.io/istio/pkg/config/schema/gvk"
    30  	"istio.io/istio/pkg/util/sets"
    31  )
    32  
    33  // ConflictingMeshGatewayHostsAnalyzer checks if multiple virtual services
    34  // associated with the mesh gateway have conflicting hosts. The behavior is
    35  // undefined if conflicts exist.
    36  type ConflictingMeshGatewayHostsAnalyzer struct{}
    37  
    38  var _ analysis.Analyzer = &ConflictingMeshGatewayHostsAnalyzer{}
    39  
    40  // Metadata implements Analyzer
    41  func (c *ConflictingMeshGatewayHostsAnalyzer) Metadata() analysis.Metadata {
    42  	return analysis.Metadata{
    43  		Name:        "virtualservice.ConflictingMeshGatewayHostsAnalyzer",
    44  		Description: "Checks if multiple virtual services associated with the mesh gateway have conflicting hosts",
    45  		Inputs: []config.GroupVersionKind{
    46  			gvk.VirtualService,
    47  		},
    48  	}
    49  }
    50  
    51  // Analyze implements Analyzer
    52  func (c *ConflictingMeshGatewayHostsAnalyzer) Analyze(ctx analysis.Context) {
    53  	hs := initMeshGatewayHosts(ctx)
    54  	reported := make(map[resource.FullName]bool)
    55  	for scopedFqdn, vsList := range hs {
    56  		scope, _ := scopedFqdn.GetScopeAndFqdn()
    57  		if scope != util.ExportToAllNamespaces {
    58  			noScopedVSList := getExportToAllNamespacesVSListForScopedHost(scopedFqdn, hs)
    59  			vsList = append(vsList, noScopedVSList...)
    60  		}
    61  		if len(vsList) > 1 {
    62  			vsNames := combineResourceEntryNames(vsList)
    63  			for i := range vsList {
    64  				if reported[vsList[i].Metadata.FullName] {
    65  					continue
    66  				}
    67  				reported[vsList[i].Metadata.FullName] = true
    68  				m := msg.NewConflictingMeshGatewayVirtualServiceHosts(vsList[i], vsNames, string(scopedFqdn))
    69  
    70  				if line, ok := util.ErrorLine(vsList[i], fmt.Sprintf(util.MetadataName)); ok {
    71  					m.Line = line
    72  				}
    73  
    74  				ctx.Report(gvk.VirtualService, m)
    75  			}
    76  		}
    77  
    78  	}
    79  }
    80  
    81  func getExportToAllNamespacesVSListForScopedHost(sh util.ScopedFqdn, meshGatewayHosts map[util.ScopedFqdn][]*resource.Instance) []*resource.Instance {
    82  	_, h := sh.GetScopeAndFqdn()
    83  	vss := make([]*resource.Instance, 0)
    84  	for sf, resources := range meshGatewayHosts {
    85  		mghScope, mgh := sf.GetScopeAndFqdn()
    86  		hName := host.Name(h)
    87  		mghName := host.Name(mgh)
    88  		if mghScope != util.ExportToAllNamespaces || !hName.Matches(mghName) {
    89  			continue
    90  		}
    91  		vss = append(vss, resources...)
    92  	}
    93  	return vss
    94  }
    95  
    96  func combineResourceEntryNames(rList []*resource.Instance) string {
    97  	names := make([]string, 0, len(rList))
    98  	for _, r := range rList {
    99  		names = append(names, r.Metadata.FullName.String())
   100  	}
   101  	sort.Strings(names)
   102  	return strings.Join(names, ",")
   103  }
   104  
   105  func initMeshGatewayHosts(ctx analysis.Context) map[util.ScopedFqdn][]*resource.Instance {
   106  	hostsVirtualServices := map[util.ScopedFqdn][]*resource.Instance{}
   107  	ctx.ForEach(gvk.VirtualService, func(r *resource.Instance) bool {
   108  		vs := r.Message.(*v1alpha3.VirtualService)
   109  		vsNamespace := r.Metadata.FullName.Namespace
   110  		vsAttachedToMeshGateway := false
   111  		// No entry in gateways imply "mesh" by default
   112  		if len(vs.Gateways) == 0 {
   113  			vsAttachedToMeshGateway = true
   114  		} else {
   115  			for _, g := range vs.Gateways {
   116  				if g == util.MeshGateway {
   117  					vsAttachedToMeshGateway = true
   118  				}
   119  			}
   120  		}
   121  		if vsAttachedToMeshGateway {
   122  			// determine the scope of hosts i.e. local to VirtualService namespace or
   123  			// all namespaces
   124  			hostsNamespaceScope := make([]string, 0)
   125  			exportToAllNamespaces := util.IsExportToAllNamespaces(vs.ExportTo)
   126  			if exportToAllNamespaces {
   127  				hostsNamespaceScope = append(hostsNamespaceScope, util.ExportToAllNamespaces)
   128  			} else {
   129  				nss := sets.New[string]()
   130  				for _, et := range vs.ExportTo {
   131  					if et == util.ExportToNamespaceLocal {
   132  						nss.Insert(vsNamespace.String())
   133  					} else {
   134  						nss.Insert(et)
   135  					}
   136  				}
   137  				hostsNamespaceScope = nss.UnsortedList()
   138  			}
   139  
   140  			for _, nsScope := range hostsNamespaceScope {
   141  				for _, h := range vs.Hosts {
   142  					scopedFqdn := util.NewScopedFqdn(nsScope, vsNamespace, h)
   143  					vsNames := hostsVirtualServices[scopedFqdn]
   144  					if len(vsNames) == 0 {
   145  						hostsVirtualServices[scopedFqdn] = []*resource.Instance{r}
   146  					} else {
   147  						hostsVirtualServices[scopedFqdn] = append(hostsVirtualServices[scopedFqdn], r)
   148  					}
   149  				}
   150  			}
   151  		}
   152  		return true
   153  	})
   154  	return hostsVirtualServices
   155  }