mirror of
				https://github.com/ClusterCockpit/cc-backend
				synced 2025-11-01 00:15:05 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			178 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			178 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (C) NHR@FAU, University Erlangen-Nuremberg.
 | |
| // All rights reserved.
 | |
| // Use of this source code is governed by a MIT-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| package api
 | |
| 
 | |
| import (
 | |
| 	"bufio"
 | |
| 	"encoding/json"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"net/http"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/ClusterCockpit/cc-backend/internal/memorystore"
 | |
| 	cclog "github.com/ClusterCockpit/cc-lib/ccLogger"
 | |
| 
 | |
| 	"github.com/influxdata/line-protocol/v2/lineprotocol"
 | |
| )
 | |
| 
 | |
| // handleFree godoc
 | |
| // @summary
 | |
| // @tags free
 | |
| // @description This endpoint allows the users to free the Buffers from the
 | |
| // metric store. This endpoint offers the users to remove then systematically
 | |
| // and also allows then to prune the data under node, if they do not want to
 | |
| // remove the whole node.
 | |
| // @produce     json
 | |
| // @param       to        query    string        false  "up to timestamp"
 | |
| // @success     200            {string} string  "ok"
 | |
| // @failure     400            {object} api.ErrorResponse       "Bad Request"
 | |
| // @failure     401            {object} api.ErrorResponse       "Unauthorized"
 | |
| // @failure     403            {object} api.ErrorResponse       "Forbidden"
 | |
| // @failure     500            {object} api.ErrorResponse       "Internal Server Error"
 | |
| // @security    ApiKeyAuth
 | |
| // @router      /free/ [post]
 | |
