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 }