Skip to content

Commit 4171b34

Browse files
authored
Merge pull request #639 from carreter/e2e
Bump go 1.22.2->1.22.5 and add E2E tests
2 parents c977ad8 + 8f39a84 commit 4171b34

File tree

261 files changed

+55714
-215
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

261 files changed

+55714
-215
lines changed

.github/workflows/e2e.yaml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,53 @@ jobs:
4848
with:
4949
name: konnectivity-agent
5050
path: _output/konnectivity-agent.tar
51+
kind-e2e:
52+
name: kind-e2e
53+
runs-on: ubuntu-20.04
54+
timeout-minutes: 100
55+
needs:
56+
- build
57+
env:
58+
REGISTRY: gcr.io/k8s-staging-kas-network-proxy
59+
KIND_IMAGE: kindest/node${{ matrix.k8s }}
60+
TAG: master
61+
CONNECTION_MODE: ${{ matrix.connection-mode }}
62+
strategy:
63+
fail-fast: false
64+
matrix:
65+
k8s: [ v1.27.11, v1.28.7, v1.29.2 ]
66+
connection-mode: [ grpc, http-connect ]
67+
steps:
68+
- name: Install kind
69+
run: |
70+
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.23.0/kind-linux-amd64
71+
chmod +x ./kind
72+
sudo mv ./kind /usr/local/bin/kind
73+
- name: Check out code
74+
uses: actions/checkout@v4
75+
- name: Set up Go
76+
uses: actions/setup-go@v5
77+
with:
78+
go-version-file: go.mod
79+
id: go
80+
- name: Download prebuilt konnectivity-server image
81+
uses: actions/download-artifact@v4
82+
with:
83+
name: konnectivity-server
84+
- name: Download prebuilt konnectivity-agent image
85+
uses: actions/download-artifact@v4
86+
with:
87+
name: konnectivity-agent
88+
- name: Load prebuilt konnectivity images
89+
run: |
90+
docker load --input konnectivity-server.tar
91+
docker load --input konnectivity-agent.tar
92+
- name: Fix konnectivity docker image tags
93+
run: |
94+
docker tag gcr.io/k8s-staging-kas-network-proxy/proxy-server:master gcr.io/k8s-staging-kas-network-proxy/proxy-server-amd64:master
95+
docker tag gcr.io/k8s-staging-kas-network-proxy/proxy-agent:master gcr.io/k8s-staging-kas-network-proxy/proxy-agent-amd64:master
96+
- name: Run e2e tests
97+
run: make test-e2e-ci
5198
e2e:
5299
name: e2e
53100
runs-on: ubuntu-20.04

Makefile

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ ALL_ARCH ?= amd64 arm arm64 ppc64le s390x
2424
# The output type could either be docker (local), or registry.
2525
OUTPUT_TYPE ?= docker
2626
GO_TOOLCHAIN ?= golang
27-
GO_VERSION ?= 1.22.2
27+
GO_VERSION ?= 1.22.5
2828
BASEIMAGE ?= gcr.io/distroless/static-debian11:nonroot
2929

3030
ifeq ($(GOPATH),)
@@ -54,6 +54,9 @@ TAG ?= $(shell git rev-parse HEAD)
5454
DOCKER_CMD ?= docker
5555
DOCKER_CLI_EXPERIMENTAL ?= enabled
5656
PROXY_SERVER_IP ?= 127.0.0.1
57+
58+
KIND_IMAGE ?= kindest/node
59+
CONNECTION_MODE ?= grpc
5760
## --------------------------------------
5861
## Testing
5962
## --------------------------------------
@@ -67,19 +70,28 @@ mock_gen:
6770
# Unit tests with faster execution (nicer for development).
6871
.PHONY: fast-test
6972
fast-test:
70-
go test -mod=vendor -race ./...
73+
go test -mod=vendor -race $(shell go list ./... | grep -v -e "/e2e$$" -e "/e2e/.*")
7174
cd konnectivity-client && go test -race ./...
7275

