@@ -18,38 +18,82 @@ limitations under the License.
1818package controller
1919
2020import (
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+
3268var _ = 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