github.com/kiali/kiali@v1.84.0/business/checkers/k8sgateways/multi_match_checker.go (about)

     1  package k8sgateways
     2  
     3  import (
     4  	"fmt"
     5  
     6  	k8s_networking_v1 "sigs.k8s.io/gateway-api/apis/v1"
     7  
     8  	"github.com/kiali/kiali/models"
     9  )
    10  
    11  type MultiMatchChecker struct {
    12  	Cluster     string
    13  	K8sGateways []*k8s_networking_v1.Gateway
    14  }
    15  
    16  const (
    17  	K8sGatewayCheckerType = "k8sgateway"
    18  )
    19  
    20  // Check validates that no two gateways share the same host+port combination
    21  func (m MultiMatchChecker) Check() models.IstioValidations {
    22  	validations := models.IstioValidations{}
    23  
    24  	for _, g := range m.K8sGateways {
    25  		gatewayRuleName := g.Name
    26  		gatewayNamespace := g.Namespace
    27  
    28  		// With addresses
    29  		for _, address := range g.Spec.Addresses {
    30  			duplicate, collidingGateways := m.findMatchIP(address, g.Name)
    31  			if duplicate {
    32  				// The above is referenced by each one below..
    33  				currentHostValidation := createError(gatewayRuleName, "k8sgateways.multimatch.ip", gatewayNamespace, m.Cluster, "spec/addresses/value", collidingGateways)
    34  				validations = validations.MergeValidations(currentHostValidation)
    35  			}
    36  		}
    37  
    38  		// With listeners
    39  		for index, listener := range g.Spec.Listeners {
    40  			duplicate, collidingGateways := m.findMatch(listener, g.Name)
    41  			// Find in a different k8s GW
    42  			if duplicate {
    43  				// The above is referenced by each one below..
    44  				currentHostValidation := createError(gatewayRuleName, "k8sgateways.multimatch.listener", gatewayNamespace, m.Cluster, fmt.Sprintf("spec/listeners[%d]/hostname", index), collidingGateways)
    45  				validations = validations.MergeValidations(currentHostValidation)
    46  			}
    47  			// Check for unique listeners in the GW
    48  			for i, l := range g.Spec.Listeners {
    49  				if listener.Name != l.Name && l.Hostname != nil && listener.Hostname != nil && *listener.Hostname == *l.Hostname && listener.Port == l.Port && listener.Protocol == l.Protocol {
    50  					currentHostValidation := createError(gatewayRuleName, "k8sgateways.unique.listener", gatewayNamespace, m.Cluster, fmt.Sprintf("spec/listeners[%d]/name", i), nil)
    51  					validations = validations.MergeValidations(currentHostValidation)
    52  				}
    53  			}
    54  		}
    55  	}
    56  
    57  	return validations
    58  }
    59  
    60  // Create validation error for k8sgateway object
    61  func createError(gatewayRuleName string, ruleCode string, namespace string, cluster string, path string, references []models.IstioValidationKey) models.IstioValidations {
    62  	key := models.IstioValidationKey{Name: gatewayRuleName, Namespace: namespace, ObjectType: K8sGatewayCheckerType, Cluster: cluster}
    63  	checks := models.Build(ruleCode, path)
    64  	rrValidation := &models.IstioValidation{
    65  		Name:       gatewayRuleName,
    66  		ObjectType: K8sGatewayCheckerType,
    67  		Valid:      true,
    68  		Checks: []*models.IstioCheck{
    69  			&checks,
    70  		},
    71  		References: references,
    72  	}
    73  
    74  	return models.IstioValidations{key: rrValidation}
    75  }
    76  
    77  // findMatch uses a linear search with regexp to check for matching gateway host + port combinations. If this becomes a bottleneck for performance, replace with a graph or trie algorithm.
    78  func (m MultiMatchChecker) findMatch(listener k8s_networking_v1.Listener, gwName string) (bool, []models.IstioValidationKey) {
    79  	collidingGateways := make([]models.IstioValidationKey, 0)
    80  
    81  	for _, gw := range m.K8sGateways {
    82  		if gw.Name == gwName {
    83  			continue
    84  		}
    85  		for _, l := range gw.Spec.Listeners {
    86  			if l.Hostname != nil && listener.Hostname != nil && *l.Hostname == *listener.Hostname && l.Port == listener.Port && l.Protocol == listener.Protocol {
    87  				key := models.IstioValidationKey{Name: gw.Name, Namespace: gw.Namespace, ObjectType: K8sGatewayCheckerType}
    88  				collidingGateways = append(collidingGateways, key)
    89  			}
    90  		}
    91  
    92  	}
    93  	return len(collidingGateways) > 0, collidingGateways
    94  }
    95  
    96  // Check duplicates IP
    97  func (m MultiMatchChecker) findMatchIP(address k8s_networking_v1.GatewayAddress, gwName string) (bool, []models.IstioValidationKey) {
    98  	collidingGateways := make([]models.IstioValidationKey, 0)
    99  
   100  	for _, aa := range m.K8sGateways {
   101  		if aa.Name == gwName {
   102  			continue
   103  		}
   104  
   105  		for _, a := range aa.Spec.Addresses {
   106  			if a.Type != nil && address.Type != nil && *a.Type == *address.Type && a.Value == address.Value {
   107  				key := models.IstioValidationKey{Name: aa.Name, Namespace: aa.Namespace, ObjectType: K8sGatewayCheckerType}
   108  				collidingGateways = append(collidingGateways, key)
   109  			}
   110  		}
   111  	}
   112  	return len(collidingGateways) > 0, collidingGateways
   113  }