Skip to content

Commit 103055b

Browse files
committed
Offboarding: Unit test the happy path
1 parent e16f187 commit 103055b

File tree

1 file changed

+185
-26
lines changed

1 file changed

+185
-26
lines changed

internal/controller/decomission_controller_test.go

Lines changed: 185 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,38 +18,81 @@ limitations under the License.
1818
package controller
1919

2020
import (
21+
"fmt"
22+
"net/http"
23+
"os"
24+
"github.com/gophercloud/gophercloud/v2/testhelper"
25+
"github.com/gophercloud/gophercloud/v2/testhelper/client"
2126
. "github.com/onsi/ginkgo/v2"
2227
. "github.com/onsi/gomega"
2328
corev1 "k8s.io/api/core/v1"
29+
"k8s.io/apimachinery/pkg/api/meta"
2430
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2531
"k8s.io/apimachinery/pkg/types"
2632
ctrl "sigs.k8s.io/controller-runtime"
27-
"sigs.k8s.io/controller-runtime/pkg/client"
33+
k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
2834

2935
kvmv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1"
3036
)
3137

38+
const (
39+
EOF = "EOF"
40+
serviceId = "service-1234"
41+
hypervisorName = "node-test"
42+
namespaceName = "namespace-test"
43+
AggregateListWithHv = `
44+
{
45+
"aggregates": [
46+
{
47+
"name": "test-aggregate2",
48+
"availability_zone": "",
49+
"deleted": false,
50+
"id": 100001,
51+
"hosts": ["note-test"]
52+
}
53+
]
54+
}
55+
`
56+
AggregateRemoveHostBody = `
57+
{
58+
"aggregate": {
59+
"name": "test-aggregate2",
60+
"availability_zone": "",
61+
"deleted": false,
62+
"id": 100001
63+
}
64+
}`
65+
)
66+
3267
var _ = Describe("Decommission Controller", func() {
33-
const (
34-
namespaceName = "namespace-test"
35-
)
3668
var (
3769
r *NodeDecommissionReconciler
38-
nodeName = types.NamespacedName{Name: "node-test"}
70+
nodeName = types.NamespacedName{Name: hypervisorName}
3971
reconcileReq = ctrl.Request{
4072
NamespacedName: nodeName,
4173
}
74+
fakeServer testhelper.FakeServer
4275
)
4376

4477
BeforeEach(func(ctx SpecContext) {
78+
fakeServer = testhelper.SetupHTTP()
79+
os.Setenv("KVM_HA_SERVICE_URL", fakeServer.Endpoint()+"instance-ha")
80+
81+
DeferCleanup(func() {
82+
os.Unsetenv("KVM_HA_SERVICE_URL")
83+
fakeServer.Teardown()
84+
})
85+
4586
r = &NodeDecommissionReconciler{
46-
Client: k8sClient,
47-
Scheme: k8sClient.Scheme(),
87+
Client: k8sClient,
88+
Scheme: k8sClient.Scheme(),
89+
computeClient: client.ServiceClient(fakeServer),
90+
placementClient: client.ServiceClient(fakeServer),
4891
}
4992

5093
By("creating the namespace for the reconciler")
5194
ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespaceName}}
52-
Expect(client.IgnoreAlreadyExists(k8sClient.Create(ctx, ns))).To(Succeed())
95+
Expect(k8sclient.IgnoreAlreadyExists(k8sClient.Create(ctx, ns))).To(Succeed())
5396

5497
DeferCleanup(func(ctx SpecContext) {
5598
Expect(k8sClient.Delete(ctx, ns)).To(Succeed())
@@ -64,7 +107,15 @@ var _ = Describe("Decommission Controller", func() {
64107
}
65108
Expect(k8sClient.Create(ctx, node)).To(Succeed())
66109
DeferCleanup(func(ctx SpecContext) {
67-
Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, node))).To(Succeed())
110+
node := &corev1.Node{}
111+
Expect(k8sclient.IgnoreNotFound(k8sClient.Get(ctx, nodeName, node))).To(Succeed())
112+
if len(node.Finalizers) > 0 {
113+
node.Finalizers = make([]string, 0)
114+
Expect(k8sClient.Update(ctx, node)).To(Succeed())
115+
}
116+
if node.Name != "" {
117+
Expect(k8sclient.IgnoreNotFound(k8sClient.Delete(ctx, node))).To(Succeed())
118+
}
68119
})
69120

70121
By("Create the hypervisor resource with lifecycle enabled")
@@ -82,23 +133,6 @@ var _ = Describe("Decommission Controller", func() {
82133
})
83134
})
84135

