Commit 10f0a790 authored by Brian Downs's avatar Brian Downs

add configmap save logic

parent 977d7923
...@@ -32,8 +32,41 @@ import ( ...@@ -32,8 +32,41 @@ import (
"go.etcd.io/etcd/clientv3/snapshot" "go.etcd.io/etcd/clientv3/snapshot"
"go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes" "go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes"
"go.etcd.io/etcd/etcdserver/etcdserverpb" "go.etcd.io/etcd/etcdserver/etcdserverpb"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilnet "k8s.io/apimachinery/pkg/util/net" utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/transport"
"k8s.io/client-go/util/retry"
)
const (
snapshotPrefix = "etcd-snapshot-"
endpoint = "https://127.0.0.1:2379"
testTimeout = time.Second * 10
manageTickerTime = time.Second * 15
learnerMaxStallTime = time.Minute * 5
// defaultDialTimeout is intentionally short so that connections timeout within the testTimeout defined above
defaultDialTimeout = 2 * time.Second
// other defaults from k8s.io/apiserver/pkg/storage/storagebackend/factory/etcd3.go
defaultKeepAliveTime = 30 * time.Second
defaultKeepAliveTimeout = 10 * time.Second
maxBackupRetention = 5
defaultRetries = 5
defaultWaitSeconds = 5
defaultTimeout = 30
k8sWrapTransportTimeout = 30
)
var (
learnerProgressKey = version.Program + "/etcd/learnerProgress"
// AddressKey will contain the value of api addresses list
AddressKey = version.Program + "/apiaddresses"
) )
type ETCD struct { type ETCD struct {
...@@ -44,6 +77,7 @@ type ETCD struct { ...@@ -44,6 +77,7 @@ type ETCD struct {
address string address string
cron *cron.Cron cron *cron.Cron
s3 *s3 s3 *s3
cs *kubernetes.Clientset
} }
type learnerProgress struct { type learnerProgress struct {
...@@ -53,42 +87,38 @@ type learnerProgress struct { ...@@ -53,42 +87,38 @@ type learnerProgress struct {
LastProgress metav1.Time `json:"lastProgress,omitempty"` LastProgress metav1.Time `json:"lastProgress,omitempty"`
} }
// Members contains a slice that holds all
// members of the cluster.
type Members struct {
Members []*etcdserverpb.Member `json:"members"`
}
// newClient create a new Kubernetes client from configuration.
func newClient(kubeConfigPath string, k8sWrapTransport transport.WrapperFunc) (*kubernetes.Clientset, error) {
config, err := clientcmd.BuildConfigFromFlags("", kubeConfigPath)
if err != nil {
return nil, err
}
if k8sWrapTransport != nil {
config.WrapTransport = k8sWrapTransport
}
config.Timeout = time.Second * defaultTimeout
return kubernetes.NewForConfig(config)
}
// NewETCD creates a new value of type // NewETCD creates a new value of type
// ETCD with an initialized cron value. // ETCD with an initialized cron value.
func NewETCD() *ETCD { func NewETCD(kubeConfigAdmin string) *ETCD {
cs, err := newClient(kubeConfigAdmin, nil)
if err != nil {
logrus.Fatalf("etcd: new k8s client: %s", err.Error())
}
return &ETCD{ return &ETCD{
cron: cron.New(), cron: cron.New(),
cs: cs,
} }
} }
var (
learnerProgressKey = version.Program + "/etcd/learnerProgress"
// AddressKey will contain the value of api addresses list
AddressKey = version.Program + "/apiaddresses"
)
const (
snapshotPrefix = "etcd-snapshot-"
endpoint = "https://127.0.0.1:2379"
testTimeout = time.Second * 10
manageTickerTime = time.Second * 15
learnerMaxStallTime = time.Minute * 5
// defaultDialTimeout is intentionally short so that connections timeout within the testTimeout defined above
defaultDialTimeout = 2 * time.Second
// other defaults from k8s.io/apiserver/pkg/storage/storagebackend/factory/etcd3.go
defaultKeepAliveTime = 30 * time.Second
defaultKeepAliveTimeout = 10 * time.Second
maxBackupRetention = 5
)
// Members contains a slice that holds all
// members of the cluster.
type Members struct {
Members []*etcdserverpb.Member `json:"members"`
}
// EndpointName returns the name of the endpoint. // EndpointName returns the name of the endpoint.
func (e *ETCD) EndpointName() string { func (e *ETCD) EndpointName() string {
return "etcd" return "etcd"
...@@ -817,7 +847,8 @@ func (e *ETCD) Snapshot(ctx context.Context, config *config.Control) error { ...@@ -817,7 +847,8 @@ func (e *ETCD) Snapshot(ctx context.Context, config *config.Control) error {
return errors.Wrap(err, "failed to get config for etcd snapshot") return errors.Wrap(err, "failed to get config for etcd snapshot")
} }
snapshotName := fmt.Sprintf("%s-%d", e.config.EtcdSnapshotName, time.Now().Unix()) nodeName := os.Getenv("NODE_NAME")
snapshotName := fmt.Sprintf("%s-%s-%d", e.config.EtcdSnapshotName, nodeName, time.Now().Unix())
snapshotPath := filepath.Join(snapshotDir, snapshotName) snapshotPath := filepath.Join(snapshotDir, snapshotName)
logrus.Infof("Saving etcd snapshot to %s", snapshotPath) logrus.Infof("Saving etcd snapshot to %s", snapshotPath)
...@@ -859,7 +890,8 @@ func (e *ETCD) Snapshot(ctx context.Context, config *config.Control) error { ...@@ -859,7 +890,8 @@ func (e *ETCD) Snapshot(ctx context.Context, config *config.Control) error {
return err return err
} }
logrus.Warnf("%#v\n", snapshots) // store the snapshot data somewhere
_ = snapshots
return nil return nil
} }
...@@ -868,6 +900,7 @@ func (e *ETCD) Snapshot(ctx context.Context, config *config.Control) error { ...@@ -868,6 +900,7 @@ func (e *ETCD) Snapshot(ctx context.Context, config *config.Control) error {
// metadata. // metadata.
type snapshotFile struct { type snapshotFile struct {
Name string `json:"name"` Name string `json:"name"`
NodeName string `json:"nodeName"`
CreatedAt string `json:"createdAt"` CreatedAt string `json:"createdAt"`
Size int64 `json:"size"` Size int64 `json:"size"`
} }
...@@ -891,7 +924,7 @@ func (e *ETCD) listSnapshots(ctx context.Context, snapshotDir string) ([]snapsho ...@@ -891,7 +924,7 @@ func (e *ETCD) listSnapshots(ctx context.Context, snapshotDir string) ([]snapsho
return nil, obj.Err return nil, obj.Err
} }
snapshots = append(snapshots, snapshotFile{ snapshots = append(snapshots, snapshotFile{
Name: obj.Key, Name: "s3://" + e.config.EtcdS3BucketName + "/" + obj.Key,
CreatedAt: obj.LastModified.String(), CreatedAt: obj.LastModified.String(),
Size: obj.Size, Size: obj.Size,
}) })
...@@ -907,7 +940,8 @@ func (e *ETCD) listSnapshots(ctx context.Context, snapshotDir string) ([]snapsho ...@@ -907,7 +940,8 @@ func (e *ETCD) listSnapshots(ctx context.Context, snapshotDir string) ([]snapsho
for _, f := range files { for _, f := range files {
snapshots = append(snapshots, snapshotFile{ snapshots = append(snapshots, snapshotFile{
Name: f.Name(), Name: filepath.Join(snapshotDir, f.Name()),
NodeName: "",
CreatedAt: f.ModTime().String(), CreatedAt: f.ModTime().String(),
Size: f.Size(), Size: f.Size(),
}) })
...@@ -916,6 +950,97 @@ func (e *ETCD) listSnapshots(ctx context.Context, snapshotDir string) ([]snapsho ...@@ -916,6 +950,97 @@ func (e *ETCD) listSnapshots(ctx context.Context, snapshotDir string) ([]snapsho
return snapshots, nil return snapshots, nil
} }
// updateConfigMap
func updateConfigMap(data map[string]string, snapshotFiles []snapshotFile) error {
for _, v := range snapshotFiles {
b, err := json.Marshal(v)
if err != nil {
return err
}
data[v.Name] = string(b)
}
return nil
}
// storeSnapshotData stores the given snapshot data in the "snapshots" ConfigMap.
func (e *ETCD) storeSnapshotData(ctx context.Context, snapshotFiles []snapshotFile) error {
snapshotConfigMap, err := e.cs.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(ctx, "snapshots", metav1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
data := make(map[string]string, len(snapshotFiles))
if err := updateConfigMap(data, snapshotFiles); err != nil {
return err
}
cm := v1.ConfigMap{
TypeMeta: metav1.TypeMeta{
Kind: "ConfigMap",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "snapshots",
Namespace: metav1.NamespaceSystem,
},
Data: data,
}
if err := retry.OnError(retry.DefaultBackoff,
func(err error) bool {
return !apierrors.IsAlreadyExists(err)
}, func() error {
_, err = e.cs.CoreV1().ConfigMaps(metav1.NamespaceSystem).Create(ctx, &cm, metav1.CreateOptions{})
return err
},
); err != nil && apierrors.IsAlreadyExists(err) {
return retry.RetryOnConflict(retry.DefaultBackoff, func() error {
snapshotConfigMap, err := e.cs.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(ctx, "snapshots", metav1.GetOptions{})
if err != nil {
return err
}
snapshotConfigMap.Data = make(map[string]string, len(snapshotFiles))
if err := updateConfigMap(snapshotConfigMap.Data, snapshotFiles); err != nil {
return err
}
_, err = e.cs.CoreV1().ConfigMaps(metav1.NamespaceSystem).Update(ctx, snapshotConfigMap, metav1.UpdateOptions{})
return err
})
} else if err != nil {
return err
}
return nil
}
return err
}
if err := retry.OnError(retry.DefaultBackoff,
func(err error) bool {
return !apierrors.IsConflict(err)
}, func() error {
snapshotConfigMap.Data = make(map[string]string, len(snapshotFiles))
if err := updateConfigMap(snapshotConfigMap.Data, snapshotFiles); err != nil {
return err
}
_, err = e.cs.CoreV1().ConfigMaps(metav1.NamespaceSystem).Update(ctx, snapshotConfigMap, metav1.UpdateOptions{})
return err
},
); err != nil && apierrors.IsConflict(err) {
return retry.RetryOnConflict(retry.DefaultBackoff, func() error {
snapshotConfigMap, err := e.cs.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(ctx, "snapshots", metav1.GetOptions{})
if err != nil {
return err
}
snapshotConfigMap.Data = make(map[string]string, len(snapshotFiles))
if err := updateConfigMap(snapshotConfigMap.Data, snapshotFiles); err != nil {
return err
}
_, err = e.cs.CoreV1().ConfigMaps(metav1.NamespaceSystem).Update(ctx, snapshotConfigMap, metav1.UpdateOptions{})
return err
})
} else if err != nil {
return err
}
return nil
}
// setSnapshotFunction schedules snapshots at the configured interval // setSnapshotFunction schedules snapshots at the configured interval
func (e *ETCD) setSnapshotFunction(ctx context.Context) { func (e *ETCD) setSnapshotFunction(ctx context.Context) {
e.cron.AddFunc(e.config.EtcdSnapshotCron, func() { e.cron.AddFunc(e.config.EtcdSnapshotCron, func() {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment