istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/analysis/analyzers/multicluster_analyzers_test.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 analyzers
    16  
    17  import (
    18  	"fmt"
    19  	"os"
    20  	"testing"
    21  
    22  	. "github.com/onsi/gomega"
    23  
    24  	"istio.io/istio/pilot/pkg/config/file"
    25  	"istio.io/istio/pkg/cluster"
    26  	"istio.io/istio/pkg/config"
    27  	"istio.io/istio/pkg/config/analysis"
    28  	"istio.io/istio/pkg/config/analysis/analyzers/multicluster"
    29  	"istio.io/istio/pkg/config/analysis/local"
    30  	"istio.io/istio/pkg/config/analysis/msg"
    31  	"istio.io/istio/pkg/config/schema/collections"
    32  	"istio.io/istio/pkg/kube"
    33  )
    34  
    35  type mcTestCase struct {
    36  	name               string
    37  	cluster1InputFiles []string
    38  	cluster2InputFiles []string
    39  	analyzer           analysis.Analyzer
    40  	expected           []message
    41  }
    42  
    43  var mcTestGrid = []mcTestCase{
    44  	{
    45  		name: "InconsistentMultiClusterService",
    46  		cluster1InputFiles: []string{
    47  			"testdata/multicluster/inconsistent-service-1.yaml",
    48  		},
    49  		cluster2InputFiles: []string{
    50  			"testdata/multicluster/inconsistent-service-2.yaml",
    51  		},
    52  		analyzer: &multicluster.ServiceAnalyzer{},
    53  		expected: []message{
    54  			{msg.MultiClusterInconsistentService, "Service my-namespace/extra-port"},
    55  			{msg.MultiClusterInconsistentService, "Service my-namespace/inconsistent-port-name"},
    56  			{msg.MultiClusterInconsistentService, "Service my-namespace/mixed-mode"},
    57  			{msg.MultiClusterInconsistentService, "Service my-namespace/mixed-type"},
    58  			{msg.MultiClusterInconsistentService, "Service my-namespace/mixed-port-protocol"},
    59  		},
    60  	},
    61  }
    62  
    63  // TestMultiClusterAnalyzers sets up two clusters and runs the multi-cluster analyzers on them.
    64  func TestMultiClusterAnalyzers(t *testing.T) {
    65  	requestedInputsByAnalyzer := make(map[string]map[config.GroupVersionKind]struct{})
    66  	// For each test case, verify we get the expected messages as output
    67  	for _, tc := range mcTestGrid {
    68  		tc := tc // Capture range variable so subtests work correctly
    69  		t.Run(tc.name, func(t *testing.T) {
    70  			g := NewWithT(t)
    71  
    72  			// Set up a hook to record which collections are accessed by each analyzer
    73  			analyzerName := tc.analyzer.Metadata().Name
    74  			cr := func(col config.GroupVersionKind) {
    75  				if _, ok := requestedInputsByAnalyzer[analyzerName]; !ok {
    76  					requestedInputsByAnalyzer[analyzerName] = make(map[config.GroupVersionKind]struct{})
    77  				}
    78  				requestedInputsByAnalyzer[analyzerName][col] = struct{}{}
    79  			}
    80  
    81  			// Set up Analyzer for this test case
    82  			sa, err := setupMultiClusterEnvironmentForCase(tc, cr)
    83  			if err != nil {
    84  				t.Fatalf("Error setting up analysis for testcase %s: %v", tc.name, err)
    85  			}
    86  
    87  			// Run the analysis
    88  			result, err := runAnalyzer(sa)
    89  			if err != nil {
    90  				t.Fatalf("Error running analysis on testcase %s: %v", tc.name, err)
    91  			}
    92  
    93  			g.Expect(extractFields(result.Messages)).To(ConsistOf(tc.expected), "%v", prettyPrintMessages(result.Messages))
    94  		})
    95  	}
    96  }
    97  
    98  func setupMultiClusterEnvironmentForCase(tc mcTestCase, cr local.CollectionReporterFn) (*local.IstiodAnalyzer, error) {
    99  	sa := local.NewIstiodAnalyzer(AllMultiClusterCombined(), "", "istio-system", cr)
   100  
   101  	// Add the test files to the fake client
   102  	if err := addStore(sa, "cluster1", tc.cluster1InputFiles); err != nil {
   103  		return nil, err
   104  	}
   105  	if err := addStore(sa, "cluster2", tc.cluster2InputFiles); err != nil {
   106  		return nil, err
   107  	}
   108  
   109  	// Include default resources
   110  	if err := sa.AddDefaultResources(); err != nil {
   111  		return nil, fmt.Errorf("error adding default resources: %v", err)
   112  	}
   113  	return sa, nil
   114  }
   115  
   116  type fakeClientImpl struct {
   117  	kube.CLIClient
   118  	clusterID cluster.ID
   119  }
   120  
   121  func (f *fakeClientImpl) ClusterID() cluster.ID {
   122  	return f.clusterID
   123  }
   124  
   125  func addStore(sa *local.IstiodAnalyzer, clusterName string, yamls []string) error {
   126  	client := &fakeClientImpl{
   127  		CLIClient: kube.NewFakeClient(),
   128  		clusterID: cluster.ID(clusterName),
   129  	}
   130  	// Gather test files
   131  	src := file.NewKubeSource(collections.All)
   132  	for i, yamlFile := range yamls {
   133  		data, err := os.ReadFile(yamlFile)
   134  		if err != nil {
   135  			return err
   136  		}
   137  		err = src.ApplyContent(fmt.Sprintf("%d", i), string(data))
   138  		if err != nil {
   139  			return err
   140  		}
   141  	}
   142  	sa.AddSourceForCluster(src, cluster.ID(clusterName))
   143  	sa.AddRunningKubeSourceWithRevision(client, clusterName, false)
   144  	return nil
   145  }