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 }