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

Add pre- and post-exec commands. #49

Merged
merged 8 commits into from
Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/docker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ jobs:
[ "${GITHUB_EVENT_NAME}" == 'release' ] && echo "tag=${GITHUB_REF##*/}" >> $GITHUB_ENV || true
[ "${GITHUB_EVENT_NAME}" == 'push' ] && echo "tag=latest" >> $GITHUB_ENV || true

- name: Build
run:
make all

- name: Build and push image
uses: docker/build-push-action@v4
with:
Expand Down
27 changes: 5 additions & 22 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,29 +1,12 @@
FROM golang:1.21 as builder
WORKDIR /work
COPY .git .git
COPY api api
COPY cmd cmd
COPY go.mod .
COPY go.sum .
COPY Makefile .
RUN make

# TODO: remove tini in a future release, not required anymore since pre- and post-exec-cmd flags
FROM krallin/ubuntu-tini as ubuntu-tini

# rethinkdb backup/restore requires the python client library
# let's make small binaries of these commands in order not to blow up the image size
FROM rethinkdb:2.4.1 as rethinkdb-python-client-builder
WORKDIR /work
RUN apt update && apt install -y python3-pip
RUN pip3 install pyinstaller==4.3.0 rethinkdb
COPY build/rethinkdb-dump.spec rethinkdb-dump.spec
COPY build/rethinkdb-restore.spec rethinkdb-restore.spec
RUN pyinstaller rethinkdb-dump.spec \
&& pyinstaller rethinkdb-restore.spec
FROM ghcr.io/metal-stack/rethinkdb-backup-tools-build:v2.4.1 as rethinkdb-backup-tools

FROM alpine:3.18
# TODO: remove tini in a future release, not required anymore since pre- and post-exec-cmd flags
RUN apk add --no-cache tini ca-certificates
COPY --from=builder /work/bin/backup-restore-sidecar /backup-restore-sidecar
COPY bin/backup-restore-sidecar /backup-restore-sidecar
COPY --from=ubuntu-tini /usr/local/bin/tini-static /ubuntu/tini
COPY --from=rethinkdb-python-client-builder /work/dist/rethinkdb-dump /work/dist/rethinkdb-restore /rethinkdb/
COPY --from=rethinkdb-backup-tools /rethinkdb-dump /rethinkdb-restore /rethinkdb/
CMD ["/backup-restore-sidecar"]
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ BACKUP_PROVIDER := $(or ${BACKUP_PROVIDER},local)

SHA := $(shell git rev-parse --short=8 HEAD)
GITVERSION := $(shell git describe --long --all)
BUILDDATE := $(shell GO111MODULE=off go run ${COMMONDIR}/time.go)
BUILDDATE := $(shell date --rfc-3339=seconds)
VERSION := $(or ${VERSION},$(shell git describe --tags --exact-match 2> /dev/null || git symbolic-ref -q --short HEAD || git rev-parse --short HEAD))

GO111MODULE := on
Expand All @@ -25,7 +25,7 @@ proto:
make -C proto protoc

.PHONY: dockerimage
dockerimage:
dockerimage: all
docker build -t ghcr.io/metal-stack/backup-restore-sidecar:${DOCKER_TAG} .

.PHONY: dockerpush
Expand Down
28 changes: 0 additions & 28 deletions build/rethinkdb-dump.spec

This file was deleted.

28 changes: 0 additions & 28 deletions build/rethinkdb-restore.spec

This file was deleted.

5 changes: 3 additions & 2 deletions cmd/internal/backup/backup.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package backup

