blob: 406caf8e54cb7185d003a85423dfdfb16e1512b7 [file] [log] [blame]
// ============LICENSE_START===============================================
// Copyright (C) 2023 Nordix Foundation. All rights reserved.
// ========================================================================
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ============LICENSE_END=================================================
//
package main
import (
"bufio"
"bytes"
"compress/gzip"
"io"
"net/http"
"os"
"runtime"
"strconv"
"strings"
"time"
log "github.com/sirupsen/logrus"
"github.com/gorilla/mux"
"net/http/pprof"
)
//== Constants ==//
const https_port = 443
const shared_files = "/files"
const template_files = "/template-files"
const file_template = "pm-template.xml.gz"
var always_return_file = os.Getenv("ALWAYS_RETURN")
var generated_files_start_time = os.Getenv("GENERATED_FILE_START_TIME")
var generated_files_timezone = os.Getenv("GENERATED_FILE_TIMEZONE")
var unzipped_template = ""
// Get static file
// Returned file is based on configuration
// If env ALWAYS_RETURN points to file under "/files", then that file is returned regardless of requested file
// Otherwise the requested file is returned if it exists
// If the requested file has a file name prefix of "NONEXISTING",then just 404 is returned
func files(w http.ResponseWriter, req *http.Request) {
start := time.Now()
if req.Method != http.MethodGet {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
vars := mux.Vars(req)
if id, ok := vars["fileid"]; ok {
if strings.HasPrefix(id, "NONEXISTING") {
w.WriteHeader(http.StatusNotFound)
}
fn := shared_files + "/" + id
if always_return_file != "" {
fn = always_return_file
}
fileBytes, err := os.ReadFile(fn)
if err != nil {
w.WriteHeader(http.StatusNotFound)
return
}
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/octet-stream")
w.Write(fileBytes)
log.Info("File retrieval : ", fn, "as ", id, ", time:", time.Since(start).String())
return
}
w.WriteHeader(http.StatusNotFound)
}
// Unzip data from a reader and push to a writer
func gunzipReaderToWriter(w io.Writer, data io.Reader) error {
gr, err1 := gzip.NewReader(data)
if err1 != nil {
return err1
}
defer gr.Close()
data2, err2 := io.ReadAll(gr)
if err2 != nil {
return err2
}
_, err3 := w.Write(data2)
if err3 != nil {
return err3
}
return nil
}
// Zip the contents of a byte buffer and push to a writer
func gzipWrite(w io.Writer, data *[]byte) error {
gw, err1 := gzip.NewWriterLevel(w, gzip.BestSpeed)
if err1 != nil {
return err1
}
defer gw.Close()
_, err2 := gw.Write(*data)
return err2
}
// Get generated file
// Returns a file generated from a template
// The requested file shall be according to this fileformat:
// A<year><month><day>.<hour><minute><timezone>-<hour><minute><timezone>_<nodename>.xml.gz
// Example: A20230220.1400+0100-1415+0100_GNODEBX-332.xml.gz
// The date and time shall be equal to or later then the date time configured start time in env GENERATED_FILE_START_TIME
// If the requested start time is earlier then the configured starttime, 404 is returned
// The returned file has nodename, start and end time set according to the requested file.
// In addition, the counter values are set to value representing the number of 15 min perioids since the
// configured start time
func generatedfiles(w http.ResponseWriter, req *http.Request) {
start := time.Now()
if req.Method != http.MethodGet {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
vars := mux.Vars(req)
if id, ok := vars["fileid"]; ok {
if strings.HasPrefix(id, "NONEXISTING") {
w.WriteHeader(http.StatusNotFound)
}
log.Debug("Request generated file:", id)
timezone := "+0000"
if generated_files_timezone != "" {
timezone = generated_files_timezone
log.Debug("Configured timezone: ", timezone)
} else {
log.Debug("Using default timezone: ", timezone)
}
fn := template_files + "/" + file_template
if unzipped_template == "" {
var buf3 bytes.Buffer
file, err := os.Open(fn)
if err != nil {
log.Error("PM template file", file_template, " does not exist")
w.WriteHeader(http.StatusInternalServerError)
return
}
errb := gunzipReaderToWriter(&buf3, bufio.NewReader(file))
if errb != nil {
log.Error("Cannot gunzip file ", file_template, " - ", errb)
return
}
unzipped_template = string(buf3.Bytes())
}
//Parse file start date/time
//Example: 20230220.1300
layout := "20060102.1504"
tref, err := time.Parse(layout, generated_files_start_time)
if err != nil {
log.Error("Env var GENERATED_FILE_START_TIME cannot be parsed")
w.WriteHeader(http.StatusInternalServerError)
return
}
//Parse file name start date/time
ts := id[1:14]
tcur, err := time.Parse(layout, ts)
if err != nil {
log.Error("File start date/time cannot be parsed: ", ts)
w.WriteHeader(http.StatusInternalServerError)
return
}
// Calculate file index based of reference time
file_index := (tcur.Unix() - tref.Unix()) / 900
if file_index < 0 {
log.Error("File start date/time before value of env var GENERATED_FILE_START_TIME :", generated_files_start_time)
w.WriteHeader(http.StatusInternalServerError)
return
}
//begintime/endtime format: 2022-04-18T19:00:00+00:00
begintime := tcur.Format("2006-01-02T15:04") + ":00" + timezone
d := time.Duration(900 * time.Second)
tend := tcur.Add(d)
endtime := tend.Format("2006-01-02T15:04") + ":00" + timezone
//Extract nodename
nodename := id[30:]
nodename = strings.Split(nodename, ".")[0]
template_string := strings.Clone(unzipped_template)
log.Debug("Replacing BEGINTIME with: ", begintime)
log.Debug("Replacing ENDTIME with: ", endtime)
log.Debug("Replacing CTR_VALUE with: ", strconv.Itoa(int(file_index)))
log.Debug("Replacing NODE_NAME with: ", nodename)
template_string = strings.Replace(template_string, "BEGINTIME", begintime, -1)
template_string = strings.Replace(template_string, "ENDTIME", endtime, -1)
template_string = strings.Replace(template_string, "CTR_VALUE", strconv.Itoa(int(file_index)), -1)
template_string = strings.Replace(template_string, "NODE_NAME", nodename, -1)
b := []byte(template_string)
var buf bytes.Buffer
err = gzipWrite(&buf, &b)
if err != nil {
log.Error("Cannot gzip file ", id, " - ", err)
return
}
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/octet-stream")
w.Write(buf.Bytes())
log.Info("File retrieval generated file: ", fn, "as ", id, ", time:", time.Since(start).String())
return
}
w.WriteHeader(http.StatusNotFound)
}
// Simple alive check
func alive(w http.ResponseWriter, req *http.Request) {
//Alive check
}
// == Main ==//
func main() {
log.SetLevel(log.InfoLevel)
//log.SetLevel(log.TraceLevel)
log.Info("Server starting...")
rtr := mux.NewRouter()
rtr.HandleFunc("/files/{fileid}", files)
rtr.HandleFunc("/generatedfiles/{fileid}", generatedfiles)
rtr.HandleFunc("/", alive)
rtr.HandleFunc("/custom_debug_path/profile", pprof.Profile)
http.Handle("/", rtr)
// Run https
log.Info("Starting https service...")
err := http.ListenAndServeTLS(":"+strconv.Itoa(https_port), "certs/server.crt", "certs/server.key", nil)
if err != nil {
log.Fatal("Cannot setup listener on https: ", err)
}
//Wait until all go routines has exited
runtime.Goexit()
log.Warn("main routine exit")
log.Warn("server i stopping...")
}