Visualization operator - update/delete datasource
[demo.git] / vnfs / DAaaS / microservices / visualization-operator / pkg / controller / grafanadatasource / grafanadatasource_controller.go
1 package grafanadatasource
2
3 import (
4         logr "github.com/go-logr/logr"
5
6         "bytes"
7         "context"
8         "encoding/json"
9         "fmt"
10         "io/ioutil"
11
12         onapv1alpha1 "visualization-operator/pkg/apis/onap/v1alpha1"
13         visualizationutils "visualization-operator/pkg/controller/utils"
14
15         "k8s.io/apimachinery/pkg/api/errors"
16         "k8s.io/apimachinery/pkg/runtime"
17         "net/http"
18         "sigs.k8s.io/controller-runtime/pkg/client"
19         "sigs.k8s.io/controller-runtime/pkg/controller"
20         "sigs.k8s.io/controller-runtime/pkg/handler"
21         "sigs.k8s.io/controller-runtime/pkg/manager"
22         "sigs.k8s.io/controller-runtime/pkg/reconcile"
23         logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
24         "sigs.k8s.io/controller-runtime/pkg/source"
25 )
26
27 var log = logf.Log.WithName("controller_grafanadatasource")
28
29 // Add creates a new GrafanaDataSource Controller and adds it to the Manager. The Manager will set fields on the Controller
30 // and Start it when the Manager is Started.
31 func Add(mgr manager.Manager) error {
32         return add(mgr, newReconciler(mgr))
33 }
34
35 // newReconciler returns a new reconcile.Reconciler
36 func newReconciler(mgr manager.Manager) reconcile.Reconciler {
37         return &ReconcileGrafanaDataSource{client: mgr.GetClient(), scheme: mgr.GetScheme()}
38 }
39
40 // add adds a new Controller to mgr with r as the reconcile.Reconciler
41 func add(mgr manager.Manager, r reconcile.Reconciler) error {
42         // Create a new controller
43         c, err := controller.New("grafanadatasource-controller", mgr, controller.Options{Reconciler: r})
44         if err != nil {
45                 return err
46         }
47
48         // Watch for changes to primary resource GrafanaDataSource
49         err = c.Watch(&source.Kind{Type: &onapv1alpha1.GrafanaDataSource{}}, &handler.EnqueueRequestForObject{})
50         if err != nil {
51                 return err
52         }
53
54         return nil
55 }
56
57 // blank assignment to verify that ReconcileGrafanaDataSource implements reconcile.Reconciler
58 var _ reconcile.Reconciler = &ReconcileGrafanaDataSource{}
59
60 // ReconcileGrafanaDataSource reconciles a GrafanaDataSource object
61 type ReconcileGrafanaDataSource struct {
62         // This client, initialized using mgr.Client() above, is a split client
63         // that reads objects from the cache and writes to the apiserver
64         client client.Client
65         scheme *runtime.Scheme
66 }
67
68 // Reconcile reads that state of the cluster for a GrafanaDataSource object and makes changes based on the state read
69 // and what is in the GrafanaDataSource.Spec
70 // Note:
71 // The Controller will requeue the Request to be processed again if the returned error is non-nil or
72 // Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
73 func (r *ReconcileGrafanaDataSource) Reconcile(request reconcile.Request) (reconcile.Result, error) {
74         reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
75         reqLogger.Info("Reconciling GrafanaDataSource")
76
77         // Fetch the GrafanaDataSource instance
78         instance := &onapv1alpha1.GrafanaDataSource{}
79         err := r.client.Get(context.TODO(), request.NamespacedName, instance)
80         if err != nil {
81                 if errors.IsNotFound(err) {
82                         // Request object not found, could have been deleted after reconcile request.
83                         // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
84                         // Return and don't requeue
85                         reqLogger.Info("GrafanaDatasource object not found")
86                         return reconcile.Result{}, nil
87                 }
88                 // Error reading the object - requeue the request.
89                 reqLogger.Info("Error reading the Grafanadatasource object, Requeing")
90                 return reconcile.Result{}, err
91         }
92
93         //Check if deletion timestamp is set. If yes, delete the GrafanaDataSource object
94         isBeingDeleted := checkDeletionTimestamp(reqLogger, instance)
95         if isBeingDeleted {
96                 //Delete the datasource from grafana
97                 err := deleteDatasource(instance)
98                 if err != nil {
99                         reqLogger.Error(err, "Unable to delete datasource")
100                         return reconcile.Result{}, err
101                 }
102                 //remove Finalizer after deletion
103                 if visualizationutils.Contains(instance.GetFinalizers(), visualizationutils.VisualizationFinalizer) {
104                         if err := removeFinalizer(reqLogger, instance); err != nil {
105                                 return reconcile.Result{}, err
106                         }
107                         err := r.client.Update(context.TODO(), instance)
108                         if err != nil {
109                                 reqLogger.Error(err, "Unable to update instance")
110                                 return reconcile.Result{}, err
111                         }
112                         return reconcile.Result{}, nil
113                 }
114         }
115
116         //Add finalizer for the CR object
117         if !visualizationutils.Contains(instance.GetFinalizers(), visualizationutils.VisualizationFinalizer) {
118                 reqLogger.Info("Adding finalizer for GrafanaDatasource")
119                 if err := addFinalizer(reqLogger, instance); err != nil {
120                         return reconcile.Result{}, err
121                 }
122                 err := r.client.Update(context.TODO(), instance)
123                 if err != nil {
124                         reqLogger.Error(err, "Unable to update instance")
125                         return reconcile.Result{}, err
126                 }
127                 return reconcile.Result{}, nil
128         }
129
130         datasources := instance.Spec.Datasources
131         grafana := instance.Spec.Grafana
132
133         reqLogger.V(1).Info(" Datasource Name ", "datasources", datasources)
134
135         //loop through all datasources in the spec
136         for _, datasource := range datasources {
137
138                 //check if datasource exists
139                 grafanaURL := grafana["url"] + "/api/datasources/name/" + datasource.Name
140                 grafanaUsername := grafana["username"]
141                 grafanaPassword := grafana["password"]
142
143                 client := &http.Client{}
144                 req, err := http.NewRequest("GET", grafanaURL, nil)
145                 if err != nil {
146                         reqLogger.Error(err, "GET REQUEST error")
147                         return reconcile.Result{}, err
148                 }
149                 req.SetBasicAuth(grafanaUsername, grafanaPassword)
150                 getResp, err := client.Do(req)
151                 if err != nil {
152                         reqLogger.Error(err, "GET RESPONSE error")
153                         return reconcile.Result{}, err
154                 }
155
156                 respBody, err := ioutil.ReadAll(getResp.Body)
157                 if err != nil {
158                         reqLogger.Error(err, "Response data not read properly")
159                         return reconcile.Result{}, err
160                 }
161
162                 respBodyBytes := []byte(respBody)
163                 var respData map[string]interface{}
164
165                 if err := json.Unmarshal(respBodyBytes, &respData); err != nil {
166                         reqLogger.Error(err, "JSON unmarshalling error")
167                         return reconcile.Result{}, err
168                 }
169
170                 respURL := fmt.Sprintf("%v", respData["url"])
171                 respID := fmt.Sprintf("%v", respData["id"])
172                 respIsDefault := respData["isDefault"]
173                 respAccess := fmt.Sprintf("%v", respData["access"])
174
175                 defer getResp.Body.Close()
176
177                 //add datasource if datasource does not exist already
178                 if getResp.StatusCode == http.StatusNotFound {
179                         reqLogger.Info("Datasource does not exist, creating one...")
180                         // create datasource
181                         if err := createDataSource(grafana, datasource); err != nil {
182                                 return reconcile.Result{}, err
183                         }
184                 } else if getResp.StatusCode == http.StatusOK {
185                         //if datasource already exists and there is any change in the spec - update it
186                         reqLogger.V(1).Info("datasource already exists", "datasource", datasource.Name)
187                         if respURL != datasource.URL || respIsDefault.(bool) != datasource.IsDefault || respAccess != datasource.Access {
188                                 if err := updateDatasource(grafana, datasource, respID); err != nil {
189                                         return reconcile.Result{}, err
190                                 }
191                         } else {
192                                 reqLogger.Info("No creation/updation of datasource needed")
193                                 return reconcile.Result{}, nil
194                         }
195                 }
196         }
197         return reconcile.Result{}, nil
198 }
199
200 func createDataSource(grafana map[string]string, datasource onapv1alpha1.Datasource) error {
201         reqLogger := log.WithValues("Datasource name", datasource.Name, "Datasource URL", datasource.URL)
202         reqLogger.Info("Creating datasource")
203
204         grafanaURL := grafana["url"] + "/api/datasources"
205         grafanaUsername := grafana["username"]
206         grafanaPassword := grafana["password"]
207
208         client := &http.Client{}
209         postBody, err := json.Marshal(datasource)
210         if err != nil {
211                 reqLogger.Error(err, "JSON Marshalling error")
212                 return err
213         }
214
215         postReq, err := http.NewRequest("POST", grafanaURL, bytes.NewBuffer(postBody))
216         if err != nil {
217                 reqLogger.Error(err, "POST REQUEST error")
218                 return err
219         }
220         postReq.Header.Set("Content-Type", "application/json")
221         postReq.SetBasicAuth(grafanaUsername, grafanaPassword)
222         postResp, err := client.Do(postReq)
223         if err != nil {
224                 reqLogger.Error(err, "POST RESPONSE error")
225                 return err
226         }
227         defer postReq.Body.Close()
228
229         if postResp.StatusCode == http.StatusOK {
230                 reqLogger.Info("Datasource created")
231                 return nil
232         }
233         return err
234 }
235
236 func updateDatasource(grafana map[string]string, datasource onapv1alpha1.Datasource, datasourceID string) error {
237         reqLogger := log.WithValues("Datasource name", datasource.Name, "Datasource URL", datasource.URL)
238         reqLogger.Info("Updating datasource")
239
240         grafanaURL := grafana["url"] + "/api/datasources/" + datasourceID
241         grafanaUsername := grafana["username"]
242         grafanaPassword := grafana["password"]
243
244         client := &http.Client{}
245         putBody, err := json.Marshal(datasource)
246         if err != nil {
247                 reqLogger.Error(err, "JSON Marshalling error")
248                 return err
249         }
250         putReq, err := http.NewRequest("PUT", grafanaURL, bytes.NewBuffer(putBody))
251         if err != nil {
252                 reqLogger.Error(err, "PUT REQUEST error")
253                 return err
254         }
255         putReq.Header.Set("Content-Type", "application/json")
256         putReq.Header.Set("Accept", "application/json")
257         putReq.SetBasicAuth(grafanaUsername, grafanaPassword)
258
259         putResp, err := client.Do(putReq)
260         if err != nil {
261                 reqLogger.Error(err, "PUT RESPONSE error")
262                 return err
263         }
264         defer putReq.Body.Close()
265
266         if putResp.StatusCode == http.StatusOK {
267                 reqLogger.Info("Datasource updated")
268                 return nil
269         }
270         return err
271 }
272
273 func deleteDatasource(instance *onapv1alpha1.GrafanaDataSource) error {
274
275         datasources := instance.Spec.Datasources
276         grafana := instance.Spec.Grafana
277
278         for _, datasource := range datasources {
279
280                 reqLogger := log.WithValues("Datasource name", datasource.Name, "Datasource URL", datasource.URL)
281                 reqLogger.Info("Deleting datasource")
282
283                 grafanaURL := grafana["url"] + "/api/datasources/name/" + datasource.Name
284                 grafanaUsername := grafana["username"]
285                 grafanaPassword := grafana["password"]
286
287                 client := &http.Client{}
288                 deleteReq, err := http.NewRequest("DELETE", grafanaURL, nil)
289                 if err != nil {
290                         reqLogger.Error(err, "DELETE request error")
291                         return err
292                 }
293
294                 deleteReq.SetBasicAuth(grafanaUsername, grafanaPassword)
295
296                 deleteResp, err := client.Do(deleteReq)
297                 if err != nil {
298                         reqLogger.Error(err, "DELETE RESPONSE error")
299                         return err
300                 }
301
302                 if deleteResp.StatusCode == http.StatusOK {
303                         reqLogger.Info("Datasource deleted")
304                         return nil
305                 }
306                 return err
307         }
308         return nil
309 }
310
311 func checkDeletionTimestamp(reqlogger logr.Logger, instance *onapv1alpha1.GrafanaDataSource) bool {
312         isMarkedForDeletion := instance.GetDeletionTimestamp() != nil
313         return isMarkedForDeletion
314 }
315
316 func addFinalizer(reqlogger logr.Logger, instance *onapv1alpha1.GrafanaDataSource) error {
317         reqlogger.Info("Adding finalizer for the GrafanaDatasource")
318         instance.SetFinalizers(append(instance.GetFinalizers(), visualizationutils.VisualizationFinalizer))
319         return nil
320 }
321
322 func removeFinalizer(reqlogger logr.Logger, instance *onapv1alpha1.GrafanaDataSource) error {
323         reqlogger.Info("Removing finalizer for the GrafanaDatasource")
324         instance.SetFinalizers(visualizationutils.Remove(instance.GetFinalizers(), visualizationutils.VisualizationFinalizer))
325         return nil
326 }