image_locality_test.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. /*
  2. Copyright 2019 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 imagelocality
  14. import (
  15. "context"
  16. "crypto/sha256"
  17. "encoding/hex"
  18. "reflect"
  19. "testing"
  20. v1 "k8s.io/api/core/v1"
  21. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. framework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1"
  23. "k8s.io/kubernetes/pkg/scheduler/internal/cache"
  24. "k8s.io/kubernetes/pkg/util/parsers"
  25. )
  26. func TestImageLocalityPriority(t *testing.T) {
  27. test40250 := v1.PodSpec{
  28. Containers: []v1.Container{
  29. {
  30. Image: "gcr.io/40",
  31. },
  32. {
  33. Image: "gcr.io/250",
  34. },
  35. },
  36. }
  37. test40300 := v1.PodSpec{
  38. Containers: []v1.Container{
  39. {
  40. Image: "gcr.io/40",
  41. },
  42. {
  43. Image: "gcr.io/300",
  44. },
  45. },
  46. }
  47. testMinMax := v1.PodSpec{
  48. Containers: []v1.Container{
  49. {
  50. Image: "gcr.io/10",
  51. },
  52. {
  53. Image: "gcr.io/2000",
  54. },
  55. },
  56. }
  57. node403002000 := v1.NodeStatus{
  58. Images: []v1.ContainerImage{
  59. {
  60. Names: []string{
  61. "gcr.io/40:" + parsers.DefaultImageTag,
  62. "gcr.io/40:v1",
  63. "gcr.io/40:v1",
  64. },
  65. SizeBytes: int64(40 * mb),
  66. },
  67. {
  68. Names: []string{
  69. "gcr.io/300:" + parsers.DefaultImageTag,
  70. "gcr.io/300:v1",
  71. },
  72. SizeBytes: int64(300 * mb),
  73. },
  74. {
  75. Names: []string{
  76. "gcr.io/2000:" + parsers.DefaultImageTag,
  77. },
  78. SizeBytes: int64(2000 * mb),
  79. },
  80. },
  81. }
  82. node25010 := v1.NodeStatus{
  83. Images: []v1.ContainerImage{
  84. {
  85. Names: []string{
  86. "gcr.io/250:" + parsers.DefaultImageTag,
  87. },
  88. SizeBytes: int64(250 * mb),
  89. },
  90. {
  91. Names: []string{
  92. "gcr.io/10:" + parsers.DefaultImageTag,
  93. "gcr.io/10:v1",
  94. },
  95. SizeBytes: int64(10 * mb),
  96. },
  97. },
  98. }
  99. nodeWithNoImages := v1.NodeStatus{}
  100. tests := []struct {
  101. pod *v1.Pod
  102. pods []*v1.Pod
  103. nodes []*v1.Node
  104. expectedList framework.NodeScoreList
  105. name string
  106. }{
  107. {
  108. // Pod: gcr.io/40 gcr.io/250
  109. // Node1
  110. // Image: gcr.io/40:latest 40MB
  111. // Score: 0 (40M/2 < 23M, min-threshold)
  112. // Node2
  113. // Image: gcr.io/250:latest 250MB
  114. // Score: 100 * (250M/2 - 23M)/(1000M - 23M) = 100
  115. pod: &v1.Pod{Spec: test40250},
  116. nodes: []*v1.Node{makeImageNode("machine1", node403002000), makeImageNode("machine2", node25010)},
  117. expectedList: []framework.NodeScore{{Name: "machine1", Score: 0}, {Name: "machine2", Score: 10}},
  118. name: "two images spread on two nodes, prefer the larger image one",
  119. },
  120. {
  121. // Pod: gcr.io/40 gcr.io/300
  122. // Node1
  123. // Image: gcr.io/40:latest 40MB, gcr.io/300:latest 300MB
  124. // Score: 100 * ((40M + 300M)/2 - 23M)/(1000M - 23M) = 15
  125. // Node2
  126. // Image: not present
  127. // Score: 0
  128. pod: &v1.Pod{Spec: test40300},
  129. nodes: []*v1.Node{makeImageNode("machine1", node403002000), makeImageNode("machine2", node25010)},
  130. expectedList: []framework.NodeScore{{Name: "machine1", Score: 15}, {Name: "machine2", Score: 0}},
  131. name: "two images on one node, prefer this node",
  132. },
  133. {
  134. // Pod: gcr.io/2000 gcr.io/10
  135. // Node1
  136. // Image: gcr.io/2000:latest 2000MB
  137. // Score: 100 (2000M/2 >= 1000M, max-threshold)
  138. // Node2
  139. // Image: gcr.io/10:latest 10MB
  140. // Score: 0 (10M/2 < 23M, min-threshold)
  141. pod: &v1.Pod{Spec: testMinMax},
  142. nodes: []*v1.Node{makeImageNode("machine1", node403002000), makeImageNode("machine2", node25010)},
  143. expectedList: []framework.NodeScore{{Name: "machine1", Score: framework.MaxNodeScore}, {Name: "machine2", Score: 0}},
  144. name: "if exceed limit, use limit",
  145. },
  146. {
  147. // Pod: gcr.io/2000 gcr.io/10
  148. // Node1
  149. // Image: gcr.io/2000:latest 2000MB
  150. // Score: 100 * (2000M/3 - 23M)/(1000M - 23M) = 65
  151. // Node2
  152. // Image: gcr.io/10:latest 10MB
  153. // Score: 0 (10M/2 < 23M, min-threshold)
  154. // Node3
  155. // Image:
  156. // Score: 0
  157. pod: &v1.Pod{Spec: testMinMax},
  158. nodes: []*v1.Node{makeImageNode("machine1", node403002000), makeImageNode("machine2", node25010), makeImageNode("machine3", nodeWithNoImages)},
  159. expectedList: []framework.NodeScore{{Name: "machine1", Score: 65}, {Name: "machine2", Score: 0}, {Name: "machine3", Score: 0}},
  160. name: "if exceed limit, use limit (with node which has no images present)",
  161. },
  162. }
  163. for _, test := range tests {
  164. t.Run(test.name, func(t *testing.T) {
  165. snapshot := cache.NewSnapshot(nil, test.nodes)
  166. state := framework.NewCycleState()
  167. fh, _ := framework.NewFramework(nil, nil, nil, framework.WithSnapshotSharedLister(snapshot))
  168. p, _ := New(nil, fh)
  169. var gotList framework.NodeScoreList
  170. for _, n := range test.nodes {
  171. nodeName := n.ObjectMeta.Name
  172. score, status := p.(framework.ScorePlugin).Score(context.Background(), state, test.pod, nodeName)
  173. if !status.IsSuccess() {
  174. t.Errorf("unexpected error: %v", status)
  175. }
  176. gotList = append(gotList, framework.NodeScore{Name: nodeName, Score: score})
  177. }
  178. if !reflect.DeepEqual(test.expectedList, gotList) {
  179. t.Errorf("expected:\n\t%+v,\ngot:\n\t%+v", test.expectedList, gotList)
  180. }
  181. })
  182. }
  183. }
  184. func TestNormalizedImageName(t *testing.T) {
  185. for _, testCase := range []struct {
  186. Name string
  187. Input string
  188. Output string
  189. }{
  190. {Name: "add :latest postfix 1", Input: "root", Output: "root:latest"},
  191. {Name: "add :latest postfix 2", Input: "gcr.io:5000/root", Output: "gcr.io:5000/root:latest"},
  192. {Name: "keep it as is 1", Input: "root:tag", Output: "root:tag"},
  193. {Name: "keep it as is 2", Input: "root@" + getImageFakeDigest("root"), Output: "root@" + getImageFakeDigest("root")},
  194. } {
  195. t.Run(testCase.Name, func(t *testing.T) {
  196. image := normalizedImageName(testCase.Input)
  197. if image != testCase.Output {
  198. t.Errorf("expected image reference: %q, got %q", testCase.Output, image)
  199. }
  200. })
  201. }
  202. }
  203. func makeImageNode(node string, status v1.NodeStatus) *v1.Node {
  204. return &v1.Node{
  205. ObjectMeta: metav1.ObjectMeta{Name: node},
  206. Status: status,
  207. }
  208. }
  209. func getImageFakeDigest(fakeContent string) string {
  210. hash := sha256.Sum256([]byte(fakeContent))
  211. return "sha256:" + hex.EncodeToString(hash[:])
  212. }