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