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