2de8b0578fa5ff2b61101648ef7ab78aec61559f
[ric-plt/appmgr.git] / cmd / appmgr / api.go
1 /*
2 ==================================================================================
3   Copyright (c) 2019 AT&T Intellectual Property.
4   Copyright (c) 2019 Nokia
5
6    Licensed under the Apache License, Version 2.0 (the "License");
7    you may not use this file except in compliance with the License.
8    You may obtain a copy of the License at
9
10        http://www.apache.org/licenses/LICENSE-2.0
11
12    Unless required by applicable law or agreed to in writing, software
13    distributed under the License is distributed on an "AS IS" BASIS,
14    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15    See the License for the specific language governing permissions and
16    limitations under the License.
17 ==================================================================================
18 */
19
20 package main
21
22 import (
23         "encoding/json"
24         "errors"
25         "github.com/gorilla/mux"
26         "github.com/spf13/viper"
27         "log"
28         "net/http"
29 )
30
31 // API functions
32
33 func (m *XappManager) Initialize(h Helmer, cm ConfigMapper) {
34         m.cm = cm
35         m.helm = h
36         m.helm.SetCM(cm)
37
38         m.router = mux.NewRouter().StrictSlash(true)
39
40         resources := []Resource{
41                 {"GET", "/ric/v1/health/alive", m.getHealthStatus},
42                 {"GET", "/ric/v1/health/ready", m.getHealthStatus},
43
44                 {"GET", "/ric/v1/xapps", m.getAllXapps},
45                 {"GET", "/ric/v1/xapps/{name}", m.getXappByName},
46                 {"GET", "/ric/v1/xapps/{name}/instances/{id}", m.getXappInstanceByName},
47                 {"POST", "/ric/v1/xapps", m.deployXapp},
48                 {"DELETE", "/ric/v1/xapps/{name}", m.undeployXapp},
49
50                 {"GET", "/ric/v1/subscriptions", m.getSubscriptions},
51                 {"POST", "/ric/v1/subscriptions", m.addSubscription},
52                 {"GET", "/ric/v1/subscriptions/{id}", m.getSubscription},
53                 {"DELETE", "/ric/v1/subscriptions/{id}", m.deleteSubscription},
54                 {"PUT", "/ric/v1/subscriptions/{id}", m.updateSubscription},
55
56                 {"GET", "/ric/v1/config", m.getConfig},
57                 {"POST", "/ric/v1/config", m.createConfig},
58                 {"PUT", "/ric/v1/config", m.updateConfig},
59                 {"DELETE", "/ric/v1/config", m.deleteConfig},
60                 {"DELETE", "/ric/v1/config/{name}", m.deleteSingleConfig},
61         }
62
63         for _, resource := range resources {
64                 handler := LogRestRequests(resource.HandlerFunc)
65                 //handler = m.serviceChecker(handler)
66                 m.router.Methods(resource.Method).Path(resource.Url).Handler(handler)
67         }
68
69         go m.finalize(h)
70 }
71
72 func (m *XappManager) finalize(h Helmer) {
73         m.sd = SubscriptionDispatcher{}
74         m.sd.Initialize()
75
76         m.helm.Initialize()
77
78         m.notifyClients()
79         m.ready = true
80 }
81
82 func (m *XappManager) serviceChecker(inner http.Handler) http.Handler {
83         return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
84                 if r.URL.RequestURI() == "/ric/v1/health/alive" || m.ready == true {
85                         inner.ServeHTTP(w, r)
86                 } else {
87                         respondWithJSON(w, http.StatusServiceUnavailable, nil)
88                 }
89         })
90 }
91
92 // API: XAPP handlers
93 func (m *XappManager) getHealthStatus(w http.ResponseWriter, r *http.Request) {
94         respondWithJSON(w, http.StatusOK, nil)
95 }
96
97 func (m *XappManager) Run() {
98         host := viper.GetString("local.host")
99         if host == "" {
100                 host = ":8080"
101         }
102         log.Printf("Xapp manager started ... serving on %s\n", host)
103
104         log.Fatal(http.ListenAndServe(host, m.router))
105 }
106
107 func (m *XappManager) getXappByName(w http.ResponseWriter, r *http.Request) {
108         xappName, ok := getResourceId(r, w, "name")
109         if ok != true {
110                 return
111         }
112
113         if xapp, err := m.helm.Status(xappName); err == nil {
114                 respondWithJSON(w, http.StatusOK, xapp)
115         } else {
116                 respondWithError(w, http.StatusNotFound, err.Error())
117         }
118 }
119
120 func (m *XappManager) getXappInstanceByName(w http.ResponseWriter, r *http.Request) {
121         xappName, ok := getResourceId(r, w, "name")
122         if ok != true {
123                 return
124         }
125
126         xapp, err := m.helm.Status(xappName)
127         if err != nil {
128                 respondWithError(w, http.StatusNotFound, err.Error())
129                 return
130         }
131
132         xappInstanceName, ok := getResourceId(r, w, "id")
133         if ok != true {
134                 return
135         }
136
137         for _, v := range xapp.Instances {
138                 if v.Name == xappInstanceName {
139                         respondWithJSON(w, http.StatusOK, v)
140                         return
141                 }
142         }
143         Logger.Error("Xapp instance not found - url=%s", r.URL.RequestURI())
144
145         respondWithError(w, http.StatusNotFound, "Xapp instance not found")
146 }
147
148 func (m *XappManager) getAllXapps(w http.ResponseWriter, r *http.Request) {
149         xapps, err := m.helm.StatusAll()
150         if err != nil {
151                 respondWithError(w, http.StatusInternalServerError, err.Error())
152                 return
153         }
154
155         respondWithJSON(w, http.StatusOK, xapps)
156 }
157
158 func (m *XappManager) deployXapp(w http.ResponseWriter, r *http.Request) {
159         if r.Body == nil {
160                 Logger.Error("No xapp data found in request body - url=%s", r.URL.RequestURI())
161                 respondWithError(w, http.StatusMethodNotAllowed, "No xapp data!")
162                 return
163         }
164
165         var cm XappDeploy
166         if err := json.NewDecoder(r.Body).Decode(&cm); err != nil {
167                 Logger.Error("Invalid xapp data in request body - url=%s", r.URL.RequestURI())
168                 respondWithError(w, http.StatusMethodNotAllowed, "Invalid xapp data!")
169                 return
170         }
171         defer r.Body.Close()
172
173         xapp, err := m.helm.Install(cm)
174         if err != nil {
175                 respondWithError(w, http.StatusInternalServerError, err.Error())
176                 return
177         }
178
179         respondWithJSON(w, http.StatusCreated, xapp)
180
181         m.sd.Publish(xapp, EventType("created"))
182 }
183
184 func (m *XappManager) undeployXapp(w http.ResponseWriter, r *http.Request) {
185         xappName, ok := getResourceId(r, w, "name")
186         if ok != true {
187                 return
188         }
189
190         xapp, err := m.helm.Delete(xappName)
191         if err != nil {
192                 respondWithError(w, http.StatusInternalServerError, err.Error())
193                 return
194         }
195
196         respondWithJSON(w, http.StatusNoContent, nil)
197
198         m.sd.Publish(xapp, EventType("deleted"))
199 }
200
201 // API: resthook handlers
202 func (m *XappManager) getSubscriptions(w http.ResponseWriter, r *http.Request) {
203         respondWithJSON(w, http.StatusOK, m.sd.GetAll())
204 }
205
206 func (m *XappManager) getSubscription(w http.ResponseWriter, r *http.Request) {
207         if id, ok := getResourceId(r, w, "id"); ok == true {
208                 if s, ok := m.sd.Get(id); ok {
209                         respondWithJSON(w, http.StatusOK, s)
210                 } else {
211                         Logger.Error("Subscription not found - url=%s", r.URL.RequestURI())
212                         respondWithError(w, http.StatusNotFound, "Subscription not found")
213                 }
214         }
215 }
216
217 func (m *XappManager) deleteSubscription(w http.ResponseWriter, r *http.Request) {
218         if id, ok := getResourceId(r, w, "id"); ok == true {
219                 if _, ok := m.sd.Delete(id); ok {
220                         respondWithJSON(w, http.StatusNoContent, nil)
221                 } else {
222                         Logger.Error("Subscription not found - url=%s", r.URL.RequestURI())
223                         respondWithError(w, http.StatusNotFound, "Subscription not found")
224                 }
225         }
226 }
227
228 func (m *XappManager) addSubscription(w http.ResponseWriter, r *http.Request) {
229         var req SubscriptionReq
230         if r.Body == nil || json.NewDecoder(r.Body).Decode(&req) != nil {
231                 Logger.Error("Invalid request payload - url=%s", r.URL.RequestURI())
232                 respondWithError(w, http.StatusMethodNotAllowed, "Invalid request payload")
233                 return
234         }
235         defer r.Body.Close()
236
237         respondWithJSON(w, http.StatusCreated, m.sd.Add(req))
238 }
239
240 func (m *XappManager) updateSubscription(w http.ResponseWriter, r *http.Request) {
241         if id, ok := getResourceId(r, w, "id"); ok == true {
242                 var req SubscriptionReq
243                 if r.Body == nil || json.NewDecoder(r.Body).Decode(&req) != nil {
244                         Logger.Error("Invalid request payload - url=%s", r.URL.RequestURI())
245                         respondWithError(w, http.StatusMethodNotAllowed, "Invalid request payload")
246                         return
247                 }
248                 defer r.Body.Close()
249
250                 if s, ok := m.sd.Update(id, req); ok {
251                         respondWithJSON(w, http.StatusOK, s)
252                 } else {
253                         Logger.Error("Subscription not found - url=%s", r.URL.RequestURI())
254                         respondWithError(w, http.StatusNotFound, "Subscription not found")
255                 }
256         }
257 }
258
259 func (m *XappManager) notifyClients() {
260         xapps, err := m.helm.StatusAll()
261         if err != nil {
262                 Logger.Info("Couldn't fetch xapps status information: %v", err.Error())
263                 return
264         }
265
266         m.sd.notifyClients(xapps, "updated")
267 }
268
269 func (m *XappManager) getConfig(w http.ResponseWriter, r *http.Request) {
270         cfg := m.cm.UploadConfig()
271         respondWithJSON(w, http.StatusOK, cfg)
272 }
273
274 func (m *XappManager) createConfig(w http.ResponseWriter, r *http.Request) {
275         var c XAppConfig
276         if parseConfig(w, r, &c) != nil {
277                 return
278         }
279
280         if errList, err := m.cm.CreateConfigMap(c); err != nil {
281                 if err.Error() != "Validation failed!" {
282                         respondWithError(w, http.StatusInternalServerError, err.Error())
283                 } else {
284                         respondWithJSON(w, http.StatusUnprocessableEntity, errList)
285                 }
286                 return
287         }
288         respondWithJSON(w, http.StatusCreated, c.Metadata)
289 }
290
291 func (m *XappManager) updateConfig(w http.ResponseWriter, r *http.Request) {
292         var c XAppConfig
293         if parseConfig(w, r, &c) != nil {
294                 return
295         }
296
297         if errList, err := m.cm.UpdateConfigMap(c); err != nil {
298                 if err.Error() != "Validation failed!" {
299                         respondWithError(w, http.StatusInternalServerError, err.Error())
300                 } else {
301                         respondWithJSON(w, http.StatusInternalServerError, errList)
302                 }
303                 return
304         }
305         respondWithJSON(w, http.StatusOK, c.Metadata)
306 }
307
308 func (m *XappManager) deleteSingleConfig(w http.ResponseWriter, r *http.Request) {
309         xappName, ok := getResourceId(r, w, "name")
310         if ok != true {
311                 return
312         }
313
314         md := ConfigMetadata{Name: xappName, Namespace: m.cm.GetNamespace(""), ConfigName: xappName + "-appconfig"}
315         m.delConfig(w, XAppConfig{Metadata: md})
316 }
317
318 func (m *XappManager) deleteConfig(w http.ResponseWriter, r *http.Request) {
319         var c XAppConfig
320         if parseConfig(w, r, &c) != nil {
321                 return
322         }
323
324         m.delConfig(w, c)
325 }
326
327 func (m *XappManager) delConfig(w http.ResponseWriter, c XAppConfig) {
328         if _, err := m.cm.DeleteConfigMap(c); err != nil {
329                 respondWithError(w, http.StatusInternalServerError, err.Error())
330                 return
331         }
332         respondWithJSON(w, http.StatusNoContent, nil)
333 }
334
335 // Helper functions
336 func respondWithError(w http.ResponseWriter, code int, message string) {
337         respondWithJSON(w, code, map[string]string{"error": message})
338 }
339
340 func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) {
341         w.Header().Set("Content-Type", "application/json")
342         w.WriteHeader(code)
343         if payload != nil {
344                 response, _ := json.Marshal(payload)
345                 w.Write(response)
346         }
347 }
348
349 func getResourceId(r *http.Request, w http.ResponseWriter, pattern string) (id string, ok bool) {
350         if id, ok = mux.Vars(r)[pattern]; ok != true {
351                 Logger.Error("Couldn't resolve name/id from the request URL")
352                 respondWithError(w, http.StatusMethodNotAllowed, "Couldn't resolve name/id from the request URL")
353                 return
354         }
355         return
356 }
357
358 func parseConfig(w http.ResponseWriter, r *http.Request, req *XAppConfig) error {
359         if r.Body == nil || json.NewDecoder(r.Body).Decode(&req) != nil {
360                 Logger.Error("Invalid request payload - url=%s", r.URL.RequestURI())
361                 respondWithError(w, http.StatusMethodNotAllowed, "Invalid request payload")
362                 return errors.New("Invalid payload")
363         }
364         defer r.Body.Close()
365
366         return nil
367 }