controller_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. /*
  2. Copyright 2018 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package nodelease
  14. import (
  15. "errors"
  16. "fmt"
  17. "testing"
  18. "time"
  19. coordinationv1 "k8s.io/api/coordination/v1"
  20. corev1 "k8s.io/api/core/v1"
  21. apiequality "k8s.io/apimachinery/pkg/api/equality"
  22. apierrors "k8s.io/apimachinery/pkg/api/errors"
  23. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  24. "k8s.io/apimachinery/pkg/runtime"
  25. "k8s.io/apimachinery/pkg/runtime/schema"
  26. "k8s.io/apimachinery/pkg/types"
  27. "k8s.io/apimachinery/pkg/util/clock"
  28. "k8s.io/apimachinery/pkg/util/diff"
  29. "k8s.io/client-go/kubernetes/fake"
  30. clienttesting "k8s.io/client-go/testing"
  31. "k8s.io/utils/pointer"
  32. )
  33. func TestNewLease(t *testing.T) {
  34. fakeClock := clock.NewFakeClock(time.Now())
  35. node := &corev1.Node{
  36. ObjectMeta: metav1.ObjectMeta{
  37. Name: "foo",
  38. UID: types.UID("foo-uid"),
  39. },
  40. }
  41. cases := []struct {
  42. desc string
  43. controller *controller
  44. base *coordinationv1.Lease
  45. expect *coordinationv1.Lease
  46. }{
  47. {
  48. desc: "nil base without node",
  49. controller: &controller{
  50. client: fake.NewSimpleClientset(),
  51. holderIdentity: node.Name,
  52. leaseDurationSeconds: 10,
  53. clock: fakeClock,
  54. },
  55. base: nil,
  56. expect: &coordinationv1.Lease{
  57. ObjectMeta: metav1.ObjectMeta{
  58. Name: node.Name,
  59. Namespace: corev1.NamespaceNodeLease,
  60. },
  61. Spec: coordinationv1.LeaseSpec{
  62. HolderIdentity: pointer.StringPtr(node.Name),
  63. LeaseDurationSeconds: pointer.Int32Ptr(10),
  64. RenewTime: &metav1.MicroTime{Time: fakeClock.Now()},
  65. },
  66. },
  67. },
  68. {
  69. desc: "nil base with node",
  70. controller: &controller{
  71. client: fake.NewSimpleClientset(node),
  72. holderIdentity: node.Name,
  73. leaseDurationSeconds: 10,
  74. clock: fakeClock,
  75. },
  76. base: nil,
  77. expect: &coordinationv1.Lease{
  78. ObjectMeta: metav1.ObjectMeta{
  79. Name: node.Name,
  80. Namespace: corev1.NamespaceNodeLease,
  81. OwnerReferences: []metav1.OwnerReference{
  82. {
  83. APIVersion: corev1.SchemeGroupVersion.WithKind("Node").Version,
  84. Kind: corev1.SchemeGroupVersion.WithKind("Node").Kind,
  85. Name: node.Name,
  86. UID: node.UID,
  87. },
  88. },
  89. },
  90. Spec: coordinationv1.LeaseSpec{
  91. HolderIdentity: pointer.StringPtr(node.Name),
  92. LeaseDurationSeconds: pointer.Int32Ptr(10),
  93. RenewTime: &metav1.MicroTime{Time: fakeClock.Now()},
  94. },
  95. },
  96. },
  97. {
  98. desc: "non-nil base without owner ref, renew time is updated",
  99. controller: &controller{
  100. client: fake.NewSimpleClientset(node),
  101. holderIdentity: node.Name,
  102. leaseDurationSeconds: 10,
  103. clock: fakeClock,
  104. },
  105. base: &coordinationv1.Lease{
  106. ObjectMeta: metav1.ObjectMeta{
  107. Name: node.Name,
  108. Namespace: corev1.NamespaceNodeLease,
  109. },
  110. Spec: coordinationv1.LeaseSpec{
  111. HolderIdentity: pointer.StringPtr(node.Name),
  112. LeaseDurationSeconds: pointer.Int32Ptr(10),
  113. RenewTime: &metav1.MicroTime{Time: fakeClock.Now().Add(-10 * time.Second)},
  114. },
  115. },
  116. expect: &coordinationv1.Lease{
  117. ObjectMeta: metav1.ObjectMeta{
  118. Name: node.Name,
  119. Namespace: corev1.NamespaceNodeLease,
  120. OwnerReferences: []metav1.OwnerReference{
  121. {
  122. APIVersion: corev1.SchemeGroupVersion.WithKind("Node").Version,
  123. Kind: corev1.SchemeGroupVersion.WithKind("Node").Kind,
  124. Name: node.Name,
  125. UID: node.UID,
  126. },
  127. },
  128. },
  129. Spec: coordinationv1.LeaseSpec{
  130. HolderIdentity: pointer.StringPtr(node.Name),
  131. LeaseDurationSeconds: pointer.Int32Ptr(10),
  132. RenewTime: &metav1.MicroTime{Time: fakeClock.Now()},
  133. },
  134. },
  135. },
  136. {
  137. desc: "non-nil base with owner ref, renew time is updated",
  138. controller: &controller{
  139. client: fake.NewSimpleClientset(node),
  140. holderIdentity: node.Name,
  141. leaseDurationSeconds: 10,
  142. clock: fakeClock,
  143. },
  144. base: &coordinationv1.Lease{
  145. ObjectMeta: metav1.ObjectMeta{
  146. Name: node.Name,
  147. Namespace: corev1.NamespaceNodeLease,
  148. OwnerReferences: []metav1.OwnerReference{
  149. {
  150. APIVersion: corev1.SchemeGroupVersion.WithKind("Node").Version,
  151. Kind: corev1.SchemeGroupVersion.WithKind("Node").Kind,
  152. Name: node.Name,
  153. UID: node.UID,
  154. },
  155. },
  156. },
  157. Spec: coordinationv1.LeaseSpec{
  158. HolderIdentity: pointer.StringPtr(node.Name),
  159. LeaseDurationSeconds: pointer.Int32Ptr(10),
  160. RenewTime: &metav1.MicroTime{Time: fakeClock.Now().Add(-10 * time.Second)},
  161. },
  162. },
  163. expect: &coordinationv1.Lease{
  164. ObjectMeta: metav1.ObjectMeta{
  165. Name: node.Name,
  166. Namespace: corev1.NamespaceNodeLease,
  167. OwnerReferences: []metav1.OwnerReference{
  168. {
  169. APIVersion: corev1.SchemeGroupVersion.WithKind("Node").Version,
  170. Kind: corev1.SchemeGroupVersion.WithKind("Node").Kind,
  171. Name: node.Name,
  172. UID: node.UID,
  173. },
  174. },
  175. },
  176. Spec: coordinationv1.LeaseSpec{
  177. HolderIdentity: pointer.StringPtr(node.Name),
  178. LeaseDurationSeconds: pointer.Int32Ptr(10),
  179. RenewTime: &metav1.MicroTime{Time: fakeClock.Now()},
  180. },
  181. },
  182. },
  183. }
  184. for _, tc := range cases {
  185. t.Run(tc.desc, func(t *testing.T) {
  186. newLease := tc.controller.newLease(tc.base)
  187. if newLease == tc.base {
  188. t.Fatalf("the new lease must be newly allocated, but got same address as base")
  189. }
  190. if !apiequality.Semantic.DeepEqual(tc.expect, newLease) {
  191. t.Errorf("unexpected result from newLease: %s", diff.ObjectDiff(tc.expect, newLease))
  192. }
  193. })
  194. }
  195. }
  196. func TestRetryUpdateLease(t *testing.T) {
  197. node := &corev1.Node{
  198. ObjectMeta: metav1.ObjectMeta{
  199. Name: "foo",
  200. UID: types.UID("foo-uid"),
  201. },
  202. }
  203. gr := schema.GroupResource{Group: "v1", Resource: "lease"}
  204. noConnectionUpdateErr := apierrors.NewServerTimeout(gr, "put", 1)
  205. optimistcLockUpdateErr := apierrors.NewConflict(gr, "lease", fmt.Errorf("conflict"))
  206. cases := []struct {
  207. desc string
  208. updateReactor func(action clienttesting.Action) (bool, runtime.Object, error)
  209. getReactor func(action clienttesting.Action) (bool, runtime.Object, error)
  210. onRepeatedHeartbeatFailure func()
  211. expectErr bool
  212. }{
  213. {
  214. desc: "no errors",
  215. updateReactor: func(action clienttesting.Action) (bool, runtime.Object, error) {
  216. return true, &coordinationv1.Lease{}, nil
  217. },
  218. getReactor: nil,
  219. onRepeatedHeartbeatFailure: nil,
  220. expectErr: false,
  221. },
  222. {
  223. desc: "connection errors",
  224. updateReactor: func(action clienttesting.Action) (bool, runtime.Object, error) {
  225. return true, nil, noConnectionUpdateErr
  226. },
  227. getReactor: nil,
  228. onRepeatedHeartbeatFailure: nil,
  229. expectErr: true,
  230. },
  231. {
  232. desc: "optimistic lock errors",
  233. updateReactor: func() func(action clienttesting.Action) (bool, runtime.Object, error) {
  234. i := 0
  235. return func(action clienttesting.Action) (bool, runtime.Object, error) {
  236. i++
  237. switch i {
  238. case 1:
  239. return true, nil, noConnectionUpdateErr
  240. case 2:
  241. return true, nil, optimistcLockUpdateErr
  242. default:
  243. return true, &coordinationv1.Lease{}, nil
  244. }
  245. }
  246. }(),
  247. getReactor: func(action clienttesting.Action) (bool, runtime.Object, error) {
  248. return true, &coordinationv1.Lease{}, nil
  249. },
  250. onRepeatedHeartbeatFailure: func() { t.Fatalf("onRepeatedHeartbeatFailure called") },
  251. expectErr: false,
  252. },
  253. }
  254. for _, tc := range cases {
  255. t.Run(tc.desc, func(t *testing.T) {
  256. cl := fake.NewSimpleClientset(node)
  257. if tc.updateReactor != nil {
  258. cl.PrependReactor("update", "leases", tc.updateReactor)
  259. }
  260. if tc.getReactor != nil {
  261. cl.PrependReactor("get", "leases", tc.getReactor)
  262. }
  263. c := &controller{
  264. clock: clock.NewFakeClock(time.Now()),
  265. client: cl,
  266. leaseClient: cl.CoordinationV1().Leases(corev1.NamespaceNodeLease),
  267. holderIdentity: node.Name,
  268. leaseDurationSeconds: 10,
  269. onRepeatedHeartbeatFailure: tc.onRepeatedHeartbeatFailure,
  270. }
  271. if err := c.retryUpdateLease(nil); tc.expectErr != (err != nil) {
  272. t.Fatalf("got %v, expected %v", err != nil, tc.expectErr)
  273. }
  274. })
  275. }
  276. }
  277. func TestUpdateUsingLatestLease(t *testing.T) {
  278. nodeName := "foo"
  279. node := &corev1.Node{
  280. ObjectMeta: metav1.ObjectMeta{
  281. Name: nodeName,
  282. UID: types.UID("foo-uid"),
  283. },
  284. }
  285. notFoundErr := apierrors.NewNotFound(coordinationv1.Resource("lease"), nodeName)
  286. internalErr := apierrors.NewInternalError(errors.New("unreachable code"))
  287. makeLease := func(name, resourceVersion string) *coordinationv1.Lease {
  288. return &coordinationv1.Lease{
  289. ObjectMeta: metav1.ObjectMeta{
  290. Namespace: corev1.NamespaceNodeLease,
  291. Name: name,
  292. ResourceVersion: resourceVersion,
  293. },
  294. }
  295. }
  296. cases := []struct {
  297. desc string
  298. existingObjs []runtime.Object
  299. latestLease *coordinationv1.Lease
  300. updateReactor func(action clienttesting.Action) (bool, runtime.Object, error)
  301. getReactor func(action clienttesting.Action) (bool, runtime.Object, error)
  302. createReactor func(action clienttesting.Action) (bool, runtime.Object, error)
  303. expectLatestLease bool
  304. expectLeaseResourceVersion string
  305. }{
  306. {
  307. desc: "latestLease is nil and need to create",
  308. existingObjs: []runtime.Object{node},
  309. latestLease: nil,
  310. updateReactor: nil,
  311. getReactor: func(action clienttesting.Action) (bool, runtime.Object, error) {
  312. return true, nil, notFoundErr
  313. },
  314. createReactor: func(action clienttesting.Action) (bool, runtime.Object, error) {
  315. return true, makeLease(nodeName, "1"), nil
  316. },
  317. expectLatestLease: true,
  318. expectLeaseResourceVersion: "1",
  319. },
  320. {
  321. desc: "latestLease is nil and need to create, node doesn't exist",
  322. existingObjs: nil,
  323. latestLease: nil,
  324. updateReactor: nil,
  325. getReactor: func(action clienttesting.Action) (bool, runtime.Object, error) {
  326. return true, nil, notFoundErr
  327. },
  328. createReactor: func(action clienttesting.Action) (bool, runtime.Object, error) {
  329. return true, makeLease(nodeName, "1"), nil
  330. },
  331. expectLatestLease: false,
  332. expectLeaseResourceVersion: "1",
  333. },
  334. {
  335. desc: "latestLease is nil and need to update",
  336. existingObjs: []runtime.Object{node},
  337. latestLease: nil,
  338. updateReactor: func(action clienttesting.Action) (bool, runtime.Object, error) {
  339. return true, makeLease(nodeName, "2"), nil
  340. },
  341. getReactor: func(action clienttesting.Action) (bool, runtime.Object, error) {
  342. return true, makeLease(nodeName, "1"), nil
  343. },
  344. expectLatestLease: true,
  345. expectLeaseResourceVersion: "2",
  346. },
  347. {
  348. desc: "latestLease exist and need to update",
  349. existingObjs: []runtime.Object{node},
  350. latestLease: makeLease(nodeName, "1"),
  351. updateReactor: func(action clienttesting.Action) (bool, runtime.Object, error) {
  352. return true, makeLease(nodeName, "2"), nil
  353. },
  354. expectLatestLease: true,
  355. expectLeaseResourceVersion: "2",
  356. },
  357. {
  358. desc: "update with latest lease failed",
  359. existingObjs: []runtime.Object{node},
  360. latestLease: makeLease(nodeName, "1"),
  361. updateReactor: func() func(action clienttesting.Action) (bool, runtime.Object, error) {
  362. i := 0
  363. return func(action clienttesting.Action) (bool, runtime.Object, error) {
  364. i++
  365. switch i {
  366. case 1:
  367. return true, nil, notFoundErr
  368. case 2:
  369. return true, makeLease(nodeName, "3"), nil
  370. default:
  371. t.Fatalf("unexpect call update lease")
  372. return true, nil, internalErr
  373. }
  374. }
  375. }(),
  376. getReactor: func(action clienttesting.Action) (bool, runtime.Object, error) {
  377. return true, makeLease(nodeName, "2"), nil
  378. },
  379. expectLatestLease: true,
  380. expectLeaseResourceVersion: "3",
  381. },
  382. }
  383. for _, tc := range cases {
  384. t.Run(tc.desc, func(t *testing.T) {
  385. cl := fake.NewSimpleClientset(tc.existingObjs...)
  386. if tc.updateReactor != nil {
  387. cl.PrependReactor("update", "leases", tc.updateReactor)
  388. }
  389. if tc.getReactor != nil {
  390. cl.PrependReactor("get", "leases", tc.getReactor)
  391. }
  392. if tc.createReactor != nil {
  393. cl.PrependReactor("create", "leases", tc.createReactor)
  394. }
  395. c := &controller{
  396. clock: clock.NewFakeClock(time.Now()),
  397. client: cl,
  398. leaseClient: cl.CoordinationV1().Leases(corev1.NamespaceNodeLease),
  399. holderIdentity: node.Name,
  400. leaseDurationSeconds: 10,
  401. latestLease: tc.latestLease,
  402. }
  403. c.sync()
  404. if tc.expectLatestLease {
  405. if tc.expectLeaseResourceVersion != c.latestLease.ResourceVersion {
  406. t.Fatalf("latestLease RV got %v, expected %v", c.latestLease.ResourceVersion, tc.expectLeaseResourceVersion)
  407. }
  408. } else {
  409. if c.latestLease != nil {
  410. t.Fatalf("unexpected latestLease: %v", c.latestLease)
  411. }
  412. }
  413. })
  414. }
  415. }