Add some rmr library stats into metrics
[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(); 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() (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 //Resource.InjectRoute(url, metricsHandler, "GET")
255 //func metricsHandler(w http.ResponseWriter, r *http.Request) {
256 //      w.Header().Set("Content-Type", "text/plain")
257 //      w.WriteHeader(http.StatusOK)
258 //      metrics, _ := Resource.GetLocalMetrics()
259 //      w.Write([]byte(metrics))
260 //}
261
262 func IsHealthProbeReady() bool {
263         return healthReady
264 }
265
266 func readyHandler(w http.ResponseWriter, r *http.Request) {
267         healthReady = true
268         respondWithJSON(w, http.StatusOK, nil)
269 }
270
271 func aliveHandler(w http.ResponseWriter, r *http.Request) {
272         respondWithJSON(w, http.StatusOK, nil)
273 }
274
275 func configHandler(w http.ResponseWriter, r *http.Request) {
276         xappName := mux.Vars(r)["name"]
277         if xappName == "" || r.Body == nil {
278                 respondWithJSON(w, http.StatusBadRequest, nil)
279                 return
280         }
281         defer r.Body.Close()
282
283         body, err := ioutil.ReadAll(r.Body)
284         if err != nil {
285                 Logger.Error("ioutil.ReadAll failed: %v", err)
286                 respondWithJSON(w, http.StatusInternalServerError, nil)
287                 return
288         }
289
290         if err := PublishConfigChange(xappName, string(body)); err != nil {
291                 respondWithJSON(w, http.StatusInternalServerError, nil)
292                 return
293         }
294
295         respondWithJSON(w, http.StatusOK, nil)
296 }
297
298 func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) {
299         w.Header().Set("Content-Type", "application/json")
300         w.WriteHeader(code)
301         if payload != nil {
302                 response, _ := json.Marshal(payload)
303                 w.Write(response)
304         }
305 }
306
307 func appconfigHandler(w http.ResponseWriter, r *http.Request) {
308
309         Logger.Info("Inside appconfigHandler")
310
311         var appconfig models.XappConfigList
312         var metadata models.ConfigMetadata
313         var xappconfig models.XAppConfig
314         name := viper.GetString("name")
315         configtype := "json"
316         metadata.XappName = &name
317         metadata.ConfigType = &configtype
318
319         configFile, err := os.Open("/opt/ric/config/config-file.json")
320         if err != nil {
321                 Logger.Error("Cannot open config file: %v", err)
322                 respondWithJSON(w, http.StatusInternalServerError, nil)
323                 // return nil,errors.New("Could Not parse the config file")
324         }
325
326         body, err := ioutil.ReadAll(configFile)
327
328         defer configFile.Close()
329
330         xappconfig.Metadata = &metadata
331         xappconfig.Config = string(body)
332
333         appconfig = append(appconfig, &xappconfig)
334
335         respondWithJSON(w, http.StatusOK, appconfig)
336
337         //return appconfig,nil
338 }