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