mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/kube-vip/kube-vip.git
synced 2026-09-20 08:03:47 +08:00
* feat(lease): record VIP ownership in lease annotations Encode the instance name, address protocol and an ordered VIP list on the Kubernetes lease so a restarting instance can tell which tagged addresses it still owns. Only the current holder publishes the annotation. Signed-off-by: Marcel Fest <marcel.fest@telekom.de> Signed-off-by: Cellebyte <marcel.fest@live.de> Signed-off-by: Marcel Fest <marcel.fest@telekom.de> * fix: flip flop on ordering when reading and writing the annotation Signed-off-by: Marcel Fest <marcel.fest@telekom.de> * fix: address feedback from review Signed-off-by: Marcel Fest <marcel.fest@telekom.de> --------- Signed-off-by: Marcel Fest <marcel.fest@telekom.de> Signed-off-by: Cellebyte <marcel.fest@live.de>
178 lines
6.6 KiB
Go
178 lines
6.6 KiB
Go
package election
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/kube-vip/kube-vip/pkg/kubevip"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/client-go/kubernetes/fake"
|
|
k8stesting "k8s.io/client-go/testing"
|
|
"k8s.io/client-go/tools/leaderelection/resourcelock"
|
|
)
|
|
|
|
func TestAnnotatedLeaseLockPersistsAnnotationsOnCreateAndUpdate(t *testing.T) {
|
|
client := fake.NewSimpleClientset()
|
|
leaseClient := client.CoordinationV1().Leases("default")
|
|
base := &resourcelock.LeaseLock{
|
|
LeaseMeta: metav1.ObjectMeta{Name: "lease", Namespace: "default"},
|
|
Client: client.CoordinationV1(),
|
|
LockConfig: resourcelock.ResourceLockConfig{
|
|
Identity: "node-a",
|
|
},
|
|
}
|
|
annotations, err := kubevip.WithLeaseVIPs(map[string]string{"example.test/preserved": "true"},
|
|
"release_a", 248, []string{"192.0.2.10"})
|
|
if err != nil {
|
|
t.Fatalf("WithLeaseVIPs() error = %v", err)
|
|
}
|
|
lock := newAnnotatedLeaseLock(base, leaseClient, "lease", annotations)
|
|
record := resourcelock.LeaderElectionRecord{HolderIdentity: "node-a"}
|
|
if err := lock.Create(context.Background(), record); err != nil {
|
|
t.Fatalf("Create() error = %v", err)
|
|
}
|
|
if err := lock.Update(context.Background(), record); err != nil {
|
|
t.Fatalf("Update() error = %v", err)
|
|
}
|
|
|
|
resource, err := leaseClient.Get(context.Background(), "lease", metav1.GetOptions{})
|
|
if err != nil {
|
|
t.Fatalf("get Lease: %v", err)
|
|
}
|
|
value, err := kubevip.ParseLeaseVIPs(resource.Annotations[kubevip.LeaseVIPs])
|
|
if err != nil {
|
|
t.Fatalf("ParseLeaseVIPs() error = %v", err)
|
|
}
|
|
if value.InstanceName != "release_a" || value.IFAProto != 248 || len(value.VIPs) != 1 ||
|
|
value.VIPs[0] != (kubevip.LeaseVIP{Index: 0, Value: "192.0.2.10", Kind: kubevip.LeaseVIPKindAddress}) {
|
|
t.Fatalf("Lease VIP metadata = %+v", value)
|
|
}
|
|
if resource.Annotations["example.test/preserved"] != "true" {
|
|
t.Fatal("Lease update dropped a configured annotation")
|
|
}
|
|
}
|
|
|
|
// A failed annotation write must not be reported to the leader elector: the lease itself
|
|
// was already written, and an error makes the elector stand down while it still holds it.
|
|
func TestAnnotatedLeaseLockAnnotationFailureDoesNotSurfaceToElector(t *testing.T) {
|
|
client := fake.NewSimpleClientset()
|
|
base := &resourcelock.LeaseLock{
|
|
LeaseMeta: metav1.ObjectMeta{Name: "lease", Namespace: "default"},
|
|
Client: client.CoordinationV1(),
|
|
LockConfig: resourcelock.ResourceLockConfig{Identity: "node-a"},
|
|
}
|
|
|
|
failing := fake.NewSimpleClientset()
|
|
failing.PrependReactor("get", "leases", func(k8stesting.Action) (bool, runtime.Object, error) {
|
|
return true, nil, fmt.Errorf("annotation backend unavailable")
|
|
})
|
|
|
|
annotations, err := kubevip.WithLeaseVIPs(nil, "release_a", 248, []string{"192.0.2.10"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
lock := newAnnotatedLeaseLock(base, failing.CoordinationV1().Leases("default"), "lease", annotations)
|
|
record := resourcelock.LeaderElectionRecord{HolderIdentity: "node-a"}
|
|
|
|
if err := lock.Create(context.Background(), record); err != nil {
|
|
t.Fatalf("Create() error = %v, want nil so the elector keeps the lease", err)
|
|
}
|
|
if err := lock.Update(context.Background(), record); err != nil {
|
|
t.Fatalf("Update() error = %v, want nil so the elector keeps the lease", err)
|
|
}
|
|
|
|
if _, err := client.CoordinationV1().Leases("default").Get(context.Background(), "lease",
|
|
metav1.GetOptions{}); err != nil {
|
|
t.Fatalf("wrapped lock did not write the lease: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAnnotatedLeaseLockFollowerDoesNotOverwriteAnnotations(t *testing.T) {
|
|
client := fake.NewSimpleClientset()
|
|
leaseClient := client.CoordinationV1().Leases("default")
|
|
newBase := func(identity string) *resourcelock.LeaseLock {
|
|
return &resourcelock.LeaseLock{
|
|
LeaseMeta: metav1.ObjectMeta{Name: "lease", Namespace: "default"},
|
|
Client: client.CoordinationV1(),
|
|
LockConfig: resourcelock.ResourceLockConfig{Identity: identity},
|
|
}
|
|
}
|
|
ownerBase := newBase("node-a")
|
|
followerBase := newBase("node-b")
|
|
active, err := kubevip.WithLeaseVIPs(nil, "release_a", 248, []string{"192.0.2.10"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
creator := newAnnotatedLeaseLock(ownerBase, leaseClient, "lease", active)
|
|
if err := creator.Create(context.Background(), resourcelock.LeaderElectionRecord{HolderIdentity: "node-a"}); err != nil {
|
|
t.Fatalf("Create() error = %v", err)
|
|
}
|
|
|
|
follower, err := kubevip.WithLeaseVIPs(nil, "release_b", 249, []string{"192.0.2.20"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
observer := newAnnotatedLeaseLock(followerBase, leaseClient, "lease", follower)
|
|
if _, _, err := observer.Get(context.Background()); err != nil {
|
|
t.Fatalf("Get() error = %v", err)
|
|
}
|
|
resource, err := leaseClient.Get(context.Background(), "lease", metav1.GetOptions{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
metadata, err := kubevip.ParseLeaseVIPs(resource.Annotations[kubevip.LeaseVIPs])
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if metadata.InstanceName != "release_a" || metadata.IFAProto != 248 {
|
|
t.Fatalf("follower overwrote active metadata: %+v", metadata)
|
|
}
|
|
}
|
|
|
|
func TestAnnotatedLeaseLockReleaseDoesNotOverwriteSuccessorMetadata(t *testing.T) {
|
|
client := fake.NewSimpleClientset()
|
|
leaseClient := client.CoordinationV1().Leases("default")
|
|
newLock := func(identity, instanceName string, protocol int, vip string) resourcelock.Interface {
|
|
base := &resourcelock.LeaseLock{
|
|
LeaseMeta: metav1.ObjectMeta{Name: "lease", Namespace: "default"},
|
|
Client: client.CoordinationV1(),
|
|
LockConfig: resourcelock.ResourceLockConfig{Identity: identity},
|
|
}
|
|
annotations, err := kubevip.WithLeaseVIPs(nil, instanceName, protocol, []string{vip})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return newAnnotatedLeaseLock(base, leaseClient, "lease", annotations)
|
|
}
|
|
|
|
first := newLock("node-a", "release_a", 248, "192.0.2.10")
|
|
if err := first.Create(context.Background(), resourcelock.LeaderElectionRecord{HolderIdentity: "node-a"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := first.Update(context.Background(), resourcelock.LeaderElectionRecord{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
second := newLock("node-b", "release_b", 249, "192.0.2.20")
|
|
if _, _, err := second.Get(context.Background()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := second.Update(context.Background(), resourcelock.LeaderElectionRecord{HolderIdentity: "node-b"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
resource, err := leaseClient.Get(context.Background(), "lease", metav1.GetOptions{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
metadata, err := kubevip.ParseLeaseVIPs(resource.Annotations[kubevip.LeaseVIPs])
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if metadata.InstanceName != "release_b" || metadata.IFAProto != 249 || metadata.VIPs[0].Value != "192.0.2.20" {
|
|
t.Fatalf("successor metadata = %+v", metadata)
|
|
}
|
|
}
|