Skip to content

Commit 3c188d6

Browse files
authored
Add federated service e2e test (#13352)
Signed-off-by: Alex Leong <alex@buoyant.io>
1 parent 3c91fc6 commit 3c188d6

File tree

6 files changed

+192
-4
lines changed

6 files changed

+192
-4
lines changed

multicluster/cmd/check.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,10 @@ func (hc *healthChecker) checkIfMirrorServicesHaveEndpoints(ctx context.Context)
677677
return err
678678
}
679679
for _, svc := range mirrorServices.Items {
680+
if svc.Annotations[k8s.RemoteDiscoveryAnnotation] != "" || svc.Annotations[k8s.LocalDiscoveryAnnotation] != "" {
681+
// This is a federated service and does not need to have endpoints.
682+
continue
683+
}
680684
// have to use a new ctx for each call, otherwise we risk reaching the original context deadline
681685
ctx, cancel := context.WithTimeout(context.Background(), healthcheck.RequestTimeout)
682686
defer cancel()

multicluster/cmd/install.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,10 @@ func buildMulticlusterInstallValues(ctx context.Context, opts *multiclusterInsta
233233
return nil, err
234234
}
235235

236+
if reg := os.Getenv(flags.EnvOverrideDockerRegistry); reg != "" {
237+
defaults.LocalServiceMirror.Image.Name = pkgcmd.RegistryOverride(defaults.LocalServiceMirror.Image.Name, reg)
238+
}
239+
236240
defaults.LocalServiceMirror.Image.Version = version.Version
237241
defaults.Gateway.Enabled = opts.gateway.Enabled
238242
defaults.Gateway.Port = opts.gateway.Port
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package multiclustertraffic
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"strings"
8+
"testing"
9+
"time"
10+
11+
"github.com/linkerd/linkerd2/pkg/k8s"
12+
"github.com/linkerd/linkerd2/testutil"
13+
kerrors "k8s.io/apimachinery/pkg/api/errors"
14+
)
15+
16+
// TestFederatedService deploys emojivoto to two clusters and has the web-svc
17+
// in both clusters join a federated service. It creates a vote-bot in the
18+
// source cluster which sends traffic to the federated service and then checks
19+
// the logs of the web-svc in both clusters. If it has successfully issued
20+
// requests, then we'll see log messages.
21+
//
22+
// We verify that the federated service exists and has no endpoints in the
23+
// source cluster.
24+
func TestFederatedService(t *testing.T) {
25+
if err := TestHelper.SwitchContext(contexts[testutil.TargetContextKey]); err != nil {
26+
testutil.AnnotatedFatalf(t,
27+
"failed to rebuild helper clientset with new context",
28+
"failed to rebuild helper clientset with new context [%s]: %v",
29+
contexts[testutil.TargetContextKey], err)
30+
}
31+
ctx := context.Background()
32+
// Create emojivoto in target cluster, to be deleted at the end of the test.
33+
annotations := map[string]string{
34+
// "config.linkerd.io/proxy-log-level": "linkerd=debug,info",
35+
}
36+
TestHelper.WithDataPlaneNamespace(ctx, "emojivoto-federated", annotations, t, func(t *testing.T, ns string) {
37+
t.Run("Deploy resources in source and target clusters", func(t *testing.T) {
38+
// Deploy federated-client in source-cluster
39+
o, err := TestHelper.KubectlWithContext("", contexts[testutil.SourceContextKey], "create", "ns", ns)
40+
if err != nil {
41+
testutil.AnnotatedFatalf(t, "failed to create ns", "failed to create ns: %s\n%s", err, o)
42+
}
43+
o, err = TestHelper.KubectlApplyWithContext("", contexts[testutil.SourceContextKey], "--namespace", ns, "-f", "testdata/federated-client.yml")
44+
if err != nil {
45+
testutil.AnnotatedFatalf(t, "failed to install federated-client", "failed to installfederated-client: %s\n%s", err, o)
46+
}
47+
48+
// Deploy emojivoto in both clusters
49+
for _, ctx := range contexts {
50+
out, err := TestHelper.KubectlApplyWithContext("", ctx, "--namespace", ns, "-f", "testdata/emojivoto-no-bot.yml")
51+
if err != nil {
52+
testutil.AnnotatedFatalf(t, "failed to install emojivoto", "failed to install emojivoto: %s\n%s", err, out)
53+
}
54+
55+
// Label the service to join the federated service.
56+
timeout := time.Minute
57+
err = testutil.RetryFor(timeout, func() error {
58+
out, err = TestHelper.KubectlWithContext("", ctx, "--namespace", ns, "label", "service/web-svc", "mirror.linkerd.io/federated=member")
59+
return err
60+
})
61+
if err != nil {
62+
testutil.AnnotatedFatalf(t, "failed to label web-svc", "%s\n%s", err, out)
63+
}
64+
}
65+
})
66+
67+
t.Run("Wait until target workloads are ready", func(t *testing.T) {
68+
// Wait until client is up and running in source cluster
69+
voteBotDeployReplica := map[string]testutil.DeploySpec{"vote-bot": {Namespace: ns, Replicas: 1}}
70+
TestHelper.WaitRolloutWithContext(t, voteBotDeployReplica, contexts[testutil.SourceContextKey])
71+
72+
// Wait until services and replicas are up and running.
73+
emojiDeployReplicas := map[string]testutil.DeploySpec{
74+
"web": {Namespace: ns, Replicas: 1},
75+
"emoji": {Namespace: ns, Replicas: 1},
76+
"voting": {Namespace: ns, Replicas: 1},
77+
}
78+
for _, ctx := range contexts {
79+
TestHelper.WaitRolloutWithContext(t, emojiDeployReplicas, ctx)
80+
}
81+
82+
})
83+
84+
timeout := time.Minute
85+
t.Run("Ensure federated service exists and has no endpoints", func(t *testing.T) {
86+
err := TestHelper.SwitchContext(contexts[testutil.SourceContextKey])
87+
if err != nil {
88+
testutil.AnnotatedFatal(t, "failed to switch contexts", err)
89+
}
90+
err = testutil.RetryFor(timeout, func() error {
91+
svc, err := TestHelper.GetService(ctx, ns, "web-svc-federated")
92+
if err != nil {
93+
return err
94+
}
95+
remoteDiscovery, found := svc.Annotations[k8s.RemoteDiscoveryAnnotation]
96+
if !found {
97+
return fmt.Errorf("federated service missing annotation: %s", k8s.RemoteDiscoveryLabel)
98+
}
99+
if remoteDiscovery != "web-svc@target" {
100+
return fmt.Errorf("federated service remote discovery was %s, expected %s", remoteDiscovery, "web-svc@target")
101+
}
102+
localDiscovery, found := svc.Annotations[k8s.LocalDiscoveryAnnotation]
103+
if !found {
104+
return fmt.Errorf("federated service missing annotation: %s", k8s.LocalDiscoveryAnnotation)
105+
}
106+
if localDiscovery != "web-svc" {
107+
return fmt.Errorf("federated service local discovery was %s, expected %s", localDiscovery, "web-svc")
108+
}
109+
110+
_, err = TestHelper.GetEndpoints(ctx, ns, "web-svc-federated")
111+
if err == nil {
112+
return errors.New("federated service should not have endpoints")
113+
}
114+
if !kerrors.IsNotFound(err) {
115+
return fmt.Errorf("failed to retrieve federated service endpoints: %w", err)
116+
}
117+
return nil
118+
})
119+
if err != nil {
120+
testutil.AnnotatedFatal(t, "timed-out verifying federated service", err)
121+
}
122+
})
123+
124+
for _, ctx := range contexts {
125+
err := testutil.RetryFor(timeout, func() error {
126+
out, err := TestHelper.KubectlWithContext("",
127+
ctx,
128+
"--namespace", ns,
129+
"logs",
130+
"--selector", "app=web-svc",
131+
"--container", "web-svc",
132+
)
133+
if err != nil {
134+
return fmt.Errorf("%w\n%s", err, out)
135+
}
136+
// Check for expected error messages
137+
for _, row := range strings.Split(out, "\n") {
138+
if strings.Contains(row, " /api/vote?choice=:doughnut: ") {
139+
return nil
140+
}
141+
}
142+
return fmt.Errorf("web-svc logs in %s cluster do not include voting errors\n%s", ctx, out)
143+
})
144+
if err != nil {
145+
testutil.AnnotatedFatal(t, fmt.Sprintf("timed-out waiting for traffic in %s cluster (%s)", ctx, timeout), err)
146+
}
147+
}
148+
})
149+
}

