istio
348 строк · 9.8 Кб
1//go:build integ
2
3// Copyright Istio Authors
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17package ambient
18
19import (
20"context"
21"errors"
22"fmt"
23"strings"
24"testing"
25"time"
26
27metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28"k8s.io/apimachinery/pkg/types"
29k8s "sigs.k8s.io/gateway-api/apis/v1"
30
31"istio.io/istio/pilot/pkg/model/kstatus"
32"istio.io/istio/pkg/config/constants"
33"istio.io/istio/pkg/test/echo/common/scheme"
34"istio.io/istio/pkg/test/framework"
35"istio.io/istio/pkg/test/framework/components/echo"
36"istio.io/istio/pkg/test/framework/components/echo/check"
37"istio.io/istio/pkg/test/framework/components/istioctl"
38"istio.io/istio/pkg/test/framework/components/namespace"
39"istio.io/istio/pkg/test/framework/resource/config/apply"
40kubetest "istio.io/istio/pkg/test/kube"
41"istio.io/istio/pkg/test/scopes"
42"istio.io/istio/pkg/test/util/retry"
43)
44
45func TestWaypointStatus(t *testing.T) {
46framework.
47NewTest(t).
48Features("traffic.ambient").
49Run(func(t framework.TestContext) {
50client := t.Clusters().Kube().Default().GatewayAPI().GatewayV1beta1().GatewayClasses()
51
52check := func() error {
53gwc, _ := client.Get(context.Background(), constants.WaypointGatewayClassName, metav1.GetOptions{})
54if gwc == nil {
55return fmt.Errorf("failed to find GatewayClass %v", constants.WaypointGatewayClassName)
56}
57cond := kstatus.GetCondition(gwc.Status.Conditions, string(k8s.GatewayClassConditionStatusAccepted))
58if cond.Status != metav1.ConditionTrue {
59return fmt.Errorf("failed to find accepted condition: %+v", cond)
60}
61if cond.ObservedGeneration != gwc.Generation {
62return fmt.Errorf("stale GWC generation: %+v", cond)
63}
64return nil
65}
66retry.UntilSuccessOrFail(t, check)
67
68// Wipe out the status
69gwc, _ := client.Get(context.Background(), constants.WaypointGatewayClassName, metav1.GetOptions{})
70gwc.Status.Conditions = nil
71client.Update(context.Background(), gwc, metav1.UpdateOptions{})
72// It should be added back
73retry.UntilSuccessOrFail(t, check)
74})
75}
76
77func TestWaypoint(t *testing.T) {
78framework.
79NewTest(t).
80Features("traffic.ambient").
81Run(func(t framework.TestContext) {
82nsConfig := namespace.NewOrFail(t, t, namespace.Config{
83Prefix: "waypoint",
84Inject: false,
85Labels: map[string]string{
86constants.DataplaneMode: "ambient",
87},
88})
89
90istioctl.NewOrFail(t, t, istioctl.Config{}).InvokeOrFail(t, []string{
91"x",
92"waypoint",
93"apply",
94"--namespace",
95nsConfig.Name(),
96"--wait",
97})
98
99saSet := []string{"sa1", "sa2", "sa3"}
100for _, sa := range saSet {
101istioctl.NewOrFail(t, t, istioctl.Config{}).InvokeOrFail(t, []string{
102"x",
103"waypoint",
104"apply",
105"--namespace",
106nsConfig.Name(),
107"--service-account",
108sa,
109"--wait",
110})
111}
112
113output, _ := istioctl.NewOrFail(t, t, istioctl.Config{}).InvokeOrFail(t, []string{
114"x",
115"waypoint",
116"list",
117"--namespace",
118nsConfig.Name(),
119})
120for _, sa := range saSet {
121if !strings.Contains(output, sa) {
122t.Fatalf("expect to find %s in output: %s", sa, output)
123}
124}
125
126output, _ = istioctl.NewOrFail(t, t, istioctl.Config{}).InvokeOrFail(t, []string{
127"x",
128"waypoint",
129"list",
130"-A",
131})
132for _, sa := range saSet {
133if !strings.Contains(output, sa) {
134t.Fatalf("expect to find %s in output: %s", sa, output)
135}
136}
137
138istioctl.NewOrFail(t, t, istioctl.Config{}).InvokeOrFail(t, []string{
139"x",
140"waypoint",
141"-n",
142nsConfig.Name(),
143"delete",
144})
145retry.UntilSuccessOrFail(t, func() error {
146if err := checkWaypointIsReady(t, nsConfig.Name(), "namespace"); err != nil {
147if errors.Is(err, kubetest.ErrNoPodsFetched) {
148return nil
149}
150return fmt.Errorf("failed to check gateway status: %v", err)
151}
152return fmt.Errorf("failed to clean up gateway in namespace: %s", nsConfig.Name())
153}, retry.Timeout(15*time.Second), retry.BackoffDelay(time.Millisecond*100))
154
155istioctl.NewOrFail(t, t, istioctl.Config{}).InvokeOrFail(t, []string{
156"x",
157"waypoint",
158"-n",
159nsConfig.Name(),
160"delete",
161"sa1",
162"sa2",
163})
164retry.UntilSuccessOrFail(t, func() error {
165for _, sa := range []string{"sa1", "sa2"} {
166if err := checkWaypointIsReady(t, nsConfig.Name(), sa); err != nil {
167if !errors.Is(err, kubetest.ErrNoPodsFetched) {
168return fmt.Errorf("failed to check gateway status: %v", err)
169}
170} else {
171return fmt.Errorf("failed to delete multiple gateways: %s not cleaned up", sa)
172}
173}
174return nil
175}, retry.Timeout(15*time.Second), retry.BackoffDelay(time.Millisecond*100))
176
177// delete all waypoints in namespace, so sa3 should be deleted
178istioctl.NewOrFail(t, t, istioctl.Config{}).InvokeOrFail(t, []string{
179"x",
180"waypoint",
181"-n",
182nsConfig.Name(),
183"delete",
184"--all",
185})
186retry.UntilSuccessOrFail(t, func() error {
187if err := checkWaypointIsReady(t, nsConfig.Name(), "sa3"); err != nil {
188if errors.Is(err, kubetest.ErrNoPodsFetched) {
189return nil
190}
191return fmt.Errorf("failed to check gateway status: %v", err)
192}
193return fmt.Errorf("failed to clean up gateway in namespace: %s", nsConfig.Name())
194}, retry.Timeout(15*time.Second), retry.BackoffDelay(time.Millisecond*100))
195})
196}
197
198func checkWaypointIsReady(t framework.TestContext, ns, name string) error {
199fetch := kubetest.NewPodFetch(t.AllClusters()[0], ns, constants.GatewayNameLabel+"="+name)
200_, err := kubetest.CheckPodsAreReady(fetch)
201return err
202}
203
204func TestSimpleHTTPSandwich(t *testing.T) {
205framework.
206NewTest(t).
207Features("traffic.ambient").
208Run(func(t framework.TestContext) {
209config := `
210apiVersion: gateway.networking.k8s.io/v1beta1
211kind: Gateway
212metadata:
213name: {{.Service}}-gateway
214namespace: {{.Namespace}}
215annotations:
216networking.istio.io/address-type: IPAddress
217networking.istio.io/service-type: ClusterIP
218ambient.istio.io/redirection: enabled
219spec:
220gatewayClassName: istio
221listeners:
222- name: {{.Service}}-fqdn
223hostname: {{.Service}}.{{.Namespace}}.svc.cluster.local
224port: {{.Port}}
225protocol: HTTP
226allowedRoutes:
227namespaces:
228from: Same
229- name: {{.Service}}-svc
230hostname: {{.Service}}.{{.Namespace}}.svc
231port: {{.Port}}
232protocol: HTTP
233allowedRoutes:
234namespaces:
235from: Same
236- name: {{.Service}}-namespace
237hostname: {{.Service}}.{{.Namespace}}
238port: {{.Port}}
239protocol: HTTP
240allowedRoutes:
241namespaces:
242from: Same
243- name: {{.Service}}-short
244hostname: {{.Service}}
245port: {{.Port}}
246protocol: HTTP
247allowedRoutes:
248namespaces:
249from: Same
250# HACK:zTunnel currently expects the HBONE port to always be on the Waypoint's Service
251# This will be fixed in future PRs to both istio and zTunnel.
252- name: fake-hbone-port
253port: 15008
254protocol: TCP
255---
256apiVersion: gateway.networking.k8s.io/v1beta1
257kind: HTTPRoute
258metadata:
259name: {{.Service}}-httproute
260spec:
261parentRefs:
262- name: {{.Service}}-gateway
263hostnames:
264- {{.Service}}.{{.Namespace}}.svc.cluster.local
265- {{.Service}}.{{.Namespace}}.svc
266- {{.Service}}.{{.Namespace}}
267- {{.Service}}
268rules:
269- matches:
270- path:
271type: PathPrefix
272value: /
273filters:
274- type: ResponseHeaderModifier
275responseHeaderModifier:
276add:
277- name: traversed-waypoint
278value: {{.Service}}-gateway
279backendRefs:
280- name: {{.Service}}
281port: {{.Port}}
282`
283
284t.ConfigKube().
285New().
286Eval(
287apps.Namespace.Name(),
288map[string]any{
289"Service": Captured,
290"Namespace": apps.Namespace.Name(),
291"Port": apps.Captured.PortForName("http").ServicePort,
292},
293config).
294ApplyOrFail(t, apply.CleanupConditionally)
295
296// Update use-waypoint for Captured service
297for _, c := range t.Clusters().Kube() {
298client := c.Kube().CoreV1().Services(apps.Namespace.Name())
299setWaypoint := func(waypoint string) error {
300annotation := []byte(fmt.Sprintf(`{"metadata":{"annotations":{"%s":"%s"}}}`,
301constants.AmbientUseWaypoint, waypoint))
302_, err := client.Patch(context.TODO(), Captured, types.MergePatchType, annotation, metav1.PatchOptions{})
303return err
304}
305
306if err := setWaypoint("captured-gateway"); err != nil {
307t.Fatal(err)
308}
309t.Cleanup(func() {
310if err := setWaypoint(""); err != nil {
311scopes.Framework.Errorf("failed resetting waypoint for %s", Captured)
312}
313})
314
315}
316
317// ensure HTTP traffic works with all hostname variants
318for _, src := range apps.All {
319src := src
320if !hboneClient(src) {
321// TODO if we hairpinning, don't skip here
322continue
323}
324t.NewSubTestf("from %s", src.ServiceName()).Run(func(t framework.TestContext) {
325if src.Config().HasSidecar() {
326t.Skip("TODO: sidecars don't properly handle use-waypoint")
327}
328for _, host := range apps.Captured.Config().HostnameVariants() {
329host := host
330t.NewSubTestf("to %s", host).Run(func(t framework.TestContext) {
331src.CallOrFail(t, echo.CallOptions{
332To: apps.Captured,
333Address: host,
334Port: echo.Port{Name: "http"},
335Scheme: scheme.HTTP,
336Count: 10,
337Check: check.And(
338check.OK(),
339check.ResponseHeader("traversed-waypoint", "captured-gateway"),
340),
341})
342})
343}
344apps.Captured.ServiceName()
345})
346}
347})
348}
349