Skip to content

Commit

Permalink
ETCD-681: Add etcd-backup-server container within separate daemonset
Browse files Browse the repository at this point in the history
  • Loading branch information
Elbehery committed Oct 16, 2024
1 parent df38d11 commit cedaaef
Show file tree
Hide file tree
Showing 5 changed files with 614 additions and 8 deletions.
59 changes: 52 additions & 7 deletions pkg/cmd/backuprestore/backupserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"os/signal"
"path"
"strings"
"syscall"
"time"

Expand All @@ -17,7 +18,13 @@ import (
"github.com/spf13/cobra"
)

const backupVolume = "/var/lib/etcd-auto-backup"
const (
BackupVolume = "/var/lib/etcd-auto-backup"
etcdCtlKeyName = "ETCDCTL_KEY"
etcdCtlCertName = "ETCDCTL_CERT"
etcdCtlCACertName = "ETCDCTL_CACERT"
nodeNameEnvVar = "NODE_NAME"
)

var shutdownSignals = []os.Signal{os.Interrupt, syscall.SIGTERM}

Expand All @@ -29,7 +36,7 @@ type backupRunnerImpl struct{}

func (b backupRunnerImpl) runBackup(backupOpts *backupOptions, pruneOpts *prune.PruneOpts) error {
dateString := time.Now().Format("2006-01-02_150405")
backupOpts.backupDir = path.Join(backupVolume, dateString)
backupOpts.backupDir = path.Join(BackupVolume, dateString)
err := backup(backupOpts)
if err != nil {
return err
Expand All @@ -46,6 +53,7 @@ func (b backupRunnerImpl) runBackup(backupOpts *backupOptions, pruneOpts *prune.
type backupServer struct {
schedule string
timeZone string
nodeName string
enabled bool
cronSchedule cron.Schedule
backupOptions
Expand All @@ -57,15 +65,14 @@ func NewBackupServer(ctx context.Context) *cobra.Command {
backupOptions: backupOptions{errOut: os.Stderr},
PruneOpts: prune.PruneOpts{
RetentionType: "None",
BackupPath: backupVolume,
BackupPath: BackupVolume,
},
}

cmd := &cobra.Command{
Use: "backup-server",
Short: "Backs up a snapshot of etcd database and static pod resources without config",
Run: func(cmd *cobra.Command, args []string) {

if err := backupSrv.Validate(); err != nil {
klog.Fatal(err)
}
Expand Down Expand Up @@ -95,13 +102,22 @@ func (b *backupServer) Validate() error {
return nil
}

if err := b.validateNameNode(); err != nil {
return err
}

if err := b.constructEnvVars(); err != nil {
klog.Infof("error constructing envVars: [%v]", err)
return err
}

cronSchedule, err := cron.ParseStandard(b.schedule)
if err != nil {
return fmt.Errorf("error parsing backup schedule %v: %w", b.schedule, err)
}
b.cronSchedule = cronSchedule

b.backupOptions.backupDir = backupVolume
b.backupOptions.backupDir = BackupVolume
err = b.backupOptions.Validate()
if err != nil {
return fmt.Errorf("error validating backup %v: %w", b.backupOptions, err)
Expand All @@ -111,7 +127,6 @@ func (b *backupServer) Validate() error {
if err != nil {
return fmt.Errorf("error validating prune args %v: %w", b.PruneOpts, err)
}

return nil
}

Expand All @@ -136,7 +151,6 @@ func (b *backupServer) Run(ctx context.Context) error {
func (b *backupServer) scheduleBackup(ctx context.Context, bck backupRunner) error {
ticker := time.NewTicker(time.Until(b.cronSchedule.Next(time.Now())))
defer ticker.Stop()

for {
select {
case <-ticker.C:
Expand All @@ -151,3 +165,34 @@ func (b *backupServer) scheduleBackup(ctx context.Context, bck backupRunner) err
}
}
}

func (b *backupServer) validateNameNode() error {
nodeNameEnv := os.Getenv(nodeNameEnvVar)
if len(nodeNameEnv) == 0 {
return fmt.Errorf("[%v] environment variable is empty", nodeNameEnvVar)
}
b.nodeName = nodeNameEnv
return nil
}

func (b *backupServer) constructEnvVars() error {
etcdCtlKeyVal := strings.Replace("/etc/kubernetes/static-pod-certs/secrets/etcd-all-certs/etcd-peer-NODE_NAME.key", nodeNameEnvVar, b.nodeName, -1)
err := os.Setenv(etcdCtlKeyName, etcdCtlKeyVal)
if err != nil {
return fmt.Errorf("error exporting [%v]: val is [%v]: %w", etcdCtlKeyName, etcdCtlKeyVal, err)
}

etcdCtlCertVal := strings.Replace("/etc/kubernetes/static-pod-certs/secrets/etcd-all-certs/etcd-peer-NODE_NAME.crt", nodeNameEnvVar, b.nodeName, -1)
err = os.Setenv(etcdCtlCertName, etcdCtlCertVal)
if err != nil {
return fmt.Errorf("error writing [%v]: val is [%v]: %w", etcdCtlCertName, etcdCtlCertVal, err)
}

etcdCtlCACertVal := "/etc/kubernetes/static-pod-certs/configmaps/etcd-all-bundles/server-ca-bundle.crt"
err = os.Setenv(etcdCtlCACertName, etcdCtlCACertVal)
if err != nil {
return fmt.Errorf("error writing [%v]: val is [%v]: %w", etcdCtlCACertName, etcdCtlCACertVal, err)
}

return nil
}
87 changes: 86 additions & 1 deletion pkg/cmd/backuprestore/backupserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package backuprestore
import (
"context"
"errors"
"fmt"
"os"
"testing"
"time"

Expand All @@ -12,17 +14,22 @@ import (
"github.com/stretchr/testify/require"
)

const validSchedule = "* * * * *"
const (
validSchedule = "* * * * *"
localHost = "localhost"
)

func TestBackupServer_Validate(t *testing.T) {
testCases := []struct {
name string
backupServer backupServer
envExist bool
expErr error
}{
{
"BackupServer is disabled",
backupServer{enabled: false},
false,
nil,
},
{
Expand All @@ -31,6 +38,7 @@ func TestBackupServer_Validate(t *testing.T) {
enabled: false,
schedule: "invalid schedule",
},
false,
nil,
},
{
Expand All @@ -42,13 +50,15 @@ func TestBackupServer_Validate(t *testing.T) {
backupDir: "",
},
},
false,
nil,
},
{
"BackupServer is enabled",
backupServer{
enabled: true,
},
true,
errors.New("error parsing backup schedule : empty spec string"),
},
{
Expand All @@ -57,6 +67,7 @@ func TestBackupServer_Validate(t *testing.T) {
enabled: true,
schedule: "invalid schedule",
},
true,
errors.New("error parsing backup schedule invalid schedule"),
},
{
Expand All @@ -68,6 +79,7 @@ func TestBackupServer_Validate(t *testing.T) {
RetentionType: prune.RetentionTypeNone,
},
},
true,
nil,
},
{
Expand All @@ -79,6 +91,7 @@ func TestBackupServer_Validate(t *testing.T) {
backupDir: "",
},
},
true,
errors.New("error parsing backup schedule invalid schedule"),
},
{
Expand All @@ -93,18 +106,29 @@ func TestBackupServer_Validate(t *testing.T) {
RetentionType: prune.RetentionTypeNone,
},
},
true,
nil,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if tc.envExist {
err := os.Setenv(nodeNameEnvVar, localHost)
require.NoError(t, err)
}

actErr := tc.backupServer.Validate()
if tc.expErr != nil {
require.Contains(t, actErr.Error(), tc.expErr.Error())
} else {
require.Equal(t, tc.expErr, actErr)
}

t.Cleanup(func() {
err := os.Unsetenv(nodeNameEnvVar)
require.NoError(t, err)
})
})
}
}
Expand Down Expand Up @@ -175,6 +199,67 @@ func TestNewBackupServer_scheduleBackup(t *testing.T) {
}
}

func TestBackupServer_validateNameNode(t *testing.T) {
testCases := []struct {
name string
inputNodeName string
envExist bool
expErr error
}{
{
name: "env var exist",
inputNodeName: localHost,
envExist: true,
expErr: nil,
},
{
name: "env var not exist",
envExist: false,
expErr: fmt.Errorf("[NODE_NAME] environment variable is empty"),
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if tc.envExist {
err := os.Setenv(nodeNameEnvVar, tc.inputNodeName)
require.NoError(t, err)
}

b := &backupServer{}
err := b.validateNameNode()
require.Equal(t, tc.expErr, err)
require.Equal(t, b.nodeName, tc.inputNodeName)

t.Cleanup(func() {
err := os.Unsetenv(nodeNameEnvVar)
require.NoError(t, err)
})
})
}
}

func TestBackupServer_constructEnvVars(t *testing.T) {
b := &backupServer{
nodeName: localHost,
}

err := b.constructEnvVars()
require.NoError(t, err)

expEtcdKey := "/etc/kubernetes/static-pod-certs/secrets/etcd-all-certs/etcd-peer-localhost.key"
act := os.Getenv(etcdCtlKeyName)
require.Equal(t, expEtcdKey, act)

expEtcdCert := "/etc/kubernetes/static-pod-certs/secrets/etcd-all-certs/etcd-peer-localhost.crt"
act = os.Getenv(etcdCtlCertName)
require.Equal(t, expEtcdCert, act)

expEtcdCACert := "/etc/kubernetes/static-pod-certs/configmaps/etcd-all-bundles/server-ca-bundle.crt"
act = os.Getenv(etcdCtlCACertName)
require.Equal(t, expEtcdCACert, act)
}

type backupRunnerMock struct {
counter int
slow bool
Expand Down
Loading

0 comments on commit cedaaef

Please sign in to comment.