Enhancements of REST-based E2 subscription interface
[ric-plt/xapp-frame.git] / pkg / xapp / restapi.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 xapp
21
22 import (
23         "encoding/json"
24         "fmt"
25         "github.com/gorilla/mux"
26         "github.com/spf13/viper"
27         "io/ioutil"
28         "net/http"
29         "os"
30
31         "gerrit.o-ran-sc.org/r/ric-plt/xapp-frame/pkg/models"
32 )
33
34 const (
35         ReadyURL     = "/ric/v1/health/ready"
36         AliveURL     = "/ric/v1/health/alive"
37         ConfigURL    = "/ric/v1/cm/{name}"
38         AppConfigURL = "/ric/v1/config"
39 )
40
41 var (
42         healthReady bool
43 )
44
45 type StatusCb func() bool
46
47 type Router struct {
48         router *mux.Router
49         cbMap  []StatusCb
50 }
51
52 func NewRouter() *Router {
53         r := &Router{
54                 router: mux.NewRouter().StrictSlash(true),
55                 cbMap:  make([]StatusCb, 0),
56         }
57
58         // Inject default routes for health probes
59         r.InjectRoute(ReadyURL, readyHandler, "GET")
60         r.InjectRoute(AliveURL, aliveHandler, "GET")
61         r.InjectRoute(ConfigURL, configHandler, "POST")
62         r.InjectRoute(AppConfigURL, appconfigHandler, "GET")
63
64         return r
65 }
66
67 func (r *Router) serviceChecker(inner http.HandlerFunc) http.HandlerFunc {
68         return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
69                 Logger.Info("restapi: method=%s url=%s", req.Method, req.URL.RequestURI())
70                 if req.URL.RequestURI() == AliveURL || r.CheckStatus() {
71                         inner.ServeHTTP(w, req)
72                 } else {
73                         respondWithJSON(w, http.StatusServiceUnavailable, nil)
74                 }
75         })
76 }
77
78 func (r *Router) InjectRoute(url string, handler http.HandlerFunc, method string) *mux.Route {
79         return r.router.Path(url).HandlerFunc(r.serviceChecker(handler)).Methods(method)
80 }
81
82 func (r *Router) InjectQueryRoute(url string, h http.HandlerFunc, m string, q ...string) *mux.Route {
83         return r.router.Path(url).HandlerFunc(r.serviceChecker(h)).Methods(m).Queries(q...)
84 }
85
86 func (r *Router) InjectRoutePrefix(prefix string, handler http.HandlerFunc) *mux.Route {
87         return r.router.PathPrefix(prefix).HandlerFunc(r.serviceChecker(handler))
88 }
89
90 func (r *Router) InjectStatusCb(f StatusCb) {
91         r.cbMap = append(r.cbMap, f)
92 }
93
94 func (r *Router) CheckStatus() (status bool) {
95         if len(r.cbMap) == 0 {
96                 return true
97         }
98
99         for _, f := range r.cbMap {
100                 status = f()
101         }
102         return
103 }
104
105 func (r *Router) GetSymptomDataParams(w http.ResponseWriter, req *http.Request) SymptomDataParams {
106         params := SymptomDataParams{}
107         queryParams := req.URL.Query()
108
109         for p := range queryParams {
110                 if p == "timeout" {
111                         fmt.Sscanf(p, "%d", &params.Timeout)
112                 }
113                 if p == "fromtime" {
114                         fmt.Sscanf(p, "%d", &params.FromTime)
115                 }
116                 if p == "totime" {
117                         fmt.Sscanf(p, "%d", &params.ToTime)
118                 }
119         }
120         return params
121 }
122
123 func (r *Router) CollectDefaultSymptomData(fileName string, data interface{}) string {
124         baseDir := Config.GetString("controls.symptomdata.baseDir")
125         if baseDir == "" {
126                 baseDir = "/tmp/xapp/"
127         }
128
129         if err := Util.CreateDir(baseDir); err != nil {
130                 Logger.Error("CreateDir failed: %v", err)
131                 return ""
132         }
133
134         if metrics, err := r.GetLocalMetrics(GetPortData("http").Port); err == nil {
135                 if err := Util.WriteToFile(baseDir+"metrics.json", metrics); err != nil {
136                         Logger.Error("writeToFile failed for metrics.json: %v", err)
137                 }
138         }
139
140         if data != nil {
141                 if b, err := json.MarshalIndent(data, "", "  "); err == nil {
142                         Util.WriteToFile(baseDir+fileName, string(b))
143                 }
144         }
145
146         rtPath := os.Getenv("RMR_STASH_RT")
147         if rtPath == "" {
148                 return baseDir
149         }
150
151         input, err := ioutil.ReadFile(rtPath)
152         if err != nil {
153                 Logger.Error("ioutil.ReadFile failed: %v", err)
154                 return baseDir
155         }
156
157         Util.WriteToFile(baseDir+"rttable.txt", string(input))
158         return baseDir
159 }
160
161 func (r *Router) SendSymptomDataJson(w http.ResponseWriter, req *http.Request, data interface{}, n string) {
162         w.Header().Set("Content-Type", "application/json")
163         w.Header().Set("Content-Disposition", "attachment; filename="+n)
164         w.WriteHeader(http.StatusOK)
165         if data != nil {
166                 response, _ := json.MarshalIndent(data, "", " ")
167                 w.Write(response)
168         }
169 }
170
171 func (r *Router) SendSymptomDataFile(w http.ResponseWriter, req *http.Request, baseDir, zipFile string) {
172         // Compress and reply with attachment
173         tmpFile, err := ioutil.TempFile("", "symptom")
174         if err != nil {
175                 r.SendSymptomDataError(w, req, "Failed to create a tmp file: "+err.Error())
176                 return
177         }
178         defer os.Remove(tmpFile.Name())
179
180         var fileList []string
181         fileList = Util.FetchFiles(baseDir, fileList)
182         err = Util.ZipFiles(tmpFile, baseDir, fileList)
183         if err != nil {
184                 r.SendSymptomDataError(w, req, "Failed to zip the files: "+err.Error())
185                 return
186         }
187
188         w.Header().Set("Content-Disposition", "attachment; filename="+zipFile)
189         http.ServeFile(w, req, tmpFile.Name())
190 }
191
192 func (r *Router) SendSymptomDataError(w http.ResponseWriter, req *http.Request, message string) {
193         w.Header().Set("Content-Disposition", "attachment; filename=error_status.txt")
194         http.Error(w, message, http.StatusInternalServerError)
195 }
196
197 func (r *Router) GetLocalMetrics(port int) (string, error) {
198         resp, err := http.Get(fmt.Sprintf("http://localhost:%d/ric/v1/metrics", port))
199         if err != nil {
200                 return "", err
201         }
202         defer resp.Body.Close()
203
204         metrics, err := ioutil.ReadAll(resp.Body)
205         if err != nil {
206                 return "", err
207         }
208
209         return string(metrics), nil
210 }
211
212 func IsHealthProbeReady() bool {
213         return healthReady
214 }
215
216 func readyHandler(w http.ResponseWriter, r *http.Request) {
217         healthReady = true
218         respondWithJSON(w, http.StatusOK, nil)
219 }
220
221 func aliveHandler(w http.ResponseWriter, r *http.Request) {
222         respondWithJSON(w, http.StatusOK, nil)
223 }
224
225 func configHandler(w http.ResponseWriter, r *http.Request) {
226         xappName := mux.Vars(r)["name"]
227         if xappName == "" || r.Body == nil {
228                 respondWithJSON(w, http.StatusBadRequest, nil)
229                 return
230         }
231         defer r.Body.Close()
232
233         body, err := ioutil.ReadAll(r.Body)
234         if err != nil {
235                 Logger.Error("ioutil.ReadAll failed: %v", err)
236                 respondWithJSON(w, http.StatusInternalServerError, nil)
237                 return
238         }
239
240         if err := PublishConfigChange(xappName, string(body)); err != nil {
241                 respondWithJSON(w, http.StatusInternalServerError, nil)
242                 return
243         }
244
245         respondWithJSON(w, http.StatusOK, nil)
246 }
247
248 func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) {
249         w.Header().Set("Content-Type", "application/json")
250         w.WriteHeader(code)
251         if payload != nil {
252                 response, _ := json.Marshal(payload)
253                 w.Write(response)
254         }
255 }
256
257 func appconfigHandler(w http.ResponseWriter, r *http.Request) {
258
259         Logger.Info("Inside appconfigHandler")
260
261         var appconfig models.XappConfigList
262         var metadata models.ConfigMetadata
263         var xappconfig models.XAppConfig
264         name := viper.GetString("name")
265         configtype := "json"
266         metadata.XappName = &name
267         metadata.ConfigType = &configtype
268
269         configFile, err := os.Open("/opt/ric/config/config-file.json")
270         if err != nil {
271                 Logger.Error("Cannot open config file: %v", err)
272                 respondWithJSON(w, http.StatusInternalServerError, nil)
273                 // return nil,errors.New("Could Not parse the config file")
274         }
275
276         body, err := ioutil.ReadAll(configFile)
277
278         defer configFile.Close()
279
280         xappconfig.Metadata = &metadata
281         xappconfig.Config = string(body)
282
283         appconfig = append(appconfig, &xappconfig)
284
285         respondWithJSON(w, http.StatusOK, appconfig)
286
287         //return appconfig,nil
288 }