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