7376
# Unit tests with fuller coverage, invoked by CI system.
7477
.PHONY: test
7578
test:
76-
go test -mod=vendor -race -covermode=atomic -coverprofile=konnectivity.out ./... && go tool cover -html=konnectivity.out -o=konnectivity.html
79+
go test -mod=vendor -race -covermode=atomic -coverprofile=konnectivity.out $(shell go list ./... | grep -v -e "/e2e$$" -e "/e2e/.*") && go tool cover -html=konnectivity.out -o=konnectivity.html
7780
cd konnectivity-client && go test -race -covermode=atomic -coverprofile=client.out ./... && go tool cover -html=client.out -o=client.html
7881

7982
.PHONY: test-integration
8083
test-integration: build
8184
go test -mod=vendor -race ./tests -agent-path $(PWD)/bin/proxy-agent
8285

86+
.PHONY: test-e2e
87+
test-e2e: docker-build
88+
go test -mod=vendor ./e2e -race -agent-image ${AGENT_FULL_IMAGE}-$(TARGETARCH):${TAG} -server-image ${SERVER_FULL_IMAGE}-$(TARGETARCH):${TAG} -kind-image ${KIND_IMAGE} -mode ${CONNECTION_MODE}
89+
90+
# e2e test runner for continuous integration that does not build a new image.
91+
.PHONY: test-e2e-ci
92+
test-e2e-ci:
93+
go test -mod=vendor ./e2e -race -agent-image ${AGENT_FULL_IMAGE}-$(TARGETARCH):${TAG} -server-image ${SERVER_FULL_IMAGE}-$(TARGETARCH):${TAG} -kind-image ${KIND_IMAGE} -mode ${CONNECTION_MODE}
94+
8395
## --------------------------------------
8496
## Binaries
8597
## --------------------------------------

