Skip to content

Commit c362ac7

Browse files
authored
Merge pull request #1199 from Nordix/lentzi90/cluster-controller-tests
🏃 Add tests for cluster controller
2 parents eb8ad43 + 65e0c82 commit c362ac7

File tree

4 files changed

+315
-1
lines changed

4 files changed

+315
-1
lines changed
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
/*
2+
Copyright 2022 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 controllers
18+
19+
import (
20+
"context"
21+
"fmt"
22+
23+
"github.com/gophercloud/gophercloud"
24+
"github.com/gophercloud/utils/openstack/clientconfig"
25+
. "github.com/onsi/ginkgo"
26+
. "github.com/onsi/gomega"
27+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28+
"k8s.io/apimachinery/pkg/types"
29+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
30+
"sigs.k8s.io/cluster-api/test/framework"
31+
"sigs.k8s.io/cluster-api/util/annotations"
32+
"sigs.k8s.io/cluster-api/util/patch"
33+
"sigs.k8s.io/controller-runtime/pkg/client"
34+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
35+
36+
infrav1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha5"
37+
"sigs.k8s.io/cluster-api-provider-openstack/pkg/scope"
38+
)
39+
40+
var (
41+
reconciler OpenStackClusterReconciler
42+
ctx context.Context
43+
testCluster *infrav1.OpenStackCluster
44+
capiCluster *clusterv1.Cluster
45+
testNamespace string
46+
)
47+
48+
var _ = Describe("OpenStackCluster controller", func() {
49+
capiClusterName := "capi-cluster"
50+
testClusterName := "test-cluster"
51+
testNum := 0
52+
53+
BeforeEach(func() {
54+
ctx = context.TODO()
55+
reconciler = OpenStackClusterReconciler{
56+
Client: k8sClient,
57+
}
58+
testNum++
59+
testNamespace = fmt.Sprintf("test-%d", testNum)
60+
61+
testCluster = &infrav1.OpenStackCluster{
62+
TypeMeta: metav1.TypeMeta{
63+
APIVersion: infrav1.GroupVersion.Group + "/" + infrav1.GroupVersion.Version,
64+
Kind: "OpenStackCluster",
65+
},
66+
ObjectMeta: metav1.ObjectMeta{
67+
Name: testClusterName,
68+
Namespace: testNamespace,
69+
OwnerReferences: []metav1.OwnerReference{
70+
{
71+
APIVersion: clusterv1.GroupVersion.Group + "/" + clusterv1.GroupVersion.Version,
72+
Kind: "Cluster",
73+
Name: capiClusterName,
74+
UID: types.UID("cluster-uid"),
75+
},
76+
},
77+
},
78+
Spec: infrav1.OpenStackClusterSpec{},
79+
Status: infrav1.OpenStackClusterStatus{},
80+
}
81+
capiCluster = &clusterv1.Cluster{
82+
TypeMeta: metav1.TypeMeta{
83+
APIVersion: clusterv1.GroupVersion.Group + "/" + clusterv1.GroupVersion.Version,
84+
Kind: "Cluster",
85+
},
86+
ObjectMeta: metav1.ObjectMeta{
87+
Name: capiClusterName,
88+
Namespace: testNamespace,
89+
},
90+
}
91+
92+
input := framework.CreateNamespaceInput{
93+
Creator: k8sClient,
94+
Name: testNamespace,
95+
}
96+
framework.CreateNamespace(ctx, input)
97+
})
98+
99+
AfterEach(func() {
100+
orphan := metav1.DeletePropagationOrphan
101+
deleteOptions := client.DeleteOptions{
102+
PropagationPolicy: &orphan,
103+
}
104+
105+
// Remove finalizers and delete openstackcluster
106+
patchHelper, err := patch.NewHelper(testCluster, k8sClient)
107+
Expect(err).To(BeNil())
108+
testCluster.SetFinalizers([]string{})
109+
err = patchHelper.Patch(ctx, testCluster)
110+
Expect(err).To(BeNil())
111+
err = k8sClient.Delete(ctx, testCluster, &deleteOptions)
112+
Expect(err).To(BeNil())
113+
// Remove finalizers and delete cluster
114+
patchHelper, err = patch.NewHelper(capiCluster, k8sClient)
115+
Expect(err).To(BeNil())
116+
capiCluster.SetFinalizers([]string{})
117+
err = patchHelper.Patch(ctx, capiCluster)
118+
Expect(err).To(BeNil())
119+
err = k8sClient.Delete(ctx, capiCluster, &deleteOptions)
120+
Expect(err).To(BeNil())
121+
input := framework.DeleteNamespaceInput{
122+
Deleter: k8sClient,
123+
Name: testNamespace,
124+
}
125+
framework.DeleteNamespace(ctx, input)
126+
})
127+
128+
It("should do nothing when owner is missing", func() {
129+
testCluster.SetName("missing-owner")
130+
testCluster.SetOwnerReferences([]metav1.OwnerReference{})
131+
132+
err := k8sClient.Create(ctx, testCluster)
133+
Expect(err).To(BeNil())
134+
err = k8sClient.Create(ctx, capiCluster)
135+
Expect(err).To(BeNil())
136+
req := createRequestFromOSCluster(testCluster)
137+
138+
result, err := reconciler.Reconcile(ctx, req)
139+
// Expect no error and empty result
140+
Expect(err).To(BeNil())
141+
Expect(result).To(Equal(reconcile.Result{}))
142+
})
143+
It("should do nothing when paused", func() {
144+
testCluster.SetName("paused")
145+
annotations.AddAnnotations(testCluster, map[string]string{clusterv1.PausedAnnotation: "true"})
146+
147+
err := k8sClient.Create(ctx, testCluster)
148+
Expect(err).To(BeNil())
149+
err = k8sClient.Create(ctx, capiCluster)
150+
Expect(err).To(BeNil())
151+
req := createRequestFromOSCluster(testCluster)
152+
153+
result, err := reconciler.Reconcile(ctx, req)
154+
// Expect no error and empty result
155+
Expect(err).To(BeNil())
156+
Expect(result).To(Equal(reconcile.Result{}))
157+
})
158+
It("should do nothing when unable to get OS client", func() {
159+
testCluster.SetName("no-openstack-client")
160+
err := k8sClient.Create(ctx, testCluster)
161+
Expect(err).To(BeNil())
162+
err = k8sClient.Create(ctx, capiCluster)
163+
Expect(err).To(BeNil())
164+
req := createRequestFromOSCluster(testCluster)
165+
166+
result, err := reconciler.Reconcile(ctx, req)
167+
// Expect error for getting OS clinet and empty result
168+
Expect(err).ToNot(BeNil())
169+
Expect(result).To(Equal(reconcile.Result{}))
170+
})
171+
172+
// TODO: This test is set to pending (PIt instead of It) since it is not working.
173+
PIt("should be able to reconcile when basition disabled", func() {
174+
// verify := false
175+
// cloud := clientconfig.Cloud{
176+
// Cloud: "test",
177+
// RegionName: "test",
178+
// Verify: &verify,
179+
// AuthInfo: &clientconfig.AuthInfo{
180+
// AuthURL: "https://example.com:5000",
181+
// Username: "testuser",
182+
// Password: "secret",
183+
// ProjectName: "test",
184+
// DomainName: "test",
185+
// UserDomainName: "test",
186+
// },
187+
// }
188+
// // TODO: Can we fake the client in some way?
189+
// providerClient, clientOpts, _, err := provider.NewClient(cloud, nil)
190+
// Expect(err).To(BeNil())
191+
// scope := &scope.Scope{
192+
// ProviderClient: providerClient,
193+
// ProviderClientOpts: clientOpts,
194+
// }
195+
196+
// TODO: This won't work without filling in proper values.
197+
scope := &scope.Scope{
198+
ProviderClient: &gophercloud.ProviderClient{},
199+
ProviderClientOpts: &clientconfig.ClientOpts{},
200+
}
201+
testCluster.SetName("no-bastion")
202+
testCluster.Spec = infrav1.OpenStackClusterSpec{
203+
Bastion: &infrav1.Bastion{
204+
Enabled: false,
205+
},
206+
}
207+
err := k8sClient.Create(ctx, testCluster)
208+
Expect(err).To(BeNil())
209+
err = k8sClient.Create(ctx, capiCluster)
210+
Expect(err).To(BeNil())
211+
212+
err = deleteBastion(scope, capiCluster, testCluster)
213+
Expect(err).To(BeNil())
214+
})
215+
})
216+
217+
func createRequestFromOSCluster(openStackCluster *infrav1.OpenStackCluster) reconcile.Request {
218+
return reconcile.Request{
219+
NamespacedName: types.NamespacedName{
220+
Name: openStackCluster.GetName(),
221+
Namespace: openStackCluster.GetNamespace(),
222+
},
223+
}
224+
}

controllers/suite_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
. "github.com/onsi/ginkgo"
2525
. "github.com/onsi/gomega"
2626
corev1 "k8s.io/api/core/v1"
27+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2728
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2829
"k8s.io/apimachinery/pkg/types"
2930
"k8s.io/client-go/kubernetes/scheme"
@@ -33,6 +34,7 @@ import (
3334
"sigs.k8s.io/controller-runtime/pkg/envtest"
3435

3536
infrav1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha5"
37+
"sigs.k8s.io/cluster-api-provider-openstack/test/helpers/external"
3638
)
3739

3840
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
@@ -57,6 +59,11 @@ var _ = BeforeSuite(func() {
5759
CRDDirectoryPaths: []string{
5860
filepath.Join("..", "config", "crd", "bases"),
5961
},
62+
// Add fake CAPI CRDs that we reference
63+
CRDs: []*apiextensionsv1.CustomResourceDefinition{
64+
external.TestClusterCRD.DeepCopy(),
65+
external.TestMachineCRD.DeepCopy(),
66+
},
6067
}
6168

6269
var err error

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ require (
1717
golang.org/x/text v0.3.7
1818
gopkg.in/ini.v1 v1.63.2
1919
k8s.io/api v0.23.0
20+
k8s.io/apiextensions-apiserver v0.23.0
2021
k8s.io/apimachinery v0.23.0
2122
k8s.io/client-go v0.23.0
2223
k8s.io/component-base v0.23.0
@@ -115,7 +116,6 @@ require (
115116
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
116117
gopkg.in/yaml.v2 v2.4.0 // indirect
117118
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
118-
k8s.io/apiextensions-apiserver v0.23.0 // indirect
119119
k8s.io/apiserver v0.23.0 // indirect
120120
k8s.io/cluster-bootstrap v0.23.0 // indirect
121121
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect

test/helpers/external/cluster.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
Copyright 2022 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 external
18+
19+
import (
20+
"golang.org/x/text/cases"
21+
"golang.org/x/text/language"
22+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
23+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24+
"k8s.io/utils/pointer"
25+
)
26+
27+
const (
28+
clusterAPIGroup = "cluster.x-k8s.io"
29+
clusterAPITestVersion = "v1beta1"
30+
)
31+
32+
var (
33+
// TestClusterCRD will generate a test cluster CustomResourceDefinition.
34+
TestClusterCRD = generateTestClusterAPICRD("cluster", "clusters")
35+
36+
// TestMachineCRD will generate a test machine CustomResourceDefinition.
37+
TestMachineCRD = generateTestClusterAPICRD("machine", "machines")
38+
)
39+
40+
func generateTestClusterAPICRD(kind, pluralKind string) *apiextensionsv1.CustomResourceDefinition {
41+
return &apiextensionsv1.CustomResourceDefinition{
42+
TypeMeta: metav1.TypeMeta{
43+
APIVersion: apiextensionsv1.SchemeGroupVersion.String(),
44+
Kind: "CustomResourceDefinition",
45+
},
46+
ObjectMeta: metav1.ObjectMeta{
47+
Name: pluralKind + "." + clusterAPIGroup,
48+
},
49+
Spec: apiextensionsv1.CustomResourceDefinitionSpec{
50+
Group: "cluster.x-k8s.io",
51+
Scope: apiextensionsv1.NamespaceScoped,
52+
Names: apiextensionsv1.CustomResourceDefinitionNames{
53+
Kind: cases.Title(language.English).String(kind),
54+
Plural: pluralKind,
55+
},
56+
Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
57+
{
58+
Name: clusterAPITestVersion,
59+
Served: true,
60+
Storage: true,
61+
Subresources: &apiextensionsv1.CustomResourceSubresources{
62+
Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
63+
},
64+
Schema: &apiextensionsv1.CustomResourceValidation{
65+
OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
66+
Type: "object",
67+
Properties: map[string]apiextensionsv1.JSONSchemaProps{
68+
"spec": {
69+
Type: "object",
70+
XPreserveUnknownFields: pointer.BoolPtr(true),
71+
},
72+
"status": {
73+
Type: "object",
74+
XPreserveUnknownFields: pointer.BoolPtr(true),
75+
},
76+
},
77+
},
78+
},
79+
},
80+
},
81+
},
82+
}
83+
}

0 commit comments

Comments
 (0)