import (
"context"
"os"
"path"

Expand All @@ -14,7 +15,7 @@ import (
)

// Start starts the backup component, which is periodically taking backups of the database
func Start(log *zap.SugaredLogger, backupSchedule string, db database.DatabaseProber, bp backuproviders.BackupProvider, metrics *metrics.Metrics, comp *compress.Compressor, stop <-chan struct{}) error {
func Start(ctx context.Context, log *zap.SugaredLogger, backupSchedule string, db database.DatabaseProber, bp backuproviders.BackupProvider, metrics *metrics.Metrics, comp *compress.Compressor) error {
log.Info("database is now available, starting periodic backups")

c := cron.New()
Expand Down Expand Up @@ -70,7 +71,7 @@ func Start(log *zap.SugaredLogger, backupSchedule string, db database.DatabasePr

c.Start()
log.Infow("scheduling next backup", "at", c.Entry(id).Next.String())
<-stop
<-ctx.Done()
c.Stop()
return nil
}
22 changes: 16 additions & 6 deletions cmd/internal/database/rethinkdb/rethinkdb.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package rethinkdb

import (
"context"
"fmt"
"net"
"os"
Expand Down Expand Up @@ -40,11 +41,13 @@ type RethinkDB struct {
passwordFile string
log *zap.SugaredLogger
executor *utils.CmdExecutor
ctx context.Context
}

// New instantiates a new rethinkdb database
func New(log *zap.SugaredLogger, datadir string, url string, passwordFile string) *RethinkDB {
func New(ctx context.Context, log *zap.SugaredLogger, datadir string, url string, passwordFile string) *RethinkDB {
return &RethinkDB{
ctx: ctx,
log: log,
datadir: datadir,
url: url,
Expand Down Expand Up @@ -125,13 +128,18 @@ func (db *RethinkDB) Recover() error {
}()

db.log.Infow("waiting for rethinkdb database to come up")
restoreDB := New(db.log, db.datadir, "localhost:1", "")
stop := make(chan struct{})

restoreDB := New(db.ctx, db.log, db.datadir, "localhost:1", "")

done := make(chan bool)
defer close(done)

probeCtx, probeCancel := context.WithTimeout(context.Background(), restoreDatabaseStartupTimeout)
defer probeCancel()

var err error
go func() {
err = probe.Start(restoreDB.log, restoreDB, stop)
err = probe.Start(probeCtx, restoreDB.log, restoreDB)
done <- true
}()
select {
Expand All @@ -140,15 +148,17 @@ func (db *RethinkDB) Recover() error {
return fmt.Errorf("error while probing: %w", err)
}
db.log.Infow("rethinkdb in sidecar is now available, now triggering restore commands...")
case <-time.After(restoreDatabaseStartupTimeout):
close(stop)
case <-probeCtx.Done():
return errors.New("rethinkdb database did not come up in time")
}

args := []string{}
if db.url != "" {
args = append(args, "--connect="+restoreDB.url)
}
if db.passwordFile != "" {
args = append(args, "--password-file="+db.passwordFile)
}
Comment on lines +159 to +161
Copy link
Contributor Author

@Gerrit91 Gerrit91 Aug 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried out the RethinkDB restore and forced the restore by running it manually from the CLI in the backup-restore-sidecar pod.

Before that:

{"level":"info","timestamp":"2023-08-24T13:32:02Z","logger":"rethinkdb","caller":"rethinkdb/rethinkdb.go:120","msg":"starting rethinkdb database within sidecar for restore"}                                                                     
{"level":"info","timestamp":"2023-08-24T13:32:02Z","logger":"rethinkdb","caller":"rethinkdb/rethinkdb.go:130","msg":"waiting for rethinkdb database to come up"}                                                                                  
{"level":"info","timestamp":"2023-08-24T13:32:02Z","logger":"rethinkdb","caller":"probe/probe.go:18","msg":"start probing database"}                                                                                                              
{"level":"info","timestamp":"2023-08-24T13:32:05Z","logger":"rethinkdb","caller":"rethinkdb/rethinkdb.go:150","msg":"rethinkdb in sidecar is now available, now triggering restore commands..."}                                                  
{"level":"info","timestamp":"2023-08-24T13:32:05Z","logger":"rethinkdb","caller":"utils/cmd.go:28","msg":"running command","command":"/usr/local/bin/rethinkdb-restore","args":"--connect=localhost:1 /tmp/backup-restore-sidecar/restore/files/re
thinkdb"}                                                                                                                                                                                                                                         
Error: restoring database was not successful: error running restore command: Usage: rethinkdb restore FILE [-c HOST:PORT] [--tls-cert FILENAME] [-p] [--password-file FILENAME] [--clients NUM] [--shards NUM_SHARDS] [--replicas NUM_REPLICAS] [-
-force] [-i (DB | DB.TABLE)]...                                                                                                                                                                                                                   
                                                                                                                                                                                                                                                  
rethinkdb-restore: error: Unable to connect to server: Could not connect to localhost:1, Wrong password exit status 2

So, I assume it is somehow broken already?

Here is with it:

{"level":"info","timestamp":"2023-08-24T13:42:17Z","logger":"rethinkdb","caller":"rethinkdb/rethinkdb.go:150","msg":"rethinkdb in sidecar is now available, now triggering restore commands..."}
{"level":"info","timestamp":"2023-08-24T13:42:17Z","logger":"rethinkdb","caller":"utils/cmd.go:28","msg":"running command","command":"/usr/local/bin/rethinkdb-restore","args":"--connect=localhost:1 --password-file=/rethinkdb-secret/rethinkdb-password.txt /tmp/backup-restore-sidecar/restore/files/rethinkdb"}
Error: restoring database was not successful: error running restore command: Extracting archive file...
  Done (0 seconds)
Importing from directory...
Error: import failed: The following tables already exist, run with --force to import into the existing tables:
  metalapi.asnpool
  metalapi.asnpoolinfo
  metalapi.event
  metalapi.filesystemlayout
  metalapi.image
  metalapi.integerpool
  metalapi.integerpoolinfo
  metalapi.ip
  metalapi.machine
  metalapi.migration
  metalapi.network
  metalapi.partition
  metalapi.size
  metalapi.sizeimageconstraint
  metalapi.switch
  metalapi.switchstatus exit status 1

I think, it is okay that the restore does not succeed when there is already data present. Not sure if it makes sense to add a --force flag.

args = append(args, rethinkDBRestoreFilePath)

out, err := db.executor.ExecuteCommandWithOutput(rethinkDBRestoreCmd, nil, args...)
Expand Down
27 changes: 18 additions & 9 deletions cmd/internal/initializer/initializer.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package initializer

import (
"context"
"fmt"
"net"
"os"
Expand Down Expand Up @@ -30,24 +31,26 @@ type Initializer struct {
db database.Database
bp providers.BackupProvider
comp *compress.Compressor
dbDataDir string
}

func New(log *zap.SugaredLogger, addr string, db database.Database, bp providers.BackupProvider, comp *compress.Compressor) *Initializer {
func New(log *zap.SugaredLogger, addr string, db database.Database, bp providers.BackupProvider, comp *compress.Compressor, dbDataDir string) *Initializer {
return &Initializer{
currentStatus: &v1.StatusResponse{
Status: v1.StatusResponse_CHECKING,
Message: "starting initializer server",
},
log: log,
addr: addr,
db: db,
bp: bp,
comp: comp,
log: log,
addr: addr,
db: db,
bp: bp,
comp: comp,
dbDataDir: dbDataDir,
}
}

// Start starts the initializer, which includes a server component and the initializer itself, which is potentially restoring a backup
func (i *Initializer) Start(stop <-chan struct{}) {
func (i *Initializer) Start(ctx context.Context) {
opts := []grpc.ServerOption{
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
grpc_ctxtags.StreamServerInterceptor(),
Expand Down Expand Up @@ -75,7 +78,7 @@ func (i *Initializer) Start(stop <-chan struct{}) {
}

go func() {
<-stop
<-ctx.Done()
i.log.Info("received stop signal, shutting down")
grpcServer.Stop()
}()
Expand Down Expand Up @@ -106,9 +109,15 @@ func (i *Initializer) Start(stop <-chan struct{}) {
func (i *Initializer) initialize() error {
i.log.Info("start running initializer")

i.log.Info("ensuring database data directory")
err := os.MkdirAll(i.dbDataDir, 0755)
if err != nil {
return fmt.Errorf("unable to ensure database data directory: %w", err)
}

i.log.Info("ensuring backup bucket")
i.currentStatus.Message = "ensuring backup bucket"
err := i.bp.EnsureBackupBucket()
err = i.bp.EnsureBackupBucket()
if err != nil {
return fmt.Errorf("unable to ensure backup bucket: %w", err)
}
Expand Down
5 changes: 3 additions & 2 deletions cmd/internal/probe/probe.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package probe

import (
"context"
"errors"
"time"

Expand All @@ -13,12 +14,12 @@ var (
)

// Start starts the database prober
func Start(log *zap.SugaredLogger, db database.DatabaseProber, stop <-chan struct{}) error {
func Start(ctx context.Context, log *zap.SugaredLogger, db database.DatabaseProber) error {
log.Info("start probing database")

for {
select {
case <-stop:
case <-ctx.Done():
return errors.New("received stop signal, stop probing")
case <-time.After(probeInterval):
err := db.Probe()
Expand Down
43 changes: 0 additions & 43 deletions cmd/internal/signals/signal.go

This file was deleted.

26 changes: 0 additions & 26 deletions cmd/internal/signals/signal_posix.go

This file was deleted.

Loading
Loading