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  }