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