d1bb2fd63fce86b6359ccc3353a9828b4be07492
[multicloud/k8s.git] / src / monitor / pkg / controller / resourcebundlestate / service_controller.go
1 package resourcebundlestate
2
3 import (
4         "context"
5         "log"
6
7         "github.com/onap/multicloud-k8s/src/monitor/pkg/apis/k8splugin/v1alpha1"
8
9         corev1 "k8s.io/api/core/v1"
10         k8serrors "k8s.io/apimachinery/pkg/api/errors"
11         "k8s.io/apimachinery/pkg/types"
12         "sigs.k8s.io/controller-runtime/pkg/client"
13         "sigs.k8s.io/controller-runtime/pkg/controller"
14         "sigs.k8s.io/controller-runtime/pkg/handler"
15         "sigs.k8s.io/controller-runtime/pkg/manager"
16         "sigs.k8s.io/controller-runtime/pkg/reconcile"
17         "sigs.k8s.io/controller-runtime/pkg/source"
18 )
19
20 // AddServiceController the new controller to the controller manager
21 func AddServiceController(mgr manager.Manager) error {
22         return addServiceController(mgr, newServiceReconciler(mgr))
23 }
24
25 func addServiceController(mgr manager.Manager, r *serviceReconciler) error {
26         // Create a new controller
27         c, err := controller.New("Service-controller", mgr, controller.Options{Reconciler: r})
28         if err != nil {
29                 return err
30         }
31
32         // Watch for changes to secondar resource Services
33         // Predicate filters Service which don't have the k8splugin label
34         err = c.Watch(&source.Kind{Type: &corev1.Service{}}, &handler.EnqueueRequestForObject{}, &servicePredicate{})
35         if err != nil {
36                 return err
37         }
38
39         return nil
40 }
41
42 func newServiceReconciler(m manager.Manager) *serviceReconciler {
43         return &serviceReconciler{client: m.GetClient()}
44 }
45
46 type serviceReconciler struct {
47         client client.Client
48 }
49
50 // Reconcile implements the loop that will update the ResourceBundleState CR
51 // whenever we get any updates from all the services we watch.
52 func (r *serviceReconciler) Reconcile(req reconcile.Request) (reconcile.Result, error) {
53         log.Printf("Updating ResourceBundleState for Service: %+v\n", req)
54
55         svc := &corev1.Service{}
56         err := r.client.Get(context.TODO(), req.NamespacedName, svc)
57         if err != nil {
58                 if k8serrors.IsNotFound(err) {
59                         log.Printf("Service not found: %+v. Remove from CR if it is stored there.\n", req.NamespacedName)
60                         // Remove the Service's status from StatusList
61                         // This can happen if we get the DeletionTimeStamp event
62                         // after the Service has been deleted.
63                         r.deleteServiceFromAllCRs(req.NamespacedName)
64                         return reconcile.Result{}, nil
65                 }
66                 log.Printf("Failed to get service: %+v\n", req.NamespacedName)
67                 return reconcile.Result{}, err
68         }
69
70         // Find the CRs which track this service via the labelselector
71         crSelector := returnLabel(svc.GetLabels())
72         if crSelector == nil {
73                 log.Println("We should not be here. The predicate should have filtered this Service")
74         }
75
76         // Get the CRs which have this label and update them all
77         // Ideally, we will have only one CR, but there is nothing
78         // preventing the creation of multiple.
79         // TODO: Consider using an admission validating webook to prevent multiple
80         rbStatusList := &v1alpha1.ResourceBundleStateList{}
81         err = listResources(r.client, req.Namespace, crSelector, rbStatusList)
82         if err != nil || len(rbStatusList.Items) == 0 {
83                 log.Printf("Did not find any CRs tracking this resource\n")
84                 return reconcile.Result{}, nil
85         }
86
87         err = r.updateCRs(rbStatusList, svc)
88         if err != nil {
89                 // Requeue the update
90                 return reconcile.Result{}, err
91         }
92
93         return reconcile.Result{}, nil
94 }
95
96 // deleteServiceFromAllCRs deletes service status from all the CRs when the Service itself has been deleted
97 // and we have not handled the updateCRs yet.
98 // Since, we don't have the service's labels, we need to look at all the CRs in this namespace
99 func (r *serviceReconciler) deleteServiceFromAllCRs(namespacedName types.NamespacedName) error {
100
101         rbStatusList := &v1alpha1.ResourceBundleStateList{}
102         err := listResources(r.client, namespacedName.Namespace, nil, rbStatusList)
103         if err != nil || len(rbStatusList.Items) == 0 {
104                 log.Printf("Did not find any CRs tracking this resource\n")
105                 return nil
106         }
107         for _, cr := range rbStatusList.Items {
108                 r.deleteFromSingleCR(&cr, namespacedName.Name)
109         }
110
111         return nil
112 }
113
114 func (r *serviceReconciler) updateCRs(crl *v1alpha1.ResourceBundleStateList, svc *corev1.Service) error {
115
116         for _, cr := range crl.Items {
117                 // Service is not scheduled for deletion
118                 if svc.DeletionTimestamp == nil {
119                         err := r.updateSingleCR(&cr, svc)
120                         if err != nil {
121                                 return err
122                         }
123                 } else {
124                         // Service is scheduled for deletion
125                         r.deleteFromSingleCR(&cr, svc.Name)
126                 }
127         }
128
129         return nil
130 }
131
132 func (r *serviceReconciler) deleteFromSingleCR(cr *v1alpha1.ResourceBundleState, name string) error {
133         cr.Status.ResourceCount--
134         length := len(cr.Status.ServiceStatuses)
135         for i, rstatus := range cr.Status.ServiceStatuses {
136                 if rstatus.Name == name {
137                         //Delete that status from the array
138                         cr.Status.ServiceStatuses[i] = cr.Status.ServiceStatuses[length-1]
139                         cr.Status.ServiceStatuses[length-1].Status = corev1.ServiceStatus{}
140                         cr.Status.ServiceStatuses = cr.Status.ServiceStatuses[:length-1]
141                         return nil
142                 }
143         }
144
145         log.Println("Did not find a status for Service in CR")
146         return nil
147 }
148
149 func (r *serviceReconciler) updateSingleCR(cr *v1alpha1.ResourceBundleState, svc *corev1.Service) error {
150
151         // Update status after searching for it in the list of resourceStatuses
152         for i, rstatus := range cr.Status.ServiceStatuses {
153                 // Look for the status if we already have it in the CR
154                 if rstatus.Name == svc.Name {
155                         svc.Status.DeepCopyInto(&cr.Status.ServiceStatuses[i].Status)
156                         err := r.client.Status().Update(context.TODO(), cr)
157                         if err != nil {
158                                 log.Printf("failed to update rbstate: %v\n", err)
159                                 return err
160                         }
161                         return nil
162                 }
163         }
164
165         // Exited for loop with no status found
166         // Increment the number of tracked resources
167         cr.Status.ResourceCount++
168
169         // Add it to CR
170         cr.Status.ServiceStatuses = append(cr.Status.ServiceStatuses, corev1.Service{
171                 ObjectMeta: svc.ObjectMeta,
172                 Status:     svc.Status,
173         })
174
175         err := r.client.Status().Update(context.TODO(), cr)
176         if err != nil {
177                 log.Printf("failed to update rbstate: %v\n", err)
178                 return err
179         }
180
181         return nil
182 }