istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/analysis/analyzers/telemetry/selector.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  package telemetry
    15  
    16  import (
    17  	"fmt"
    18  
    19  	"k8s.io/apimachinery/pkg/labels"
    20  
    21  	"istio.io/api/telemetry/v1alpha1"
    22  	"istio.io/istio/pkg/config"
    23  	"istio.io/istio/pkg/config/analysis"
    24  	"istio.io/istio/pkg/config/analysis/analyzers/util"
    25  	"istio.io/istio/pkg/config/analysis/msg"
    26  	"istio.io/istio/pkg/config/resource"
    27  	"istio.io/istio/pkg/config/schema/gvk"
    28  )
    29  
    30  // SelectorAnalyzer validates, per namespace, that:
    31  // * telemetry resources that define a workload selector match at least one pod
    32  // * there aren't multiple telemetry resources that select overlapping pods
    33  type SelectorAnalyzer struct{}
    34  
    35  var _ analysis.Analyzer = &SelectorAnalyzer{}
    36  
    37  // Metadata implements Analyzer
    38  func (a *SelectorAnalyzer) Metadata() analysis.Metadata {
    39  	return analysis.Metadata{
    40  		Name: "telemetry.SelectorAnalyzer",
    41  		Description: "Validates that telemetries that define a selector " +
    42  			"match at least one pod, and that there aren't multiple telemetry resources that select overlapping pods",
    43  		Inputs: []config.GroupVersionKind{
    44  			gvk.Telemetry,
    45  			gvk.Pod,
    46  		},
    47  	}
    48  }
    49  
    50  // Analyze implements Analyzer
    51  func (a *SelectorAnalyzer) Analyze(c analysis.Context) {
    52  	podsToTelemetries := make(map[resource.FullName][]*resource.Instance)
    53  
    54  	// This is using an unindexed approach for matching selectors.
    55  	// Using an index for selectoes is problematic because selector != label
    56  	// We can match a label to a selector, but we can't generate a selector from a label.
    57  	c.ForEach(gvk.Telemetry, func(rs *resource.Instance) bool {
    58  		s := rs.Message.(*v1alpha1.Telemetry)
    59  
    60  		// For this analysis, ignore Telemetries with no selectors specified at all.
    61  		if s.Selector == nil || len(s.GetSelector().MatchLabels) == 0 {
    62  			return true
    63  		}
    64  
    65  		sNs := rs.Metadata.FullName.Namespace
    66  		sel := labels.SelectorFromSet(s.GetSelector().MatchLabels)
    67  
    68  		foundPod := false
    69  		c.ForEach(gvk.Pod, func(rp *resource.Instance) bool {
    70  			pNs := rp.Metadata.FullName.Namespace
    71  			podLabels := labels.Set(rp.Metadata.Labels)
    72  
    73  			// Only attempt to match in the same namespace
    74  			if pNs != sNs {
    75  				return true
    76  			}
    77  
    78  			if sel.Matches(podLabels) {
    79  				foundPod = true
    80  				podsToTelemetries[rp.Metadata.FullName] = append(podsToTelemetries[rp.Metadata.FullName], rs)
    81  			}
    82  
    83  			return true
    84  		})
    85  
    86  		if !foundPod {
    87  			m := msg.NewReferencedResourceNotFound(rs, "selector", sel.String())
    88  
    89  			label := util.ExtractLabelFromSelectorString(sel.String())
    90  			if line, ok := util.ErrorLine(rs, fmt.Sprintf(util.TelemetrySelector, label)); ok {
    91  				m.Line = line
    92  			}
    93  
    94  			c.Report(gvk.Telemetry, m)
    95  		}
    96  
    97  		return true
    98  	})
    99  
   100  	for p, sList := range podsToTelemetries {
   101  		if len(sList) == 1 {
   102  			continue
   103  		}
   104  
   105  		sNames := getNames(sList)
   106  		for _, rs := range sList {
   107  			m := msg.NewConflictingTelemetryWorkloadSelectors(rs, sNames, p.Namespace.String(), p.Name.String())
   108  
   109  			if line, ok := util.ErrorLine(rs, fmt.Sprintf(util.MetadataName)); ok {
   110  				m.Line = line
   111  			}
   112  
   113  			c.Report(gvk.Telemetry, m)
   114  		}
   115  	}
   116  }