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