2 ==================================================================================
3 Copyright (c) 2019 AT&T Intellectual Property.
4 Copyright (c) 2019 Nokia
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
10 http://www.apache.org/licenses/LICENSE-2.0
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 ==================================================================================
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"
41 ReadyURL = "/ric/v1/health/ready"
42 AliveURL = "/ric/v1/health/alive"
43 ConfigURL = "/ric/v1/cm/{name}"
44 AppConfigURL = "/ric/v1/config"
51 type StatusCb func() bool
58 func NewRouter() *Router {
60 router: mux.NewRouter().StrictSlash(true),
61 cbMap: make([]StatusCb, 0),
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")
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)
79 respondWithJSON(w, http.StatusServiceUnavailable, nil)
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)
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...)
92 func (r *Router) InjectRoutePrefix(prefix string, handler http.HandlerFunc) *mux.Route {
93 return r.router.PathPrefix(prefix).HandlerFunc(r.serviceChecker(handler))
96 func (r *Router) InjectStatusCb(f StatusCb) {
97 r.cbMap = append(r.cbMap, f)
100 func (r *Router) CheckStatus() (status bool) {
101 if len(r.cbMap) == 0 {
105 for _, f := range r.cbMap {
111 func (r *Router) GetSymptomDataParams(w http.ResponseWriter, req *http.Request) SymptomDataParams {
112 params := SymptomDataParams{}
113 queryParams := req.URL.Query()
115 Logger.Info("GetSymptomDataParams: %+v", queryParams)
117 for p := range queryParams {
119 fmt.Sscanf(p, "%d", ¶ms.Timeout)
122 fmt.Sscanf(p, "%d", ¶ms.FromTime)
125 fmt.Sscanf(p, "%d", ¶ms.ToTime)
131 func (r *Router) CollectDefaultSymptomData(fileName string, data interface{}) string {
132 baseDir := Config.GetString("controls.symptomdata.baseDir")
134 baseDir = "/tmp/xapp/"
137 if err := Util.CreateDir(baseDir); err != nil {
138 Logger.Error("CreateDir failed: %v", err)
143 // Collect some general information into one file
154 lines = append(lines, fmt.Sprintf("uptime: %02d:%02d:%02d", h, m, s))
156 Util.WriteToFile(baseDir+"information.txt", strings.Join(lines, "\n"))
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)
168 // Collect currently used config file
170 cfile := viper.ConfigFileUsed()
171 input, err := ioutil.ReadFile(cfile)
173 Util.WriteToFile(baseDir+path.Base(cfile), string(input))
175 Logger.Error("ioutil.ReadFile failed: %v", err)
179 // Collect environment
181 Util.WriteToFile(baseDir+"environment.txt", strings.Join(os.Environ(), "\n"))
184 // Collect RMR rt table
186 rtPath := os.Getenv("RMR_STASH_RT")
188 input, err = ioutil.ReadFile(rtPath)
190 Util.WriteToFile(baseDir+"rttable.txt", string(input))
192 Logger.Error("ioutil.ReadFile failed: %v", err)
197 // Put data that was provided as argument
200 if b, err := json.MarshalIndent(data, "", " "); err == nil {
201 Util.WriteToFile(baseDir+fileName, string(b))
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)
213 response, _ := json.MarshalIndent(data, "", " ")
218 func (r *Router) SendSymptomDataFile(w http.ResponseWriter, req *http.Request, baseDir, zipFile string) {
220 var fileList []string
221 fileList = Util.FetchFiles(baseDir, fileList)
222 tmpFileName, err := Util.ZipFilesToTmpFile(baseDir, "symptom", fileList)
224 r.SendSymptomDataError(w, req, err.Error())
227 defer os.Remove(tmpFileName)
229 w.Header().Set("Content-Disposition", "attachment; filename="+zipFile)
230 http.ServeFile(w, req, tmpFileName)
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)
238 func (r *Router) GetLocalMetrics() (string, error) {
239 buf := &bytes.Buffer{}
240 enc := expfmt.NewEncoder(buf, expfmt.FmtText)
241 vals, err := prometheus.DefaultGatherer.Gather()
243 return fmt.Sprintf("#metrics get error: %s\n", err.Error()), fmt.Errorf("Could get local metrics %w", err)
245 for _, val := range vals {
246 err = enc.Encode(val)
248 buf.WriteString(fmt.Sprintf("#metrics enc err:%s\n", err.Error()))
251 return string(buf.Bytes()), nil
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))
262 func IsHealthProbeReady() bool {
266 func readyHandler(w http.ResponseWriter, r *http.Request) {
268 respondWithJSON(w, http.StatusOK, nil)
271 func aliveHandler(w http.ResponseWriter, r *http.Request) {
272 respondWithJSON(w, http.StatusOK, nil)
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)
283 body, err := ioutil.ReadAll(r.Body)
285 Logger.Error("ioutil.ReadAll failed: %v", err)
286 respondWithJSON(w, http.StatusInternalServerError, nil)
290 if err := PublishConfigChange(xappName, string(body)); err != nil {
291 respondWithJSON(w, http.StatusInternalServerError, nil)
295 respondWithJSON(w, http.StatusOK, nil)
298 func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) {
299 w.Header().Set("Content-Type", "application/json")
302 response, _ := json.Marshal(payload)
307 func appconfigHandler(w http.ResponseWriter, r *http.Request) {
309 Logger.Info("Inside appconfigHandler")
311 var appconfig models.XappConfigList
312 var metadata models.ConfigMetadata
313 var xappconfig models.XAppConfig
314 name := viper.GetString("name")
316 metadata.XappName = &name
317 metadata.ConfigType = &configtype
320 cfiles := []string{viper.ConfigFileUsed(), "/opt/ric/config/config-file.json"}
323 var configFile *os.File
324 for _, cfile := range cfiles {
325 configFile, err = os.Open(cfile)
328 Logger.Error("Cannot open config file: %s err: %v", cfile, err)
331 if err != nil || configFile == nil {
332 Logger.Error("Cannot open any of listed config files: %v", cfiles)
333 respondWithJSON(w, http.StatusInternalServerError, nil)
337 body, err := ioutil.ReadAll(configFile)
338 defer configFile.Close()
340 xappconfig.Metadata = &metadata
341 xappconfig.Config = string(body)
343 appconfig = append(appconfig, &xappconfig)
345 respondWithJSON(w, http.StatusOK, appconfig)