stateful_set_utils_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. /*
  2. Copyright 2016 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 statefulset
  14. import (
  15. "fmt"
  16. "math/rand"
  17. "reflect"
  18. "sort"
  19. "strconv"
  20. "testing"
  21. "time"
  22. "k8s.io/apimachinery/pkg/api/resource"
  23. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  24. "k8s.io/apimachinery/pkg/types"
  25. apps "k8s.io/api/apps/v1"
  26. "k8s.io/api/core/v1"
  27. podutil "k8s.io/kubernetes/pkg/api/v1/pod"
  28. "k8s.io/kubernetes/pkg/controller/history"
  29. )
  30. func TestGetParentNameAndOrdinal(t *testing.T) {
  31. set := newStatefulSet(3)
  32. pod := newStatefulSetPod(set, 1)
  33. if parent, ordinal := getParentNameAndOrdinal(pod); parent != set.Name {
  34. t.Errorf("Extracted the wrong parent name expected %s found %s", set.Name, parent)
  35. } else if ordinal != 1 {
  36. t.Errorf("Extracted the wrong ordinal expected %d found %d", 1, ordinal)
  37. }
  38. pod.Name = "1-bar"
  39. if parent, ordinal := getParentNameAndOrdinal(pod); parent != "" {
  40. t.Error("Expected empty string for non-member Pod parent")
  41. } else if ordinal != -1 {
  42. t.Error("Expected -1 for non member Pod ordinal")
  43. }
  44. }
  45. func TestIsMemberOf(t *testing.T) {
  46. set := newStatefulSet(3)
  47. set2 := newStatefulSet(3)
  48. set2.Name = "foo2"
  49. pod := newStatefulSetPod(set, 1)
  50. if !isMemberOf(set, pod) {
  51. t.Error("isMemberOf retruned false negative")
  52. }
  53. if isMemberOf(set2, pod) {
  54. t.Error("isMemberOf returned false positive")
  55. }
  56. }
  57. func TestIdentityMatches(t *testing.T) {
  58. set := newStatefulSet(3)
  59. pod := newStatefulSetPod(set, 1)
  60. if !identityMatches(set, pod) {
  61. t.Error("Newly created Pod has a bad identity")
  62. }
  63. pod.Name = "foo"
  64. if identityMatches(set, pod) {
  65. t.Error("identity matches for a Pod with the wrong name")
  66. }
  67. pod = newStatefulSetPod(set, 1)
  68. pod.Namespace = ""
  69. if identityMatches(set, pod) {
  70. t.Error("identity matches for a Pod with the wrong namespace")
  71. }
  72. pod = newStatefulSetPod(set, 1)
  73. delete(pod.Labels, apps.StatefulSetPodNameLabel)
  74. if identityMatches(set, pod) {
  75. t.Error("identity matches for a Pod with the wrong statefulSetPodNameLabel")
  76. }
  77. }
  78. func TestStorageMatches(t *testing.T) {
  79. set := newStatefulSet(3)
  80. pod := newStatefulSetPod(set, 1)
  81. if !storageMatches(set, pod) {
  82. t.Error("Newly created Pod has a invalid stroage")
  83. }
  84. pod.Spec.Volumes = nil
  85. if storageMatches(set, pod) {
  86. t.Error("Pod with invalid Volumes has valid storage")
  87. }
  88. pod = newStatefulSetPod(set, 1)
  89. for i := range pod.Spec.Volumes {
  90. pod.Spec.Volumes[i].PersistentVolumeClaim = nil
  91. }
  92. if storageMatches(set, pod) {
  93. t.Error("Pod with invalid Volumes claim valid storage")
  94. }
  95. pod = newStatefulSetPod(set, 1)
  96. for i := range pod.Spec.Volumes {
  97. if pod.Spec.Volumes[i].PersistentVolumeClaim != nil {
  98. pod.Spec.Volumes[i].PersistentVolumeClaim.ClaimName = "foo"
  99. }
  100. }
  101. if storageMatches(set, pod) {
  102. t.Error("Pod with invalid Volumes claim valid storage")
  103. }
  104. pod = newStatefulSetPod(set, 1)
  105. pod.Name = "bar"
  106. if storageMatches(set, pod) {
  107. t.Error("Pod with invalid ordinal has valid storage")
  108. }
  109. }
  110. func TestUpdateIdentity(t *testing.T) {
  111. set := newStatefulSet(3)
  112. pod := newStatefulSetPod(set, 1)
  113. if !identityMatches(set, pod) {
  114. t.Error("Newly created Pod has a bad identity")
  115. }
  116. pod.Namespace = ""
  117. if identityMatches(set, pod) {
  118. t.Error("identity matches for a Pod with the wrong namespace")
  119. }
  120. updateIdentity(set, pod)
  121. if !identityMatches(set, pod) {
  122. t.Error("updateIdentity failed to update the Pods namespace")
  123. }
  124. delete(pod.Labels, apps.StatefulSetPodNameLabel)
  125. updateIdentity(set, pod)
  126. if !identityMatches(set, pod) {
  127. t.Error("updateIdentity failed to restore the statefulSetPodName label")
  128. }
  129. }
  130. func TestUpdateStorage(t *testing.T) {
  131. set := newStatefulSet(3)
  132. pod := newStatefulSetPod(set, 1)
  133. if !storageMatches(set, pod) {
  134. t.Error("Newly created Pod has a invalid stroage")
  135. }
  136. pod.Spec.Volumes = nil
  137. if storageMatches(set, pod) {
  138. t.Error("Pod with invalid Volumes has valid storage")
  139. }
  140. updateStorage(set, pod)
  141. if !storageMatches(set, pod) {
  142. t.Error("updateStorage failed to recreate volumes")
  143. }
  144. pod = newStatefulSetPod(set, 1)
  145. for i := range pod.Spec.Volumes {
  146. pod.Spec.Volumes[i].PersistentVolumeClaim = nil
  147. }
  148. if storageMatches(set, pod) {
  149. t.Error("Pod with invalid Volumes claim valid storage")
  150. }
  151. updateStorage(set, pod)
  152. if !storageMatches(set, pod) {
  153. t.Error("updateStorage failed to recreate volume claims")
  154. }
  155. pod = newStatefulSetPod(set, 1)
  156. for i := range pod.Spec.Volumes {
  157. if pod.Spec.Volumes[i].PersistentVolumeClaim != nil {
  158. pod.Spec.Volumes[i].PersistentVolumeClaim.ClaimName = "foo"
  159. }
  160. }
  161. if storageMatches(set, pod) {
  162. t.Error("Pod with invalid Volumes claim valid storage")
  163. }
  164. updateStorage(set, pod)
  165. if !storageMatches(set, pod) {
  166. t.Error("updateStorage failed to recreate volume claim names")
  167. }
  168. }
  169. func TestIsRunningAndReady(t *testing.T) {
  170. set := newStatefulSet(3)
  171. pod := newStatefulSetPod(set, 1)
  172. if isRunningAndReady(pod) {
  173. t.Error("isRunningAndReady does not respect Pod phase")
  174. }
  175. pod.Status.Phase = v1.PodRunning
  176. if isRunningAndReady(pod) {
  177. t.Error("isRunningAndReady does not respect Pod condition")
  178. }
  179. condition := v1.PodCondition{Type: v1.PodReady, Status: v1.ConditionTrue}
  180. podutil.UpdatePodCondition(&pod.Status, &condition)
  181. if !isRunningAndReady(pod) {
  182. t.Error("Pod should be running and ready")
  183. }
  184. }
  185. func TestAscendingOrdinal(t *testing.T) {
  186. set := newStatefulSet(10)
  187. pods := make([]*v1.Pod, 10)
  188. perm := rand.Perm(10)
  189. for i, v := range perm {
  190. pods[i] = newStatefulSetPod(set, v)
  191. }
  192. sort.Sort(ascendingOrdinal(pods))
  193. if !sort.IsSorted(ascendingOrdinal(pods)) {
  194. t.Error("ascendingOrdinal fails to sort Pods")
  195. }
  196. }
  197. func TestOverlappingStatefulSets(t *testing.T) {
  198. sets := make([]*apps.StatefulSet, 10)
  199. perm := rand.Perm(10)
  200. for i, v := range perm {
  201. sets[i] = newStatefulSet(10)
  202. sets[i].CreationTimestamp = metav1.NewTime(sets[i].CreationTimestamp.Add(time.Duration(v) * time.Second))
  203. }
  204. sort.Sort(overlappingStatefulSets(sets))
  205. if !sort.IsSorted(overlappingStatefulSets(sets)) {
  206. t.Error("ascendingOrdinal fails to sort Pods")
  207. }
  208. for i, v := range perm {
  209. sets[i] = newStatefulSet(10)
  210. sets[i].Name = strconv.FormatInt(int64(v), 10)
  211. }
  212. sort.Sort(overlappingStatefulSets(sets))
  213. if !sort.IsSorted(overlappingStatefulSets(sets)) {
  214. t.Error("ascendingOrdinal fails to sort Pods")
  215. }
  216. }
  217. func TestNewPodControllerRef(t *testing.T) {
  218. set := newStatefulSet(1)
  219. pod := newStatefulSetPod(set, 0)
  220. controllerRef := metav1.GetControllerOf(pod)
  221. if controllerRef == nil {
  222. t.Fatalf("No ControllerRef found on new pod")
  223. }
  224. if got, want := controllerRef.APIVersion, apps.SchemeGroupVersion.String(); got != want {
  225. t.Errorf("controllerRef.APIVersion = %q, want %q", got, want)
  226. }
  227. if got, want := controllerRef.Kind, "StatefulSet"; got != want {
  228. t.Errorf("controllerRef.Kind = %q, want %q", got, want)
  229. }
  230. if got, want := controllerRef.Name, set.Name; got != want {
  231. t.Errorf("controllerRef.Name = %q, want %q", got, want)
  232. }
  233. if got, want := controllerRef.UID, set.UID; got != want {
  234. t.Errorf("controllerRef.UID = %q, want %q", got, want)
  235. }
  236. if got, want := *controllerRef.Controller, true; got != want {
  237. t.Errorf("controllerRef.Controller = %v, want %v", got, want)
  238. }
  239. }
  240. func TestCreateApplyRevision(t *testing.T) {
  241. set := newStatefulSet(1)
  242. set.Status.CollisionCount = new(int32)
  243. revision, err := newRevision(set, 1, set.Status.CollisionCount)
  244. if err != nil {
  245. t.Fatal(err)
  246. }
  247. set.Spec.Template.Spec.Containers[0].Name = "foo"
  248. if set.Annotations == nil {
  249. set.Annotations = make(map[string]string)
  250. }
  251. key := "foo"
  252. expectedValue := "bar"
  253. set.Annotations[key] = expectedValue
  254. restoredSet, err := ApplyRevision(set, revision)
  255. if err != nil {
  256. t.Fatal(err)
  257. }
  258. restoredRevision, err := newRevision(restoredSet, 2, restoredSet.Status.CollisionCount)
  259. if err != nil {
  260. t.Fatal(err)
  261. }
  262. if !history.EqualRevision(revision, restoredRevision) {
  263. t.Errorf("wanted %v got %v", string(revision.Data.Raw), string(restoredRevision.Data.Raw))
  264. }
  265. value, ok := restoredRevision.Annotations[key]
  266. if !ok {
  267. t.Errorf("missing annotation %s", key)
  268. }
  269. if value != expectedValue {
  270. t.Errorf("for annotation %s wanted %s got %s", key, expectedValue, value)
  271. }
  272. }
  273. func TestRollingUpdateApplyRevision(t *testing.T) {
  274. set := newStatefulSet(1)
  275. set.Status.CollisionCount = new(int32)
  276. currentSet := set.DeepCopy()
  277. currentRevision, err := newRevision(set, 1, set.Status.CollisionCount)
  278. if err != nil {
  279. t.Fatal(err)
  280. }
  281. set.Spec.Template.Spec.Containers[0].Env = []v1.EnvVar{{Name: "foo", Value: "bar"}}
  282. updateSet := set.DeepCopy()
  283. updateRevision, err := newRevision(set, 2, set.Status.CollisionCount)
  284. if err != nil {
  285. t.Fatal(err)
  286. }
  287. restoredCurrentSet, err := ApplyRevision(set, currentRevision)
  288. if err != nil {
  289. t.Fatal(err)
  290. }
  291. if !reflect.DeepEqual(currentSet.Spec.Template, restoredCurrentSet.Spec.Template) {
  292. t.Errorf("want %v got %v", currentSet.Spec.Template, restoredCurrentSet.Spec.Template)
  293. }
  294. restoredUpdateSet, err := ApplyRevision(set, updateRevision)
  295. if err != nil {
  296. t.Fatal(err)
  297. }
  298. if !reflect.DeepEqual(updateSet.Spec.Template, restoredUpdateSet.Spec.Template) {
  299. t.Errorf("want %v got %v", updateSet.Spec.Template, restoredUpdateSet.Spec.Template)
  300. }
  301. }
  302. func TestGetPersistentVolumeClaims(t *testing.T) {
  303. // nil inherits statefulset labels
  304. pod := newPod()
  305. statefulSet := newStatefulSet(1)
  306. statefulSet.Spec.Selector.MatchLabels = nil
  307. claims := getPersistentVolumeClaims(statefulSet, pod)
  308. pvc := newPVC("datadir-foo-0")
  309. pvc.SetNamespace(v1.NamespaceDefault)
  310. resultClaims := map[string]v1.PersistentVolumeClaim{"datadir": pvc}
  311. if !reflect.DeepEqual(claims, resultClaims) {
  312. t.Fatalf("Unexpected pvc:\n %+v\n, desired pvc:\n %+v", claims, resultClaims)
  313. }
  314. // nil inherits statefulset labels
  315. statefulSet.Spec.Selector.MatchLabels = map[string]string{"test": "test"}
  316. claims = getPersistentVolumeClaims(statefulSet, pod)
  317. pvc.SetLabels(map[string]string{"test": "test"})
  318. resultClaims = map[string]v1.PersistentVolumeClaim{"datadir": pvc}
  319. if !reflect.DeepEqual(claims, resultClaims) {
  320. t.Fatalf("Unexpected pvc:\n %+v\n, desired pvc:\n %+v", claims, resultClaims)
  321. }
  322. // non-nil with non-overlapping labels merge pvc and statefulset labels
  323. statefulSet.Spec.Selector.MatchLabels = map[string]string{"name": "foo"}
  324. statefulSet.Spec.VolumeClaimTemplates[0].ObjectMeta.Labels = map[string]string{"test": "test"}
  325. claims = getPersistentVolumeClaims(statefulSet, pod)
  326. pvc.SetLabels(map[string]string{"test": "test", "name": "foo"})
  327. resultClaims = map[string]v1.PersistentVolumeClaim{"datadir": pvc}
  328. if !reflect.DeepEqual(claims, resultClaims) {
  329. t.Fatalf("Unexpected pvc:\n %+v\n, desired pvc:\n %+v", claims, resultClaims)
  330. }
  331. // non-nil with overlapping labels merge pvc and statefulset labels and prefer statefulset labels
  332. statefulSet.Spec.Selector.MatchLabels = map[string]string{"test": "foo"}
  333. statefulSet.Spec.VolumeClaimTemplates[0].ObjectMeta.Labels = map[string]string{"test": "test"}
  334. claims = getPersistentVolumeClaims(statefulSet, pod)
  335. pvc.SetLabels(map[string]string{"test": "foo"})
  336. resultClaims = map[string]v1.PersistentVolumeClaim{"datadir": pvc}
  337. if !reflect.DeepEqual(claims, resultClaims) {
  338. t.Fatalf("Unexpected pvc:\n %+v\n, desired pvc:\n %+v", claims, resultClaims)
  339. }
  340. }
  341. func newPod() *v1.Pod {
  342. return &v1.Pod{
  343. ObjectMeta: metav1.ObjectMeta{
  344. Name: "foo-0",
  345. Namespace: v1.NamespaceDefault,
  346. },
  347. Spec: v1.PodSpec{
  348. Containers: []v1.Container{
  349. {
  350. Name: "nginx",
  351. Image: "nginx",
  352. },
  353. },
  354. },
  355. }
  356. }
  357. func newPVC(name string) v1.PersistentVolumeClaim {
  358. return v1.PersistentVolumeClaim{
  359. ObjectMeta: metav1.ObjectMeta{
  360. Name: name,
  361. },
  362. Spec: v1.PersistentVolumeClaimSpec{
  363. Resources: v1.ResourceRequirements{
  364. Requests: v1.ResourceList{
  365. v1.ResourceStorage: *resource.NewQuantity(1, resource.BinarySI),
  366. },
  367. },
  368. },
  369. }
  370. }
  371. func newStatefulSetWithVolumes(replicas int, name string, petMounts []v1.VolumeMount, podMounts []v1.VolumeMount) *apps.StatefulSet {
  372. mounts := append(petMounts, podMounts...)
  373. claims := []v1.PersistentVolumeClaim{}
  374. for _, m := range petMounts {
  375. claims = append(claims, newPVC(m.Name))
  376. }
  377. vols := []v1.Volume{}
  378. for _, m := range podMounts {
  379. vols = append(vols, v1.Volume{
  380. Name: m.Name,
  381. VolumeSource: v1.VolumeSource{
  382. HostPath: &v1.HostPathVolumeSource{
  383. Path: fmt.Sprintf("/tmp/%v", m.Name),
  384. },
  385. },
  386. })
  387. }
  388. template := v1.PodTemplateSpec{
  389. Spec: v1.PodSpec{
  390. Containers: []v1.Container{
  391. {
  392. Name: "nginx",
  393. Image: "nginx",
  394. VolumeMounts: mounts,
  395. },
  396. },
  397. Volumes: vols,
  398. },
  399. }
  400. template.Labels = map[string]string{"foo": "bar"}
  401. return &apps.StatefulSet{
  402. TypeMeta: metav1.TypeMeta{
  403. Kind: "StatefulSet",
  404. APIVersion: "apps/v1",
  405. },
  406. ObjectMeta: metav1.ObjectMeta{
  407. Name: name,
  408. Namespace: v1.NamespaceDefault,
  409. UID: types.UID("test"),
  410. },
  411. Spec: apps.StatefulSetSpec{
  412. Selector: &metav1.LabelSelector{
  413. MatchLabels: map[string]string{"foo": "bar"},
  414. },
  415. Replicas: func() *int32 { i := int32(replicas); return &i }(),
  416. Template: template,
  417. VolumeClaimTemplates: claims,
  418. ServiceName: "governingsvc",
  419. UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType},
  420. RevisionHistoryLimit: func() *int32 {
  421. limit := int32(2)
  422. return &limit
  423. }(),
  424. },
  425. }
  426. }
  427. func newStatefulSet(replicas int) *apps.StatefulSet {
  428. petMounts := []v1.VolumeMount{
  429. {Name: "datadir", MountPath: "/tmp/zookeeper"},
  430. }
  431. podMounts := []v1.VolumeMount{
  432. {Name: "home", MountPath: "/home"},
  433. }
  434. return newStatefulSetWithVolumes(replicas, "foo", petMounts, podMounts)
  435. }