123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431 |
- /*
- Copyright 2018 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package nodelease
- import (
- "errors"
- "fmt"
- "testing"
- "time"
- coordinationv1 "k8s.io/api/coordination/v1"
- corev1 "k8s.io/api/core/v1"
- apiequality "k8s.io/apimachinery/pkg/api/equality"
- apierrors "k8s.io/apimachinery/pkg/api/errors"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/runtime"
- "k8s.io/apimachinery/pkg/runtime/schema"
- "k8s.io/apimachinery/pkg/types"
- "k8s.io/apimachinery/pkg/util/clock"
- "k8s.io/apimachinery/pkg/util/diff"
- "k8s.io/client-go/kubernetes/fake"
- clienttesting "k8s.io/client-go/testing"
- "k8s.io/utils/pointer"
- )
- func TestNewLease(t *testing.T) {
- fakeClock := clock.NewFakeClock(time.Now())
- node := &corev1.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- UID: types.UID("foo-uid"),
- },
- }
- cases := []struct {
- desc string
- controller *controller
- base *coordinationv1.Lease
- expect *coordinationv1.Lease
- }{
- {
- desc: "nil base without node",
- controller: &controller{
- client: fake.NewSimpleClientset(),
- holderIdentity: node.Name,
- leaseDurationSeconds: 10,
- clock: fakeClock,
- },
- base: nil,
- expect: &coordinationv1.Lease{
- ObjectMeta: metav1.ObjectMeta{
- Name: node.Name,
- Namespace: corev1.NamespaceNodeLease,
- },
- Spec: coordinationv1.LeaseSpec{
- HolderIdentity: pointer.StringPtr(node.Name),
- LeaseDurationSeconds: pointer.Int32Ptr(10),
- RenewTime: &metav1.MicroTime{Time: fakeClock.Now()},
- },
- },
- },
- {
- desc: "nil base with node",
- controller: &controller{
- client: fake.NewSimpleClientset(node),
- holderIdentity: node.Name,
- leaseDurationSeconds: 10,
- clock: fakeClock,
- },
- base: nil,
- expect: &coordinationv1.Lease{
- ObjectMeta: metav1.ObjectMeta{
- Name: node.Name,
- Namespace: corev1.NamespaceNodeLease,
- OwnerReferences: []metav1.OwnerReference{
- {
- APIVersion: corev1.SchemeGroupVersion.WithKind("Node").Version,
- Kind: corev1.SchemeGroupVersion.WithKind("Node").Kind,
- Name: node.Name,
- UID: node.UID,
- },
- },
- },
- Spec: coordinationv1.LeaseSpec{
- HolderIdentity: pointer.StringPtr(node.Name),
- LeaseDurationSeconds: pointer.Int32Ptr(10),
- RenewTime: &metav1.MicroTime{Time: fakeClock.Now()},
- },
- },
- },
- {
- desc: "non-nil base without owner ref, renew time is updated",
- controller: &controller{
- client: fake.NewSimpleClientset(node),
- holderIdentity: node.Name,
- leaseDurationSeconds: 10,
- clock: fakeClock,
- },
- base: &coordinationv1.Lease{
- ObjectMeta: metav1.ObjectMeta{
- Name: node.Name,
- Namespace: corev1.NamespaceNodeLease,
- },
- Spec: coordinationv1.LeaseSpec{
- HolderIdentity: pointer.StringPtr(node.Name),
- LeaseDurationSeconds: pointer.Int32Ptr(10),
- RenewTime: &metav1.MicroTime{Time: fakeClock.Now().Add(-10 * time.Second)},
- },
- },
- expect: &coordinationv1.Lease{
- ObjectMeta: metav1.ObjectMeta{
- Name: node.Name,
- Namespace: corev1.NamespaceNodeLease,
- OwnerReferences: []metav1.OwnerReference{
- {
- APIVersion: corev1.SchemeGroupVersion.WithKind("Node").Version,
- Kind: corev1.SchemeGroupVersion.WithKind("Node").Kind,
- Name: node.Name,
- UID: node.UID,
- },
- },
- },
- Spec: coordinationv1.LeaseSpec{
- HolderIdentity: pointer.StringPtr(node.Name),
- LeaseDurationSeconds: pointer.Int32Ptr(10),
- RenewTime: &metav1.MicroTime{Time: fakeClock.Now()},
- },
- },
- },
- {
- desc: "non-nil base with owner ref, renew time is updated",
- controller: &controller{
- client: fake.NewSimpleClientset(node),
- holderIdentity: node.Name,
- leaseDurationSeconds: 10,
- clock: fakeClock,
- },
- base: &coordinationv1.Lease{
- ObjectMeta: metav1.ObjectMeta{
- Name: node.Name,
- Namespace: corev1.NamespaceNodeLease,
- OwnerReferences: []metav1.OwnerReference{
- {
- APIVersion: corev1.SchemeGroupVersion.WithKind("Node").Version,
- Kind: corev1.SchemeGroupVersion.WithKind("Node").Kind,
- Name: node.Name,
- UID: node.UID,
- },
- },
- },
- Spec: coordinationv1.LeaseSpec{
- HolderIdentity: pointer.StringPtr(node.Name),
- LeaseDurationSeconds: pointer.Int32Ptr(10),
- RenewTime: &metav1.MicroTime{Time: fakeClock.Now().Add(-10 * time.Second)},
- },
- },
- expect: &coordinationv1.Lease{
- ObjectMeta: metav1.ObjectMeta{
- Name: node.Name,
- Namespace: corev1.NamespaceNodeLease,
- OwnerReferences: []metav1.OwnerReference{
- {
- APIVersion: corev1.SchemeGroupVersion.WithKind("Node").Version,
- Kind: corev1.SchemeGroupVersion.WithKind("Node").Kind,
- Name: node.Name,
- UID: node.UID,
- },
- },
- },
- Spec: coordinationv1.LeaseSpec{
- HolderIdentity: pointer.StringPtr(node.Name),
- LeaseDurationSeconds: pointer.Int32Ptr(10),
- RenewTime: &metav1.MicroTime{Time: fakeClock.Now()},
- },
- },
- },
- }
- for _, tc := range cases {
- t.Run(tc.desc, func(t *testing.T) {
- newLease := tc.controller.newLease(tc.base)
- if newLease == tc.base {
- t.Fatalf("the new lease must be newly allocated, but got same address as base")
- }
- if !apiequality.Semantic.DeepEqual(tc.expect, newLease) {
- t.Errorf("unexpected result from newLease: %s", diff.ObjectDiff(tc.expect, newLease))
- }
- })
- }
- }
- func TestRetryUpdateLease(t *testing.T) {
- node := &corev1.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- UID: types.UID("foo-uid"),
- },
- }
- gr := schema.GroupResource{Group: "v1", Resource: "lease"}
- noConnectionUpdateErr := apierrors.NewServerTimeout(gr, "put", 1)
- optimistcLockUpdateErr := apierrors.NewConflict(gr, "lease", fmt.Errorf("conflict"))
- cases := []struct {
- desc string
- updateReactor func(action clienttesting.Action) (bool, runtime.Object, error)
- getReactor func(action clienttesting.Action) (bool, runtime.Object, error)
- onRepeatedHeartbeatFailure func()
- expectErr bool
- }{
- {
- desc: "no errors",
- updateReactor: func(action clienttesting.Action) (bool, runtime.Object, error) {
- return true, &coordinationv1.Lease{}, nil
- },
- getReactor: nil,
- onRepeatedHeartbeatFailure: nil,
- expectErr: false,
- },
- {
- desc: "connection errors",
- updateReactor: func(action clienttesting.Action) (bool, runtime.Object, error) {
- return true, nil, noConnectionUpdateErr
- },
- getReactor: nil,
- onRepeatedHeartbeatFailure: nil,
- expectErr: true,
- },
- {
- desc: "optimistic lock errors",
- updateReactor: func() func(action clienttesting.Action) (bool, runtime.Object, error) {
- i := 0
- return func(action clienttesting.Action) (bool, runtime.Object, error) {
- i++
- switch i {
- case 1:
- return true, nil, noConnectionUpdateErr
- case 2:
- return true, nil, optimistcLockUpdateErr
- default:
- return true, &coordinationv1.Lease{}, nil
- }
- }
- }(),
- getReactor: func(action clienttesting.Action) (bool, runtime.Object, error) {
- return true, &coordinationv1.Lease{}, nil
- },
- onRepeatedHeartbeatFailure: func() { t.Fatalf("onRepeatedHeartbeatFailure called") },
- expectErr: false,
- },
- }
- for _, tc := range cases {
- t.Run(tc.desc, func(t *testing.T) {
- cl := fake.NewSimpleClientset(node)
- if tc.updateReactor != nil {
- cl.PrependReactor("update", "leases", tc.updateReactor)
- }
- if tc.getReactor != nil {
- cl.PrependReactor("get", "leases", tc.getReactor)
- }
- c := &controller{
- clock: clock.NewFakeClock(time.Now()),
- client: cl,
- leaseClient: cl.CoordinationV1().Leases(corev1.NamespaceNodeLease),
- holderIdentity: node.Name,
- leaseDurationSeconds: 10,
- onRepeatedHeartbeatFailure: tc.onRepeatedHeartbeatFailure,
- }
- if err := c.retryUpdateLease(nil); tc.expectErr != (err != nil) {
- t.Fatalf("got %v, expected %v", err != nil, tc.expectErr)
- }
- })
- }
- }
- func TestUpdateUsingLatestLease(t *testing.T) {
- nodeName := "foo"
- node := &corev1.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: nodeName,
- UID: types.UID("foo-uid"),
- },
- }
- notFoundErr := apierrors.NewNotFound(coordinationv1.Resource("lease"), nodeName)
- internalErr := apierrors.NewInternalError(errors.New("unreachable code"))
- makeLease := func(name, resourceVersion string) *coordinationv1.Lease {
- return &coordinationv1.Lease{
- ObjectMeta: metav1.ObjectMeta{
- Namespace: corev1.NamespaceNodeLease,
- Name: name,
- ResourceVersion: resourceVersion,
- },
- }
- }
- cases := []struct {
- desc string
- existingObjs []runtime.Object
- latestLease *coordinationv1.Lease
- updateReactor func(action clienttesting.Action) (bool, runtime.Object, error)
- getReactor func(action clienttesting.Action) (bool, runtime.Object, error)
- createReactor func(action clienttesting.Action) (bool, runtime.Object, error)
- expectLatestLease bool
- expectLeaseResourceVersion string
- }{
- {
- desc: "latestLease is nil and need to create",
- existingObjs: []runtime.Object{node},
- latestLease: nil,
- updateReactor: nil,
- getReactor: func(action clienttesting.Action) (bool, runtime.Object, error) {
- return true, nil, notFoundErr
- },
- createReactor: func(action clienttesting.Action) (bool, runtime.Object, error) {
- return true, makeLease(nodeName, "1"), nil
- },
- expectLatestLease: true,
- expectLeaseResourceVersion: "1",
- },
- {
- desc: "latestLease is nil and need to create, node doesn't exist",
- existingObjs: nil,
- latestLease: nil,
- updateReactor: nil,
- getReactor: func(action clienttesting.Action) (bool, runtime.Object, error) {
- return true, nil, notFoundErr
- },
- createReactor: func(action clienttesting.Action) (bool, runtime.Object, error) {
- return true, makeLease(nodeName, "1"), nil
- },
- expectLatestLease: false,
- expectLeaseResourceVersion: "1",
- },
- {
- desc: "latestLease is nil and need to update",
- existingObjs: []runtime.Object{node},
- latestLease: nil,
- updateReactor: func(action clienttesting.Action) (bool, runtime.Object, error) {
- return true, makeLease(nodeName, "2"), nil
- },
- getReactor: func(action clienttesting.Action) (bool, runtime.Object, error) {
- return true, makeLease(nodeName, "1"), nil
- },
- expectLatestLease: true,
- expectLeaseResourceVersion: "2",
- },
- {
- desc: "latestLease exist and need to update",
- existingObjs: []runtime.Object{node},
- latestLease: makeLease(nodeName, "1"),
- updateReactor: func(action clienttesting.Action) (bool, runtime.Object, error) {
- return true, makeLease(nodeName, "2"), nil
- },
- expectLatestLease: true,
- expectLeaseResourceVersion: "2",
- },
- {
- desc: "update with latest lease failed",
- existingObjs: []runtime.Object{node},
- latestLease: makeLease(nodeName, "1"),
- updateReactor: func() func(action clienttesting.Action) (bool, runtime.Object, error) {
- i := 0
- return func(action clienttesting.Action) (bool, runtime.Object, error) {
- i++
- switch i {
- case 1:
- return true, nil, notFoundErr
- case 2:
- return true, makeLease(nodeName, "3"), nil
- default:
- t.Fatalf("unexpect call update lease")
- return true, nil, internalErr
- }
- }
- }(),
- getReactor: func(action clienttesting.Action) (bool, runtime.Object, error) {
- return true, makeLease(nodeName, "2"), nil
- },
- expectLatestLease: true,
- expectLeaseResourceVersion: "3",
- },
- }
- for _, tc := range cases {
- t.Run(tc.desc, func(t *testing.T) {
- cl := fake.NewSimpleClientset(tc.existingObjs...)
- if tc.updateReactor != nil {
- cl.PrependReactor("update", "leases", tc.updateReactor)
- }
- if tc.getReactor != nil {
- cl.PrependReactor("get", "leases", tc.getReactor)
- }
- if tc.createReactor != nil {
- cl.PrependReactor("create", "leases", tc.createReactor)
- }
- c := &controller{
- clock: clock.NewFakeClock(time.Now()),
- client: cl,
- leaseClient: cl.CoordinationV1().Leases(corev1.NamespaceNodeLease),
- holderIdentity: node.Name,
- leaseDurationSeconds: 10,
- latestLease: tc.latestLease,
- }
- c.sync()
- if tc.expectLatestLease {
- if tc.expectLeaseResourceVersion != c.latestLease.ResourceVersion {
- t.Fatalf("latestLease RV got %v, expected %v", c.latestLease.ResourceVersion, tc.expectLeaseResourceVersion)
- }
- } else {
- if c.latestLease != nil {
- t.Fatalf("unexpected latestLease: %v", c.latestLease)
- }
- }
- })
- }
- }
|