Cleanup and restructure

This commit is contained in:
Jan Eitzinger 2022-02-03 11:35:42 +01:00
parent 4cccbb20d8
commit ad705f1424
3 changed files with 191 additions and 323 deletions

View File

@ -2,10 +2,13 @@ package main
import (
"net/http"
"net/url"
"strconv"
"strings"
"github.com/ClusterCockpit/cc-backend/auth"
"github.com/ClusterCockpit/cc-backend/config"
"github.com/ClusterCockpit/cc-backend/schema"
"github.com/ClusterCockpit/cc-backend/templates"
"github.com/gorilla/mux"
)
@ -20,10 +23,59 @@ type Route struct {
Setup func(i InfoType, r *http.Request) InfoType
}
func buildFilterPresets(query url.Values) map[string]interface{} {
filterPresets := map[string]interface{}{}
if query.Get("cluster") != "" {
filterPresets["cluster"] = query.Get("cluster")
}
if query.Get("partition") != "" {
filterPresets["partition"] = query.Get("partition")
}
if query.Get("project") != "" {
filterPresets["project"] = query.Get("project")
filterPresets["projectMatch"] = "eq"
}
if query.Get("state") != "" && schema.JobState(query.Get("state")).Valid() {
filterPresets["state"] = query.Get("state")
}
if rawtags, ok := query["tag"]; ok {
tags := make([]int, len(rawtags))
for i, tid := range rawtags {
var err error
tags[i], err = strconv.Atoi(tid)
if err != nil {
tags[i] = -1
}
}
filterPresets["tags"] = tags
}
if query.Get("numNodes") != "" {
parts := strings.Split(query.Get("numNodes"), "-")
if len(parts) == 2 {
a, e1 := strconv.Atoi(parts[0])
b, e2 := strconv.Atoi(parts[1])
if e1 == nil && e2 == nil {
filterPresets["numNodes"] = map[string]int{"from": a, "to": b}
}
}
}
if query.Get("jobId") != "" {
filterPresets["jobId"] = query.Get("jobId")
}
if query.Get("arrayJobId") != "" {
if num, err := strconv.Atoi(query.Get("arrayJobId")); err == nil {
filterPresets["arrayJobId"] = num
}
}
return filterPresets
}
func setupRoutes(router *mux.Router, routes []Route) {
for _, route := range routes {
_route := route
router.HandleFunc(_route.Route, func(rw http.ResponseWriter, r *http.Request) {
route := route
router.HandleFunc(route.Route, func(rw http.ResponseWriter, r *http.Request) {
conf, err := config.GetUIConfig(r)
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
@ -42,17 +94,22 @@ func setupRoutes(router *mux.Router, routes []Route) {
infos["admin"] = false
}
infos = _route.Setup(infos, r)
infos = route.Setup(infos, r)
if id, ok := infos["id"]; ok {
_route.Title = strings.Replace(_route.Title, "<ID>", id.(string), 1)
route.Title = strings.Replace(route.Title, "<ID>", id.(string), 1)
}
templates.Render(rw, r, _route.Template, &templates.Page{
Title: _route.Title,
page := templates.Page{
Title: route.Title,
Config: conf,
Infos: infos,
FilterPresets: buildFilterPresets(r.URL.Query()),
})
}
if route.Filter {
page.FilterPresets = buildFilterPresets(r.URL.Query())
}
templates.Render(rw, r, route.Template, &page)
})
}
}

124
runtimeSetup.go Normal file
View File

@ -0,0 +1,124 @@
package main
import (
"bufio"
"errors"
"fmt"
"os"
"os/exec"
"os/user"
"strconv"
"strings"
"syscall"
)
func loadEnv(file string) error {
f, err := os.Open(file)
if err != nil {
return err
}
defer f.Close()
s := bufio.NewScanner(bufio.NewReader(f))
for s.Scan() {
line := s.Text()
if strings.HasPrefix(line, "#") || len(line) == 0 {
continue
}
if strings.Contains(line, "#") {
return errors.New("'#' are only supported at the start of a line")
}
line = strings.TrimPrefix(line, "export ")
parts := strings.SplitN(line, "=", 2)
if len(parts) != 2 {
return fmt.Errorf("unsupported line: %#v", line)
}
key := strings.TrimSpace(parts[0])
val := strings.TrimSpace(parts[1])
if strings.HasPrefix(val, "\"") {
if !strings.HasSuffix(val, "\"") {
return fmt.Errorf("unsupported line: %#v", line)
}
runes := []rune(val[1 : len(val)-1])
sb := strings.Builder{}
for i := 0; i < len(runes); i++ {
if runes[i] == '\\' {
i++
switch runes[i] {
case 'n':
sb.WriteRune('\n')
case 'r':
sb.WriteRune('\r')
case 't':
sb.WriteRune('\t')
case '"':
sb.WriteRune('"')
default:
return fmt.Errorf("unsupprorted escape sequence in quoted string: backslash %#v", runes[i])
}
continue
}
sb.WriteRune(runes[i])
}
val = sb.String()
}
os.Setenv(key, val)
}
return s.Err()
}
func dropPrivileges() error {
if programConfig.Group != "" {
g, err := user.LookupGroup(programConfig.Group)
if err != nil {
return err
}
gid, _ := strconv.Atoi(g.Gid)
if err := syscall.Setgid(gid); err != nil {
return err
}
}
if programConfig.User != "" {
u, err := user.Lookup(programConfig.User)
if err != nil {
return err
}
uid, _ := strconv.Atoi(u.Uid)
if err := syscall.Setuid(uid); err != nil {
return err
}
}
return nil
}
// If started via systemd, inform systemd that we are running:
// https://www.freedesktop.org/software/systemd/man/sd_notify.html
func systemdNotifiy(ready bool, status string) {
if os.Getenv("NOTIFY_SOCKET") == "" {
// Not started using systemd
return
}
args := []string{fmt.Sprintf("--pid=%d", os.Getpid())}
if ready {
args = append(args, "--ready")
}
if status != "" {
args = append(args, fmt.Sprintf("--status=%s", status))
}
cmd := exec.Command("systemd-notify", args...)
cmd.Run() // errors ignored on purpose, there is not much to do anyways.
}

313
server.go
View File

@ -1,7 +1,6 @@
package main
import (
"bufio"
"context"
"crypto/tls"
"encoding/json"
@ -11,12 +10,8 @@ import (
"io"
"net"
"net/http"
"net/url"
"os"
"os/exec"
"os/signal"
"os/user"
"strconv"
"strings"
"sync"
"syscall"
@ -31,7 +26,6 @@ import (
"github.com/ClusterCockpit/cc-backend/graph/generated"
"github.com/ClusterCockpit/cc-backend/log"
"github.com/ClusterCockpit/cc-backend/metricdata"
"github.com/ClusterCockpit/cc-backend/schema"
"github.com/ClusterCockpit/cc-backend/templates"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
@ -455,310 +449,3 @@ func main() {
wg.Wait()
log.Print("Gracefull shutdown completed!")
}
func buildFilterPresets(query url.Values) map[string]interface{} {
filterPresets := map[string]interface{}{}
if query.Get("cluster") != "" {
filterPresets["cluster"] = query.Get("cluster")
}
if query.Get("partition") != "" {
filterPresets["partition"] = query.Get("partition")
}
if query.Get("project") != "" {
filterPresets["project"] = query.Get("project")
filterPresets["projectMatch"] = "eq"
}
if query.Get("state") != "" && schema.JobState(query.Get("state")).Valid() {
filterPresets["state"] = query.Get("state")
}
if rawtags, ok := query["tag"]; ok {
tags := make([]int, len(rawtags))
for i, tid := range rawtags {
var err error
tags[i], err = strconv.Atoi(tid)
if err != nil {
tags[i] = -1
}
}
filterPresets["tags"] = tags
}
if query.Get("numNodes") != "" {
parts := strings.Split(query.Get("numNodes"), "-")
if len(parts) == 2 {
a, e1 := strconv.Atoi(parts[0])
b, e2 := strconv.Atoi(parts[1])
if e1 == nil && e2 == nil {
filterPresets["numNodes"] = map[string]int{"from": a, "to": b}
}
}
}
if query.Get("jobId") != "" {
filterPresets["jobId"] = query.Get("jobId")
}
if query.Get("arrayJobId") != "" {
if num, err := strconv.Atoi(query.Get("arrayJobId")); err == nil {
filterPresets["arrayJobId"] = num
}
}
return filterPresets
}
// func monitoringRoutes(router *mux.Router, resolver *graph.Resolver) {
// router.HandleFunc("/monitoring/jobs/", func(rw http.ResponseWriter, r *http.Request) {
// conf, infos, err := prepareRoute(r)
// if err != nil {
// http.Error(rw, err.Error(), http.StatusInternalServerError)
// return
// }
// templates.Render(rw, r, "monitoring/jobs.tmpl", &templates.Page{
// Title: "Jobs - ClusterCockpit",
// Config: conf,
// Infos: infos,
// FilterPresets: buildFilterPresets(r.URL.Query()),
// })
// })
// router.HandleFunc("/monitoring/job/{id:[0-9]+}", func(rw http.ResponseWriter, r *http.Request) {
// conf, infos, err := prepareRoute(r)
// if err != nil {
// http.Error(rw, err.Error(), http.StatusInternalServerError)
// return
// }
// id := mux.Vars(r)["id"]
// job, err := resolver.Query().Job(r.Context(), id)
// if err != nil {
// http.Error(rw, err.Error(), http.StatusNotFound)
// return
// }
// infos["id"] = id
// infos["jobId"] = job.JobID
// infos["clusterId"] = job.Cluster
// templates.Render(rw, r, "monitoring/job.tmpl", &templates.Page{
// Title: fmt.Sprintf("Job %d - ClusterCockpit", job.JobID),
// Config: conf,
// Infos: infos,
// })
// })
// router.HandleFunc("/monitoring/users/", func(rw http.ResponseWriter, r *http.Request) {
// conf, infos, err := prepareRoute(r)
// if err != nil {
// http.Error(rw, err.Error(), http.StatusInternalServerError)
// return
// }
// infos["listType"] = "USER"
// templates.Render(rw, r, "monitoring/list.tmpl", &templates.Page{
// Title: "Users - ClusterCockpit",
// Config: conf,
// FilterPresets: buildFilterPresets(r.URL.Query()),
// Infos: infos,
// })
// })
// router.HandleFunc("/monitoring/projects/", func(rw http.ResponseWriter, r *http.Request) {
// conf, infos, err := prepareRoute(r)
// if err != nil {
// http.Error(rw, err.Error(), http.StatusInternalServerError)
// return
// }
// infos["listType"] = "PROJECT"
// templates.Render(rw, r, "monitoring/list.tmpl", &templates.Page{
// Title: "Projects - ClusterCockpit",
// Config: conf,
// FilterPresets: buildFilterPresets(r.URL.Query()),
// Infos: infos,
// })
// })
// router.HandleFunc("/monitoring/user/{id}", func(rw http.ResponseWriter, r *http.Request) {
// conf, infos, err := prepareRoute(r)
// if err != nil {
// http.Error(rw, err.Error(), http.StatusInternalServerError)
// return
// }
// id := mux.Vars(r)["id"]
// // TODO: One could check if the user exists, but that would be unhelpfull if authentication
// // is disabled or the user does not exist but has started jobs.
// infos["username"] = id
// templates.Render(rw, r, "monitoring/user.tmpl", &templates.Page{
// Title: fmt.Sprintf("User %s - ClusterCockpit", id),
// Config: conf,
// Infos: infos,
// FilterPresets: buildFilterPresets(r.URL.Query()),
// })
// })
// router.HandleFunc("/monitoring/systems/", func(rw http.ResponseWriter, r *http.Request) {
// // TODO: List all clusters?
// http.Redirect(rw, r, "/", http.StatusTemporaryRedirect)
// })
// router.HandleFunc("/monitoring/systems/{cluster}", func(rw http.ResponseWriter, r *http.Request) {
// conf, infos, err := prepareRoute(r)
// if err != nil {
// http.Error(rw, err.Error(), http.StatusInternalServerError)
// return
// }
// vars := mux.Vars(r)
// infos["cluster"] = vars["cluster"]
// from, to := r.URL.Query().Get("from"), r.URL.Query().Get("to")
// if from != "" || to != "" {
// infos["from"] = from
// infos["to"] = to
// }
// templates.Render(rw, r, "monitoring/systems.tmpl", &templates.Page{
// Title: fmt.Sprintf("Cluster %s - ClusterCockpit", vars["cluster"]),
// Config: conf,
// Infos: infos,
// })
// })
// router.HandleFunc("/monitoring/node/{cluster}/{hostname}", func(rw http.ResponseWriter, r *http.Request) {
// conf, infos, err := prepareRoute(r)
// if err != nil {
// http.Error(rw, err.Error(), http.StatusInternalServerError)
// return
// }
// vars := mux.Vars(r)
// infos["cluster"] = vars["cluster"]
// infos["hostname"] = vars["hostname"]
// from, to := r.URL.Query().Get("from"), r.URL.Query().Get("to")
// if from != "" || to != "" {
// infos["from"] = from
// infos["to"] = to
// }
// templates.Render(rw, r, "monitoring/node.tmpl", &templates.Page{
// Title: fmt.Sprintf("Host %s - ClusterCockpit", vars["hostname"]),
// Config: conf,
// Infos: infos,
// })
// })
// }
func loadEnv(file string) error {
f, err := os.Open(file)
if err != nil {
return err
}
defer f.Close()
s := bufio.NewScanner(bufio.NewReader(f))
for s.Scan() {
line := s.Text()
if strings.HasPrefix(line, "#") || len(line) == 0 {
continue
}
if strings.Contains(line, "#") {
return errors.New("'#' are only supported at the start of a line")
}
line = strings.TrimPrefix(line, "export ")
parts := strings.SplitN(line, "=", 2)
if len(parts) != 2 {
return fmt.Errorf("unsupported line: %#v", line)
}
key := strings.TrimSpace(parts[0])
val := strings.TrimSpace(parts[1])
if strings.HasPrefix(val, "\"") {
if !strings.HasSuffix(val, "\"") {
return fmt.Errorf("unsupported line: %#v", line)
}
runes := []rune(val[1 : len(val)-1])
sb := strings.Builder{}
for i := 0; i < len(runes); i++ {
if runes[i] == '\\' {
i++
switch runes[i] {
case 'n':
sb.WriteRune('\n')
case 'r':
sb.WriteRune('\r')
case 't':
sb.WriteRune('\t')
case '"':
sb.WriteRune('"')
default:
return fmt.Errorf("unsupprorted escape sequence in quoted string: backslash %#v", runes[i])
}
continue
}
sb.WriteRune(runes[i])
}
val = sb.String()
}
os.Setenv(key, val)
}
return s.Err()
}
func dropPrivileges() error {
if programConfig.Group != "" {
g, err := user.LookupGroup(programConfig.Group)
if err != nil {
return err
}
gid, _ := strconv.Atoi(g.Gid)
if err := syscall.Setgid(gid); err != nil {
return err
}
}
if programConfig.User != "" {
u, err := user.Lookup(programConfig.User)
if err != nil {
return err
}
uid, _ := strconv.Atoi(u.Uid)
if err := syscall.Setuid(uid); err != nil {
return err
}
}
return nil
}
// If started via systemd, inform systemd that we are running:
// https://www.freedesktop.org/software/systemd/man/sd_notify.html
func systemdNotifiy(ready bool, status string) {
if os.Getenv("NOTIFY_SOCKET") == "" {
// Not started using systemd
return
}
args := []string{fmt.Sprintf("--pid=%d", os.Getpid())}
if ready {
args = append(args, "--ready")
}
if status != "" {
args = append(args, fmt.Sprintf("--status=%s", status))
}
cmd := exec.Command("systemd-notify", args...)
cmd.Run() // errors ignored on purpose, there is not much to do anyways.
}