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