xapp-frame go imrpovements
[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         // Read config-files
320         cfiles := []string{viper.ConfigFileUsed(), "/opt/ric/config/config-file.json"}
321
322         var err error
323         var configFile *os.File
324         for _, cfile := range cfiles {
325                 configFile, err = os.Open(cfile)
326                 if err != nil {
327                         configFile = nil
328                         Logger.Error("Cannot open config file: %s err: %v", cfile, err)
329                 }
330         }
331         if err != nil || configFile == nil {
332                 Logger.Error("Cannot open any of listed config files: %v", cfiles)
333                 respondWithJSON(w, http.StatusInternalServerError, nil)
334                 return
335         }
336
337         body, err := ioutil.ReadAll(configFile)
338         defer configFile.Close()
339
340         xappconfig.Metadata = &metadata
341         xappconfig.Config = string(body)
342
343         appconfig = append(appconfig, &xappconfig)
344
345         respondWithJSON(w, http.StatusOK, appconfig)
346 }