test/integration/multicluster/multicluster-traffic/mc_traffic_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,7 @@ func TestCheckGatewayAfterRepairEndpoints(t *testing.T) {
145145

146146
// TestTargetTraffic inspects the target cluster's web-svc pod to see if the
147147
// source cluster's vote-bot has been able to hit it with requests. If it has
148-
// successfully issued requests, then we'll see log messages indicating that the
149-
// web-svc can't reach the voting-svc (because it's not running).
148+
// successfully issued requests, then we'll see log messages.
150149
//
151150
// TODO it may be clearer to invoke `linkerd diagnostics proxy-metrics` to check whether we see
152151
// connections from the gateway pod to the web-svc?

test/integration/multicluster/multicluster-traffic/pod_to_pod_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ import (
1414

1515
// TestPodToPodTraffic inspects the target cluster's web-svc pod to see if the
1616
// source cluster's vote-bot has been able to hit it with requests. If it has
17-
// successfully issued requests, then we'll see log messages indicating that the
18-
// web-svc can't reach the voting-svc (because it's not running).
17+
// successfully issued requests, then we'll see log messages.
1918
//
2019
// We verify that the service has been mirrored in remote discovery mode by
2120
// checking that it had no endpoints in the source cluster.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
labels:
5+
app.kubernetes.io/name: vote-bot
6+
app.kubernetes.io/part-of: emojivoto
7+
app.kubernetes.io/version: v10
8+
name: vote-bot
9+
spec:
10+
replicas: 1
11+
selector:
12+
matchLabels:
13+
app: vote-bot
14+
version: v10
15+
template:
16+
metadata:
17+
annotations:
18+
linkerd.io/inject: enabled
19+
labels:
20+
app: vote-bot
21+
version: v10
22+
spec:
23+
containers:
24+
- command:
25+
- emojivoto-vote-bot
26+
env:
27+
- name: WEB_HOST
28+
value: web-svc-federated:80
29+
image: buoyantio/emojivoto-web:v10
30+
name: vote-bot
31+
resources:
32+
requests:
33+
cpu: 10m

0 commit comments

Comments
 (0)