Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: backup and restore sqlite database #21584

Merged
merged 10 commits into from
Jun 2, 2021
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ vendor
# binary databases
influxd.bolt
*.db
*.sqlite

# GPG private keys
private.key
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ This release adds an embedded SQLite database for storing metadata required by t
1. [21543](https://github.com/influxdata/influxdb/pull/21543): Added `influxd` configuration flag `--sqlite-path` for specifying a user-defined path to the SQLite database file
1. [21543](https://github.com/influxdata/influxdb/pull/21543): Updated `influxd` configuration flag `--store` to work with string values `disk` or `memory`. Memory continues to store metadata in-memory for testing; `disk` will persist metadata to disk via bolt and SQLite
1. [21547](https://github.com/influxdata/influxdb/pull/21547): Allow hiding the tooltip independently of the static legend
1. [21584](https://github.com/influxdata/influxdb/pull/21584): Added the `api/v2/backup/metadata` endpoint for backing up both KV and SQL metadata, and the `api/v2/restore/sql` for restoring SQL metadata.

### Bug Fixes

Expand Down
13 changes: 13 additions & 0 deletions authorizer/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,16 @@ func (b BackupService) BackupShard(ctx context.Context, w io.Writer, shardID uin
}
return b.s.BackupShard(ctx, w, shardID, since)
}

// The Lock and Unlock methods below do not have authorization checks and should only be used
// when appropriate authorization has already been confirmed, such as behind a middleware. They
// are intended to be used for coordinating the locking and unlocking of the kv and sql metadata
// databases during a backup. They are made available here to allow the calls to pass-through to the
// underlying service.
func (b BackupService) LockKVStore() {
b.s.LockKVStore()
}

func (b BackupService) UnlockKVStore() {
b.s.UnlockKVStore()
}
57 changes: 57 additions & 0 deletions authorizer/sql_backup_restore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package authorizer

import (
"context"
"io"

"github.com/influxdata/influxdb/v2"
"github.com/influxdata/influxdb/v2/kit/tracing"
)

var _ influxdb.SqlBackupRestoreService = (*SqlBackupRestoreService)(nil)

// SqlBackupRestoreService wraps a influxdb.SqlBackupRestoreService and authorizes actions
// against it appropriately.
type SqlBackupRestoreService struct {
s influxdb.SqlBackupRestoreService
}

// NewSqlBackupRestoreService constructs an instance of an authorizing backup service.
func NewSqlBackupRestoreService(s influxdb.SqlBackupRestoreService) *SqlBackupRestoreService {
return &SqlBackupRestoreService{
s: s,
}
}

func (s SqlBackupRestoreService) BackupSqlStore(ctx context.Context, w io.Writer) error {
span, ctx := tracing.StartSpanFromContext(ctx)
defer span.Finish()

if err := IsAllowedAll(ctx, influxdb.OperPermissions()); err != nil {
return err
}
return s.s.BackupSqlStore(ctx, w)
}

func (s SqlBackupRestoreService) RestoreSqlStore(ctx context.Context, r io.Reader) error {
span, ctx := tracing.StartSpanFromContext(ctx)
defer span.Finish()

if err := IsAllowedAll(ctx, influxdb.OperPermissions()); err != nil {
return err
}
return s.s.RestoreSqlStore(ctx, r)
}

// The Lock and Unlock methods below do not have authorization checks and should only be used
// when appropriate authorization has already been confirmed, such as behind a middleware. They
// are intended to be used for coordinating the locking and unlocking of the kv and sql metadata
// databases during a backup. They are made available here to allow the calls to pass-through to the
// underlying service.
func (s SqlBackupRestoreService) LockSqlStore() {
s.s.LockSqlStore()
}

func (s SqlBackupRestoreService) UnlockSqlStore() {
s.s.UnlockSqlStore()
}
104 changes: 104 additions & 0 deletions authorizer/sql_backup_restore_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package authorizer_test

import (
"bytes"
"context"
"testing"

"github.com/golang/mock/gomock"
"github.com/influxdata/influxdb/v2/kit/platform/errors"
"github.com/stretchr/testify/require"

"github.com/influxdata/influxdb/v2"
"github.com/influxdata/influxdb/v2/authorizer"
influxdbcontext "github.com/influxdata/influxdb/v2/context"
"github.com/influxdata/influxdb/v2/mock"
)

func Test_BackupSqlStore(t *testing.T) {
t.Parallel()

tests := []struct {
name string
permList []influxdb.Permission
wantErr error
}{
{
"authorized to do the backup",
influxdb.OperPermissions(),
nil,
},
{
"not authorized to do the backup",
influxdb.ReadAllPermissions(),
&errors.Error{
Msg: "write:authorizations is unauthorized",
Code: errors.EUnauthorized,
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrlr := gomock.NewController(t)
svc := mock.NewMockSqlBackupRestoreService(ctrlr)
s := authorizer.NewSqlBackupRestoreService(svc)

w := bytes.NewBuffer([]byte{})

if tt.wantErr == nil {
svc.EXPECT().
BackupSqlStore(gomock.Any(), w).
Return(nil)
}

ctx := influxdbcontext.SetAuthorizer(context.Background(), mock.NewMockAuthorizer(false, tt.permList))
err := s.BackupSqlStore(ctx, w)
require.Equal(t, tt.wantErr, err)
})
}
}

func Test_RestoreSqlStore(t *testing.T) {
t.Parallel()

tests := []struct {
name string
permList []influxdb.Permission
wantErr error
}{
{
"authorized to do the restore",
influxdb.OperPermissions(),
nil,
},
{
"not authorized to do the restore",
influxdb.ReadAllPermissions(),
&errors.Error{
Msg: "write:authorizations is unauthorized",
Code: errors.EUnauthorized,
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrlr := gomock.NewController(t)
svc := mock.NewMockSqlBackupRestoreService(ctrlr)
s := authorizer.NewSqlBackupRestoreService(svc)

w := bytes.NewBuffer([]byte{})

if tt.wantErr == nil {
svc.EXPECT().
RestoreSqlStore(gomock.Any(), w).
Return(nil)
}

ctx := influxdbcontext.SetAuthorizer(context.Background(), mock.NewMockAuthorizer(false, tt.permList))
err := s.RestoreSqlStore(ctx, w)
require.Equal(t, tt.wantErr, err)
})
}
}
21 changes: 21 additions & 0 deletions backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,27 @@ type BackupService interface {

// BackupShard downloads a backup file for a single shard.
BackupShard(ctx context.Context, w io.Writer, shardID uint64, since time.Time) error

// LockKVStore locks the database.
LockKVStore()

// UnlockKVStore unlocks the database.
UnlockKVStore()
}

// SqlBackupRestoreService represents the backup and restore functions for the sqlite database.
type SqlBackupRestoreService interface {
// BackupSqlStore creates a live backup copy of the sqlite database.
BackupSqlStore(ctx context.Context, w io.Writer) error

// RestoreSqlStore restores & replaces the sqlite database.
RestoreSqlStore(ctx context.Context, r io.Reader) error

// LockSqlStore locks the database.
LockSqlStore()

// UnlockSqlStore unlocks the database.
UnlockSqlStore()
}

// RestoreService represents the data restore functions of InfluxDB.
Expand Down
10 changes: 10 additions & 0 deletions bolt/kv.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ func (s *KVStore) Close() error {
return nil
}

// LockKVStore locks the database for reading during a backup.
func (s *KVStore) Lock() {
s.mu.RLock()
}

// UnlockKVStore removes the read lock used during a backup.
func (s *KVStore) Unlock() {
s.mu.RUnlock()
}

// DB returns a reference to the current Bolt database.
func (s *KVStore) DB() *bolt.DB {
s.mu.RLock()
Expand Down
8 changes: 8 additions & 0 deletions cmd/influxd/launcher/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,14 @@ func (t *TemporaryEngine) BackupKVStore(ctx context.Context, w io.Writer) error
return t.engine.BackupKVStore(ctx, w)
}

func (t *TemporaryEngine) LockKVStore() {
t.engine.LockKVStore()
}

func (t *TemporaryEngine) UnlockKVStore() {
t.engine.UnlockKVStore()
}

func (t *TemporaryEngine) RestoreKVStore(ctx context.Context, r io.Reader) error {
return t.engine.RestoreKVStore(ctx, r)
}
Expand Down
13 changes: 7 additions & 6 deletions cmd/influxd/launcher/launcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -771,12 +771,13 @@ func (m *Launcher) run(ctx context.Context, opts *InfluxdOpts) (err error) {
BucketFinder: ts.BucketService,
LogBucketName: platform.MonitoringSystemBucketName,
},
DeleteService: deleteService,
BackupService: backupService,
RestoreService: restoreService,
AuthorizationService: authSvc,
AuthorizationV1Service: authSvcV1,
PasswordV1Service: passwordV1,
DeleteService: deleteService,
BackupService: backupService,
SqlBackupRestoreService: m.sqlStore,
RestoreService: restoreService,
AuthorizationService: authSvc,
AuthorizationV1Service: authSvcV1,
PasswordV1Service: passwordV1,
AuthorizerV1: &authv1.Authorizer{
AuthV1: authSvcV1,
AuthV2: authSvc,
Expand Down
3 changes: 3 additions & 0 deletions http/api_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ type APIBackend struct {
PointsWriter storage.PointsWriter
DeleteService influxdb.DeleteService
BackupService influxdb.BackupService
SqlBackupRestoreService influxdb.SqlBackupRestoreService
RestoreService influxdb.RestoreService
AuthorizationService influxdb.AuthorizationService
AuthorizationV1Service influxdb.AuthorizationService
Expand Down Expand Up @@ -199,10 +200,12 @@ func NewAPIHandler(b *APIBackend, opts ...APIHandlerOptFn) *APIHandler {

backupBackend := NewBackupBackend(b)
backupBackend.BackupService = authorizer.NewBackupService(backupBackend.BackupService)
backupBackend.SqlBackupRestoreService = authorizer.NewSqlBackupRestoreService(backupBackend.SqlBackupRestoreService)
h.Mount(prefixBackup, NewBackupHandler(backupBackend))

restoreBackend := NewRestoreBackend(b)
restoreBackend.RestoreService = authorizer.NewRestoreService(restoreBackend.RestoreService)
restoreBackend.SqlBackupRestoreService = authorizer.NewSqlBackupRestoreService(restoreBackend.SqlBackupRestoreService)
h.Mount(prefixRestore, NewRestoreHandler(restoreBackend))

h.Mount(dbrp.PrefixDBRP, dbrp.NewHTTPHandler(b.Logger, b.DBRPService, b.OrganizationService))
Expand Down
Loading