Repository: k3s-io/k3s
File: pkg/etcd/etcd.go (Go)

Code before:
// snapshot attempts to save a new snapshot to the configured directory, and then clean up any old
// snapshots in excess of the retention limits.
func (e *ETCD) snapshot(ctx context.Context) {
	status, err := e.client.Status(ctx, endpoint)
	if err != nil {
		logrus.Errorf("Failed to check etcd status for snapshot: %v", err)
		return
	}

	if status.IsLearner {
		logrus.Warnf("Skipping snapshot: not supported for learner")
		return
	}

	snapshotDir, err := snapshotDir(e.config)
	if err != nil {
		logrus.Errorf("Failed to get the snapshot dir: %v", err)
		return
	}
	sManager := snapshot.NewV3(nil)
	tlsConfig, err := toTLSConfig(e.runtime)
	if err != nil {
		logrus.Errorf("Failed to get tls config for etcd snapshot: %v", err)
		return
	}
	etcdConfig := etcd.Config{
		Context:   ctx,
		Endpoints: []string{endpoint},
		TLS:       tlsConfig,
	}
	snapshotPath := filepath.Join(snapshotDir, snapshotPrefix+strconv.Itoa(int(time.Now().Unix())))
	logrus.Infof("Saving etcd snapshot to %s", snapshotPath)

	if err := sManager.Save(ctx, etcdConfig, snapshotPath); err != nil {
		logrus.Errorf("Failed to save snapshot: %v", err)
		return
	}
	if err := snapshotRetention(e.config.EtcdSnapshotRetention, snapshotDir); err != nil {
		logrus.Errorf("Failed to apply snapshot retention: %v", err)
		return
	}
}

// setSnapshotFunction schedules snapshots at the configured interval
func (e *ETCD) setSnapshotFunction(ctx context.Context) {
	e.cron.AddFunc(e.config.EtcdSnapshotCron, func() { e.snapshot(ctx) })
}

// Restore performs a restore of the ETCD datastore from
// the given snapshot path. This operation exists upon
// completion.

Review comment (bug):
I think we also need to keep the etcd client in check by checking and setting default values for the timeouts:

```
	// DialTimeout is the timeout for failing to establish a connection.
	DialTimeout time.Duration `json:"dial-timeout"`

	// DialKeepAliveTime is the time after which client pings the server to see if
	// transport is alive.
	DialKeepAliveTime time.Duration `json:"dial-keep-alive-time"`

	// DialKeepAliveTimeout is the time that the client waits for a response for the
	// keep-alive probe. If the response is not received in this time, the connection is closed.
	DialKeepAliveTimeout time.Duration `json:"dial-keep-alive-timeout"`
```

Code after:
// snapshot attempts to save a new snapshot to the configured directory, and then clean up any old
// snapshots in excess of the retention limits.
func (e *ETCD) snapshot(ctx context.Context) {
	status, err := e.client.Status(ctx, endpoint)
	if err != nil {
		logrus.Errorf("Failed to check etcd status for snapshot: %v", err)
		return
	}

	if status.IsLearner {
		logrus.Warnf("Skipping snapshot: not supported for learner")
		return
	}

	snapshotDir, err := snapshotDir(e.config)
	if err != nil {
		logrus.Errorf("Failed to get the snapshot dir: %v", err)
		return
	}

	cfg, err := getClientConfig(ctx, e.runtime, endpoint)
	if err != nil {
		logrus.Errorf("Failed to get config for etcd snapshot: %v", err)
		return
	}

	snapshotPath := filepath.Join(snapshotDir, snapshotPrefix+strconv.Itoa(int(time.Now().Unix())))
	logrus.Infof("Saving etcd snapshot to %s", snapshotPath)

	if err := snapshot.NewV3(nil).Save(ctx, *cfg, snapshotPath); err != nil {
		logrus.Errorf("Failed to save snapshot: %v", err)
		return
	}
	if err := snapshotRetention(e.config.EtcdSnapshotRetention, snapshotDir); err != nil {
		logrus.Errorf("Failed to apply snapshot retention: %v", err)
		return
	}
}

// setSnapshotFunction schedules snapshots at the configured interval
func (e *ETCD) setSnapshotFunction(ctx context.Context) {
	e.cron.AddFunc(e.config.EtcdSnapshotCron, func() { e.snapshot(ctx) })
}

// Restore performs a restore of the ETCD datastore from
// the given snapshot path. This operation exists upon
// completion.
func (e *ETCD) Restore(ctx context.Context) error {
	// check the old etcd data dir
	oldDataDir := dataDir(e.config) + "-old"
	if s, err := os.Stat(oldDataDir); err == nil && s.IsDir() {