Skip to content

Commit eeed1f0

Browse files
committed
Add pkg/acceptance
Signed-off-by: Nelo-T. Wallus <[email protected]> Signed-off-by: Nelo-T. Wallus <[email protected]>
1 parent 7b6391c commit eeed1f0

File tree

7 files changed

+503
-0
lines changed

7 files changed

+503
-0
lines changed

pkg/acceptance/client.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package acceptance
18+
19+
import (
20+
"testing"
21+
22+
corev1 "k8s.io/api/core/v1"
23+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24+
"k8s.io/client-go/kubernetes"
25+
"k8s.io/client-go/rest"
26+
)
27+
28+
func getClient(t testing.TB, cfg *rest.Config) kubernetes.Interface {
29+
client, err := kubernetes.NewForConfig(cfg)
30+
if err != nil {
31+
t.Fatalf("Failed to create client: %v", err)
32+
}
33+
return client
34+
}
35+
36+
func writeConfigMap(t testing.TB, cfg *rest.Config, name, data string) {
37+
client := getClient(t, cfg).CoreV1().ConfigMaps("default")
38+
39+
cm := &corev1.ConfigMap{
40+
ObjectMeta: metav1.ObjectMeta{
41+
Name: name,
42+
Namespace: "default",
43+
},
44+
Data: map[string]string{
45+
"data": data,
46+
},
47+
}
48+
49+
if _, err := client.Create(t.Context(), cm, metav1.CreateOptions{}); err != nil {
50+
t.Fatalf("Failed to create ConfigMap: %v", err)
51+
}
52+
}
53+
54+
func getConfigMap(t testing.TB, cfg *rest.Config, name string) string {
55+
client := getClient(t, cfg).CoreV1().ConfigMaps("default")
56+
57+
cm, err := client.Get(t.Context(), name, metav1.GetOptions{})
58+
if err != nil {
59+
t.Fatalf("Failed to get ConfigMap: %v", err)
60+
}
61+
return cm.Data["data"]
62+
}

pkg/acceptance/cluster.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package acceptance
18+
19+
import (
20+
"context"
21+
"crypto/rand"
22+
"testing"
23+
24+
"github.com/google/go-cmp/cmp"
25+
26+
"k8s.io/client-go/rest"
27+
28+
"sigs.k8s.io/controller-runtime/pkg/cluster"
29+
30+
mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager"
31+
)
32+
33+
// ClusterGenerator is a function that generates a new cluster.
34+
// The cluster is expected to be available through the provider.
35+
// The return values are the cluster name, the rest.Config to access the
36+
// cluster, and an error if the cluster could not be created.
37+
//
38+
// The context is cancelled when the cluster is to be removed.
39+
//
40+
// The ErrorHandler can be used to report errors from goroutines started
41+
// by the generator.
42+
type ClusterGenerator func(context.Context, ErrorHandler) (string, *rest.Config, error)
43+
44+
// UnknownClusterName is a random cluster name used to test the
45+
// return of a correct error for non-existing clusters.
46+
// Providers may use this in their test to verify their generated
47+
// names do not accidentally collide with this name.
48+
var UnknownClusterName = rand.Text()
49+
50+
// RandomClusterName generates a random cluster name that is not
51+
// UnknownClusterName.
52+
func RandomClusterName() string {
53+
name := rand.Text()
54+
if name == UnknownClusterName {
55+
return RandomClusterName()
56+
}
57+
return name
58+
}
59+
60+
func createCluster(t testing.TB, clusterGenerator ClusterGenerator) (string, *rest.Config) {
61+
t.Helper()
62+
clusterName, clusterCfg, err := clusterGenerator(t.Context(), errorHandler(t, "cluster generator"))
63+
if err != nil {
64+
t.Fatalf("Failed to create cluster: %v", err)
65+
}
66+
return clusterName, clusterCfg
67+
}
68+
69+
func getCluster(t testing.TB, manager mcmanager.Manager, clusterName string, clusterCfg *rest.Config) cluster.Cluster {
70+
t.Helper()
71+
t.Logf("Retrieving cluster %q", clusterName)
72+
73+
var cl cluster.Cluster
74+
eventually(t, func() error {
75+
var err error
76+
cl, err = manager.GetCluster(t.Context(), clusterName)
77+
return err
78+
}, WaitTimeout, PollInterval, "cluster %q not found", clusterName)
79+
80+
cfg := rest.CopyConfig(cl.GetConfig())
81+
// These values are not persisted in the kubeconfig
82+
cfg.QPS = clusterCfg.QPS
83+
cfg.Burst = clusterCfg.Burst
84+
85+
if diff := cmp.Diff(clusterCfg, cfg); diff != "" {
86+
t.Errorf("Cluster config mismatch: %s", diff)
87+
}
88+
89+
return cl
90+
}

pkg/acceptance/doc.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// Package acceptance contains acceptance tests for providers utilizing
18+
// multicluster-runtime.
19+
package acceptance

pkg/acceptance/eventually.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package acceptance
18+
19+
import (
20+
"testing"
21+
"time"
22+
)
23+
24+
var (
25+
// WaitTimeout is the maximum time to wait for an operation to
26+
// complete. It is the equivalent to the timeout of e.g.
27+
// assert.Eventually from testify.
28+
WaitTimeout = 1 * time.Minute
29+
// PollInterval is the interval between checks for an operation to
30+
// complete. It is the equivalent to the polling interval of e.g.
31+
// assert.Eventually from testify.
32+
PollInterval = 1 * time.Second
33+
)
34+
35+
// eventually is a modified coal-copy of assert.Eventually from testify
36+
func eventually(t testing.TB, condition func() error, waitFor time.Duration, tick time.Duration, msg string, args ...any) {
37+
t.Helper()
38+
39+
ch := make(chan error, 1)
40+
checkCond := func() { ch <- condition() }
41+
42+
timer := time.NewTimer(waitFor)
43+
defer timer.Stop()
44+
45+
ticker := time.NewTicker(tick)
46+
defer ticker.Stop()
47+
48+
var tickC <-chan time.Time
49+
50+
// Check the condition once first on the initial call.
51+
go checkCond()
52+
53+
for {
54+
select {
55+
case <-timer.C:
56+
t.Errorf(msg, args...)
57+
case <-tickC:
58+
tickC = nil
59+
go checkCond()
60+
case v := <-ch:
61+
if v == nil {
62+
return
63+
}
64+
t.Logf("Condition not yet satisfied: %v", v)
65+
tickC = ticker.C
66+
}
67+
}
68+
}

0 commit comments

Comments
 (0)