e2e/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# End-to-end tests for konnectivity-network-proxy running in a kind cluster
2+
3+
These e2e tests deploy the KNP agent and server to a local [kind](https://kind.sigs.k8s.io/)
4+
cluster to verify their functionality.
5+
6+
These can be run automatically using `make e2e-test`.
7+
8+
## Setup in `main_test.go`
9+
10+
Before any of the actual tests are run, the `TestMain()` function
11+
in `main_test.go` performs the following set up steps:
12+
13+
- Spin up a new kind cluster with the node image provided by the `-kind-image` flag.
14+
- Sideload the KNP agent and server images provided with `-agent-image` and `-server-image` into the cluster.
15+
- Deploy the necessary RBAC and service templates for both the KNP agent and server (see `renderAndApplyManifests`).
16+
17+
## The tests
18+
19+
### `static_count_test.go`
20+
21+
These tests deploy the KNP servers and agents to the previously created kind cluster.
22+
After the deployments are up, the tests check that both the agent and server report
23+
the correct number of connections on their metrics endpoints.

e2e/main_test.go

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
package e2e
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"flag"
7+
"fmt"
8+
"log"
9+
"os"
10+
"path"
11+
"testing"
12+
"text/template"
13+
"time"
14+
15+
"k8s.io/apimachinery/pkg/runtime/schema"
16+
"k8s.io/client-go/kubernetes/scheme"
17+
"sigs.k8s.io/controller-runtime/pkg/client"
18+
"sigs.k8s.io/e2e-framework/klient/wait"
19+
"sigs.k8s.io/e2e-framework/klient/wait/conditions"
20+
"sigs.k8s.io/e2e-framework/pkg/env"
21+
"sigs.k8s.io/e2e-framework/pkg/envconf"
22+
"sigs.k8s.io/e2e-framework/pkg/envfuncs"
23+
"sigs.k8s.io/e2e-framework/support/kind"
24+
)
25+
26+
var (
27+
testenv env.Environment
28+
agentImage = flag.String("agent-image", "", "The proxy agent's docker image.")
29+
serverImage = flag.String("server-image", "", "The proxy server's docker image.")
30+
kindImage = flag.String("kind-image", "kindest/node", "Image to use for kind nodes.")
31+
connectionMode = flag.String("mode", "grpc", "Connection mode to use during e2e tests.")
32+
)
33+
34+
func TestMain(m *testing.M) {
35+
flag.Parse()
36+
if *agentImage == "" {
37+
log.Fatalf("must provide agent image with -agent-image")
38+
}
39+
if *serverImage == "" {
40+
log.Fatalf("must provide server image with -server-image")
41+
}
42+
43+
scheme.AddToScheme(scheme.Scheme)
44+
45+
testenv = env.New()
46+
kindClusterName := "kind-test"
47+
kindCluster := kind.NewCluster(kindClusterName).WithOpts(kind.WithImage(*kindImage))
48+
49+
testenv.Setup(
50+
envfuncs.CreateCluster(kindCluster, kindClusterName),
51+
envfuncs.LoadImageToCluster(kindClusterName, *agentImage),
52+
envfuncs.LoadImageToCluster(kindClusterName, *serverImage),
53+
renderAndApplyManifests,
54+
)
55+
56+
testenv.Finish(envfuncs.DestroyCluster(kindClusterName))
57+
58+
os.Exit(testenv.Run(m))
59+
}
60+
61+
// renderTemplate renders a template from e2e/templates into a kubernetes object.
62+
// Template paths are relative to e2e/templates.
63+
func renderTemplate(file string, params any) (client.Object, *schema.GroupVersionKind, error) {
64+
b := &bytes.Buffer{}
65+
66+
tmp, err := template.ParseFiles(path.Join("templates/", file))
67+
if err != nil {
68+
return nil, nil, fmt.Errorf("could not parse template %v: %w", file, err)
69+
}
70+
71+
err = tmp.Execute(b, params)
72+
if err != nil {
73+
return nil, nil, fmt.Errorf("could not execute template %v: %w", file, err)
74+
}
75+
76+
decoder := scheme.Codecs.UniversalDeserializer()
77+
78+
obj, gvk, err := decoder.Decode(b.Bytes(), nil, nil)
79+
if err != nil {
80+
return nil, nil, fmt.Errorf("could not decode rendered yaml into kubernetes object: %w", err)
81+
}
82+
83+
return obj.(client.Object), gvk, nil
84+
}
85+
86+
type KeyValue struct {
87+
Key string
88+
Value string
89+
}
90+
91+
type DeploymentConfig struct {
92+
Replicas int
93+
Image string
94+
Args []KeyValue
95+
}
96+
97+
func renderAndApplyManifests(ctx context.Context, cfg *envconf.Config) (context.Context, error) {
98+
client := cfg.Client()
99+
100+
// Render agent RBAC and Service templates.
101+
agentServiceAccount, _, err := renderTemplate("agent/serviceaccount.yaml", struct{}{})
102+
if err != nil {
103+
return nil, err
104+
}
105+
agentClusterRole, _, err := renderTemplate("agent/clusterrole.yaml", struct{}{})
106+
if err != nil {
107+
return nil, err
108+
}
109+
agentClusterRoleBinding, _, err := renderTemplate("agent/clusterrolebinding.yaml", struct{}{})
110+
if err != nil {
111+
return ctx, err
112+
}
113+
agentService, _, err := renderTemplate("agent/service.yaml", struct{}{})
114+
if err != nil {
115+
return ctx, err
116+
}
117+
118+
// Submit agent RBAC templates to k8s.
119+
err = client.Resources().Create(ctx, agentServiceAccount)
120+
if err != nil {
121+
return ctx, err
122+
}
123+
err = client.Resources().Create(ctx, agentClusterRole)
124+
if err != nil {
125+
return ctx, err
126+
}
127+
err = client.Resources().Create(ctx, agentClusterRoleBinding)
128+
if err != nil {
129+
return ctx, err
130+
}
131+
err = client.Resources().Create(ctx, agentService)
132+
if err != nil {
133+
return ctx, err
134+
}
135+
136+
// Render server RBAC and Service templates.
137+
serverClusterRoleBinding, _, err := renderTemplate("server/clusterrolebinding.yaml", struct{}{})
138+
if err != nil {
139+
return ctx, err
140+
}
141+
serverService, _, err := renderTemplate("server/service.yaml", struct{}{})
142+
if err != nil {
143+
return ctx, err
144+
}
145+
146+
// Submit server templates to k8s.
147+
err = client.Resources().Create(ctx, serverClusterRoleBinding)
148+
if err != nil {
149+
return ctx, err
150+
}
151+
err = client.Resources().Create(ctx, serverService)
152+
if err != nil {
153+
return ctx, err
154+
}
155+
156+
return ctx, nil
157+
}
158+
159+
func deployAndWaitForDeployment(deployment client.Object) func(context.Context, *testing.T, *envconf.Config) context.Context {
160+
return func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context {
161+
client := cfg.Client()
162+
err := client.Resources().Create(ctx, deployment)
163+
if err != nil {
164+
t.Fatalf("could not create Deployment: %v", err)
165+
}
166+
167+
err = wait.For(
168+
conditions.New(client.Resources()).DeploymentAvailable(deployment.GetName(), deployment.GetNamespace()),
169+
wait.WithTimeout(1*time.Minute),
170+
wait.WithInterval(10*time.Second),
171+
)
172+
if err != nil {
173+
t.Fatalf("waiting for Deployment failed: %v", err)
174+
}
175+
176+
return ctx
177+
}
178+
}

