Skip to content

Commit 23ab488

Browse files
committed
Add framework for api validation tests using envtest
1 parent 7f8a66b commit 23ab488

File tree

2 files changed

+227
-0
lines changed

2 files changed

+227
-0
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
Copyright 2024 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 apivalidations
18+
19+
import (
20+
"context"
21+
"fmt"
22+
23+
. "github.com/onsi/ginkgo/v2"
24+
. "github.com/onsi/gomega"
25+
corev1 "k8s.io/api/core/v1"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
ctrl "sigs.k8s.io/controller-runtime"
28+
"sigs.k8s.io/controller-runtime/pkg/client"
29+
"sigs.k8s.io/controller-runtime/pkg/metrics/server"
30+
"sigs.k8s.io/controller-runtime/pkg/webhook"
31+
32+
infrav1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1"
33+
)
34+
35+
var _ = Describe("APIValidations", func() {
36+
var (
37+
mgrCancel context.CancelFunc
38+
mgrDone chan struct{}
39+
40+
namespace corev1.Namespace
41+
)
42+
43+
BeforeEach(func() {
44+
By("Setting up manager and webhooks")
45+
mgr, err := ctrl.NewManager(cfg, ctrl.Options{
46+
Scheme: testScheme,
47+
Metrics: server.Options{
48+
BindAddress: "0",
49+
},
50+
WebhookServer: webhook.NewServer(webhook.Options{
51+
Port: testEnv.WebhookInstallOptions.LocalServingPort,
52+
Host: testEnv.WebhookInstallOptions.LocalServingHost,
53+
CertDir: testEnv.WebhookInstallOptions.LocalServingCertDir,
54+
}),
55+
})
56+
Expect(err).ToNot(HaveOccurred(), "Manager setup should succeed")
57+
58+
Expect((&infrav1.OpenStackMachineTemplateWebhook{}).SetupWebhookWithManager(mgr)).To(Succeed(), "OpenStackMachineTemplate webhook should be registered with manager")
59+
Expect((&infrav1.OpenStackMachineTemplateList{}).SetupWebhookWithManager(mgr)).To(Succeed(), "OpenStackMachineTemplateList webhook should be registered with manager")
60+
Expect((&infrav1.OpenStackCluster{}).SetupWebhookWithManager(mgr)).To(Succeed(), "OpenStackCluster webhook should be registered with manager")
61+
Expect((&infrav1.OpenStackClusterTemplate{}).SetupWebhookWithManager(mgr)).To(Succeed(), "OpenStackClusterTemplate webhook should be registered with manager")
62+
Expect((&infrav1.OpenStackMachine{}).SetupWebhookWithManager(mgr)).To(Succeed(), "OpenStackMachine webhook should be registered with manager")
63+
Expect((&infrav1.OpenStackMachineList{}).SetupWebhookWithManager(mgr)).To(Succeed(), "OpenStackMachineList webhook should be registered with manager")
64+
Expect((&infrav1.OpenStackClusterList{}).SetupWebhookWithManager(mgr)).To(Succeed(), "OpenStackClusterList webhook should be registered with manager")
65+
66+
By("Starting manager")
67+
var mgrCtx context.Context
68+
mgrDone = make(chan struct{})
69+
mgrCtx, mgrCancel = context.WithCancel(context.Background())
70+
71+
go func() {
72+
defer GinkgoRecover()
73+
defer close(mgrDone)
74+
Expect(mgr.Start(mgrCtx)).To(Succeed(), "Manager should start")
75+
}()
76+
DeferCleanup(func() {
77+
By("Tearing down manager")
78+
mgrCancel()
79+
Eventually(mgrDone).Should(BeClosed(), "Manager should stop")
80+
})
81+
82+
By("Creating namespace")
83+
namespace = corev1.Namespace{}
84+
namespace.GenerateName = "test-"
85+
Expect(k8sClient.Create(ctx, &namespace)).To(Succeed(), "Namespace creation should succeed")
86+
DeferCleanup(func() {
87+
By("Deleting namespace")
88+
Expect(k8sClient.Delete(ctx, &namespace, client.PropagationPolicy(metav1.DeletePropagationForeground))).To(Succeed(), "Namespace deletion should succeed")
89+
})
90+
By(fmt.Sprintf("Using namespace %s", namespace.Name))
91+
})
92+
93+
Context("OpenStackCluster", func() {
94+
var cluster *infrav1.OpenStackCluster
95+
96+
BeforeEach(func() {
97+
// Initialise a basic cluster object in the correct namespace
98+
cluster = &infrav1.OpenStackCluster{}
99+
cluster.Namespace = namespace.Name
100+
cluster.Name = "cluster"
101+
})
102+
103+
It("should allow the smallest permissible cluster spec", func() {
104+
Expect(k8sClient.Create(ctx, cluster)).To(Succeed(), "OpenStackCluster creation should succeed")
105+
})
106+
107+
It("should only allow controlPlaneEndpoint to be set once", func() {
108+
By("Creating a bare cluster")
109+
Expect(k8sClient.Create(ctx, cluster)).To(Succeed(), "OpenStackCluster creation should succeed")
110+
111+
By("Setting the control plane endpoint")
112+
cluster.Spec.ControlPlaneEndpoint.Host = "foo"
113+
cluster.Spec.ControlPlaneEndpoint.Port = 1234
114+
Expect(k8sClient.Update(ctx, cluster)).To(Succeed(), "Setting control plane endpoint should succeed")
115+
116+
By("Modifying the control plane endpoint")
117+
cluster.Spec.ControlPlaneEndpoint.Host = "bar"
118+
Expect(k8sClient.Update(ctx, cluster)).NotTo(Succeed(), "Updating control plane endpoint should fail")
119+
})
120+
121+
It("should allow an empty managed security groups definition", func() {
122+
cluster.Spec.ManagedSecurityGroups = &infrav1.ManagedSecurityGroups{}
123+
Expect(k8sClient.Create(ctx, cluster)).To(Succeed(), "OpenStackCluster creation should succeed")
124+
})
125+
})
126+
})
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
Copyright 2024 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 apivalidations
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"path/filepath"
23+
"strconv"
24+
"testing"
25+
26+
. "github.com/onsi/ginkgo/v2"
27+
. "github.com/onsi/gomega"
28+
"k8s.io/apimachinery/pkg/runtime"
29+
"k8s.io/client-go/discovery"
30+
"k8s.io/client-go/kubernetes/scheme"
31+
"k8s.io/client-go/rest"
32+
"sigs.k8s.io/controller-runtime/pkg/client"
33+
"sigs.k8s.io/controller-runtime/pkg/envtest"
34+
"sigs.k8s.io/controller-runtime/pkg/envtest/komega"
35+
36+
infrav1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1"
37+
)
38+
39+
var (
40+
cfg *rest.Config
41+
k8sClient client.Client
42+
testEnv *envtest.Environment
43+
testScheme *runtime.Scheme
44+
ctx = context.Background()
45+
)
46+
47+
func TestAPIs(t *testing.T) {
48+
RegisterFailHandler(Fail)
49+
50+
RunSpecs(t, "Controller Suite")
51+
}
52+
53+
var _ = BeforeSuite(func() {
54+
By("bootstrapping test environment")
55+
testEnv = &envtest.Environment{
56+
CRDDirectoryPaths: []string{
57+
// NOTE: These are the bare CRDs without conversion webhooks
58+
filepath.Join("..", "..", "..", "..", "config", "crd", "bases"),
59+
},
60+
ErrorIfCRDPathMissing: true,
61+
WebhookInstallOptions: envtest.WebhookInstallOptions{
62+
Paths: []string{
63+
filepath.Join("..", "..", "..", "..", "config", "webhook"),
64+
},
65+
},
66+
}
67+
68+
var err error
69+
cfg, err = testEnv.Start()
70+
Expect(err).NotTo(HaveOccurred(), "test environment should start")
71+
Expect(cfg).NotTo(BeNil(), "test environment should return a configuration")
72+
DeferCleanup(func() error {
73+
By("tearing down the test environment")
74+
return testEnv.Stop()
75+
})
76+
77+
testScheme = scheme.Scheme
78+
Expect(infrav1.AddToScheme(testScheme)).To(Succeed())
79+
80+
//+kubebuilder:scaffold:scheme
81+
82+
k8sClient, err = client.New(cfg, client.Options{Scheme: testScheme})
83+
Expect(err).NotTo(HaveOccurred())
84+
Expect(k8sClient).NotTo(BeNil())
85+
86+
// CEL requires Kube 1.25 and above, so check for the minimum server version.
87+
discoveryClient, err := discovery.NewDiscoveryClientForConfig(cfg)
88+
Expect(err).ToNot(HaveOccurred())
89+
90+
serverVersion, err := discoveryClient.ServerVersion()
91+
Expect(err).ToNot(HaveOccurred())
92+
93+
Expect(serverVersion.Major).To(Equal("1"))
94+
95+
minorInt, err := strconv.Atoi(serverVersion.Minor)
96+
Expect(err).ToNot(HaveOccurred())
97+
Expect(minorInt).To(BeNumerically(">=", 25), fmt.Sprintf("This test suite requires a Kube API server of at least version 1.25, current version is 1.%s", serverVersion.Minor))
98+
99+
komega.SetClient(k8sClient)
100+
komega.SetContext(ctx)
101+
})

0 commit comments

Comments
 (0)