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