e2e/metrics_assertions_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package e2e
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"testing"
8+
9+
corev1 "k8s.io/api/core/v1"
10+
11+
"github.com/prometheus/common/expfmt"
12+
"sigs.k8s.io/e2e-framework/klient/k8s/resources"
13+
"sigs.k8s.io/e2e-framework/pkg/envconf"
14+
)
15+
16+
func getMetricsGaugeValue(url string, name string) (int, error) {
17+
resp, err := http.Get(url)
18+
if err != nil {
19+
return 0, fmt.Errorf("could not get metrics from url %v: %w", url, err)
20+
}
21+
22+
metricsParser := &expfmt.TextParser{}
23+
metricsFamilies, err := metricsParser.TextToMetricFamilies(resp.Body)
24+
if err != nil {
25+
return 0, fmt.Errorf("could not parse metrics: %w", err)
26+
}
27+
defer resp.Body.Close()
28+
29+
metricFamily, exists := metricsFamilies[name]
30+
if !exists {
31+
return 0, fmt.Errorf("metric %v does not exist", name)
32+
}
33+
value := int(metricFamily.GetMetric()[0].GetGauge().GetValue())
34+
return value, nil
35+
}
36+
37+
func assertAgentsAreConnected(expectedConnections int, adminPort int) func(context.Context, *testing.T, *envconf.Config) context.Context {
38+
return func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context {
39+
client := cfg.Client()
40+
41+
var agentPods *corev1.PodList
42+
err := client.Resources().List(ctx, agentPods, resources.WithLabelSelector("k8s-app=konnectivity-agent"))
43+
if err != nil {
44+
t.Fatalf("couldn't get agent pods (label selector 'k8s-app=konnectivity-agent'): %v", err)
45+
}
46+
47+
for _, agentPod := range agentPods.Items {
48+
numConnections, err := getMetricsGaugeValue(fmt.Sprintf("%v:%v/metrics", agentPod.Status.PodIP, adminPort), "konnectivity_network_proxy_agent_open_server_connections")
49+
if err != nil {
50+
t.Fatalf("couldn't get agent metric 'konnectivity_network_proxy_agent_open_server_connections' for pod %v: %v", agentPod.Name, err)
51+
}
52+
53+
if numConnections != expectedConnections {
54+
t.Errorf("incorrect number of connected servers (want: %d, got: %d)", expectedConnections, numConnections)
55+
}
56+
}
57+
58+
return ctx
59+
}
60+
}
61+
62+
func assertServersAreConnected(expectedConnections int, adminPort int) func(context.Context, *testing.T, *envconf.Config) context.Context {
63+
return func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context {
64+
client := cfg.Client()
65+
66+
var serverPods *corev1.PodList
67+
err := client.Resources().List(ctx, serverPods, resources.WithLabelSelector("k8s-app=konnectivity-server"))
68+
if err != nil {
69+
t.Fatalf("couldn't get server pods (label selector 'k8s-app=konnectivity-server'): %v", err)
70+
}
71+
72+
for _, serverPod := range serverPods.Items {
73+
numConnections, err := getMetricsGaugeValue(fmt.Sprintf("%v:%v/metrics", serverPod.Status.PodIP, adminPort), "konnectivity_network_proxy_server_ready_backend_connections")
74+
if err != nil {
75+
t.Fatalf("couldn't get agent metric 'konnectivity_network_proxy_server_ready_backend_connections' for pod %v: %v", serverPod.Name, err)
76+
}
77+
78+
if numConnections != expectedConnections {
79+
t.Errorf("incorrect number of connected agents (want: %d, got: %d)", expectedConnections, numConnections)
80+
}
81+
}
82+
83+
return ctx
84+
}
85+
}

0 commit comments

Comments
 (0)