| //go:build integ |
| // +build integ |
| |
| // Copyright Istio Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package analysis |
| |
| import ( |
| "context" |
| "fmt" |
| "testing" |
| "time" |
| ) |
| |
| import ( |
| "github.com/google/go-cmp/cmp" |
| "google.golang.org/protobuf/testing/protocmp" |
| "istio.io/api/meta/v1alpha1" |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| ) |
| |
| import ( |
| "github.com/apache/dubbo-go-pixiu/pkg/config/analysis/msg" |
| "github.com/apache/dubbo-go-pixiu/pkg/test/framework" |
| "github.com/apache/dubbo-go-pixiu/pkg/test/framework/components/namespace" |
| "github.com/apache/dubbo-go-pixiu/pkg/test/framework/features" |
| "github.com/apache/dubbo-go-pixiu/pkg/test/framework/label" |
| "github.com/apache/dubbo-go-pixiu/pkg/test/util/retry" |
| ) |
| |
| func TestStatusExistsByDefault(t *testing.T) { |
| // This test is not yet implemented |
| framework.NewTest(t). |
| NotImplementedYet(features.Usability_Observability_Status_DefaultExists) |
| } |
| |
| func TestAnalysisWritesStatus(t *testing.T) { |
| framework.NewTest(t). |
| Features(features.Usability_Observability_Status). |
| // TODO: make feature labels heirarchical constants like: |
| // Label(features.Usability.Observability.Status). |
| RequiresLocalControlPlane(). |
| Label(label.CustomSetup). |
| Run(func(t framework.TestContext) { |
| ns := namespace.NewOrFail(t, t, namespace.Config{ |
| Prefix: "default", |
| Inject: true, |
| Revision: "", |
| Labels: nil, |
| }) |
| t.ConfigIstio().YAML(ns.Name(), ` |
| apiVersion: v1 |
| kind: Service |
| metadata: |
| name: reviews |
| spec: |
| selector: |
| app: reviews |
| type: ClusterIP |
| ports: |
| - name: http-monitoring |
| port: 15014 |
| protocol: TCP |
| targetPort: 15014 |
| `).ApplyOrFail(t) |
| // Apply bad config (referencing invalid host) |
| t.ConfigIstio().YAML(ns.Name(), ` |
| apiVersion: networking.istio.io/v1alpha3 |
| kind: VirtualService |
| metadata: |
| name: reviews |
| spec: |
| gateways: [missing-gw] |
| hosts: |
| - reviews |
| http: |
| - route: |
| - destination: |
| host: reviews |
| `).ApplyOrFail(t) |
| // Status should report error |
| retry.UntilSuccessOrFail(t, func() error { |
| return expectVirtualServiceStatus(t, ns, true) |
| }, retry.Timeout(time.Minute*5)) |
| // Apply config to make this not invalid |
| t.ConfigIstio().YAML(ns.Name(), ` |
| apiVersion: networking.istio.io/v1alpha3 |
| kind: Gateway |
| metadata: |
| name: missing-gw |
| spec: |
| selector: |
| istio: ingressgateway |
| servers: |
| - port: |
| number: 80 |
| name: http |
| protocol: HTTP |
| hosts: |
| - "*" |
| `).ApplyOrFail(t) |
| // Status should no longer report error |
| retry.UntilSuccessOrFail(t, func() error { |
| return expectVirtualServiceStatus(t, ns, false) |
| }) |
| }) |
| } |
| |
| func TestWorkloadEntryUpdatesStatus(t *testing.T) { |
| framework.NewTest(t). |
| Features(features.Usability_Observability_Status). |
| Run(func(t framework.TestContext) { |
| ns := namespace.NewOrFail(t, t, namespace.Config{ |
| Prefix: "default", |
| Inject: true, |
| Revision: "", |
| Labels: nil, |
| }) |
| |
| // create WorkloadEntry |
| t.ConfigIstio().YAML(ns.Name(), ` |
| apiVersion: networking.istio.io/v1alpha3 |
| kind: WorkloadEntry |
| metadata: |
| name: vm-1 |
| spec: |
| address: 127.0.0.1 |
| `).ApplyOrFail(t) |
| |
| retry.UntilSuccessOrFail(t, func() error { |
| // we should expect an empty array not nil |
| return expectWorkloadEntryStatus(t, ns, nil) |
| }) |
| |
| // add one health condition and one other condition |
| addedConds := []*v1alpha1.IstioCondition{ |
| { |
| Type: "Health", |
| Reason: "DontTellAnyoneButImNotARealReason", |
| Status: "True", |
| }, |
| { |
| Type: "SomeRandomType", |
| Reason: "ImNotHealthSoDontTouchMe", |
| Status: "True", |
| }, |
| } |
| |
| // Get WorkloadEntry to append to |
| we, err := t.Clusters().Default().Istio().NetworkingV1alpha3().WorkloadEntries(ns.Name()).Get(context.TODO(), "vm-1", metav1.GetOptions{}) |
| if err != nil { |
| t.Error(err) |
| } |
| |
| if we.Status.Conditions == nil { |
| we.Status.Conditions = []*v1alpha1.IstioCondition{} |
| } |
| // append to conditions |
| we.Status.Conditions = append(we.Status.Conditions, addedConds...) |
| // update the status |
| _, err = t.Clusters().Default().Istio().NetworkingV1alpha3().WorkloadEntries(ns.Name()).UpdateStatus(context.TODO(), we, metav1.UpdateOptions{}) |
| if err != nil { |
| t.Error(err) |
| } |
| // we should have all the conditions present |
| retry.UntilSuccessOrFail(t, func() error { |
| // should update |
| return expectWorkloadEntryStatus(t, ns, []*v1alpha1.IstioCondition{ |
| { |
| Type: "Health", |
| Reason: "DontTellAnyoneButImNotARealReason", |
| Status: "True", |
| }, |
| { |
| Type: "SomeRandomType", |
| Reason: "ImNotHealthSoDontTouchMe", |
| Status: "True", |
| }, |
| }) |
| }) |
| |
| // get the workload entry to replace the health condition field |
| we, err = t.Clusters().Default().Istio().NetworkingV1alpha3().WorkloadEntries(ns.Name()).Get(context.TODO(), "vm-1", metav1.GetOptions{}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| // replacing the condition |
| for i, cond := range we.Status.Conditions { |
| if cond.Type == "Health" { |
| we.Status.Conditions[i] = &v1alpha1.IstioCondition{ |
| Type: "Health", |
| Reason: "LooksLikeIHavebeenReplaced", |
| Status: "False", |
| } |
| } |
| } |
| |
| // update this new status |
| _, err = t.Clusters().Default().Istio().NetworkingV1alpha3().WorkloadEntries(ns.Name()).UpdateStatus(context.TODO(), we, metav1.UpdateOptions{}) |
| |
| if err != nil { |
| t.Error(err) |
| } |
| retry.UntilSuccessOrFail(t, func() error { |
| // should update |
| return expectWorkloadEntryStatus(t, ns, []*v1alpha1.IstioCondition{ |
| { |
| Type: "Health", |
| Reason: "LooksLikeIHavebeenReplaced", |
| Status: "False", |
| }, |
| { |
| Type: "SomeRandomType", |
| Reason: "ImNotHealthSoDontTouchMe", |
| Status: "True", |
| }, |
| }) |
| }) |
| }) |
| } |
| |
| func expectVirtualServiceStatus(t framework.TestContext, ns namespace.Instance, hasError bool) error { |
| c := t.Clusters().Default() |
| |
| x, err := c.Istio().NetworkingV1alpha3().VirtualServices(ns.Name()).Get(context.TODO(), "reviews", metav1.GetOptions{}) |
| if err != nil { |
| t.Fatalf("unexpected test failure: can't get virtualservice: %v", err) |
| } |
| |
| status := &x.Status |
| |
| if hasError { |
| if len(status.ValidationMessages) < 1 { |
| return fmt.Errorf("expected validation messages to exist, but got nothing") |
| } |
| found := false |
| for _, validation := range status.ValidationMessages { |
| if validation.Type.Code == msg.ReferencedResourceNotFound.Code() { |
| found = true |
| } |
| } |
| if !found { |
| return fmt.Errorf("expected error %v to exist", msg.ReferencedResourceNotFound.Code()) |
| } |
| } else if status.ValidationMessages != nil && len(status.ValidationMessages) > 0 { |
| return fmt.Errorf("expected no validation messages, but got %d", len(status.ValidationMessages)) |
| } |
| |
| if len(status.Conditions) < 1 { |
| return fmt.Errorf("expected conditions to exist, but got nothing") |
| } |
| found := false |
| for _, condition := range status.Conditions { |
| if condition.Type == "Reconciled" { |
| found = true |
| if condition.Status != "True" { |
| return fmt.Errorf("expected Reconciled to be true but was %v", condition.Status) |
| } |
| } |
| } |
| if !found { |
| return fmt.Errorf("expected Reconciled condition to exist, but got %v", status.Conditions) |
| } |
| return nil |
| } |
| |
| func expectWorkloadEntryStatus(t framework.TestContext, ns namespace.Instance, expectedConds []*v1alpha1.IstioCondition) error { |
| c := t.Clusters().Default() |
| |
| x, err := c.Istio().NetworkingV1alpha3().WorkloadEntries(ns.Name()).Get(context.TODO(), "vm-1", metav1.GetOptions{}) |
| if err != nil { |
| t.Fatalf("unexpected test failure: can't get workloadentry: %v", err) |
| return err |
| } |
| |
| statusConds := x.Status.Conditions |
| |
| // todo for some reason when a WorkloadEntry is created a "Reconciled" Condition isn't added. |
| for i, cond := range x.Status.Conditions { |
| // remove reconciled conditions for when WorkloadEntry starts initializing |
| // with a reconciled status. |
| if cond.Type == "Reconciled" { |
| statusConds = append(statusConds[:i], statusConds[i+1:]...) |
| } |
| } |
| |
| if !cmp.Equal(statusConds, expectedConds, protocmp.Transform()) { |
| return fmt.Errorf("expected conditions %v got %v", expectedConds, statusConds) |
| } |
| return nil |
| } |