Skip to content

Commit fd5dd23

Browse files
committed
Offboarding: Unit test the happy path
1 parent e998a1a commit fd5dd23

File tree

1 file changed

+186
-26
lines changed

1 file changed

+186
-26
lines changed

internal/controller/decomission_controller_test.go

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

2020
import (
21+
"fmt"
22+
"net/http"
23+
"os"
24+
25+
"github.com/gophercloud/gophercloud/v2/testhelper"
26+
"github.com/gophercloud/gophercloud/v2/testhelper/client"
2127
. "github.com/onsi/ginkgo/v2"
2228
. "github.com/onsi/gomega"
2329
corev1 "k8s.io/api/core/v1"
30+
"k8s.io/apimachinery/pkg/api/meta"
2431
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2532
"k8s.io/apimachinery/pkg/types"
2633
ctrl "sigs.k8s.io/controller-runtime"
27-
"sigs.k8s.io/controller-runtime/pkg/client"
34+
k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
2835

2936
kvmv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1"
3037
)
3138

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

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

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

5498
DeferCleanup(func(ctx SpecContext) {
5599
Expect(k8sClient.Delete(ctx, ns)).To(Succeed())
@@ -64,7 +108,15 @@ var _ = Describe("Decommission Controller", func() {
64108
}
65109
Expect(k8sClient.Create(ctx, node)).To(Succeed())
66110
DeferCleanup(func(ctx SpecContext) {
67-
Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, node))).To(Succeed())
111+
node := &corev1.Node{}
112+
Expect(k8sclient.IgnoreNotFound(k8sClient.Get(ctx, nodeName, node))).To(Succeed())
113+
if len(node.Finalizers) > 0 {
114+
node.Finalizers = make([]string, 0)
115+
Expect(k8sClient.Update(ctx, node)).To(Succeed())
116+
}
117+
if node.Name != "" {
118+
Expect(k8sclient.IgnoreNotFound(k8sClient.Delete(ctx, node))).To(Succeed())
119+
}
68120
})
69121

70122
By("Create the hypervisor resource with lifecycle enabled")
@@ -82,23 +134,6 @@ var _ = Describe("Decommission Controller", func() {
82134
})
83135
})
84136

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

0 commit comments

Comments
 (0)