Skip to content

Commit

Permalink
Add backup endpoint for SQLite renters (#1580)
Browse files Browse the repository at this point in the history
Closes #1550

Adds:
- endpoint to back up sqlite database to a file
- cli cmd to back up a sqlite database
  • Loading branch information
ChrisSchinnerl authored Oct 23, 2024
2 parents bc5d9dc + 2ae9a08 commit 609d9c3
Show file tree
Hide file tree
Showing 11 changed files with 375 additions and 112 deletions.
7 changes: 7 additions & 0 deletions api/bus.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ var (
ErrInvalidLimit = errors.New("limit must be -1 or bigger")
ErrMarkerNotFound = errors.New("marker not found")
ErrMaxFundAmountExceeded = errors.New("renewal exceeds max fund amount")
ErrInvalidDatabase = errors.New("invalid database type")
ErrBackupNotSupported = errors.New("backups not supported for used database")
ErrExplorerDisabled = errors.New("explorer is disabled")
)

Expand Down Expand Up @@ -63,6 +65,11 @@ type (
Accounts []Account `json:"accounts"`
}

BackupRequest struct {
Database string `json:"database"`
Path string `json:"path"`
}

// BusStateResponse is the response type for the /bus/state endpoint.
BusStateResponse struct {
StartTime TimeRFC3339 `json:"startTime"`
Expand Down
53 changes: 25 additions & 28 deletions bus/bus.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ type (
Store interface {
AccountStore
AutopilotStore
BackupStore
ChainStore
HostStore
MetadataStore
Expand All @@ -186,6 +187,11 @@ type (
UpdateAutopilot(ctx context.Context, ap api.Autopilot) error
}

// BackupStore is the interface of a store that can be backed up.
BackupStore interface {
Backup(ctx context.Context, dbID, dst string) error
}

// A ChainStore stores information about the chain.
ChainStore interface {
ChainIndex(ctx context.Context) (types.ChainIndex, error)
Expand Down Expand Up @@ -317,13 +323,7 @@ type Bus struct {
cs ChainSubscriber
s Syncer
w Wallet

accounts AccountStore
as AutopilotStore
hs HostStore
ms MetadataStore
mtrcs MetricsStore
ss SettingStore
store Store

rhp2 *rhp2.Client
rhp3 *rhp3.Client
Expand All @@ -344,16 +344,11 @@ func New(ctx context.Context, masterKey [32]byte, am AlertManager, wm WebhooksMa
startTime: time.Now(),
masterKey: masterKey,

accounts: store,
explorer: ibus.NewExplorer(explorerURL),
s: s,
cm: cm,
w: w,
hs: store,
as: store,
ms: store,
mtrcs: store,
ss: store,
explorer: ibus.NewExplorer(explorerURL),
store: store,

alerts: alerts.WithOrigin(am, "bus"),
alertMgr: am,
Expand Down Expand Up @@ -500,6 +495,8 @@ func (b *Bus) Handler() http.Handler {
"POST /syncer/connect": b.syncerConnectHandler,
"GET /syncer/peers": b.syncerPeersHandler,

"POST /system/sqlite3/backup": b.postSystemSQLite3BackupHandler,

"GET /txpool/recommendedfee": b.txpoolFeeHandler,
"GET /txpool/transactions": b.txpoolTransactionsHandler,
"POST /txpool/broadcast": b.txpoolBroadcastHandler,
Expand Down Expand Up @@ -532,7 +529,7 @@ func (b *Bus) Shutdown(ctx context.Context) error {
}

func (b *Bus) addContract(ctx context.Context, rev rhpv2.ContractRevision, contractPrice, initialRenterFunds types.Currency, startHeight uint64, state string) (api.ContractMetadata, error) {
if err := b.ms.PutContract(ctx, api.ContractMetadata{
if err := b.store.PutContract(ctx, api.ContractMetadata{
ID: rev.ID(),
HostKey: rev.HostKey(),
StartHeight: startHeight,
Expand All @@ -545,7 +542,7 @@ func (b *Bus) addContract(ctx context.Context, rev rhpv2.ContractRevision, contr
return api.ContractMetadata{}, err
}

added, err := b.ms.Contract(ctx, rev.ID())
added, err := b.store.Contract(ctx, rev.ID())
if err != nil {
return api.ContractMetadata{}, err
}
Expand All @@ -563,7 +560,7 @@ func (b *Bus) addContract(ctx context.Context, rev rhpv2.ContractRevision, contr
}

func (b *Bus) addRenewal(ctx context.Context, renewedFrom types.FileContractID, rev rhpv2.ContractRevision, contractPrice, initialRenterFunds types.Currency, startHeight uint64, state string) (api.ContractMetadata, error) {
if err := b.ms.AddRenewal(ctx, api.ContractMetadata{
if err := b.store.AddRenewal(ctx, api.ContractMetadata{
ID: rev.ID(),
HostKey: rev.HostKey(),
RenewedFrom: renewedFrom,
Expand All @@ -577,7 +574,7 @@ func (b *Bus) addRenewal(ctx context.Context, renewedFrom types.FileContractID,
return api.ContractMetadata{}, fmt.Errorf("couldn't add renewal: %w", err)
}

renewal, err := b.ms.Contract(ctx, rev.ID())
renewal, err := b.store.Contract(ctx, rev.ID())
if err != nil {
return api.ContractMetadata{}, err
}
Expand Down Expand Up @@ -608,7 +605,7 @@ func (b *Bus) broadcastContract(ctx context.Context, fcid types.FileContractID)
}()

// fetch contract
c, err := b.ms.Contract(ctx, fcid)
c, err := b.store.Contract(ctx, fcid)
if err != nil {
return types.TransactionID{}, fmt.Errorf("couldn't fetch contract; %w", err)
}
Expand Down Expand Up @@ -659,23 +656,23 @@ func (b *Bus) broadcastContract(ctx context.Context, fcid types.FileContractID)
func (b *Bus) fetchSetting(ctx context.Context, key string) (interface{}, error) {
switch key {
case stores.SettingGouging:
gs, err := b.ss.GougingSettings(ctx)
gs, err := b.store.GougingSettings(ctx)
if errors.Is(err, sql.ErrSettingNotFound) {
return api.DefaultGougingSettings, nil
} else if err != nil {
return nil, err
}
return gs, nil
case stores.SettingPinned:
ps, err := b.ss.PinnedSettings(ctx)
ps, err := b.store.PinnedSettings(ctx)
if errors.Is(err, sql.ErrSettingNotFound) {
ps = api.DefaultPinnedSettings
} else if err != nil {
return nil, err
}

// populate the Autopilots map with the current autopilots
aps, err := b.as.Autopilots(ctx)
aps, err := b.store.Autopilots(ctx)
if err != nil {
return nil, fmt.Errorf("failed to fetch autopilots: %w", err)
}
Expand All @@ -689,15 +686,15 @@ func (b *Bus) fetchSetting(ctx context.Context, key string) (interface{}, error)
}
return ps, nil
case stores.SettingUpload:
us, err := b.ss.UploadSettings(ctx)
us, err := b.store.UploadSettings(ctx)
if errors.Is(err, sql.ErrSettingNotFound) {
return api.DefaultUploadSettings(b.cm.TipState().Network.Name), nil
} else if err != nil {
return nil, err
}
return us, nil
case stores.SettingS3:
s3s, err := b.ss.S3Settings(ctx)
s3s, err := b.store.S3Settings(ctx)
if errors.Is(err, sql.ErrSettingNotFound) {
return api.DefaultS3Settings, nil
} else if err != nil {
Expand Down Expand Up @@ -918,7 +915,7 @@ func (b *Bus) updateSetting(ctx context.Context, key string, value any) error {
panic("invalid type") // developer error
}
gs := value.(api.GougingSettings)
if err := b.ss.UpdateGougingSettings(ctx, gs); err != nil {
if err := b.store.UpdateGougingSettings(ctx, gs); err != nil {
return fmt.Errorf("failed to update gouging settings: %w", err)
}
payload = api.EventSettingUpdate{
Expand All @@ -932,7 +929,7 @@ func (b *Bus) updateSetting(ctx context.Context, key string, value any) error {
panic("invalid type") // developer error
}
ps := value.(api.PinnedSettings)
if err := b.ss.UpdatePinnedSettings(ctx, ps); err != nil {
if err := b.store.UpdatePinnedSettings(ctx, ps); err != nil {
return fmt.Errorf("failed to update pinned settings: %w", err)
}
payload = api.EventSettingUpdate{
Expand All @@ -946,7 +943,7 @@ func (b *Bus) updateSetting(ctx context.Context, key string, value any) error {
panic("invalid type") // developer error
}
us := value.(api.UploadSettings)
if err := b.ss.UpdateUploadSettings(ctx, us); err != nil {
if err := b.store.UpdateUploadSettings(ctx, us); err != nil {
return fmt.Errorf("failed to update upload settings: %w", err)
}
payload = api.EventSettingUpdate{
Expand All @@ -959,7 +956,7 @@ func (b *Bus) updateSetting(ctx context.Context, key string, value any) error {
panic("invalid type") // developer error
}
s3s := value.(api.S3Settings)
if err := b.ss.UpdateS3Settings(ctx, s3s); err != nil {
if err := b.store.UpdateS3Settings(ctx, s3s); err != nil {
return fmt.Errorf("failed to update S3 settings: %w", err)
}
payload = api.EventSettingUpdate{
Expand Down
10 changes: 10 additions & 0 deletions bus/client/client.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package client

import (
"context"

"go.sia.tech/jape"
"go.sia.tech/renterd/api"
)
Expand All @@ -18,6 +20,14 @@ func New(addr, password string) *Client {
}}
}

func (c *Client) Backup(ctx context.Context, database, dstPath string) (err error) {
err = c.c.WithContext(ctx).POST("/system/sqlite3/backup", api.BackupRequest{
Database: database,
Path: dstPath,
}, nil)
return
}

// State returns the current state of the bus.
func (c *Client) State() (state api.BusStateResponse, err error) {
err = c.c.GET("/state", &state)
Expand Down
Loading

0 comments on commit 609d9c3

Please sign in to comment.