github.com/kiali/kiali@v1.84.0/business/checkers/virtualservices/route_checker.go (about) 1 package virtualservices 2 3 import ( 4 "fmt" 5 6 api_networking_v1beta1 "istio.io/api/networking/v1beta1" 7 networking_v1beta1 "istio.io/client-go/pkg/apis/networking/v1beta1" 8 9 "github.com/kiali/kiali/kubernetes" 10 "github.com/kiali/kiali/models" 11 ) 12 13 type RouteChecker struct { 14 Namespaces []string 15 VirtualService *networking_v1beta1.VirtualService 16 } 17 18 // Check returns both an array of IstioCheck and a boolean indicating if the current route rule is valid. 19 // The array of IstioChecks contains the result of running the following validations: 20 // 1. All weights with a numeric number. 21 // 2. All weights have value between 0 and 100. 22 // 3. Sum of all weights are 100 (if only one weight, then it assumes that is 100). 23 // 4. All the route has to have weight label. 24 func (route RouteChecker) Check() ([]*models.IstioCheck, bool) { 25 checks, valid := make([]*models.IstioCheck, 0), true 26 27 cs, v := route.checkHttpRoutes() 28 checks = append(checks, cs...) 29 valid = valid && v 30 31 cs, v = route.checkTcpRoutes() 32 checks = append(checks, cs...) 33 valid = valid && v 34 35 cs, v = route.checkTlsRoutes() 36 checks = append(checks, cs...) 37 valid = valid && v 38 39 return checks, valid 40 } 41 42 func (route RouteChecker) checkHttpRoutes() ([]*models.IstioCheck, bool) { 43 validations := make([]*models.IstioCheck, 0) 44 valid := true 45 46 for routeIdx, httpRoute := range route.VirtualService.Spec.Http { 47 if httpRoute == nil { 48 continue 49 } 50 51 // Getting a []DestinationWeight 52 destinationWeights := httpRoute.Route 53 54 if len(destinationWeights) == 1 { 55 if destinationWeights[0] == nil { 56 continue 57 } 58 weight := destinationWeights[0].Weight 59 // We can't rely on nil value as Weight is an integer that will be always present 60 if weight > 0 && weight < 100 { 61 valid = true 62 path := fmt.Sprintf("spec/http[%d]/route[%d]/weight", routeIdx, 0) 63 validation := models.Build("virtualservices.route.singleweight", path) 64 validations = append(validations, &validation) 65 } 66 } 67 68 route.trackHttpSubset(routeIdx, "http", destinationWeights, &validations) 69 } 70 71 return validations, valid 72 } 73 74 func (route RouteChecker) checkTcpRoutes() ([]*models.IstioCheck, bool) { 75 validations := make([]*models.IstioCheck, 0) 76 valid := true 77 78 for routeIdx, tcpRoute := range route.VirtualService.Spec.Tcp { 79 if tcpRoute == nil { 80 continue 81 } 82 83 // Getting a []DestinationWeight 84 destinationWeights := tcpRoute.Route 85 86 if len(destinationWeights) == 1 { 87 if destinationWeights[0] == nil { 88 continue 89 } 90 weight := destinationWeights[0].Weight 91 if weight < 100 { 92 valid = true 93 path := fmt.Sprintf("spec/tcp[%d]/route[%d]/weight", routeIdx, 0) 94 validation := models.Build("virtualservices.route.singleweight", path) 95 validations = append(validations, &validation) 96 } 97 } 98 99 route.trackTcpTlsSubset(routeIdx, "tcp", destinationWeights, &validations) 100 } 101 102 return validations, valid 103 } 104 105 func (route RouteChecker) checkTlsRoutes() ([]*models.IstioCheck, bool) { 106 validations := make([]*models.IstioCheck, 0) 107 valid := true 108 109 for routeIdx, tlsRoute := range route.VirtualService.Spec.Tls { 110 if tlsRoute == nil { 111 continue 112 } 113 114 // Getting a []DestinationWeight 115 destinationWeights := tlsRoute.Route 116 117 if len(destinationWeights) == 1 { 118 if destinationWeights[0] == nil { 119 continue 120 } 121 weight := destinationWeights[0].Weight 122 if weight < 100 { 123 valid = true 124 path := fmt.Sprintf("spec/tls[%d]/route[%d]/weight", routeIdx, 0) 125 validation := models.Build("virtualservices.route.singleweight", path) 126 validations = append(validations, &validation) 127 } 128 } 129 130 route.trackTcpTlsSubset(routeIdx, "tls", destinationWeights, &validations) 131 } 132 133 return validations, valid 134 } 135 136 func (route RouteChecker) trackHttpSubset(routeIdx int, kind string, destinationWeights []*api_networking_v1beta1.HTTPRouteDestination, checks *[]*models.IstioCheck) { 137 subsetCollitions := map[string][]int{} 138 139 for destWeightIdx, destinationWeight := range destinationWeights { 140 if destinationWeight == nil { 141 continue 142 } 143 if destinationWeight.Destination == nil { 144 return 145 } 146 fqdn := kubernetes.GetHost(destinationWeight.Destination.Host, route.VirtualService.Namespace, route.Namespaces) 147 subset := destinationWeight.Destination.Subset 148 key := fmt.Sprintf("%s%s", fqdn.String(), subset) 149 collisions := subsetCollitions[key] 150 if collisions == nil { 151 collisions = make([]int, 0, len(destinationWeights)) 152 } 153 subsetCollitions[key] = append(collisions, destWeightIdx) 154 155 } 156 appendSubsetDuplicity(routeIdx, kind, subsetCollitions, checks) 157 } 158 159 func (route RouteChecker) trackTcpTlsSubset(routeIdx int, kind string, destinationWeights []*api_networking_v1beta1.RouteDestination, checks *[]*models.IstioCheck) { 160 subsetCollitions := map[string][]int{} 161 162 for destWeightIdx, destinationWeight := range destinationWeights { 163 if destinationWeight == nil { 164 continue 165 } 166 if destinationWeight.Destination == nil { 167 return 168 } 169 fqdn := kubernetes.GetHost(destinationWeight.Destination.Host, route.VirtualService.Namespace, route.Namespaces) 170 subset := destinationWeight.Destination.Subset 171 key := fmt.Sprintf("%s%s", fqdn.String(), subset) 172 collisions := subsetCollitions[key] 173 if collisions == nil { 174 collisions = make([]int, 0, len(destinationWeights)) 175 } 176 subsetCollitions[key] = append(collisions, destWeightIdx) 177 178 } 179 appendSubsetDuplicity(routeIdx, kind, subsetCollitions, checks) 180 } 181 182 func appendSubsetDuplicity(routeIdx int, kind string, collistionsMap map[string][]int, checks *[]*models.IstioCheck) { 183 for _, dups := range collistionsMap { 184 if len(dups) > 1 { 185 for _, dup := range dups { 186 path := fmt.Sprintf("spec/%s[%d]/route[%d]/host", kind, routeIdx, dup) 187 validation := models.Build("virtualservices.route.repeatedsubset", path) 188 *checks = append(*checks, &validation) 189 } 190 } 191 } 192 }