@@ -18,40 +18,82 @@ limitations under the License.
1818package controller
1919
2020import (
21- "context"
21+ "fmt"
22+ "net/http"
23+ "os"
2224
25+ "github.com/gophercloud/gophercloud/v2/testhelper"
26+ "github.com/gophercloud/gophercloud/v2/testhelper/client"
2327 . "github.com/onsi/ginkgo/v2"
2428 . "github.com/onsi/gomega"
2529 corev1 "k8s.io/api/core/v1"
30+ "k8s.io/apimachinery/pkg/api/meta"
2631 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2732 "k8s.io/apimachinery/pkg/types"
2833 ctrl "sigs.k8s.io/controller-runtime"
29- "sigs.k8s.io/controller-runtime/pkg/client"
34+ k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
3035
3136 kvmv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1"
3237)
3338
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+
3468var _ = Describe ("Decommission Controller" , func () {
35- const (
36- namespaceName = "namespace-test"
37- )
3869 var (
3970 r * NodeDecommissionReconciler
40- nodeName = types.NamespacedName {Name : "node-test" }
71+ nodeName = types.NamespacedName {Name : hypervisorName }
4172 reconcileReq = ctrl.Request {
4273 NamespacedName : nodeName ,
4374 }
75+ fakeServer testhelper.FakeServer
4476 )
4577
4678 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+
4787 r = & NodeDecommissionReconciler {
48- Client : k8sClient ,
49- Scheme : k8sClient .Scheme (),
88+ Client : k8sClient ,
89+ Scheme : k8sClient .Scheme (),
90+ computeClient : client .ServiceClient (fakeServer ),
91+ placementClient : client .ServiceClient (fakeServer ),
5092 }
5193
5294 By ("creating the namespace for the reconciler" )
5395 ns := & corev1.Namespace {ObjectMeta : metav1.ObjectMeta {Name : namespaceName }}
54- Expect (client .IgnoreAlreadyExists (k8sClient .Create (ctx , ns ))).To (Succeed ())
96+ Expect (k8sclient .IgnoreAlreadyExists (k8sClient .Create (ctx , ns ))).To (Succeed ())
5597
5698 DeferCleanup (func (ctx SpecContext ) {
5799 Expect (k8sClient .Delete (ctx , ns )).To (Succeed ())
@@ -66,7 +108,15 @@ var _ = Describe("Decommission Controller", func() {
66108 }
67109 Expect (k8sClient .Create (ctx , node )).To (Succeed ())
68110 DeferCleanup (func (ctx SpecContext ) {
69- 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+ }
70120 })
71121
72122 By ("Create the hypervisor resource with lifecycle enabled" )
@@ -84,32 +134,140 @@ var _ = Describe("Decommission Controller", func() {
84134 })
85135 })
86136
87- AfterEach (func (ctx context.Context ) {
88- node := & corev1.Node {ObjectMeta : metav1.ObjectMeta {Name : nodeName .Name }}
89- By ("Cleanup the specific node and hypervisor resource" )
90- Expect (client .IgnoreNotFound (k8sClient .Delete (ctx , node ))).To (Succeed ())
91-
92- // Due to the decommissioning finalizer, we need to reconcile once more to delete the node completely
93- req := ctrl.Request {
94- NamespacedName : types.NamespacedName {Name : nodeName .Name },
95- }
96- _ , err := r .Reconcile (ctx , req )
97- Expect (err ).NotTo (HaveOccurred ())
137+ Context ("When reconciling a node" , func () {
138+ It ("should set the finalizer" , func (ctx SpecContext ) {
139+ By ("reconciling the created resource" )
140+ _ , err := r .Reconcile (ctx , reconcileReq )
141+ Expect (err ).NotTo (HaveOccurred ())
142+ node := & corev1.Node {}
98143
99- nodelist := & corev1. NodeList {}
100- Expect (k8sClient . List ( ctx , nodelist )) .To (Succeed ( ))
101- Expect ( nodelist . Items ). To ( BeEmpty () )
144+ Expect ( k8sClient . Get ( ctx , nodeName , node )). To ( Succeed ())
145+ Expect (node . Finalizers ) .To (ContainElement ( decommissionFinalizerName ))
146+ } )
102147 })
103148
104- Context ("When reconciling a node" , func () {
105- It ( "should set the finalizer" , func (ctx context. Context ) {
106- By ("reconciling the created resource " )
149+ Context ("When terminating a node" , func () {
150+ JustBeforeEach ( func (ctx SpecContext ) {
151+ By ("reconciling first reconciling the to add the finalizer " )
107152 _ , err := r .Reconcile (ctx , reconcileReq )
108153 Expect (err ).NotTo (HaveOccurred ())
109154 node := & corev1.Node {}
110155
111156 Expect (k8sClient .Get (ctx , nodeName , node )).To (Succeed ())
112157 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 ())
113171 })
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+
114272 })
115273})
0 commit comments