200 lines
5.7 KiB
Go
200 lines
5.7 KiB
Go
package http
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/NYTimes/gziphandler"
|
|
"github.com/influxdata/httprouter"
|
|
"github.com/influxdata/influxdb/v2"
|
|
"github.com/influxdata/influxdb/v2/authorizer"
|
|
"github.com/influxdata/influxdb/v2/kit/platform/errors"
|
|
"github.com/influxdata/influxdb/v2/kit/tracing"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// BackupBackend is all services and associated parameters required to construct the BackupHandler.
|
|
type BackupBackend struct {
|
|
Logger *zap.Logger
|
|
errors.HTTPErrorHandler
|
|
|
|
BackupService influxdb.BackupService
|
|
SqlBackupRestoreService influxdb.SqlBackupRestoreService
|
|
BucketManifestWriter influxdb.BucketManifestWriter
|
|
}
|
|
|
|
// NewBackupBackend returns a new instance of BackupBackend.
|
|
func NewBackupBackend(b *APIBackend) *BackupBackend {
|
|
return &BackupBackend{
|
|
Logger: b.Logger.With(zap.String("handler", "backup")),
|
|
|
|
HTTPErrorHandler: b.HTTPErrorHandler,
|
|
BackupService: b.BackupService,
|
|
SqlBackupRestoreService: b.SqlBackupRestoreService,
|
|
BucketManifestWriter: b.BucketManifestWriter,
|
|
}
|
|
}
|
|
|
|
// BackupHandler is http handler for backup service.
|
|
type BackupHandler struct {
|
|
*httprouter.Router
|
|
errors.HTTPErrorHandler
|
|
Logger *zap.Logger
|
|
|
|
BackupService influxdb.BackupService
|
|
SqlBackupRestoreService influxdb.SqlBackupRestoreService
|
|
BucketManifestWriter influxdb.BucketManifestWriter
|
|
}
|
|
|
|
const (
|
|
prefixBackup = "/api/v2/backup"
|
|
backupKVStorePath = prefixBackup + "/kv"
|
|
backupShardPath = prefixBackup + "/shards/:shardID"
|
|
backupMetadataPath = prefixBackup + "/metadata"
|
|
)
|
|
|
|
// NewBackupHandler creates a new handler at /api/v2/backup to receive backup requests.
|
|
func NewBackupHandler(b *BackupBackend) *BackupHandler {
|
|
h := &BackupHandler{
|
|
HTTPErrorHandler: b.HTTPErrorHandler,
|
|
Router: NewRouter(b.HTTPErrorHandler),
|
|
Logger: b.Logger,
|
|
BackupService: b.BackupService,
|
|
SqlBackupRestoreService: b.SqlBackupRestoreService,
|
|
BucketManifestWriter: b.BucketManifestWriter,
|
|
}
|
|
|
|
h.HandlerFunc(http.MethodGet, backupKVStorePath, h.handleBackupKVStore) // Deprecated
|
|
|
|
h.Handler(http.MethodGet, backupShardPath, gziphandler.GzipHandler(http.HandlerFunc(h.handleBackupShard)))
|
|
h.Handler(http.MethodGet, backupMetadataPath, gziphandler.GzipHandler(h.requireOperPermissions(http.HandlerFunc(h.handleBackupMetadata))))
|
|
|
|
return h
|
|
}
|
|
|
|
// requireOperPermissions returns an "unauthorized" response for requests that do not have OperPermissions.
|
|
// This is needed for the handleBackupMetadata handler, which sets a header prior to
|
|
// accessing any methods on the BackupService which would also return an "authorized" response.
|
|
func (h *BackupHandler) requireOperPermissions(next http.Handler) http.HandlerFunc {
|
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
if err := authorizer.IsAllowedAll(ctx, influxdb.OperPermissions()); err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
}
|
|
return http.HandlerFunc(fn)
|
|
}
|
|
|
|
func (h *BackupHandler) handleBackupKVStore(w http.ResponseWriter, r *http.Request) {
|
|
span, r := tracing.ExtractFromHTTPRequest(r, "BackupHandler.handleBackupKVStore")
|
|
defer span.Finish()
|
|
|
|
ctx := r.Context()
|
|
|
|
if err := h.BackupService.BackupKVStore(ctx, w); err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (h *BackupHandler) handleBackupShard(w http.ResponseWriter, r *http.Request) {
|
|
span, r := tracing.ExtractFromHTTPRequest(r, "BackupHandler.handleBackupShard")
|
|
defer span.Finish()
|
|
|
|
ctx := r.Context()
|
|
|
|
params := httprouter.ParamsFromContext(ctx)
|
|
shardID, err := strconv.ParseUint(params.ByName("shardID"), 10, 64)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
var since time.Time
|
|
if s := r.URL.Query().Get("since"); s != "" {
|
|
if since, err = time.ParseInLocation(time.RFC3339, s, time.UTC); err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
}
|
|
|
|
if err := h.BackupService.BackupShard(ctx, w, shardID, since); err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (h *BackupHandler) handleBackupMetadata(w http.ResponseWriter, r *http.Request) {
|
|
span, r := tracing.ExtractFromHTTPRequest(r, "BackupHandler.handleBackupMetadata")
|
|
defer span.Finish()
|
|
|
|
ctx := r.Context()
|
|
|
|
// Lock the sqlite and bolt databases prior to writing the response to prevent
|
|
// data inconsistencies.
|
|
h.BackupService.RLockKVStore()
|
|
defer h.BackupService.RUnlockKVStore()
|
|
|
|
h.SqlBackupRestoreService.RLockSqlStore()
|
|
defer h.SqlBackupRestoreService.RUnlockSqlStore()
|
|
|
|
dataWriter := multipart.NewWriter(w)
|
|
w.Header().Set("Content-Type", "multipart/mixed; boundary="+dataWriter.Boundary())
|
|
|
|
parts := []struct {
|
|
contentType string
|
|
contentDisposition string
|
|
writeFn func(io.Writer) error
|
|
}{
|
|
{
|
|
"application/octet-stream",
|
|
fmt.Sprintf("attachment; name=%q", "kv"),
|
|
func(fw io.Writer) error {
|
|
return h.BackupService.BackupKVStore(ctx, fw)
|
|
},
|
|
},
|
|
{
|
|
"application/octet-stream",
|
|
fmt.Sprintf("attachment; name=%q", "sql"),
|
|
func(fw io.Writer) error {
|
|
return h.SqlBackupRestoreService.BackupSqlStore(ctx, fw)
|
|
},
|
|
},
|
|
{
|
|
"application/json; charset=utf-8",
|
|
fmt.Sprintf("attachment; name=%q", "buckets"),
|
|
func(fw io.Writer) error {
|
|
return h.BucketManifestWriter.WriteManifest(ctx, fw)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, p := range parts {
|
|
pw, err := dataWriter.CreatePart(map[string][]string{
|
|
"Content-Type": {p.contentType},
|
|
"Content-Disposition": {p.contentDisposition},
|
|
})
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
if err := p.writeFn(pw); err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
}
|
|
|
|
if err := dataWriter.Close(); err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
}
|