-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
1,280 additions
and
172 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
package redis | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
"path" | ||
"time" | ||
|
||
"github.com/redis/go-redis/v9" | ||
"github.com/spf13/afero" | ||
|
||
"github.com/metal-stack/backup-restore-sidecar/cmd/internal/utils" | ||
"github.com/metal-stack/backup-restore-sidecar/pkg/constants" | ||
"go.uber.org/zap" | ||
) | ||
|
||
const ( | ||
redisDumpFile = "dump.rdb" | ||
) | ||
|
||
// Redis implements the database interface | ||
type Redis struct { | ||
log *zap.SugaredLogger | ||
executor *utils.CmdExecutor | ||
datadir string | ||
|
||
client *redis.Client | ||
} | ||
|
||
// New instantiates a new redis database | ||
func New(log *zap.SugaredLogger, datadir string, addr string, password *string) (*Redis, error) { | ||
if addr == "" { | ||
return nil, fmt.Errorf("redis addr cannot be empty") | ||
} | ||
|
||
opts := &redis.Options{ | ||
Addr: addr, | ||
} | ||
if password != nil { | ||
opts.Password = *password | ||
} | ||
|
||
client := redis.NewClient(opts) | ||
|
||
return &Redis{ | ||
log: log, | ||
datadir: datadir, | ||
executor: utils.NewExecutor(log), | ||
client: client, | ||
}, nil | ||
} | ||
|
||
// Backup takes a dump of redis with the redis client. | ||
func (db *Redis) Backup(ctx context.Context) error { | ||
if err := os.RemoveAll(constants.BackupDir); err != nil { | ||
return fmt.Errorf("could not clean backup directory: %w", err) | ||
} | ||
|
||
if err := os.MkdirAll(constants.BackupDir, 0777); err != nil { | ||
return fmt.Errorf("could not create backup directory: %w", err) | ||
} | ||
|
||
start := time.Now() | ||
_, err := db.client.Save(ctx).Result() | ||
if err != nil { | ||
return fmt.Errorf("could not create a dump: %w", err) | ||
} | ||
resp, err := db.client.ConfigGet(ctx, "dir").Result() | ||
if err != nil { | ||
return fmt.Errorf("could not get config: %w", err) | ||
} | ||
dumpDir := resp["dir"] | ||
dumpFile := path.Join(dumpDir, redisDumpFile) | ||
|
||
db.log.Infow("dump created successfully", "file", dumpFile, "duration", time.Since(start).String()) | ||
|
||
// we need to do a copy here and cannot simply rename as the file system is | ||
// mounted by two containers. the dump is created in the database container, | ||
// the copy is done in the backup-restore-sidecar container. os.Rename would | ||
// lead to an error. | ||
|
||
err = utils.Copy(afero.NewOsFs(), dumpFile, path.Join(constants.BackupDir, redisDumpFile)) | ||
if err != nil { | ||
return fmt.Errorf("unable to copy dumpfile to backupdir: %w", err) | ||
} | ||
|
||
err = os.Remove(dumpFile) | ||
if err != nil { | ||
return fmt.Errorf("unable to clean up dump: %w", err) | ||
} | ||
|
||
db.log.Debugw("successfully took backup of redis") | ||
return nil | ||
} | ||
|
||
// Check indicates whether a restore of the database is required or not. | ||
func (db *Redis) Check(_ context.Context) (bool, error) { | ||
empty, err := utils.IsEmpty(db.datadir) | ||
if err != nil { | ||
return false, err | ||
} | ||
|
||
if empty { | ||
db.log.Info("data directory is empty") | ||
return true, err | ||
} | ||
|
||
return false, nil | ||
} | ||
|
||
// Probe figures out if the database is running and available for taking backups. | ||
func (db *Redis) Probe(ctx context.Context) error { | ||
_, err := db.client.Ping(ctx).Result() | ||
if err != nil { | ||
return fmt.Errorf("connection error: %w", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Recover restores a database backup | ||
func (db *Redis) Recover(ctx context.Context) error { | ||
dump := path.Join(constants.RestoreDir, redisDumpFile) | ||
|
||
if _, err := os.Stat(dump); os.IsNotExist(err) { | ||
return fmt.Errorf("restore file not present: %s", dump) | ||
} | ||
|
||
if err := utils.RemoveContents(db.datadir); err != nil { | ||
return fmt.Errorf("could not clean database data directory: %w", err) | ||
} | ||
|
||
start := time.Now() | ||
|
||
err := utils.Copy(afero.NewOsFs(), dump, path.Join(db.datadir, redisDumpFile)) | ||
if err != nil { | ||
return fmt.Errorf("unable to recover %w", err) | ||
} | ||
|
||
db.log.Infow("successfully restored redis database", "duration", time.Since(start).String()) | ||
|
||
return nil | ||
} | ||
|
||
// Upgrade performs an upgrade of the database in case a newer version of the database is detected. | ||
func (db *Redis) Upgrade(_ context.Context) error { | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
# THESE EXAMPLES ARE GENERATED! | ||
# Use them as a template for your deployment, but do not commit manual changes to these files. | ||
--- | ||
apiVersion: apps/v1 | ||
kind: StatefulSet | ||
metadata: | ||
creationTimestamp: null | ||
labels: | ||
app: keydb | ||
name: keydb | ||
spec: | ||
replicas: 1 | ||
selector: | ||
matchLabels: | ||
app: keydb | ||
serviceName: keydb | ||
template: | ||
metadata: | ||
creationTimestamp: null | ||
labels: | ||
app: keydb | ||
spec: | ||
containers: | ||
- command: | ||
- backup-restore-sidecar | ||
- wait | ||
image: eqalpha/keydb:alpine | ||
livenessProbe: | ||
exec: | ||
command: | ||
- keydb-cli | ||
- ping | ||
failureThreshold: 3 | ||
initialDelaySeconds: 15 | ||
periodSeconds: 5 | ||
successThreshold: 1 | ||
timeoutSeconds: 1 | ||
name: keydb | ||
ports: | ||
- containerPort: 6379 | ||
name: client | ||
protocol: TCP | ||
readinessProbe: | ||
exec: | ||
command: | ||
- keydb-cli | ||
- ping | ||
failureThreshold: 3 | ||
initialDelaySeconds: 15 | ||
periodSeconds: 5 | ||
successThreshold: 1 | ||
timeoutSeconds: 1 | ||
resources: {} | ||
volumeMounts: | ||
- mountPath: /data | ||
name: data | ||
- mountPath: /usr/local/bin/backup-restore-sidecar | ||
name: bin-provision | ||
subPath: backup-restore-sidecar | ||
- mountPath: /etc/backup-restore-sidecar | ||
name: backup-restore-sidecar-config | ||
- command: | ||
- backup-restore-sidecar | ||
- start | ||
- --log-level=debug | ||
image: eqalpha/keydb:alpine | ||
name: backup-restore-sidecar | ||
ports: | ||
- containerPort: 8000 | ||
name: grpc | ||
resources: {} | ||
volumeMounts: | ||
- mountPath: /backup | ||
name: backup | ||
- mountPath: /data | ||
name: data | ||
- mountPath: /etc/backup-restore-sidecar | ||
name: backup-restore-sidecar-config | ||
- mountPath: /usr/local/bin/backup-restore-sidecar | ||
name: bin-provision | ||
subPath: backup-restore-sidecar | ||
initContainers: | ||
- command: | ||
- cp | ||
- /backup-restore-sidecar | ||
- /bin-provision | ||
image: ghcr.io/metal-stack/backup-restore-sidecar:latest | ||
imagePullPolicy: IfNotPresent | ||
name: backup-restore-sidecar-provider | ||
resources: {} | ||
volumeMounts: | ||
- mountPath: /bin-provision | ||
name: bin-provision | ||
volumes: | ||
- name: data | ||
persistentVolumeClaim: | ||
claimName: data | ||
- name: backup | ||
persistentVolumeClaim: | ||
claimName: backup | ||
- configMap: | ||
name: backup-restore-sidecar-config-keydb | ||
name: backup-restore-sidecar-config | ||
- emptyDir: {} | ||
name: bin-provision | ||
updateStrategy: {} | ||
volumeClaimTemplates: | ||
- metadata: | ||
creationTimestamp: null | ||
name: data | ||
spec: | ||
accessModes: | ||
- ReadWriteOnce | ||
resources: | ||
requests: | ||
storage: 1Gi | ||
status: {} | ||
- metadata: | ||
creationTimestamp: null | ||
name: backup | ||
spec: | ||
accessModes: | ||
- ReadWriteOnce | ||
resources: | ||
requests: | ||
storage: 1Gi | ||
status: {} | ||
status: | ||
availableReplicas: 0 | ||
replicas: 0 | ||
--- | ||
apiVersion: v1 | ||
data: | ||
config.yaml: | | ||
--- | ||
bind-addr: 0.0.0.0 | ||
db: keydb | ||
db-data-directory: /data/ | ||
backup-provider: local | ||
backup-cron-schedule: "*/1 * * * *" | ||
object-prefix: keydb-test | ||
redis-addr: localhost:6379 | ||
post-exec-cmds: | ||
- keydb-server | ||
kind: ConfigMap | ||
metadata: | ||
creationTimestamp: null | ||
name: backup-restore-sidecar-config-keydb |
Oops, something went wrong.