istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/pilot/analysis/analysis_test.go (about)

     1  //go:build integ
     2  // +build integ
     3  
     4  // Copyright Istio Authors
     5  //
     6  // Licensed under the Apache License, Version 2.0 (the "License");
     7  // you may not use this file except in compliance with the License.
     8  // You may obtain a copy of the License at
     9  //
    10  //     http://www.apache.org/licenses/LICENSE-2.0
    11  //
    12  // Unless required by applicable law or agreed to in writing, software
    13  // distributed under the License is distributed on an "AS IS" BASIS,
    14  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15  // See the License for the specific language governing permissions and
    16  // limitations under the License.
    17  
    18  package analysis
    19  
    20  import (
    21  	"context"
    22  	"fmt"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/google/go-cmp/cmp"
    27  	"google.golang.org/protobuf/testing/protocmp"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  
    30  	"istio.io/api/meta/v1alpha1"
    31  	"istio.io/istio/pkg/config/analysis/msg"
    32  	"istio.io/istio/pkg/test/framework"
    33  	"istio.io/istio/pkg/test/framework/components/istioctl"
    34  	"istio.io/istio/pkg/test/framework/components/namespace"
    35  	"istio.io/istio/pkg/test/framework/label"
    36  	"istio.io/istio/pkg/test/util/retry"
    37  )
    38  
    39  func TestWait(t *testing.T) {
    40  	// nolint: staticcheck
    41  	framework.NewTest(t).
    42  		RequiresSingleCluster().
    43  		RequiresLocalControlPlane().
    44  		Run(func(t framework.TestContext) {
    45  			ns := namespace.NewOrFail(t, t, namespace.Config{
    46  				Prefix: "default",
    47  				Inject: true,
    48  			})
    49  			t.ConfigIstio().YAML(ns.Name(), `
    50  apiVersion: networking.istio.io/v1alpha3
    51  kind: VirtualService
    52  metadata:
    53    name: reviews
    54  spec:
    55    gateways: [missing-gw]
    56    hosts:
    57    - reviews
    58    http:
    59    - route:
    60      - destination:
    61          host: reviews
    62  `).ApplyOrFail(t)
    63  			istioCtl := istioctl.NewOrFail(t, t, istioctl.Config{Cluster: t.Clusters().Default()})
    64  			istioCtl.InvokeOrFail(t, []string{"x", "wait", "-v", "VirtualService", "reviews." + ns.Name()})
    65  		})
    66  }
    67  
    68  func TestAnalysisWritesStatus(t *testing.T) {
    69  	// nolint: staticcheck
    70  	framework.NewTest(t).
    71  		RequiresLocalControlPlane().
    72  		Label(label.CustomSetup).
    73  		Run(func(t framework.TestContext) {
    74  			ns := namespace.NewOrFail(t, t, namespace.Config{
    75  				Prefix:   "default",
    76  				Inject:   true,
    77  				Revision: "",
    78  				Labels:   nil,
    79  			})
    80  			t.ConfigIstio().YAML(ns.Name(), `
    81  apiVersion: v1
    82  kind: Service
    83  metadata:
    84    name: reviews
    85  spec:
    86    selector:
    87      app: reviews
    88    type: ClusterIP
    89    ports:
    90    - name: http-monitoring
    91      port: 15014
    92      protocol: TCP
    93      targetPort: 15014
    94  `).ApplyOrFail(t)
    95  			// Apply bad config (referencing invalid host)
    96  			t.ConfigIstio().YAML(ns.Name(), `
    97  apiVersion: networking.istio.io/v1alpha3
    98  kind: VirtualService
    99  metadata:
   100    name: reviews
   101  spec:
   102    gateways: [missing-gw]
   103    hosts:
   104    - reviews
   105    http:
   106    - route:
   107      - destination: 
   108          host: reviews
   109  `).ApplyOrFail(t)
   110  			// Status should report error
   111  			retry.UntilSuccessOrFail(t, func() error {
   112  				return expectVirtualServiceStatus(t, ns, true)
   113  			}, retry.Timeout(time.Minute*5))
   114  			// Apply config to make this not invalid
   115  			t.ConfigIstio().YAML(ns.Name(), `
   116  apiVersion: networking.istio.io/v1alpha3
   117  kind: Gateway
   118  metadata:
   119    name: missing-gw
   120  spec:
   121    selector:
   122      istio: ingressgateway
   123    servers:
   124    - port:
   125        number: 80
   126        name: http
   127        protocol: HTTP
   128      hosts:
   129      - "*"
   130  `).ApplyOrFail(t)
   131  			// Status should no longer report error
   132  			retry.UntilSuccessOrFail(t, func() error {
   133  				return expectVirtualServiceStatus(t, ns, false)
   134  			})
   135  		})
   136  }
   137  
   138  func TestWorkloadEntryUpdatesStatus(t *testing.T) {
   139  	framework.NewTest(t).
   140  		Run(func(t framework.TestContext) {
   141  			ns := namespace.NewOrFail(t, t, namespace.Config{
   142  				Prefix:   "default",
   143  				Inject:   true,
   144  				Revision: "",
   145  				Labels:   nil,
   146  			})
   147  
   148  			// create WorkloadEntry
   149  			t.ConfigIstio().YAML(ns.Name(), `
   150  apiVersion: networking.istio.io/v1alpha3
   151  kind: WorkloadEntry
   152  metadata:
   153    name: vm-1
   154  spec:
   155    address: 127.0.0.1
   156  `).ApplyOrFail(t)
   157  
   158  			retry.UntilSuccessOrFail(t, func() error {
   159  				// we should expect an empty array not nil
   160  				return expectWorkloadEntryStatus(t, ns, nil)
   161  			})
   162  
   163  			// add one health condition and one other condition
   164  			addedConds := []*v1alpha1.IstioCondition{
   165  				{
   166  					Type:   "Health",
   167  					Reason: "DontTellAnyoneButImNotARealReason",
   168  					Status: "True",
   169  				},
   170  				{
   171  					Type:   "SomeRandomType",
   172  					Reason: "ImNotHealthSoDontTouchMe",
   173  					Status: "True",
   174  				},
   175  			}
   176  
   177  			// Get WorkloadEntry to append to
   178  			we, err := t.Clusters().Default().Istio().NetworkingV1alpha3().WorkloadEntries(ns.Name()).Get(context.TODO(), "vm-1", metav1.GetOptions{})
   179  			if err != nil {
   180  				t.Error(err)
   181  			}
   182  
   183  			if we.Status.Conditions == nil {
   184  				we.Status.Conditions = []*v1alpha1.IstioCondition{}
   185  			}
   186  			// append to conditions
   187  			we.Status.Conditions = append(we.Status.Conditions, addedConds...)
   188  			// update the status
   189  			_, err = t.Clusters().Default().Istio().NetworkingV1alpha3().WorkloadEntries(ns.Name()).UpdateStatus(context.TODO(), we, metav1.UpdateOptions{})
   190  			if err != nil {
   191  				t.Error(err)
   192  			}
   193  			// we should have all the conditions present
   194  			retry.UntilSuccessOrFail(t, func() error {
   195  				// should update
   196  				return expectWorkloadEntryStatus(t, ns, []*v1alpha1.IstioCondition{
   197  					{
   198  						Type:   "Health",
   199  						Reason: "DontTellAnyoneButImNotARealReason",
   200  						Status: "True",
   201  					},
   202  					{
   203  						Type:   "SomeRandomType",
   204  						Reason: "ImNotHealthSoDontTouchMe",
   205  						Status: "True",
   206  					},
   207  				})
   208  			})
   209  
   210  			// get the workload entry to replace the health condition field
   211  			we, err = t.Clusters().Default().Istio().NetworkingV1alpha3().WorkloadEntries(ns.Name()).Get(context.TODO(), "vm-1", metav1.GetOptions{})
   212  			if err != nil {
   213  				t.Fatal(err)
   214  			}
   215  			// replacing the condition
   216  			for i, cond := range we.Status.Conditions {
   217  				if cond.Type == "Health" {
   218  					we.Status.Conditions[i] = &v1alpha1.IstioCondition{
   219  						Type:   "Health",
   220  						Reason: "LooksLikeIHavebeenReplaced",
   221  						Status: "False",
   222  					}
   223  				}
   224  			}
   225  
   226  			// update this new status
   227  			_, err = t.Clusters().Default().Istio().NetworkingV1alpha3().WorkloadEntries(ns.Name()).UpdateStatus(context.TODO(), we, metav1.UpdateOptions{})
   228  			if err != nil {
   229  				t.Error(err)
   230  			}
   231  			retry.UntilSuccessOrFail(t, func() error {
   232  				// should update
   233  				return expectWorkloadEntryStatus(t, ns, []*v1alpha1.IstioCondition{
   234  					{
   235  						Type:   "Health",
   236  						Reason: "LooksLikeIHavebeenReplaced",
   237  						Status: "False",
   238  					},
   239  					{
   240  						Type:   "SomeRandomType",
   241  						Reason: "ImNotHealthSoDontTouchMe",
   242  						Status: "True",
   243  					},
   244  				})
   245  			})
   246  		})
   247  }
   248  
   249  func expectVirtualServiceStatus(t framework.TestContext, ns namespace.Instance, hasError bool) error {
   250  	c := t.Clusters().Default()
   251  
   252  	x, err := c.Istio().NetworkingV1alpha3().VirtualServices(ns.Name()).Get(context.TODO(), "reviews", metav1.GetOptions{})
   253  	if err != nil {
   254  		t.Fatalf("unexpected test failure: can't get virtualservice: %v", err)
   255  	}
   256  
   257  	status := &x.Status
   258  
   259  	if hasError {
   260  		if len(status.ValidationMessages) < 1 {
   261  			return fmt.Errorf("expected validation messages to exist, but got nothing")
   262  		}
   263  		found := false
   264  		for _, validation := range status.ValidationMessages {
   265  			if validation.Type.Code == msg.ReferencedResourceNotFound.Code() {
   266  				found = true
   267  			}
   268  		}
   269  		if !found {
   270  			return fmt.Errorf("expected error %v to exist", msg.ReferencedResourceNotFound.Code())
   271  		}
   272  	} else if status.ValidationMessages != nil && len(status.ValidationMessages) > 0 {
   273  		return fmt.Errorf("expected no validation messages, but got %d", len(status.ValidationMessages))
   274  	}
   275  
   276  	if len(status.Conditions) < 1 {
   277  		return fmt.Errorf("expected conditions to exist, but got nothing")
   278  	}
   279  	found := false
   280  	for _, condition := range status.Conditions {
   281  		if condition.Type == "Reconciled" {
   282  			found = true
   283  			if condition.Status != "True" {
   284  				return fmt.Errorf("expected Reconciled to be true but was %v", condition.Status)
   285  			}
   286  		}
   287  	}
   288  	if !found {
   289  		return fmt.Errorf("expected Reconciled condition to exist, but got %v", status.Conditions)
   290  	}
   291  	return nil
   292  }
   293  
   294  func expectWorkloadEntryStatus(t framework.TestContext, ns namespace.Instance, expectedConds []*v1alpha1.IstioCondition) error {
   295  	c := t.Clusters().Default()
   296  
   297  	x, err := c.Istio().NetworkingV1alpha3().WorkloadEntries(ns.Name()).Get(context.TODO(), "vm-1", metav1.GetOptions{})
   298  	if err != nil {
   299  		t.Fatalf("unexpected test failure: can't get workloadentry: %v", err)
   300  		return err
   301  	}
   302  
   303  	statusConds := x.Status.Conditions
   304  
   305  	// todo for some reason when a WorkloadEntry is created a "Reconciled" Condition isn't added.
   306  	for i, cond := range x.Status.Conditions {
   307  		// remove reconciled conditions for when WorkloadEntry starts initializing
   308  		// with a reconciled status.
   309  		if cond.Type == "Reconciled" {
   310  			statusConds = append(statusConds[:i], statusConds[i+1:]...)
   311  		}
   312  	}
   313  
   314  	if !cmp.Equal(statusConds, expectedConds, protocmp.Transform()) {
   315  		return fmt.Errorf("expected conditions %v got %v", expectedConds, statusConds)
   316  	}
   317  	return nil
   318  }