diff --git a/Makefile.orig b/Makefile.orig deleted file mode 100644 index e796920..0000000 --- a/Makefile.orig +++ /dev/null @@ -1,126 +0,0 @@ -TARGET = ./cc-metric-store -VERSION = 1.3.0 -GIT_HASH := $(shell git rev-parse --short HEAD || echo 'development') -CURRENT_TIME = $(shell date +"%Y-%m-%d:T%H:%M:%S") -LD_FLAGS = '-s -X main.date=${CURRENT_TIME} -X main.version=${VERSION} -X main.commit=${GIT_HASH}' - -.PHONY: clean test tags $(TARGET) - -.NOTPARALLEL: - -$(TARGET): - $(info ===> BUILD cc-backend) - @go build -ldflags=${LD_FLAGS} ./cmd/cc-metric-store - -<<<<<<< HEAD -======= -BINDIR ?= bin - - -.PHONY: all -all: $(APP) - -$(APP): $(GOSRC) - go get - go build -o $(APP) $(GOSRC_APP) $(GOSRC_FILES) - -install: $(APP) - @WORKSPACE=$(PREFIX) - @if [ -z "$${WORKSPACE}" ]; then exit 1; fi - @mkdir --parents --verbose $${WORKSPACE}/usr/$(BINDIR) - @install -Dpm 755 $(APP) $${WORKSPACE}/usr/$(BINDIR)/$(APP) - @install -Dpm 600 config.json $${WORKSPACE}/etc/$(APP)/$(APP).json - -.PHONY: clean test fmt -.ONESHELL: ->>>>>>> main -clean: - $(info ===> CLEAN) - @go clean - @rm -f $(TARGET) - -test: - $(info ===> TESTING) - @go clean -testcache - @go build ./... - @go vet ./... - @go test ./... -<<<<<<< HEAD - -tags: - $(info ===> TAGS) - @ctags -R -======= - -fmt: - go fmt $(GOSRC_APP) - -# Run linter for the Go programming language. -# Using static analysis, it finds bugs and performance issues, offers simplifications, and enforces style rules -.PHONY: staticcheck -staticcheck: - go install honnef.co/go/tools/cmd/staticcheck@latest - $$(go env GOPATH)/bin/staticcheck ./... - -.ONESHELL: -.PHONY: RPM -RPM: scripts/cc-metric-store.spec - @WORKSPACE="$${PWD}" - @SPECFILE="$${WORKSPACE}/scripts/cc-metric-store.spec" - # Setup RPM build tree - @eval $$(rpm --eval "ARCH='%{_arch}' RPMDIR='%{_rpmdir}' SOURCEDIR='%{_sourcedir}' SPECDIR='%{_specdir}' SRPMDIR='%{_srcrpmdir}' BUILDDIR='%{_builddir}'") - @mkdir --parents --verbose "$${RPMDIR}" "$${SOURCEDIR}" "$${SPECDIR}" "$${SRPMDIR}" "$${BUILDDIR}" - # Create source tarball - @COMMITISH="HEAD" - @VERS=$$(git describe --tags $${COMMITISH}) - @VERS=$${VERS#v} - @VERS=$$(echo $$VERS | sed -e s+'-'+'_'+g) - @if [ "$${VERS}" = "" ]; then VERS="0.0.1"; fi - @eval $$(rpmspec --query --queryformat "NAME='%{name}' VERSION='%{version}' RELEASE='%{release}' NVR='%{NVR}' NVRA='%{NVRA}'" --define="VERS $${VERS}" "$${SPECFILE}") - @PREFIX="$${NAME}-$${VERSION}" - @FORMAT="tar.gz" - @SRCFILE="$${SOURCEDIR}/$${PREFIX}.$${FORMAT}" - @git archive --verbose --format "$${FORMAT}" --prefix="$${PREFIX}/" --output="$${SRCFILE}" $${COMMITISH} - # Build RPM and SRPM - @rpmbuild -ba --define="VERS $${VERS}" --rmsource --clean "$${SPECFILE}" - # Report RPMs and SRPMs when in GitHub Workflow - @if [[ "$${GITHUB_ACTIONS}" == true ]]; then - @ RPMFILE="$${RPMDIR}/$${ARCH}/$${NVRA}.rpm" - @ SRPMFILE="$${SRPMDIR}/$${NVR}.src.rpm" - @ echo "RPM: $${RPMFILE}" - @ echo "SRPM: $${SRPMFILE}" - @ echo "::set-output name=SRPM::$${SRPMFILE}" - @ echo "::set-output name=RPM::$${RPMFILE}" - @fi - -.ONESHELL: -.PHONY: DEB -DEB: scripts/cc-metric-store.deb.control $(APP) - @BASEDIR=$${PWD} - @WORKSPACE=$${PWD}/.dpkgbuild - @DEBIANDIR=$${WORKSPACE}/debian - @DEBIANBINDIR=$${WORKSPACE}/DEBIAN - @mkdir --parents --verbose $$WORKSPACE $$DEBIANBINDIR - #@mkdir --parents --verbose $$DEBIANDIR - @CONTROLFILE="$${BASEDIR}/scripts/cc-metric-store.deb.control" - @COMMITISH="HEAD" - @VERS=$$(git describe --tags --abbrev=0 $${COMMITISH}) - @VERS=$${VERS#v} - @VERS=$$(echo $$VERS | sed -e s+'-'+'_'+g) - @if [ "$${VERS}" = "" ]; then VERS="0.0.1"; fi - @ARCH=$$(uname -m) - @ARCH=$$(echo $$ARCH | sed -e s+'_'+'-'+g) - @if [ "$${ARCH}" = "x86-64" ]; then ARCH=amd64; fi - @PREFIX="$${NAME}-$${VERSION}_$${ARCH}" - @SIZE_BYTES=$$(du -bcs --exclude=.dpkgbuild "$$WORKSPACE"/ | awk '{print $$1}' | head -1 | sed -e 's/^0\+//') - @SIZE="$$(awk -v size="$$SIZE_BYTES" 'BEGIN {print (size/1024)+1}' | awk '{print int($$0)}')" - #@sed -e s+"{VERSION}"+"$$VERS"+g -e s+"{INSTALLED_SIZE}"+"$$SIZE"+g -e s+"{ARCH}"+"$$ARCH"+g $$CONTROLFILE > $${DEBIANDIR}/control - @sed -e s+"{VERSION}"+"$$VERS"+g -e s+"{INSTALLED_SIZE}"+"$$SIZE"+g -e s+"{ARCH}"+"$$ARCH"+g $$CONTROLFILE > $${DEBIANBINDIR}/control - @make PREFIX=$${WORKSPACE} install - @DEB_FILE="cc-metric-store_$${VERS}_$${ARCH}.deb" - @dpkg-deb -b $${WORKSPACE} "$$DEB_FILE" - @rm -r "$${WORKSPACE}" - @if [ "$${GITHUB_ACTIONS}" = "true" ]; then - @ echo "::set-output name=DEB::$${DEB_FILE}" - @fi ->>>>>>> main diff --git a/README.md b/README.md index e9b5be7..1a21efc 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,29 @@ [![Build & Test](https://github.com/ClusterCockpit/cc-metric-store/actions/workflows/test.yml/badge.svg)](https://github.com/ClusterCockpit/cc-metric-store/actions/workflows/test.yml) -The cc-metric-store provides a simple in-memory time series database for storing metrics of cluster nodes at preconfigured intervals. It is meant to be used as part of the [ClusterCockpit suite](https://github.com/ClusterCockpit). As all data is kept in-memory (but written to disk as compressed JSON for long term storage), accessing it is very fast. It also provides aggregations over time *and* nodes/sockets/cpus. +The cc-metric-store provides a simple in-memory time series database for storing +metrics of cluster nodes at preconfigured intervals. It is meant to be used as +part of the [ClusterCockpit suite](https://github.com/ClusterCockpit). As all +data is kept in-memory (but written to disk as compressed JSON for long term +storage), accessing it is very fast. It also provides aggregations over time +_and_ nodes/sockets/cpus. -There are major limitations: Data only gets written to disk at periodic checkpoints, not as soon as it is received. +There are major limitations: Data only gets written to disk at periodic +checkpoints, not as soon as it is received. -Go look at the `TODO.md` file and the [GitHub Issues](https://github.com/ClusterCockpit/cc-metric-store/issues) for a progress overview. Things work, but are not properly tested. -The [NATS.io](https://nats.io/) based writing endpoint consumes messages in [this format of the InfluxDB line protocol](https://github.com/ClusterCockpit/cc-specifications/blob/master/metrics/lineprotocol_alternative.md). +Go look at the `TODO.md` file and the [GitHub +Issues](https://github.com/ClusterCockpit/cc-metric-store/issues) for a progress +overview. Things work, but are not properly tested. The +[NATS.io](https://nats.io/) based writing endpoint consumes messages in [this +format of the InfluxDB line +protocol](https://github.com/ClusterCockpit/cc-specifications/blob/master/metrics/lineprotocol_alternative.md). -### REST API Endpoints +## REST API Endpoints -The REST API is documented in [openapi.yaml](./openapi.yaml) in the OpenAPI 3.0 format. +The REST API is documented in [openapi.yaml](./api/openapi.yaml) in the OpenAPI +3.0 format. -### Run tests +## Run tests Some benchmarks concurrently access the `MemoryStore`, so enabling the [Race Detector](https://golang.org/doc/articles/race_detector) might be useful. @@ -28,18 +39,21 @@ go test -v ./... go test -bench=. -race -v ./... ``` -### What are these selectors mentioned in the code? +## What are these selectors mentioned in the code? -Tags in InfluxDB are used to build indexes over the stored data. InfluxDB-Tags have no -relation to each other, they do not depend on each other and have no hierarchy. -Different tags build up different indexes (I am no expert at all, but this is how i think they work). +Tags in InfluxDB are used to build indexes over the stored data. InfluxDB-Tags +have no relation to each other, they do not depend on each other and have no +hierarchy. Different tags build up different indexes (I am no expert at all, but +this is how i think they work). -This project also works as a time-series database and uses the InfluxDB line protocol. -Unlike InfluxDB, the data is indexed by one single strictly hierarchical tree structure. -A selector is build out of the tags in the InfluxDB line protocol, and can be used to select -a node (not in the sense of a compute node, can also be a socket, cpu, ...) in that tree. -The implementation calls those nodes `level` to avoid confusion. -It is impossible to access data only by knowing the *socket* or *cpu* tag, all higher up levels have to be specified as well. +This project also works as a time-series database and uses the InfluxDB line +protocol. Unlike InfluxDB, the data is indexed by one single strictly +hierarchical tree structure. A selector is build out of the tags in the InfluxDB +line protocol, and can be used to select a node (not in the sense of a compute +node, can also be a socket, cpu, ...) in that tree. The implementation calls +those nodes `level` to avoid confusion. It is impossible to access data only by +knowing the _socket_ or _cpu_ tag, all higher up levels have to be specified as +well. This is what the hierarchy currently looks like: @@ -59,43 +73,49 @@ This is what the hierarchy currently looks like: - ... Example selectors: + 1. `["cluster1", "host1", "cpu0"]`: Select only the cpu0 of host1 in cluster1 2. `["cluster1", "host1", ["cpu4", "cpu5", "cpu6", "cpu7"]]`: Select only CPUs 4-7 of host1 in cluster1 3. `["cluster1", "host1"]`: Select the complete node. If querying for a CPU-specific metric such as floats, all CPUs are implied -### Config file +## Config file -All durations are specified as string that will be parsed [like this](https://pkg.go.dev/time#ParseDuration) (Allowed suffixes: `s`, `m`, `h`, ...). +All durations are specified as string that will be parsed [like +this](https://pkg.go.dev/time#ParseDuration) (Allowed suffixes: `s`, `m`, `h`, +...). - `metrics`: Map of metric-name to objects with the following properties - - `frequency`: Timestep/Interval/Resolution of this metric - - `aggregation`: Can be `"sum"`, `"avg"` or `null` - - `null` means aggregation across nodes is forbidden for this metric - - `"sum"` means that values from the child levels are summed up for the parent level - - `"avg"` means that values from the child levels are averaged for the parent level - - `scope`: Unused at the moment, should be something like `"node"`, `"socket"` or `"hwthread"` + - `frequency`: Timestep/Interval/Resolution of this metric + - `aggregation`: Can be `"sum"`, `"avg"` or `null` + - `null` means aggregation across nodes is forbidden for this metric + - `"sum"` means that values from the child levels are summed up for the parent level + - `"avg"` means that values from the child levels are averaged for the parent level + - `scope`: Unused at the moment, should be something like `"node"`, `"socket"` or `"hwthread"` - `nats`: - - `address`: Url of NATS.io server, example: "nats://localhost:4222" - - `username` and `password`: Optional, if provided use those for the connection - - `subscriptions`: - - `subscribe-to`: Where to expect the measurements to be published - - `cluster-tag`: Default value for the cluster tag + - `address`: Url of NATS.io server, example: "nats://localhost:4222" + - `username` and `password`: Optional, if provided use those for the connection + - `subscriptions`: + - `subscribe-to`: Where to expect the measurements to be published + - `cluster-tag`: Default value for the cluster tag - `http-api`: - - `address`: Address to bind to, for example `0.0.0.0:8080` - - `https-cert-file` and `https-key-file`: Optional, if provided enable HTTPS using those files as certificate/key + - `address`: Address to bind to, for example `0.0.0.0:8080` + - `https-cert-file` and `https-key-file`: Optional, if provided enable HTTPS using those files as certificate/key - `jwt-public-key`: Base64 encoded string, use this to verify requests to the HTTP API - `retention-on-memory`: Keep all values in memory for at least that amount of time - `checkpoints`: - - `interval`: Do checkpoints every X seconds/minutes/hours - - `directory`: Path to a directory - - `restore`: After a restart, load the last X seconds/minutes/hours of data back into memory + - `interval`: Do checkpoints every X seconds/minutes/hours + - `directory`: Path to a directory + - `restore`: After a restart, load the last X seconds/minutes/hours of data back into memory - `archive`: - - `interval`: Move and compress all checkpoints not needed anymore every X seconds/minutes/hours - - `directory`: Path to a directory + - `interval`: Move and compress all checkpoints not needed anymore every X seconds/minutes/hours + - `directory`: Path to a directory -### Test the complete setup (excluding ClusterCockpit itself) +## Test the complete setup (excluding cc-backend itself) -There are two ways for sending data to the cc-metric-store, both of which are supported by the [cc-metric-collector](https://github.com/ClusterCockpit/cc-metric-collector). This example uses Nats, the alternative is to use HTTP. +There are two ways for sending data to the cc-metric-store, both of which are +supported by the +[cc-metric-collector](https://github.com/ClusterCockpit/cc-metric-collector). +This example uses Nats, the alternative is to use HTTP. ```sh # Only needed once, downloads the docker image @@ -105,7 +125,9 @@ docker pull nats:latest docker run -p 4222:4222 -ti nats:latest ``` -Second, build and start the [cc-metric-collector](https://github.com/ClusterCockpit/cc-metric-collector) using the following as Sink-Config: +Second, build and start the +[cc-metric-collector](https://github.com/ClusterCockpit/cc-metric-collector) +using the following as Sink-Config: ```json { @@ -116,18 +138,20 @@ Second, build and start the [cc-metric-collector](https://github.com/ClusterCock } ``` -Third, build and start the metric store. For this example here, the `config.json` file -already in the repository should work just fine. +Third, build and start the metric store. For this example here, the +`config.json` file already in the repository should work just fine. ```sh # Assuming you have a clone of this repo in ./cc-metric-store: cd cc-metric-store -go get -go build +make ./cc-metric-store ``` -And finally, use the API to fetch some data. The API is protected by JWT based authentication if `jwt-public-key` is set in `config.json`. You can use this JWT for testing: `eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJ1c2VyIjoiYWRtaW4iLCJyb2xlcyI6WyJST0xFX0FETUlOIiwiUk9MRV9BTkFMWVNUIiwiUk9MRV9VU0VSIl19.d-3_3FZTsadPjDEdsWrrQ7nS0edMAR4zjl-eK7rJU3HziNBfI9PDHDIpJVHTNN5E5SlLGLFXctWyKAkwhXL-Dw` +And finally, use the API to fetch some data. The API is protected by JWT based +authentication if `jwt-public-key` is set in `config.json`. You can use this JWT +for testing: +`eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJ1c2VyIjoiYWRtaW4iLCJyb2xlcyI6WyJST0xFX0FETUlOIiwiUk9MRV9BTkFMWVNUIiwiUk9MRV9VU0VSIl19.d-3_3FZTsadPjDEdsWrrQ7nS0edMAR4zjl-eK7rJU3HziNBfI9PDHDIpJVHTNN5E5SlLGLFXctWyKAkwhXL-Dw` ```sh JWT="eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJ1c2VyIjoiYWRtaW4iLCJyb2xlcyI6WyJST0xFX0FETUlOIiwiUk9MRV9BTkFMWVNUIiwiUk9MRV9VU0VSIl19.d-3_3FZTsadPjDEdsWrrQ7nS0edMAR4zjl-eK7rJU3HziNBfI9PDHDIpJVHTNN5E5SlLGLFXctWyKAkwhXL-Dw" @@ -142,6 +166,7 @@ curl -H "Authorization: Bearer $JWT" -D - "http://localhost:8080/api/query" -d " ``` For debugging there is a debug endpoint to dump the current content to stdout: + ```sh JWT="eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJ1c2VyIjoiYWRtaW4iLCJyb2xlcyI6WyJST0xFX0FETUlOIiwiUk9MRV9BTkFMWVNUIiwiUk9MRV9VU0VSIl19.d-3_3FZTsadPjDEdsWrrQ7nS0edMAR4zjl-eK7rJU3HziNBfI9PDHDIpJVHTNN5E5SlLGLFXctWyKAkwhXL-Dw" diff --git a/TODO.md b/TODO.md index 91b2eab..36b55f4 100644 --- a/TODO.md +++ b/TODO.md @@ -1,14 +1,15 @@ -# TODO +# TODOs - Improve checkpoints/archives - - Store information in each buffer if already archived - - Do not create new checkpoint if all buffers already archived + - Store information in each buffer if already archived + - Do not create new checkpoint if all buffers already archived - Missing Testcases: - - General tests - - Check for corner cases that should fail gracefully - - Write a more realistic `ToArchive`/`FromArchive` tests + - General tests + - Check for corner cases that should fail gracefully + - Write a more realistic `ToArchive`/`FromArchive` tests - Optimization: Once a buffer is full, calculate min, max and avg - - Calculate averages buffer-wise, average weighted by length of buffer - - Only the head-buffer needs to be fully traversed -- Optimization: If aggregating over hwthreads/cores/sockets cache those results and reuse some of that for new queres aggregating only over the newer data + - Calculate averages buffer-wise, average weighted by length of buffer + - Only the head-buffer needs to be fully traversed +- Optimization: If aggregating over hwthreads/cores/sockets cache those results + and reuse some of that for new queres aggregating only over the newer data - ... diff --git a/openapi.yaml b/api/openapi.yaml similarity index 100% rename from openapi.yaml rename to api/openapi.yaml diff --git a/go.mod.orig b/go.mod.orig deleted file mode 100644 index cd78a68..0000000 --- a/go.mod.orig +++ /dev/null @@ -1,37 +0,0 @@ -module github.com/ClusterCockpit/cc-metric-store - -go 1.19 - -require ( -<<<<<<< HEAD - github.com/golang-jwt/jwt/v4 v4.0.0 - github.com/google/gops v0.3.22 - github.com/gorilla/mux v1.8.0 - github.com/influxdata/line-protocol/v2 v2.2.0 - github.com/nats-io/nats.go v1.11.0 -======= - github.com/golang-jwt/jwt/v5 v5.2.1 - github.com/google/gops v0.3.28 - github.com/gorilla/mux v1.8.1 - github.com/influxdata/line-protocol/v2 v2.2.1 - github.com/nats-io/nats.go v1.33.1 -) - -require ( - github.com/klauspost/compress v1.17.7 // indirect - github.com/nats-io/nkeys v0.4.7 // indirect - github.com/nats-io/nuid v1.0.1 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/sys v0.18.0 // indirect ->>>>>>> main -) - -require ( - github.com/golang/protobuf v1.5.2 // indirect - github.com/nats-io/nats-server/v2 v2.2.6 // indirect - github.com/nats-io/nkeys v0.3.0 // indirect - github.com/nats-io/nuid v1.0.1 // indirect - golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b // indirect - golang.org/x/sys v0.0.0-20210902050250-f475640dd07b // indirect - google.golang.org/protobuf v1.26.0 // indirect -) diff --git a/go.sum.orig b/go.sum.orig deleted file mode 100644 index 686ec91..0000000 --- a/go.sum.orig +++ /dev/null @@ -1,110 +0,0 @@ -<<<<<<< HEAD -github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= -======= ->>>>>>> main -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/frankban/quicktest v1.11.0/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= -github.com/frankban/quicktest v1.11.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= -github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk= -github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU= -<<<<<<< HEAD -github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-ole/go-ole v1.2.6-0.20210915003542-8b1f7f90f6b1/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o= -github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -======= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= ->>>>>>> main -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gops v0.3.28 h1:2Xr57tqKAmQYRAfG12E+yLcoa2Y42UJo2lOrUFL9ark= -github.com/google/gops v0.3.28/go.mod h1:6f6+Nl8LcHrzJwi8+p0ii+vmBFSlB4f8cOOkTJ7sk4c= -github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= -github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/influxdata/line-protocol-corpus v0.0.0-20210519164801-ca6fa5da0184/go.mod h1:03nmhxzZ7Xk2pdG+lmMd7mHDfeVOYFyhOgwO61qWU98= -github.com/influxdata/line-protocol-corpus v0.0.0-20210922080147-aa28ccfb8937 h1:MHJNQ+p99hFATQm6ORoLmpUCF7ovjwEFshs/NHzAbig= -github.com/influxdata/line-protocol-corpus v0.0.0-20210922080147-aa28ccfb8937/go.mod h1:BKR9c0uHSmRgM/se9JhFHtTT7JTO67X23MtKMHtZcpo= -github.com/influxdata/line-protocol/v2 v2.0.0-20210312151457-c52fdecb625a/go.mod h1:6+9Xt5Sq1rWx+glMgxhcg2c0DUaehK+5TDcPZ76GypY= -github.com/influxdata/line-protocol/v2 v2.1.0/go.mod h1:QKw43hdUBg3GTk2iC3iyCxksNj7PX9aUSeYOYE/ceHY= -<<<<<<< HEAD -github.com/influxdata/line-protocol/v2 v2.2.0 h1:UPmAqE15Hw5zu9E10SYhoXVLWnEJkWnuCbaCiRsA3c0= -github.com/influxdata/line-protocol/v2 v2.2.0/go.mod h1:DmB3Cnh+3oxmG6LOBIxce4oaL4CPj3OmMPgvauXh+tM= -github.com/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19/go.mod h1:hY+WOq6m2FpbvyrI93sMaypsttvaIL5nhVR92dTMUcQ= -github.com/klauspost/compress v1.11.12 h1:famVnQVu7QwryBN4jNseQdUKES71ZAOnB6UQQJPZvqk= -github.com/klauspost/compress v1.11.12/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -======= -github.com/influxdata/line-protocol/v2 v2.2.1 h1:EAPkqJ9Km4uAxtMRgUubJyqAr6zgWM0dznKMLRauQRE= -github.com/influxdata/line-protocol/v2 v2.2.1/go.mod h1:DmB3Cnh+3oxmG6LOBIxce4oaL4CPj3OmMPgvauXh+tM= -github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= -github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= ->>>>>>> main -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/nats-io/nats.go v1.33.1 h1:8TxLZZ/seeEfR97qV0/Bl939tpDnt2Z2fK3HkPypj70= -github.com/nats-io/nats.go v1.33.1/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= -github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= -github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= -github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -<<<<<<< HEAD -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/shirou/gopsutil/v3 v3.21.9/go.mod h1:YWp/H8Qs5fVmf17v7JNZzA0mPJ+mS2e9JdiUF9LlKzQ= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= -github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= -github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b h1:wSOdpTq0/eI46Ez/LkDwIsAKA71YP2SRKBODiRWM0as= -golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210902050250-f475640dd07b h1:S7hKs0Flbq0bbc9xgYt4stIEG1zNDFqyrPwAX2Wj/sE= -golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI= -golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -======= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= ->>>>>>> main -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -<<<<<<< HEAD -rsc.io/goversion v1.2.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo= -======= ->>>>>>> main diff --git a/internal/api/api.go.orig b/internal/api/api.go.orig deleted file mode 100644 index 6226b7b..0000000 --- a/internal/api/api.go.orig +++ /dev/null @@ -1,454 +0,0 @@ -package api - -import ( - "bufio" - "context" - "crypto/ed25519" - "encoding/base64" - "encoding/json" - "fmt" - "io" - "log" - "math" - "net/http" - "strconv" - "strings" - "time" - -<<<<<<< HEAD:internal/api/api.go - "github.com/ClusterCockpit/cc-metric-store/internal/config" - "github.com/ClusterCockpit/cc-metric-store/internal/memorystore" - "github.com/ClusterCockpit/cc-metric-store/internal/util" -======= - "github.com/golang-jwt/jwt/v5" ->>>>>>> main:api.go - "github.com/gorilla/mux" - "github.com/influxdata/line-protocol/v2/lineprotocol" -) - -type ApiMetricData struct { -<<<<<<< HEAD:internal/api/api.go - Error *string `json:"error,omitempty"` - Data util.FloatArray `json:"data,omitempty"` - From int64 `json:"from"` - To int64 `json:"to"` - Avg util.Float `json:"avg"` - Min util.Float `json:"min"` - Max util.Float `json:"max"` -======= - Error *string `json:"error,omitempty"` - Data FloatArray `json:"data,omitempty"` - From int64 `json:"from"` - To int64 `json:"to"` - Avg Float `json:"avg"` - Min Float `json:"min"` - Max Float `json:"max"` ->>>>>>> main:api.go -} - -// TODO: Optimize this, just like the stats endpoint! -func (data *ApiMetricData) AddStats() { - n := 0 - sum, min, max := 0.0, math.MaxFloat64, -math.MaxFloat64 - for _, x := range data.Data { - if x.IsNaN() { - continue - } - - n += 1 - sum += float64(x) - min = math.Min(min, float64(x)) - max = math.Max(max, float64(x)) - } - - if n > 0 { - avg := sum / float64(n) - data.Avg = util.Float(avg) - data.Min = util.Float(min) - data.Max = util.Float(max) - } else { - data.Avg, data.Min, data.Max = util.NaN, util.NaN, util.NaN - } -} - -func (data *ApiMetricData) ScaleBy(f util.Float) { - if f == 0 || f == 1 { - return - } - - data.Avg *= f - data.Min *= f - data.Max *= f - for i := 0; i < len(data.Data); i++ { - data.Data[i] *= f - } -} - -func (data *ApiMetricData) PadDataWithNull(ms *memorystore.MemoryStore, from, to int64, metric string) { - minfo, ok := ms.Metrics[metric] - if !ok { - return - } - - if (data.From / minfo.Frequency) > (from / minfo.Frequency) { - padfront := int((data.From / minfo.Frequency) - (from / minfo.Frequency)) - ndata := make([]util.Float, 0, padfront+len(data.Data)) - for i := 0; i < padfront; i++ { - ndata = append(ndata, util.NaN) - } - for j := 0; j < len(data.Data); j++ { - ndata = append(ndata, data.Data[j]) - } - data.Data = ndata - } -} - -func handleFree(rw http.ResponseWriter, r *http.Request) { - rawTo := r.URL.Query().Get("to") - if rawTo == "" { - http.Error(rw, "'to' is a required query parameter", http.StatusBadRequest) - return - } - - to, err := strconv.ParseInt(rawTo, 10, 64) - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - 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 - // } - - if r.Method != http.MethodPost { - http.Error(rw, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - - 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 { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - n += bn - } - - rw.WriteHeader(http.StatusOK) - fmt.Fprintf(rw, "buffers freed: %d\n", n) -} - -func handleWrite(rw http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(rw, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - - bytes, err := io.ReadAll(r.Body) - if err != nil { - log.Printf("error while reading request body: %s", err.Error()) - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - ms := memorystore.GetMemoryStore() - dec := lineprotocol.NewDecoderWithBytes(bytes) - if err := decodeLine(dec, ms, r.URL.Query().Get("cluster")); err != nil { - log.Printf("/api/write error: %s", err.Error()) - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } - rw.WriteHeader(http.StatusOK) -} - -type ApiQueryRequest struct { - Cluster string `json:"cluster"` - Queries []ApiQuery `json:"queries"` - ForAllNodes []string `json:"for-all-nodes"` - From int64 `json:"from"` - To int64 `json:"to"` - WithStats bool `json:"with-stats"` - WithData bool `json:"with-data"` - WithPadding bool `json:"with-padding"` -} - -type ApiQueryResponse struct { - Queries []ApiQuery `json:"queries,omitempty"` - Results [][]ApiMetricData `json:"results"` -} - -type ApiQuery struct { -<<<<<<< HEAD:internal/api/api.go - Type *string `json:"type,omitempty"` - SubType *string `json:"subtype,omitempty"` - Metric string `json:"metric"` - Hostname string `json:"host"` - TypeIds []string `json:"type-ids,omitempty"` - SubTypeIds []string `json:"subtype-ids,omitempty"` - ScaleFactor util.Float `json:"scale-by,omitempty"` - Aggregate bool `json:"aggreg"` -======= - Type *string `json:"type,omitempty"` - SubType *string `json:"subtype,omitempty"` - Metric string `json:"metric"` - Hostname string `json:"host"` - TypeIds []string `json:"type-ids,omitempty"` - SubTypeIds []string `json:"subtype-ids,omitempty"` - ScaleFactor Float `json:"scale-by,omitempty"` - Aggregate bool `json:"aggreg"` ->>>>>>> main:api.go -} - -func handleQuery(rw http.ResponseWriter, r *http.Request) { - var err error - req := ApiQueryRequest{WithStats: true, WithData: true, WithPadding: true} -<<<<<<< HEAD:internal/api/api.go - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { -======= - if err = json.NewDecoder(r.Body).Decode(&req); err != nil { ->>>>>>> main:api.go - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } - - ms := memorystore.GetMemoryStore() - - response := ApiQueryResponse{ - Results: make([][]ApiMetricData, 0, len(req.Queries)), - } - if req.ForAllNodes != nil { - nodes := ms.ListChildren([]string{req.Cluster}) - for _, node := range nodes { - for _, metric := range req.ForAllNodes { - q := ApiQuery{ - Metric: metric, - Hostname: node, - } - req.Queries = append(req.Queries, q) - response.Queries = append(response.Queries, q) - } - } - } - - for _, query := range req.Queries { - sels := make([]util.Selector, 0, 1) - if query.Aggregate || query.Type == nil { - sel := util.Selector{{String: req.Cluster}, {String: query.Hostname}} - if query.Type != nil { - if len(query.TypeIds) == 1 { - sel = append(sel, util.SelectorElement{String: *query.Type + query.TypeIds[0]}) - } else { - ids := make([]string, len(query.TypeIds)) - for i, id := range query.TypeIds { - ids[i] = *query.Type + id - } - sel = append(sel, util.SelectorElement{Group: ids}) - } - - if query.SubType != nil { - if len(query.SubTypeIds) == 1 { - sel = append(sel, util.SelectorElement{String: *query.SubType + query.SubTypeIds[0]}) - } else { - ids := make([]string, len(query.SubTypeIds)) - for i, id := range query.SubTypeIds { - ids[i] = *query.SubType + id - } - sel = append(sel, util.SelectorElement{Group: ids}) - } - } - } - sels = append(sels, sel) - } else { - for _, typeId := range query.TypeIds { - if query.SubType != nil { - for _, subTypeId := range query.SubTypeIds { -<<<<<<< HEAD:internal/api/api.go - sels = append(sels, util.Selector{ -======= - sels = append(sels, Selector{ ->>>>>>> main:api.go - {String: req.Cluster}, - {String: query.Hostname}, - {String: *query.Type + typeId}, - {String: *query.SubType + subTypeId}, - }) - } - } else { - sels = append(sels, util.Selector{ - {String: req.Cluster}, - {String: query.Hostname}, - {String: *query.Type + typeId}, - }) - } - } - } - - // log.Printf("query: %#v\n", query) - // log.Printf("sels: %#v\n", sels) - - res := make([]ApiMetricData, 0, len(sels)) - for _, sel := range sels { - data := ApiMetricData{} - data.Data, data.From, data.To, err = ms.Read(sel, query.Metric, req.From, req.To) - // log.Printf("data: %#v, %#v, %#v, %#v", data.Data, data.From, data.To, err) - if err != nil { - msg := err.Error() - data.Error = &msg - res = append(res, data) - continue - } - - if req.WithStats { - data.AddStats() - } - if query.ScaleFactor != 0 { - data.ScaleBy(query.ScaleFactor) - } - if req.WithPadding { - data.PadDataWithNull(ms, req.From, req.To, query.Metric) - } - if !req.WithData { - data.Data = nil - } - res = append(res, data) - } - response.Results = append(response.Results, res) - } - - rw.Header().Set("Content-Type", "application/json") - bw := bufio.NewWriter(rw) - defer bw.Flush() - if err := json.NewEncoder(bw).Encode(response); err != nil { - log.Print(err) - return - } -} - -func handleDebug(rw http.ResponseWriter, r *http.Request) { - raw := r.URL.Query().Get("selector") - selector := []string{} - if len(raw) != 0 { - selector = strings.Split(raw, ":") - } - -<<<<<<< HEAD:internal/api/api.go - ms := memorystore.GetMemoryStore() - if err := ms.DebugDump(bufio.NewWriter(rw), selector); err != nil { - rw.WriteHeader(http.StatusBadRequest) - rw.Write([]byte(err.Error())) - } -======= - return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - authheader := r.Header.Get("Authorization") - if authheader == "" || !strings.HasPrefix(authheader, "Bearer ") { - http.Error(rw, "Use JWT Authentication", http.StatusUnauthorized) - return - } - - rawtoken := authheader[len("Bearer "):] - cacheLock.RLock() - token, ok := cache[rawtoken] - if ok && token.Valid { - cacheLock.RUnlock() - next.ServeHTTP(rw, r) - return - - } - // The actual token is ignored for now. - // In case expiration and so on are specified, the Parse function - // already returns an error for expired tokens. - var err error - token, err = jwt.Parse(rawtoken, func(t *jwt.Token) (interface{}, error) { - if t.Method != jwt.SigningMethodEdDSA { - return nil, errors.New("only Ed25519/EdDSA supported") - } - - return publicKey, nil - }) - - switch { - case token.Valid: - cacheLock.Lock() - cache[rawtoken] = token - cacheLock.Unlock() - case errors.Is(err, jwt.ErrTokenMalformed): - log.Print("That is not a token") - case errors.Is(err, jwt.ErrTokenSignatureInvalid): - log.Print("Invalid signature") - case errors.Is(err, jwt.ErrTokenExpired) || errors.Is(err, jwt.ErrTokenNotValidYet): - log.Print("Token is either expired or not active yet") - default: - log.Print("Couldn't handle this token:", err) - } - if err != nil { - http.Error(rw, err.Error(), http.StatusUnauthorized) - return - } - - // Let request through... - next.ServeHTTP(rw, r) - }) ->>>>>>> main:api.go -} - -func StartApiServer(ctx context.Context, httpConfig *config.HttpConfig) error { - r := mux.NewRouter() - - r.HandleFunc("/api/free", handleFree) - r.HandleFunc("/api/write", handleWrite) - r.HandleFunc("/api/query", handleQuery) - r.HandleFunc("/api/debug", handleDebug) - - server := &http.Server{ - Handler: r, - Addr: httpConfig.Address, - WriteTimeout: 30 * time.Second, - ReadTimeout: 30 * time.Second, - } - - if len(config.Keys.JwtPublicKey) > 0 { - buf, err := base64.StdEncoding.DecodeString(config.Keys.JwtPublicKey) - if err != nil { - return err - } - publicKey := ed25519.PublicKey(buf) - server.Handler = authentication(server.Handler, publicKey) - } - - go func() { - if httpConfig.CertFile != "" && httpConfig.KeyFile != "" { - log.Printf("API https endpoint listening on '%s'\n", httpConfig.Address) - err := server.ListenAndServeTLS(httpConfig.CertFile, httpConfig.KeyFile) - if err != nil && err != http.ErrServerClosed { - log.Println(err) - } - } else { - log.Printf("API http endpoint listening on '%s'\n", httpConfig.Address) - err := server.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - log.Println(err) - } - } - }() - - for { - <-ctx.Done() - err := server.Shutdown(context.Background()) - log.Println("API server shut down") - return err - } -}