2021-09-14 15:20:38 +00:00
|
|
|
package etcd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
|
2022-03-02 23:47:27 +00:00
|
|
|
"github.com/k3s-io/k3s/pkg/version"
|
2021-09-14 15:20:38 +00:00
|
|
|
controllerv1 "github.com/rancher/wrangler/pkg/generated/controllers/core/v1"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
v1 "k8s.io/api/core/v1"
|
|
|
|
)
|
|
|
|
|
2022-04-06 00:11:24 +00:00
|
|
|
func registerMemberHandlers(ctx context.Context, etcd *ETCD) {
|
|
|
|
nodes := etcd.config.Runtime.Core.Core().V1().Node()
|
2021-09-14 15:20:38 +00:00
|
|
|
e := &etcdMemberHandler{
|
|
|
|
etcd: etcd,
|
|
|
|
nodeController: nodes,
|
|
|
|
ctx: ctx,
|
|
|
|
}
|
2022-04-06 00:11:24 +00:00
|
|
|
|
|
|
|
logrus.Infof("Starting managed etcd member removal controller")
|
2021-09-15 15:41:30 +00:00
|
|
|
nodes.OnChange(ctx, "managed-etcd-controller", e.sync)
|
|
|
|
nodes.OnRemove(ctx, "managed-etcd-controller", e.onRemove)
|
2021-09-14 15:20:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
removalAnnotation = "etcd." + version.Program + ".cattle.io/remove"
|
|
|
|
removedNodeNameAnnotation = "etcd." + version.Program + ".cattle.io/removed-node-name"
|
|
|
|
)
|
|
|
|
|
|
|
|
type etcdMemberHandler struct {
|
|
|
|
etcd *ETCD
|
|
|
|
nodeController controllerv1.NodeController
|
|
|
|
ctx context.Context
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *etcdMemberHandler) sync(key string, node *v1.Node) (*v1.Node, error) {
|
|
|
|
if node == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, ok := node.Labels[EtcdRoleLabel]; !ok {
|
|
|
|
logrus.Debugf("Node %s was not labeled etcd node, skipping sync", key)
|
|
|
|
return node, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
node = node.DeepCopy()
|
|
|
|
|
|
|
|
if removalRequested, ok := node.Annotations[removalAnnotation]; ok {
|
|
|
|
if removed, ok := node.Annotations[removedNodeNameAnnotation]; ok {
|
|
|
|
// check to see if removed is true. if it is, nothing to do.
|
|
|
|
if currentNodeName, ok := node.Annotations[NodeNameAnnotation]; ok {
|
|
|
|
if currentNodeName != removed {
|
|
|
|
// If the current node name is not the same as the removed node name, reset the tainted annotation and removed node name
|
2022-04-06 00:11:24 +00:00
|
|
|
logrus.Infof("Resetting removed node flag as removed node name (did not match current node name")
|
2021-09-14 15:20:38 +00:00
|
|
|
delete(node.Annotations, removedNodeNameAnnotation)
|
|
|
|
node.Annotations[removalAnnotation] = "false"
|
|
|
|
return e.nodeController.Update(node)
|
|
|
|
}
|
|
|
|
// this is the case where the current node name matches the removed node name. We have already removed the
|
|
|
|
// node, so no need to perform any action. Fallthrough to the non-op below.
|
|
|
|
}
|
|
|
|
// This is the edge case where the removed annotation exists, but there is not a current node name annotation.
|
|
|
|
// This should be a non-op, as we can't remove the node anyway.
|
|
|
|
logrus.Debugf("etcd member %s was already marked via annotations as removed", key)
|
|
|
|
return node, nil
|
|
|
|
}
|
|
|
|
if strings.ToLower(removalRequested) == "true" {
|
|
|
|
// remove the member.
|
|
|
|
name, ok := node.Annotations[NodeNameAnnotation]
|
|
|
|
if !ok {
|
|
|
|
return node, fmt.Errorf("node name annotation for node %s not found", key)
|
|
|
|
}
|
|
|
|
address, ok := node.Annotations[NodeAddressAnnotation]
|
|
|
|
if !ok {
|
|
|
|
return node, fmt.Errorf("node address annotation for node %s not found", key)
|
|
|
|
}
|
|
|
|
|
|
|
|
logrus.Debugf("removing etcd member from cluster name: %s address: %s", name, address)
|
|
|
|
if err := e.etcd.RemovePeer(e.ctx, name, address, true); err != nil {
|
|
|
|
return node, err
|
|
|
|
}
|
|
|
|
logrus.Debugf("etcd member removal successful for name: %s address: %s", name, address)
|
|
|
|
// Set the removed node name annotation and clean up the other etcd node annotations.
|
|
|
|
// These will be set if the tombstone file is then created and the etcd member is re-added, to their new
|
|
|
|
// respective values.
|
|
|
|
node.Annotations[removedNodeNameAnnotation] = name
|
|
|
|
delete(node.Annotations, NodeNameAnnotation)
|
|
|
|
delete(node.Annotations, NodeAddressAnnotation)
|
|
|
|
return e.nodeController.Update(node)
|
|
|
|
}
|
|
|
|
// In the event that we had an unexpected removal value, simply return.
|
|
|
|
// Fallthrough to the non-op below.
|
|
|
|
}
|
|
|
|
// This is a non-op, as we don't have a tainted annotation to worry about.
|
|
|
|
return node, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *etcdMemberHandler) onRemove(key string, node *v1.Node) (*v1.Node, error) {
|
|
|
|
if _, ok := node.Labels[EtcdRoleLabel]; !ok {
|
|
|
|
logrus.Debugf("Node %s was not labeled etcd node, skipping etcd member removal", key)
|
|
|
|
return node, nil
|
|
|
|
}
|
2021-09-15 15:41:30 +00:00
|
|
|
logrus.Infof("Removing etcd member %s from cluster", key)
|
|
|
|
if removalRequested, ok := node.Annotations[removalAnnotation]; ok {
|
|
|
|
if strings.ToLower(removalRequested) == "true" {
|
|
|
|
if removedNodeName, ok := node.Annotations[removedNodeNameAnnotation]; ok {
|
|
|
|
if len(removedNodeName) > 0 {
|
|
|
|
// If we received a node to delete that has already been removed via annotation, it will be missing
|
|
|
|
// the corresponding node name and address annotations.
|
|
|
|
logrus.Infof("etcd member %s was already removed as member name %s via annotation from the cluster", key, removedNodeName)
|
|
|
|
return node, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-09-14 15:20:38 +00:00
|
|
|
name, ok := node.Annotations[NodeNameAnnotation]
|
|
|
|
if !ok {
|
|
|
|
return node, fmt.Errorf("node name annotation for node %s not found", key)
|
|
|
|
}
|
|
|
|
address, ok := node.Annotations[NodeAddressAnnotation]
|
|
|
|
if !ok {
|
|
|
|
return node, fmt.Errorf("node address annotation for node %s not found", key)
|
|
|
|
}
|
|
|
|
return node, e.etcd.RemovePeer(e.ctx, name, address, true)
|
|
|
|
}
|