istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/analysis/analyzers/virtualservice/destinationhosts.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  
    20  	"istio.io/api/networking/v1alpha3"
    21  	"istio.io/istio/pkg/config"
    22  	"istio.io/istio/pkg/config/analysis"
    23  	"istio.io/istio/pkg/config/analysis/analyzers/util"
    24  	"istio.io/istio/pkg/config/analysis/msg"
    25  	"istio.io/istio/pkg/config/resource"
    26  	"istio.io/istio/pkg/config/schema/gvk"
    27  )
    28  
    29  // DestinationHostAnalyzer checks the destination hosts associated with each virtual service
    30  type DestinationHostAnalyzer struct{}
    31  
    32  var _ analysis.Analyzer = &DestinationHostAnalyzer{}
    33  
    34  type hostAndSubset struct {
    35  	host   resource.FullName
    36  	subset string
    37  }
    38  
    39  // Metadata implements Analyzer
    40  func (a *DestinationHostAnalyzer) Metadata() analysis.Metadata {
    41  	return analysis.Metadata{
    42  		Name:        "virtualservice.DestinationHostAnalyzer",
    43  		Description: "Checks the destination hosts associated with each virtual service",
    44  		Inputs: []config.GroupVersionKind{
    45  			gvk.ServiceEntry,
    46  			gvk.VirtualService,
    47  			gvk.Service,
    48  		},
    49  	}
    50  }
    51  
    52  // Analyze implements Analyzer
    53  func (a *DestinationHostAnalyzer) Analyze(ctx analysis.Context) {
    54  	// Precompute the set of service entry hosts that exist (there can be more than one defined per ServiceEntry CRD)
    55  	serviceEntryHosts := util.InitServiceEntryHostMap(ctx)
    56  	virtualServiceDestinations := initVirtualServiceDestinations(ctx)
    57  
    58  	ctx.ForEach(gvk.VirtualService, func(r *resource.Instance) bool {
    59  		a.analyzeVirtualService(r, ctx, serviceEntryHosts)
    60  		a.analyzeSubset(r, ctx, virtualServiceDestinations)
    61  		return true
    62  	})
    63  }
    64  
    65  func (a *DestinationHostAnalyzer) analyzeSubset(r *resource.Instance, ctx analysis.Context, vsDestinations map[resource.FullName][]*v1alpha3.Destination) {
    66  	vs := r.Message.(*v1alpha3.VirtualService)
    67  
    68  	// if there's no gateway specified, we're done
    69  	if len(vs.Gateways) == 0 {
    70  		return
    71  	}
    72  
    73  	for ruleIndex, http := range vs.Http {
    74  		for routeIndex, route := range http.Route {
    75  			if route.Destination.Subset == "" {
    76  				for virtualservice, destinations := range vsDestinations {
    77  					for _, destination := range destinations {
    78  						if destination.Host == route.Destination.Host {
    79  							m := msg.NewIngressRouteRulesNotAffected(r, virtualservice.String(), r.Metadata.FullName.String())
    80  
    81  							key := fmt.Sprintf(util.DestinationHost, http.Name, ruleIndex, routeIndex)
    82  							if line, ok := util.ErrorLine(r, key); ok {
    83  								m.Line = line
    84  							}
    85  
    86  							ctx.Report(gvk.VirtualService, m)
    87  						}
    88  					}
    89  				}
    90  			}
    91  		}
    92  	}
    93  }
    94  
    95  // get all virtualservice that have destination with subset
    96  func initVirtualServiceDestinations(ctx analysis.Context) map[resource.FullName][]*v1alpha3.Destination {
    97  	virtualservices := make(map[resource.FullName][]*v1alpha3.Destination)
    98  
    99  	ctx.ForEach(gvk.VirtualService, func(r *resource.Instance) bool {
   100  		virtualservice := r.Message.(*v1alpha3.VirtualService)
   101  		for _, routes := range virtualservice.Http {
   102  			for _, destinations := range routes.Route {
   103  				// if there's no subset specified, we're done
   104  				if destinations.Destination.Subset != "" {
   105  					for _, host := range virtualservice.Hosts {
   106  						if destinations.Destination.Host == host {
   107  							virtualservices[r.Metadata.FullName] = append(virtualservices[r.Metadata.FullName], destinations.Destination)
   108  						}
   109  					}
   110  				}
   111  			}
   112  		}
   113  
   114  		return true
   115  	})
   116  
   117  	return virtualservices
   118  }
   119  
   120  func (a *DestinationHostAnalyzer) analyzeVirtualService(r *resource.Instance, ctx analysis.Context,
   121  	serviceEntryHosts map[util.ScopedFqdn]*v1alpha3.ServiceEntry,
   122  ) {
   123  	vs := r.Message.(*v1alpha3.VirtualService)
   124  
   125  	for _, d := range getRouteDestinations(vs) {
   126  		s := util.GetDestinationHost(r.Metadata.FullName.Namespace, vs.ExportTo, d.Destination.GetHost(), serviceEntryHosts)
   127  		if s == nil {
   128  
   129  			m := msg.NewReferencedResourceNotFound(r, "host", d.Destination.GetHost())
   130  
   131  			key := fmt.Sprintf(util.DestinationHost, d.RouteRule, d.ServiceIndex, d.DestinationIndex)
   132  			if line, found := util.ErrorLine(r, key); found {
   133  				m.Line = line
   134  			}
   135  
   136  			ctx.Report(gvk.VirtualService, m)
   137  			continue
   138  		}
   139  		checkServiceEntryPorts(ctx, r, d, s)
   140  	}
   141  
   142  	for _, d := range getHTTPMirrorDestinations(vs) {
   143  		s := util.GetDestinationHost(r.Metadata.FullName.Namespace, vs.ExportTo, d.Destination.GetHost(), serviceEntryHosts)
   144  		if s == nil {
   145  
   146  			m := msg.NewReferencedResourceNotFound(r, "mirror host", d.Destination.GetHost())
   147  
   148  			var key string
   149  			if d.RouteRule == "http.mirror" {
   150  				key = fmt.Sprintf(util.MirrorHost, d.ServiceIndex)
   151  			} else {
   152  				key = fmt.Sprintf(util.MirrorsHost, d.ServiceIndex, d.DestinationIndex)
   153  			}
   154  			if line, ok := util.ErrorLine(r, key); ok {
   155  				m.Line = line
   156  			}
   157  
   158  			ctx.Report(gvk.VirtualService, m)
   159  			continue
   160  		}
   161  		checkServiceEntryPorts(ctx, r, d, s)
   162  	}
   163  }
   164  
   165  func checkServiceEntryPorts(ctx analysis.Context, r *resource.Instance, d *AnnotatedDestination, s *v1alpha3.ServiceEntry) {
   166  	if d.Destination.GetPort() == nil {
   167  		// If destination port isn't specified, it's only a problem if the service being referenced exposes multiple ports.
   168  		if len(s.GetPorts()) > 1 {
   169  			var portNumbers []int
   170  			for _, p := range s.GetPorts() {
   171  				portNumbers = append(portNumbers, int(p.GetNumber()))
   172  			}
   173  
   174  			m := msg.NewVirtualServiceDestinationPortSelectorRequired(r, d.Destination.GetHost(), portNumbers)
   175  
   176  			var key string
   177  			if d.RouteRule == "http.mirror" {
   178  				key = fmt.Sprintf(util.MirrorHost, d.ServiceIndex)
   179  			} else if d.RouteRule == "http.mirrors" {
   180  				key = fmt.Sprintf(util.MirrorsHost, d.ServiceIndex, d.DestinationIndex)
   181  			} else {
   182  				key = fmt.Sprintf(util.DestinationHost, d.RouteRule, d.ServiceIndex, d.DestinationIndex)
   183  			}
   184  			if line, ok := util.ErrorLine(r, key); ok {
   185  				m.Line = line
   186  			}
   187  
   188  			ctx.Report(gvk.VirtualService, m)
   189  			return
   190  		}
   191  
   192  		// Otherwise, it's not needed and we're done here.
   193  		return
   194  	}
   195  
   196  	foundPort := false
   197  	for _, p := range s.GetPorts() {
   198  		if d.Destination.GetPort().GetNumber() == p.GetNumber() {
   199  			foundPort = true
   200  			break
   201  		}
   202  	}
   203  	if !foundPort {
   204  
   205  		m := msg.NewReferencedResourceNotFound(r, "host:port",
   206  			fmt.Sprintf("%s:%d", d.Destination.GetHost(), d.Destination.GetPort().GetNumber()))
   207  
   208  		var key string
   209  		if d.RouteRule == "http.mirror" {
   210  			key = fmt.Sprintf(util.MirrorHost, d.ServiceIndex)
   211  		} else if d.RouteRule == "http.mirrors" {
   212  			key = fmt.Sprintf(util.MirrorsHost, d.ServiceIndex, d.DestinationIndex)
   213  		} else {
   214  			key = fmt.Sprintf(util.DestinationHost, d.RouteRule, d.ServiceIndex, d.DestinationIndex)
   215  		}
   216  		if line, ok := util.ErrorLine(r, key); ok {
   217  			m.Line = line
   218  		}
   219  
   220  		ctx.Report(gvk.VirtualService, m)
   221  	}
   222  }