85-
AfterEach(func(ctx SpecContext) {
86-
node := &corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeName.Name}}
87-
By("Cleanup the specific node and hypervisor resource")
88-
Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, node))).To(Succeed())
89-
90-
// Due to the decommissioning finalizer, we need to reconcile once more to delete the node completely
91-
req := ctrl.Request{
92-
NamespacedName: types.NamespacedName{Name: nodeName.Name},
93-
}
94-
_, err := r.Reconcile(ctx, req)
95-
Expect(err).NotTo(HaveOccurred())
96-
97-
nodelist := &corev1.NodeList{}
98-
Expect(k8sClient.List(ctx, nodelist)).To(Succeed())
99-
Expect(nodelist.Items).To(BeEmpty())
100-
})
101-
102136
Context("When reconciling a node", func() {
103137
It("should set the finalizer", func(ctx SpecContext) {
104138
By("reconciling the created resource")
@@ -110,4 +144,129 @@ var _ = Describe("Decommission Controller", func() {
110144
Expect(node.Finalizers).To(ContainElement(decommissionFinalizerName))
111145
})
112146
})
147+
148+
Context("When terminating a node", func() {
149+
JustBeforeEach(func(ctx SpecContext) {
150+
By("reconciling first reconciling the to add the finalizer")
151+
_, err := r.Reconcile(ctx, reconcileReq)
152+
Expect(err).NotTo(HaveOccurred())
153+
node := &corev1.Node{}
154+
155+
Expect(k8sClient.Get(ctx, nodeName, node)).To(Succeed())
156+
Expect(node.Finalizers).To(ContainElement(decommissionFinalizerName))
157+
158+
By("and then terminating then node")
159+
node.Status.Conditions = append(node.Status.Conditions, corev1.NodeCondition{
160+
Type: "Terminating",
161+
Status: corev1.ConditionTrue,
162+
Reason: "dontcare",
163+
Message: "dontcare",
164+
})
165+
Expect(k8sClient.Status().Update(ctx, node)).To(Succeed())
166+
Expect(k8sClient.Delete(ctx, node)).To(Succeed())
167+
nodelist := &corev1.NodeList{}
168+
Expect(k8sClient.List(ctx, nodelist)).To(Succeed())
169+
Expect(nodelist.Items).NotTo(BeEmpty())
170+
})
171+
172+
When("the hypervisor was set to ready", func() {
173+
getHypervisorsCalled := 0
174+
BeforeEach(func(ctx SpecContext) {
175+
hv := &kvmv1.Hypervisor{}
176+
Expect(k8sClient.Get(ctx, nodeName, hv)).To(Succeed())
177+
meta.SetStatusCondition(&hv.Status.Conditions,
178+
metav1.Condition{
179+
Type: kvmv1.ConditionTypeReady,
180+
Status: metav1.ConditionTrue,
181+
Reason: "dontcare",
182+
Message: "dontcare",
183+
},
184+
)
185+
Expect(k8sClient.Status().Update(ctx, hv)).To(Succeed())
186+
187+
fakeServer.Mux.HandleFunc("GET /os-hypervisors/detail", func(w http.ResponseWriter, r *http.Request) {
188+
w.Header().Add("Content-Type", "application/json")
189+
w.WriteHeader(http.StatusOK)
190+
getHypervisorsCalled++
191+
Expect(fmt.Fprintf(w, HypervisorWithServers, serviceId, "some reason", hypervisorName)).ToNot(BeNil())
192+
})
193+
194+
fakeServer.Mux.HandleFunc("GET /os-aggregates", func(w http.ResponseWriter, r *http.Request) {
195+
w.Header().Add("Content-Type", "application/json")
196+
w.WriteHeader(http.StatusOK)
197+
198+
_, err := fmt.Fprint(w, AggregateListWithHv)
199+
Expect(err).NotTo(HaveOccurred())
200+
})
201+
202+
fakeServer.Mux.HandleFunc("POST /os-aggregates/100001/action", func(w http.ResponseWriter, r *http.Request) {
203+
// parse request
204+
Expect(r.Header.Get("Content-Type")).To(Equal("application/json"))
205+
expectedBody := `{"remove_host":{"host":"hv-test"}}`
206+
body := make([]byte, r.ContentLength)
207+
_, err := r.Body.Read(body)
208+
Expect(err == nil || err.Error() == EOF).To(BeTrue())
209+
Expect(string(body)).To(MatchJSON(expectedBody))
210+
211+
// send response
212+
w.Header().Add("Content-Type", "application/json")
213+
w.WriteHeader(http.StatusOK)
214+
215+
_, err = fmt.Fprint(w, AggregateRemoveHostBody)
216+
Expect(err).NotTo(HaveOccurred())
217+
})
218+
219+
// c48f6247-abe4-4a24-824e-ea39e108874f comes from the HypervisorWithServers const
220+
fakeServer.Mux.HandleFunc("GET /resource_providers/c48f6247-abe4-4a24-824e-ea39e108874f", func(w http.ResponseWriter, r *http.Request) {
221+
w.Header().Add("Content-Type", "application/json")
222+
w.WriteHeader(http.StatusOK)
223+
224+
_, err := fmt.Fprint(w, `{"uuid": "rp-uuid", "name": "hv-test"}`)
225+
Expect(err).NotTo(HaveOccurred())
226+
})
227+
228+
fakeServer.Mux.HandleFunc("GET /resource_providers/rp-uuid/allocations", func(w http.ResponseWriter, r *http.Request) {
229+
w.Header().Add("Content-Type", "application/json")
230+
w.WriteHeader(http.StatusOK)
231+
232+
_, err := fmt.Fprint(w, `{"allocations": {}}}`)
233+
Expect(err).NotTo(HaveOccurred())
234+
235+
})
236+
fakeServer.Mux.HandleFunc("DELETE /resource_providers/rp-uuid", func(w http.ResponseWriter, r *http.Request) {
237+
w.WriteHeader(http.StatusAccepted)
238+
})
239+
})
240+
241+
It("should set the hypervisor condition", func(ctx SpecContext) {
242+
By("reconciling the created resource")
243+
_, err := r.Reconcile(ctx, reconcileReq)
244+
Expect(err).NotTo(HaveOccurred())
245+
hypervisor := &kvmv1.Hypervisor{}
246+
Expect(k8sClient.Get(ctx, nodeName, hypervisor)).To(Succeed())
247+
Expect(hypervisor.Status.Conditions).To(ContainElement(
248+
SatisfyAll(
249+
HaveField("Type", kvmv1.ConditionTypeReady),
250+
HaveField("Status", metav1.ConditionFalse),
251+
HaveField("Reason", "Decommissioning"),
252+
),
253+
))
254+
})
255+
256+
It("should remove the finalizer", func(ctx SpecContext) {
257+
By("reconciling the created resource")
258+
for range 3 {
259+
_, err := r.Reconcile(ctx, reconcileReq)
260+
Expect(err).NotTo(HaveOccurred())
261+
}
262+
Expect(getHypervisorsCalled).To(BeNumerically(">", 0))
263+
264+
node := &corev1.Node{}
265+
err := k8sClient.Get(ctx, nodeName, node)
266+
Expect(err).To(HaveOccurred())
267+
Expect(k8sclient.IgnoreNotFound(err)).To(Succeed())
268+
})
269+
})
270+
271+
})
113272
})

0 commit comments

Comments
 (0)