Add /v3/cluster/snapshot endpoint

This commit is contained in:
Ingo Oppermann
2023-06-27 15:11:31 +02:00
parent dd128ac99b
commit 89379b2acd
10 changed files with 110 additions and 13 deletions

View File

@@ -242,7 +242,13 @@ func (a *api) RemoveServer(c echo.Context) error {
// @Success 500 {array} Error
// @Router /v1/snapshot [get]
func (a *api) Snapshot(c echo.Context) error {
data, err := a.cluster.Snapshot()
origin := c.Request().Header.Get("X-Cluster-Origin")
if origin == a.id {
return Err(http.StatusLoopDetected, "", "breaking circuit")
}
data, err := a.cluster.Snapshot(origin)
if err != nil {
a.logger.Debug().WithError(err).Log("Unable to create snaphot")
return Err(http.StatusInternalServerError, "", "unable to create snapshot: %s", err.Error())

View File

@@ -249,8 +249,8 @@ func (c *APIClient) UnsetKV(origin string, key string) error {
return err
}
func (c *APIClient) Snapshot() (io.ReadCloser, error) {
return c.stream(http.MethodGet, "/v1/snapshot", "", nil, "")
func (c *APIClient) Snapshot(origin string) (io.ReadCloser, error) {
return c.stream(http.MethodGet, "/v1/snapshot", "", nil, origin)
}
func (c *APIClient) IsReady(origin string) error {

View File

@@ -48,7 +48,7 @@ type Cluster interface {
Join(origin, id, raftAddress, peerAddress string) error
Leave(origin, id string) error // gracefully remove a node from the cluster
Snapshot() (io.ReadCloser, error)
Snapshot(origin string) (io.ReadCloser, error)
Shutdown() error
@@ -827,10 +827,10 @@ func (c *cluster) Join(origin, id, raftAddress, peerAddress string) error {
return nil
}
func (c *cluster) Snapshot() (io.ReadCloser, error) {
func (c *cluster) Snapshot(origin string) (io.ReadCloser, error) {
if !c.IsRaftLeader() {
c.logger.Debug().Log("Not leader, forwarding to leader")
return c.forwarder.Snapshot()
return c.forwarder.Snapshot(origin)
}
return c.raft.Snapshot()

View File

@@ -20,7 +20,7 @@ type Forwarder interface {
Join(origin, id, raftAddress, peerAddress string) error
Leave(origin, id string) error
Snapshot() (io.ReadCloser, error)
Snapshot(origin string) (io.ReadCloser, error)
AddProcess(origin string, config *app.Config) error
UpdateProcess(origin string, id app.ProcessID, config *app.Config) error
@@ -140,12 +140,12 @@ func (f *forwarder) Leave(origin, id string) error {
return client.Leave(origin, id)
}
func (f *forwarder) Snapshot() (io.ReadCloser, error) {
func (f *forwarder) Snapshot(origin string) (io.ReadCloser, error) {
f.lock.RLock()
client := f.client
f.lock.RUnlock()
return client.Snapshot()
return client.Snapshot(origin)
}
func (f *forwarder) AddProcess(origin string, config *app.Config) error {

View File

@@ -2,7 +2,8 @@ package raft
import (
"bytes"
"encoding/gob"
"encoding/base64"
"encoding/json"
"fmt"
"io"
gonet "net"
@@ -287,7 +288,7 @@ func (rcw *readCloserWrapper) Close() error {
type Snapshot struct {
Metadata *hcraft.SnapshotMeta
Data []byte
Data string
}
func (r *raft) Snapshot() (io.ReadCloser, error) {
@@ -311,11 +312,11 @@ func (r *raft) Snapshot() (io.ReadCloser, error) {
snapshot := Snapshot{
Metadata: metadata,
Data: data,
Data: base64.StdEncoding.EncodeToString(data),
}
buffer := bytes.Buffer{}
enc := gob.NewEncoder(&buffer)
enc := json.NewEncoder(&buffer)
err = enc.Encode(snapshot)
if err != nil {
return nil, err

View File

@@ -1441,6 +1441,32 @@ const docTemplate = `{
}
}
},
"/api/v3/cluster/snapshot": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Retrieve snapshot of the cluster DB",
"produces": [
"application/octet-stream"
],
"tags": [
"v16.?.?"
],
"summary": "Retrieve snapshot of the cluster DB",
"operationId": "cluster-3-snapshot",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "file"
}
}
}
}
},
"/api/v3/config": {
"get": {
"security": [

View File

@@ -1433,6 +1433,32 @@
}
}
},
"/api/v3/cluster/snapshot": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Retrieve snapshot of the cluster DB",
"produces": [
"application/octet-stream"
],
"tags": [
"v16.?.?"
],
"summary": "Retrieve snapshot of the cluster DB",
"operationId": "cluster-3-snapshot",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "file"
}
}
}
}
},
"/api/v3/config": {
"get": {
"security": [

View File

@@ -3323,6 +3323,22 @@ paths:
summary: Add JSON metadata with a process under the given key
tags:
- v16.?.?
/api/v3/cluster/snapshot:
get:
description: Retrieve snapshot of the cluster DB
operationId: cluster-3-snapshot
produces:
- application/octet-stream
responses:
"200":
description: OK
schema:
type: file
security:
- ApiKeyAuth: []
summary: Retrieve snapshot of the cluster DB
tags:
- v16.?.?
/api/v3/config:
get:
description: Retrieve the currently active Restreamer configuration

View File

@@ -1291,3 +1291,23 @@ func (h *ClusterHandler) ListStoreKV(c echo.Context) error {
return c.JSON(http.StatusOK, kvs)
}
// GetSnapshot returns a current snapshot of the cluster DB
// @Summary Retrieve snapshot of the cluster DB
// @Description Retrieve snapshot of the cluster DB
// @Tags v16.?.?
// @ID cluster-3-snapshot
// @Produce application/octet-stream
// @Success 200 {file} byte
// @Security ApiKeyAuth
// @Router /api/v3/cluster/snapshot [get]
func (h *ClusterHandler) GetSnapshot(c echo.Context) error {
r, err := h.cluster.Snapshot("")
if err != nil {
return api.Err(http.StatusInternalServerError, "", "failed to retrieve snapshot: %w", err)
}
defer r.Close()
return c.Stream(http.StatusOK, "application/octet-stream", r)
}

View File

@@ -675,6 +675,8 @@ func (s *server) setRoutesV3(v3 *echo.Group) {
if s.v3handler.cluster != nil {
v3.GET("/cluster", s.v3handler.cluster.About)
v3.GET("/cluster/snapshot", s.v3handler.cluster.GetSnapshot)
v3.GET("/cluster/db/process", s.v3handler.cluster.ListStoreProcesses)
v3.GET("/cluster/db/user", s.v3handler.cluster.ListStoreIdentities)
v3.GET("/cluster/db/user/:name", s.v3handler.cluster.ListStoreIdentity)