dbdc7b1b4fb436b11c33a8aad350fbb1a2934ab7
[nonrtric/plt/ranpm.git] / https-server / main.go
1 //  ============LICENSE_START===============================================
2 //  Copyright (C) 2023 Nordix Foundation. All rights reserved.
3 //  Copyright (C) 2023-2024 OpenInfra Foundation Europe. All rights reserved.
4 //  ========================================================================
5 //  Licensed under the Apache License, Version 2.0 (the "License");
6 //  you may not use this file except in compliance with the License.
7 //  You may obtain a copy of the License at
8 //
9 //       http://www.apache.org/licenses/LICENSE-2.0
10 //
11 //  Unless required by applicable law or agreed to in writing, software
12 //  distributed under the License is distributed on an "AS IS" BASIS,
13 //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 //  See the License for the specific language governing permissions and
15 //  limitations under the License.
16 //  ============LICENSE_END=================================================
17 //
18
19 package main
20
21 import (
22         "bufio"
23         "bytes"
24         "compress/gzip"
25         "io"
26         "math/rand"
27         "net/http"
28         "os"
29         "runtime"
30         "strconv"
31         "strings"
32         "time"
33
34         log "github.com/sirupsen/logrus"
35
36         "github.com/gorilla/mux"
37
38         "net/http/pprof"
39 )
40
41 //== Constants ==//
42
43 const https_port = 443
44
45 const shared_files = "/files"
46 const template_files = "/template-files"
47
48 const file_template = "pm-template.xml.gz"
49
50 var always_return_file = os.Getenv("ALWAYS_RETURN")
51 var generated_files_start_time = os.Getenv("GENERATED_FILE_START_TIME")
52 var generated_files_timezone = os.Getenv("GENERATED_FILE_TIMEZONE")
53 var random_min_value, min_value_error = strconv.Atoi(os.Getenv("RANDOM_MIN_NUMBER"))
54 var random_max_value, max_value_error = strconv.Atoi(os.Getenv("RANDOM_MAX_NUMBER"))
55
56 var unzipped_template = ""
57
58 // Get static file
59 // Returned file is based on configuration
60 // If env ALWAYS_RETURN points to file under "/files", then that file is returned regardless of requested file
61 // Otherwise the requested file is returned if it exists
62 // If the requested file has a file name prefix of "NONEXISTING",then just 404 is returned
63 func files(w http.ResponseWriter, req *http.Request) {
64         start := time.Now()
65         if req.Method != http.MethodGet {
66                 w.WriteHeader(http.StatusMethodNotAllowed)
67                 return
68         }
69
70         vars := mux.Vars(req)
71
72         if id, ok := vars["fileid"]; ok {
73                 if strings.HasPrefix(id, "NONEXISTING") {
74                         w.WriteHeader(http.StatusNotFound)
75                 }
76                 fn := shared_files + "/" + id
77                 if always_return_file != "" {
78                         fn = always_return_file
79                 }
80                 fileBytes, err := os.ReadFile(fn)
81                 if err != nil {
82                         w.WriteHeader(http.StatusNotFound)
83                         return
84                 }
85                 w.WriteHeader(http.StatusOK)
86                 w.Header().Set("Content-Type", "application/octet-stream")
87                 w.Write(fileBytes)
88
89                 log.Info("File retrieval : ", fn, "as ", id, ", time:", time.Since(start).String())
90                 return
91         }
92
93         w.WriteHeader(http.StatusNotFound)
94 }
95
96 // Unzip data from a reader and push to a writer
97 func gunzipReaderToWriter(w io.Writer, data io.Reader) error {
98         gr, err1 := gzip.NewReader(data)
99
100         if err1 != nil {
101                 return err1
102         }
103         defer gr.Close()
104         data2, err2 := io.ReadAll(gr)
105         if err2 != nil {
106                 return err2
107         }
108         _, err3 := w.Write(data2)
109         if err3 != nil {
110                 return err3
111         }
112         return nil
113 }
114
115 // Zip the contents of a byte buffer and push to a writer
116 func gzipWrite(w io.Writer, data *[]byte) error {
117         gw, err1 := gzip.NewWriterLevel(w, gzip.BestSpeed)
118
119         if err1 != nil {
120                 return err1
121         }
122         defer gw.Close()
123         _, err2 := gw.Write(*data)
124         return err2
125 }
126
127 // Get generated file
128 // Returns a file generated from a template
129 // The requested file shall be according to this fileformat:
130 // A<year><month><day>.<hour><minute><timezone>-<hour><minute><timezone>_<nodename>.xml.gz
131 // Example: A20230220.1400+0100-1415+0100_GNODEBX-332.xml.gz
132 // The date and time shall be equal to or later then the date time configured start time in env GENERATED_FILE_START_TIME
133 // If the requested start time is earlier then the configured starttime, 404 is returned
134 // The returned file has nodename, start and end time set according to the requested file.
135 // In addition, the counter values are set to value representing the number of 15 min perioids since the
136 // configured start time
137 func generatedfiles(w http.ResponseWriter, req *http.Request) {
138         start := time.Now()
139         if req.Method != http.MethodGet {
140                 w.WriteHeader(http.StatusMethodNotAllowed)
141                 return
142         }
143
144         vars := mux.Vars(req)
145
146         if id, ok := vars["fileid"]; ok {
147                 if strings.HasPrefix(id, "NONEXISTING") {
148                         w.WriteHeader(http.StatusNotFound)
149                 }
150                 log.Debug("Request generated file:", id)
151                 timezone := "+0000"
152                 if generated_files_timezone != "" {
153                         timezone = generated_files_timezone
154                         log.Debug("Configured timezone: ", timezone)
155                 } else {
156                         log.Debug("Using default timezone: ", timezone)
157                 }
158
159                 fn := template_files + "/" + file_template
160                 if unzipped_template == "" {
161
162                         var buf3 bytes.Buffer
163                         file, err := os.Open(fn)
164                         if err != nil {
165                                 log.Error("PM template file", file_template, " does not exist")
166                                 w.WriteHeader(http.StatusInternalServerError)
167                                 return
168                         }
169                         errb := gunzipReaderToWriter(&buf3, bufio.NewReader(file))
170                         if errb != nil {
171                                 log.Error("Cannot gunzip file ", file_template, " - ", errb)
172                                 return
173                         }
174
175                         unzipped_template = string(buf3.Bytes())
176
177                 }
178
179                 //Parse file start date/time
180                 //Example: 20230220.1300
181
182                 layout := "20060102.1504"
183                 tref, err := time.Parse(layout, generated_files_start_time)
184                 if err != nil {
185                         log.Error("Env var GENERATED_FILE_START_TIME cannot be parsed")
186                         w.WriteHeader(http.StatusInternalServerError)
187                         return
188                 }
189
190                 //Parse file name start date/time
191
192                 ts := id[1:14]
193                 tcur, err := time.Parse(layout, ts)
194                 if err != nil {
195                         log.Error("File start date/time cannot be parsed: ", ts)
196                         w.WriteHeader(http.StatusInternalServerError)
197                         return
198                 }
199
200                 // Calculate file index based of reference time
201                 file_index := (tcur.Unix() - tref.Unix()) / 900
202                 if file_index < 0 {
203                         log.Error("File start date/time before value of env var GENERATED_FILE_START_TIME :", generated_files_start_time)
204                         w.WriteHeader(http.StatusInternalServerError)
205                         return
206                 }
207
208                 //begintime/endtime format: 2022-04-18T19:00:00+00:00
209                 begintime := tcur.Format("2006-01-02T15:04") + ":00" + timezone
210                 d := time.Duration(900 * time.Second)
211                 tend := tcur.Add(d)
212                 endtime := tend.Format("2006-01-02T15:04") + ":00" + timezone
213
214                 //Extract nodename
215                 nodename := id[30:]
216                 nodename = strings.Split(nodename, ".")[0]
217
218                 //Generating random number
219                 random_value := rand.Intn(random_max_value-random_min_value+1) + random_min_value
220
221                 template_string := strings.Clone(unzipped_template)
222
223                 log.Debug("Replacing BEGINTIME with: ", begintime)
224                 log.Debug("Replacing ENDTIME with: ", endtime)
225                 log.Debug("Replacing RANDOM_CTR_VALUE with: ", strconv.Itoa(int(random_value)))
226                 log.Debug("Replacing CTR_VALUE with: ", strconv.Itoa(int(file_index)))
227                 log.Debug("Replacing NODE_NAME with: ", nodename)
228
229                 template_string = strings.Replace(template_string, "BEGINTIME", begintime, -1)
230                 template_string = strings.Replace(template_string, "ENDTIME", endtime, -1)
231
232                 template_string = strings.Replace(template_string, "RANDOM_CTR_VALUE", strconv.Itoa(int(random_value)), -1)
233                 template_string = strings.Replace(template_string, "CTR_VALUE", strconv.Itoa(int(file_index)), -1)
234
235                 template_string = strings.Replace(template_string, "NODE_NAME", nodename, -1)
236
237                 b := []byte(template_string)
238                 var buf bytes.Buffer
239                 err = gzipWrite(&buf, &b)
240                 if err != nil {
241                         log.Error("Cannot gzip file ", id, " - ", err)
242                         return
243                 }
244
245                 w.WriteHeader(http.StatusOK)
246                 w.Header().Set("Content-Type", "application/octet-stream")
247                 w.Write(buf.Bytes())
248
249                 log.Info("File retrieval generated file: ", fn, "as ", id, ", time:", time.Since(start).String())
250                 return
251         }
252
253         w.WriteHeader(http.StatusNotFound)
254 }
255
256 // Simple alive check
257 func alive(w http.ResponseWriter, req *http.Request) {
258         //Alive check
259 }
260
261 // == Main ==//
262 func main() {
263
264         log.SetLevel(log.InfoLevel)
265         //log.SetLevel(log.TraceLevel)
266
267         log.Info("Server starting...")
268
269         if min_value_error != nil {
270                 random_min_value = 1
271         }
272         if max_value_error != nil {
273                 random_max_value = 10
274         }
275
276         rtr := mux.NewRouter()
277         rtr.HandleFunc("/files/{fileid}", files)
278         rtr.HandleFunc("/generatedfiles/{fileid}", generatedfiles)
279         rtr.HandleFunc("/", alive)
280
281         rtr.HandleFunc("/custom_debug_path/profile", pprof.Profile)
282
283         http.Handle("/", rtr)
284
285         // Run https
286         log.Info("Starting https service...")
287         err := http.ListenAndServeTLS(":"+strconv.Itoa(https_port), "certs/server.crt", "certs/server.key", nil)
288         if err != nil {
289                 log.Fatal("Cannot setup listener on https: ", err)
290         }
291
292         //Wait until all go routines has exited
293         runtime.Goexit()
294
295         log.Warn("main routine exit")
296         log.Warn("server i stopping...")
297 }