Add a new API to return the list of undeployed xApps
[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/search", m.searchAllXapps},
46                 {"GET", "/ric/v1/xapps/{name}", m.getXappByName},
47                 {"GET", "/ric/v1/xapps/{name}/instances/{id}", m.getXappInstanceByName},
48                 {"POST", "/ric/v1/xapps", m.deployXapp},
49                 {"DELETE", "/ric/v1/xapps/{name}", m.undeployXapp},
50
51                 {"GET", "/ric/v1/subscriptions", m.getSubscriptions},
52                 {"POST", "/ric/v1/subscriptions", m.addSubscription},
53                 {"GET", "/ric/v1/subscriptions/{id}", m.getSubscription},
54                 {"DELETE", "/ric/v1/subscriptions/{id}", m.deleteSubscription},
55                 {"PUT", "/ric/v1/subscriptions/{id}", m.updateSubscription},
56
57                 {"GET", "/ric/v1/config", m.getConfig},
58                 {"POST", "/ric/v1/config", m.createConfig},
59                 {"PUT", "/ric/v1/config", m.updateConfig},
60                 {"DELETE", "/ric/v1/config", m.deleteConfig},
61                 {"DELETE", "/ric/v1/config/{name}", m.deleteSingleConfig},
62         }
63
64         for _, resource := range resources {
65                 handler := LogRestRequests(resource.HandlerFunc)
66                 //handler = m.serviceChecker(handler)
67                 m.router.Methods(resource.Method).Path(resource.Url).Handler(handler)
68         }
69
70         go m.finalize(h)
71 }
72
73 func (m *XappManager) finalize(h Helmer) {
74         m.sd = SubscriptionDispatcher{}
75         m.sd.Initialize()
76
77         m.helm.Initialize()
78
79         m.notifyClients()
80         m.ready = true
81 }
82
83 func (m *XappManager) serviceChecker(inner http.Handler) http.Handler {
84         return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
85                 if r.URL.RequestURI() == "/ric/v1/health/alive" || m.ready == true {
86                         inner.ServeHTTP(w, r)
87                 } else {
88                         respondWithJSON(w, http.StatusServiceUnavailable, nil)
89                 }
90         })
91 }
92
93 // API: XAPP handlers
94 func (m *XappManager) getHealthStatus(w http.ResponseWriter, r *http.Request) {
95         respondWithJSON(w, http.StatusOK, nil)
96 }
97
98 func (m *XappManager) Run() {
99         host := viper.GetString("local.host")
100         if host == "" {
101                 host = ":8080"
102         }
103         log.Printf("Xapp manager started ... serving on %s\n", host)
104
105         log.Fatal(http.ListenAndServe(host, m.router))
106 }
107
108 func (m *XappManager) getXappByName(w http.ResponseWriter, r *http.Request) {
109         xappName, ok := getResourceId(r, w, "name")
110         if ok != true {
111                 return
112         }
113
114         if xapp, err := m.helm.Status(xappName); err == nil {
115                 respondWithJSON(w, http.StatusOK, xapp)
116         } else {
117                 respondWithError(w, http.StatusNotFound, err.Error())
118         }
119 }
120
121 func (m *XappManager) getXappInstanceByName(w http.ResponseWriter, r *http.Request) {
122         xappName, ok := getResourceId(r, w, "name")
123         if ok != true {
124                 return
125         }
126
127         xapp, err := m.helm.Status(xappName)
128         if err != nil {
129                 respondWithError(w, http.StatusNotFound, err.Error())
130                 return
131         }
132
133         xappInstanceName, ok := getResourceId(r, w, "id")
134         if ok != true {
135                 return
136         }
137
138         for _, v := range xapp.Instances {
139                 if v.Name == xappInstanceName {
140                         respondWithJSON(w, http.StatusOK, v)
141                         return
142                 }
143         }
144         Logger.Error("Xapp instance not found - url=%s", r.URL.RequestURI())
145
146         respondWithError(w, http.StatusNotFound, "Xapp instance not found")
147 }
148
149 func (m *XappManager) getAllXapps(w http.ResponseWriter, r *http.Request) {
150         xapps, err := m.helm.StatusAll()
151         if err != nil {
152                 respondWithError(w, http.StatusInternalServerError, err.Error())
153                 return
154         }
155
156         respondWithJSON(w, http.StatusOK, xapps)
157 }
158
159 func (m *XappManager) searchAllXapps(w http.ResponseWriter, r *http.Request) {
160         respondWithJSON(w, http.StatusOK, m.helm.SearchAll())
161 }
162
163 func (m *XappManager) deployXapp(w http.ResponseWriter, r *http.Request) {
164         if r.Body == nil {
165                 Logger.Error("No xapp data found in request body - url=%s", r.URL.RequestURI())
166                 respondWithError(w, http.StatusMethodNotAllowed, "No xapp data!")
167                 return
168         }
169
170         var cm XappDeploy
171         if err := json.NewDecoder(r.Body).Decode(&cm); err != nil {
172                 Logger.Error("Invalid xapp data in request body - url=%s", r.URL.RequestURI())
173                 respondWithError(w, http.StatusMethodNotAllowed, "Invalid xapp data!")
174                 return
175         }
176         defer r.Body.Close()
177
178         xapp, err := m.helm.Install(cm)
179         if err != nil {
180                 respondWithError(w, http.StatusInternalServerError, err.Error())
181                 return
182         }
183
184         respondWithJSON(w, http.StatusCreated, xapp)
185
186         m.sd.Publish(xapp, EventType("created"))
187 }
188
189 func (m *XappManager) undeployXapp(w http.ResponseWriter, r *http.Request) {
190         xappName, ok := getResourceId(r, w, "name")
191         if ok != true {
192                 return
193         }
194
195         xapp, err := m.helm.Delete(xappName)
196         if err != nil {
197                 respondWithError(w, http.StatusInternalServerError, err.Error())
198                 return
199         }
200
201         respondWithJSON(w, http.StatusNoContent, nil)
202
203         m.sd.Publish(xapp, EventType("deleted"))
204 }
205
206 // API: resthook handlers
207 func (m *XappManager) getSubscriptions(w http.ResponseWriter, r *http.Request) {
208         respondWithJSON(w, http.StatusOK, m.sd.GetAll())
209 }
210
211 func (m *XappManager) getSubscription(w http.ResponseWriter, r *http.Request) {
212         if id, ok := getResourceId(r, w, "id"); ok == true {
213                 if s, ok := m.sd.Get(id); ok {
214                         respondWithJSON(w, http.StatusOK, s)
215                 } else {
216                         Logger.Error("Subscription not found - url=%s", r.URL.RequestURI())
217                         respondWithError(w, http.StatusNotFound, "Subscription not found")
218                 }
219         }
220 }
221
222 func (m *XappManager) deleteSubscription(w http.ResponseWriter, r *http.Request) {
223         if id, ok := getResourceId(r, w, "id"); ok == true {
224                 if _, ok := m.sd.Delete(id); ok {
225                         respondWithJSON(w, http.StatusNoContent, nil)
226                 } else {
227                         Logger.Error("Subscription not found - url=%s", r.URL.RequestURI())
228                         respondWithError(w, http.StatusNotFound, "Subscription not found")
229                 }
230         }
231 }
232
233 func (m *XappManager) addSubscription(w http.ResponseWriter, r *http.Request) {
234         var req SubscriptionReq
235         if r.Body == nil || json.NewDecoder(r.Body).Decode(&req) != nil {
236                 Logger.Error("Invalid request payload - url=%s", r.URL.RequestURI())
237                 respondWithError(w, http.StatusMethodNotAllowed, "Invalid request payload")
238                 return
239         }
240         defer r.Body.Close()
241
242         respondWithJSON(w, http.StatusCreated, m.sd.Add(req))
243 }
244
245 func (m *XappManager) updateSubscription(w http.ResponseWriter, r *http.Request) {
246         if id, ok := getResourceId(r, w, "id"); ok == true {
247                 var req SubscriptionReq
248                 if r.Body == nil || json.NewDecoder(r.Body).Decode(&req) != nil {
249                         Logger.Error("Invalid request payload - url=%s", r.URL.RequestURI())
250                         respondWithError(w, http.StatusMethodNotAllowed, "Invalid request payload")
251                         return
252                 }
253                 defer r.Body.Close()
254
255                 if s, ok := m.sd.Update(id, req); ok {
256                         respondWithJSON(w, http.StatusOK, s)
257                 } else {
258                         Logger.Error("Subscription not found - url=%s", r.URL.RequestURI())
259                         respondWithError(w, http.StatusNotFound, "Subscription not found")
260                 }
261         }
262 }
263
264 func (m *XappManager) notifyClients() {
265         xapps, err := m.helm.StatusAll()
266         if err != nil {
267                 Logger.Info("Couldn't fetch xapps status information: %v", err.Error())
268                 return
269         }
270
271         m.sd.notifyClients(xapps, "updated")
272 }
273
274 func (m *XappManager) getConfig(w http.ResponseWriter, r *http.Request) {
275         cfg := m.cm.UploadConfig()
276         respondWithJSON(w, http.StatusOK, cfg)
277 }
278
279 func (m *XappManager) createConfig(w http.ResponseWriter, r *http.Request) {
280         var c XAppConfig
281         if parseConfig(w, r, &c) != nil {
282                 return
283         }
284
285         if errList, err := m.cm.CreateConfigMap(c); err != nil {
286                 if err.Error() != "Validation failed!" {
287                         respondWithError(w, http.StatusInternalServerError, err.Error())
288                 } else {
289                         respondWithJSON(w, http.StatusUnprocessableEntity, errList)
290                 }
291                 return
292         }
293         respondWithJSON(w, http.StatusCreated, c.Metadata)
294 }
295
296 func (m *XappManager) updateConfig(w http.ResponseWriter, r *http.Request) {
297         var c XAppConfig
298         if parseConfig(w, r, &c) != nil {
299                 return
300         }
301
302         if errList, err := m.cm.UpdateConfigMap(c); err != nil {
303                 if err.Error() != "Validation failed!" {
304                         respondWithError(w, http.StatusInternalServerError, err.Error())
305                 } else {
306                         respondWithJSON(w, http.StatusInternalServerError, errList)
307                 }
308                 return
309         }
310         respondWithJSON(w, http.StatusOK, c.Metadata)
311 }
312
313 func (m *XappManager) deleteSingleConfig(w http.ResponseWriter, r *http.Request) {
314         xappName, ok := getResourceId(r, w, "name")
315         if ok != true {
316                 return
317         }
318
319         md := ConfigMetadata{Name: xappName, Namespace: m.cm.GetNamespace(""), ConfigName: xappName + "-appconfig"}
320         m.delConfig(w, XAppConfig{Metadata: md})
321 }
322
323 func (m *XappManager) deleteConfig(w http.ResponseWriter, r *http.Request) {
324         var c XAppConfig
325         if parseConfig(w, r, &c) != nil {
326                 return
327         }
328
329         m.delConfig(w, c)
330 }
331
332 func (m *XappManager) delConfig(w http.ResponseWriter, c XAppConfig) {
333         if _, err := m.cm.DeleteConfigMap(c); err != nil {
334                 respondWithError(w, http.StatusInternalServerError, err.Error())
335                 return
336         }
337         respondWithJSON(w, http.StatusNoContent, nil)
338 }
339
340 // Helper functions
341 func respondWithError(w http.ResponseWriter, code int, message string) {
342         respondWithJSON(w, code, map[string]string{"error": message})
343 }
344
345 func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) {
346         w.Header().Set("Content-Type", "application/json")
347         w.WriteHeader(code)
348         if payload != nil {
349                 response, _ := json.Marshal(payload)
350                 w.Write(response)
351         }
352 }
353
354 func getResourceId(r *http.Request, w http.ResponseWriter, pattern string) (id string, ok bool) {
355         if id, ok = mux.Vars(r)[pattern]; ok != true {
356                 Logger.Error("Couldn't resolve name/id from the request URL")
357                 respondWithError(w, http.StatusMethodNotAllowed, "Couldn't resolve name/id from the request URL")
358                 return
359         }
360         return
361 }
362
363 func parseConfig(w http.ResponseWriter, r *http.Request, req *XAppConfig) error {
364         if r.Body == nil || json.NewDecoder(r.Body).Decode(&req) != nil {
365                 Logger.Error("Invalid request payload - url=%s", r.URL.RequestURI())
366                 respondWithError(w, http.StatusMethodNotAllowed, "Invalid request payload")
367                 return errors.New("Invalid payload")
368         }
369         defer r.Body.Close()
370
371         return nil
372 }