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