| func freeMetrics(rw http.ResponseWriter, r *http.Request) {
 | |
| 	rawTo := r.URL.Query().Get("to")
 | |
| 	if rawTo == "" {
 | |
| 		handleError(errors.New("'to' is a required query parameter"), http.StatusBadRequest, rw)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	to, err := strconv.ParseInt(rawTo, 10, 64)
 | |
| 	if err != nil {
 | |
| 		handleError(err, http.StatusInternalServerError, rw)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// // TODO: lastCheckpoint might be modified by different go-routines.
 | |
| 	// // Load it using the sync/atomic package?
 | |
| 	// freeUpTo := lastCheckpoint.Unix()
 | |
| 	// if to < freeUpTo {
 | |
| 	// 	freeUpTo = to
 | |
| 	// }
 | |
| 
 | |
| 	bodyDec := json.NewDecoder(r.Body)
 | |
| 	var selectors [][]string
 | |
| 	err = bodyDec.Decode(&selectors)
 | |
| 	if err != nil {
 | |
| 		http.Error(rw, err.Error(), http.StatusBadRequest)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	ms := memorystore.GetMemoryStore()
 | |
| 	n := 0
 | |
| 	for _, sel := range selectors {
 | |
| 		bn, err := ms.Free(sel, to)
 | |
| 		if err != nil {
 | |
| 			handleError(err, http.StatusInternalServerError, rw)
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		n += bn
 | |
| 	}
 | |
| 
 | |
| 	rw.WriteHeader(http.StatusOK)
 | |
| 	fmt.Fprintf(rw, "buffers freed: %d\n", n)
 | |
| }
 | |
| 
 | |
| // handleWrite godoc
 | |
| // @summary Receive metrics in InfluxDB line-protocol
 | |
| // @tags write
 | |
| // @description Write data to the in-memory store in the InfluxDB line-protocol using [this format](https://github.com/ClusterCockpit/cc-specifications/blob/master/metrics/lineprotocol_alternative.md)
 | |
| 
 | |
| // @accept      plain
 | |
| // @produce     json
 | |
| // @param       cluster        query string false "If the lines in the body do not have a cluster tag, use this value instead."
 | |
| // @success     200            {string} string  "ok"
 | |
| // @failure     400            {object} api.ErrorResponse       "Bad Request"
 | |
| // @failure     401            {object} api.ErrorResponse       "Unauthorized"
 | |
| // @failure     403            {object} api.ErrorResponse       "Forbidden"
 | |
| // @failure     500            {object} api.ErrorResponse       "Internal Server Error"
 | |
| // @security    ApiKeyAuth
 | |
| // @router      /write/ [post]
 | |
| func writeMetrics(rw http.ResponseWriter, r *http.Request) {
 | |
| 	bytes, err := io.ReadAll(r.Body)
 | |
| 	rw.Header().Add("Content-Type", "application/json")
 | |
| 	if err != nil {
 | |
| 		handleError(err, http.StatusInternalServerError, rw)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	ms := memorystore.GetMemoryStore()
 | |
| 	dec := lineprotocol.NewDecoderWithBytes(bytes)
 | |
| 	if err := memorystore.DecodeLine(dec, ms, r.URL.Query().Get("cluster")); err != nil {
 | |
| 		cclog.Errorf("/api/write error: %s", err.Error())
 | |
| 		handleError(err, http.StatusBadRequest, rw)
 | |
| 		return
 | |
| 	}
 | |
| 	rw.WriteHeader(http.StatusOK)
 | |
| }
 | |
| 
 | |
| // handleDebug godoc
 | |
| // @summary Debug endpoint
 | |
| // @tags debug
 | |
| // @description This endpoint allows the users to print the content of
 | |
| // nodes/clusters/metrics to review the state of the data.
 | |
| // @produce     json
 | |
| // @param       selector        query    string            false "Selector"
 | |
| // @success     200            {string} string  "Debug dump"
 | |
| // @failure     400            {object} api.ErrorResponse       "Bad Request"
 | |
| // @failure     401            {object} api.ErrorResponse       "Unauthorized"
 | |
| // @failure     403            {object} api.ErrorResponse       "Forbidden"
 | |
| // @failure     500            {object} api.ErrorResponse       "Internal Server Error"
 | |
| // @security    ApiKeyAuth
 | |
| // @router      /debug/ [post]
 | |
| func debugMetrics(rw http.ResponseWriter, r *http.Request) {
 | |
| 	raw := r.URL.Query().Get("selector")
 | |
| 	rw.Header().Add("Content-Type", "application/json")
 | |
| 	selector := []string{}
 | |
| 	if len(raw) != 0 {
 | |
| 		selector = strings.Split(raw, ":")
 | |
| 	}
 | |
| 
 | |
| 	ms := memorystore.GetMemoryStore()
 | |
| 	if err := ms.DebugDump(bufio.NewWriter(rw), selector); err != nil {
 | |
| 		handleError(err, http.StatusBadRequest, rw)
 | |
| 		return
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // handleHealthCheck godoc
 | |
| // @summary HealthCheck endpoint
 | |
| // @tags healthcheck
 | |
| // @description This endpoint allows the users to check if a node is healthy
 | |
| // @produce     json
 | |
| // @param       selector        query    string            false "Selector"
 | |
| // @success     200            {string} string  "Debug dump"
 | |
| // @failure     400            {object} api.ErrorResponse       "Bad Request"
 | |
| // @failure     401            {object} api.ErrorResponse       "Unauthorized"
 | |
| // @failure     403            {object} api.ErrorResponse       "Forbidden"
 | |
| // @failure     500            {object} api.ErrorResponse       "Internal Server Error"
 | |
| // @security    ApiKeyAuth
 | |
| // @router      /healthcheck/ [get]
 | |
| func metricsHealth(rw http.ResponseWriter, r *http.Request) {
 | |
| 	rawCluster := r.URL.Query().Get("cluster")
 | |
| 	rawNode := r.URL.Query().Get("node")
 | |
| 
 | |
| 	if rawCluster == "" || rawNode == "" {
 | |
| 		handleError(errors.New("'cluster' and 'node' are required query parameter"), http.StatusBadRequest, rw)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	rw.Header().Add("Content-Type", "application/json")
 | |
| 
 | |
| 	selector := []string{rawCluster, rawNode}
 | |
| 
 | |
| 	ms := memorystore.GetMemoryStore()
 | |
| 	if err := ms.HealthCheck(bufio.NewWriter(rw), selector); err != nil {
 | |
| 		handleError(err, http.StatusBadRequest, rw)
 | |
| 		return
 | |
| 	}
 | |
| }
 |