github.com/kubewharf/katalyst-core@v0.5.3/pkg/controller/tide/tide_test.go (about) 1 /* 2 Copyright 2022 The Katalyst Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package tide 18 19 import ( 20 "context" 21 "fmt" 22 "testing" 23 24 "github.com/stretchr/testify/assert" 25 corev1 "k8s.io/api/core/v1" 26 "k8s.io/apimachinery/pkg/api/resource" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/labels" 29 "k8s.io/apimachinery/pkg/runtime" 30 "k8s.io/apimachinery/pkg/types" 31 "k8s.io/apimachinery/pkg/util/intstr" 32 "k8s.io/client-go/tools/cache" 33 34 v1alpha12 "github.com/kubewharf/katalyst-api/pkg/apis/tide/v1alpha1" 35 katalystbase "github.com/kubewharf/katalyst-core/cmd/base" 36 ) 37 38 func TestTide_RunOnce(t1 *testing.T) { 39 t1.Parallel() 40 41 nodePool := &v1alpha12.TideNodePool{ 42 ObjectMeta: metav1.ObjectMeta{ 43 Name: "np1", 44 }, 45 Spec: v1alpha12.TideNodePoolSpec{ 46 NodeConfigs: v1alpha12.NodeConfigs{ 47 NodeSelector: map[string]string{"test": "test"}, 48 }, 49 }, 50 } 51 type args struct { 52 ctx context.Context 53 nodeList []runtime.Object 54 podList []runtime.Object 55 tideNodePool *v1alpha12.TideNodePool 56 } 57 tests := []struct { 58 name string 59 args args 60 wantOnlineNodeCount int 61 wantOfflineNodeCount int 62 }{ 63 { 64 name: "release empty node to offline", 65 args: args{ 66 ctx: context.Background(), 67 nodeList: []runtime.Object{ 68 buildTideNode(NewNodePoolWrapper(nodePool.DeepCopy()), "n1", 1000, 1000, true), 69 buildTideNode(NewNodePoolWrapper(nodePool.DeepCopy()), "n2", 1000, 1000, true), 70 }, 71 podList: nil, 72 tideNodePool: nodePool.DeepCopy(), 73 }, 74 wantOnlineNodeCount: 1, 75 wantOfflineNodeCount: 1, 76 }, 77 { 78 name: "release empty node to online", 79 args: args{ 80 ctx: context.Background(), 81 nodeList: []runtime.Object{ 82 buildTideNode(NewNodePoolWrapper(nodePool.DeepCopy()), "n3", 1000, 1000, false), 83 buildTideNode(NewNodePoolWrapper(nodePool.DeepCopy()), "n4", 1000, 1000, false), 84 }, 85 podList: []runtime.Object{buildOnlinePod(NewNodePoolWrapper(nodePool.DeepCopy()), "p1", 500, 500)}, 86 tideNodePool: nodePool.DeepCopy(), 87 }, 88 wantOnlineNodeCount: 1, 89 wantOfflineNodeCount: 1, 90 }, 91 } 92 for _, tt := range tests { 93 tt := tt 94 t1.Run(tt.name, func(t1 *testing.T) { 95 t1.Parallel() 96 97 controlCtx, err := katalystbase.GenerateFakeGenericContext(append(tt.args.nodeList, tt.args.podList...)) 98 if err != nil { 99 t1.Error(err) 100 } 101 t, err := NewTide(tt.args.ctx, controlCtx, nil, nil) 102 if err != nil { 103 t1.Error(err) 104 } 105 controlCtx.StartInformer(tt.args.ctx) 106 syncd := cache.WaitForCacheSync(tt.args.ctx.Done(), t.nodeListerSynced, t.tideListerSynced, t.podListerSynced) 107 assert.True(t1, syncd) 108 wrapper := NewNodePoolWrapper(tt.args.tideNodePool) 109 checker := func(pod *corev1.Pod) bool { 110 return labels.SelectorFromSet(map[string]string{LabelPodTypeKey: LabelOnlinePodValue}).Matches(labels.Set(pod.GetLabels())) 111 } 112 t.RunOnce(tt.args.ctx, checker, wrapper) 113 nodes, err := t.client.KubeClient.CoreV1().Nodes().List(tt.args.ctx, metav1.ListOptions{}) 114 if err != nil { 115 t1.Error(err) 116 } 117 onlineNodes, offlineNodes := 0, 0 118 for _, node := range nodes.Items { 119 fmt.Println(node.Labels) 120 if wrapper.GetOnlineTideNodeSelector().Matches(labels.Set(node.Labels)) { 121 onlineNodes++ 122 } 123 if wrapper.GetOfflineTideNodeSelector().Matches(labels.Set(node.Labels)) { 124 offlineNodes++ 125 } 126 } 127 fmt.Println(onlineNodes, offlineNodes) 128 assert.Equal(t1, onlineNodes, tt.wantOnlineNodeCount) 129 assert.Equal(t1, offlineNodes, tt.wantOfflineNodeCount) 130 }) 131 } 132 } 133 134 func buildNode(nodePool NodePoolWrapper, name string, millicpu int64, mem int64) *corev1.Node { 135 node := &corev1.Node{ 136 ObjectMeta: metav1.ObjectMeta{ 137 Name: name, 138 SelfLink: fmt.Sprintf("/api/v1/nodes/%s", name), 139 Labels: nodePool.GetNodeSelector(), 140 }, 141 Spec: corev1.NodeSpec{ 142 ProviderID: name, 143 }, 144 Status: corev1.NodeStatus{ 145 Capacity: corev1.ResourceList{ 146 corev1.ResourcePods: *resource.NewQuantity(100, resource.DecimalSI), 147 }, 148 }, 149 } 150 151 if millicpu >= 0 { 152 node.Status.Capacity[corev1.ResourceCPU] = *resource.NewMilliQuantity(millicpu, resource.DecimalSI) 153 } 154 if mem >= 0 { 155 node.Status.Capacity[corev1.ResourceMemory] = *resource.NewQuantity(mem, resource.DecimalSI) 156 } 157 158 node.Status.Allocatable = corev1.ResourceList{} 159 for k, v := range node.Status.Capacity { 160 node.Status.Allocatable[k] = v 161 } 162 163 return node 164 } 165 166 // buildTideNode creates a tide node with specified capacity. 167 func buildTideNode(nodePool NodePoolWrapper, name string, millicpu int64, mem int64, isOnline bool) *corev1.Node { 168 node := buildNode(nodePool, name, millicpu, mem) 169 node.Labels[nodePool.GetTideLabel().Key] = nodePool.GetTideLabel().Value 170 node.Labels[LabelNodePoolKey] = nodePool.GetName() 171 if isOnline { 172 node.Labels[nodePool.GetOnlineLabel().Key] = nodePool.GetOnlineLabel().Value 173 node.Spec.Taints = []corev1.Taint{ 174 { 175 Key: nodePool.GetEvictOfflinePodTaint().Key, 176 Value: nodePool.GetEvictOfflinePodTaint().Value, 177 Effect: corev1.TaintEffect(nodePool.GetEvictOfflinePodTaint().Effect), 178 }, 179 } 180 } else { 181 node.Labels[nodePool.GetOfflineLabel().Key] = nodePool.GetOfflineLabel().Value 182 node.Spec.Taints = []corev1.Taint{ 183 { 184 Key: nodePool.GetEvictOnlinePodTaint().Key, 185 Value: nodePool.GetEvictOnlinePodTaint().Value, 186 Effect: corev1.TaintEffect(nodePool.GetEvictOnlinePodTaint().Effect), 187 }, 188 } 189 } 190 191 return node 192 } 193 194 func buildOnlinePod(nodePool NodePoolWrapper, name string, cpu int64, mem int64) *corev1.Pod { 195 startTime := metav1.Unix(0, 0) 196 pod := &corev1.Pod{ 197 ObjectMeta: metav1.ObjectMeta{ 198 UID: types.UID(name), 199 Namespace: "default", 200 Name: name, 201 SelfLink: fmt.Sprintf("/api/v1/namespaces/default/pods/%s", name), 202 Annotations: map[string]string{}, 203 Labels: map[string]string{LabelPodTypeKey: LabelOnlinePodValue}, 204 }, 205 Spec: corev1.PodSpec{ 206 Containers: []corev1.Container{ 207 { 208 Resources: corev1.ResourceRequirements{ 209 Requests: corev1.ResourceList{}, 210 }, 211 }, 212 }, 213 Tolerations: []corev1.Toleration{ 214 { 215 Key: nodePool.GetEvictOfflinePodTaint().Key, 216 Operator: corev1.TolerationOpEqual, 217 Value: nodePool.GetEvictOfflinePodTaint().Value, 218 Effect: corev1.TaintEffect(nodePool.GetEvictOfflinePodTaint().Effect), 219 TolerationSeconds: nil, 220 }, 221 }, 222 }, 223 Status: corev1.PodStatus{ 224 StartTime: &startTime, 225 Conditions: []corev1.PodCondition{ 226 { 227 Type: corev1.PodScheduled, 228 Status: corev1.ConditionFalse, 229 LastProbeTime: metav1.Time{}, 230 LastTransitionTime: metav1.Time{}, 231 Reason: corev1.PodReasonUnschedulable, 232 Message: "", 233 }, 234 }, 235 }, 236 } 237 238 if cpu >= 0 { 239 pod.Spec.Containers[0].Resources.Requests[corev1.ResourceCPU] = *resource.NewMilliQuantity(cpu, resource.DecimalSI) 240 } 241 if mem >= 0 { 242 pod.Spec.Containers[0].Resources.Requests[corev1.ResourceMemory] = *resource.NewQuantity(mem, resource.DecimalSI) 243 } 244 245 return pod 246 } 247 248 func TestTide_Reconcile(t1 *testing.T) { 249 t1.Parallel() 250 251 nodeReserve := intstr.FromString("30%") 252 nodePool := &v1alpha12.TideNodePool{ 253 ObjectMeta: metav1.ObjectMeta{ 254 Name: "np1", 255 }, 256 Spec: v1alpha12.TideNodePoolSpec{ 257 NodeConfigs: v1alpha12.NodeConfigs{ 258 NodeSelector: map[string]string{"test": "test"}, 259 Reserve: v1alpha12.ReserveOptions{ 260 Online: &nodeReserve, 261 Offline: &nodeReserve, 262 }, 263 }, 264 }, 265 } 266 type args struct { 267 ctx context.Context 268 nodeList []runtime.Object 269 podList []runtime.Object 270 tideNodePool *v1alpha12.TideNodePool 271 } 272 tests := []struct { 273 name string 274 args args 275 276 wantReserveOnlineNodeCount int 277 wantReserveOfflineNodeCount int 278 wantReserveTideNodeCount int 279 }{ 280 { 281 name: "normal", 282 args: args{ 283 ctx: context.Background(), 284 nodeList: []runtime.Object{ 285 buildNode(NewNodePoolWrapper(nodePool), "n5", 1000, 1000), 286 buildNode(NewNodePoolWrapper(nodePool), "n6", 1000, 1000), 287 buildNode(NewNodePoolWrapper(nodePool), "n7", 1000, 1000), 288 buildNode(NewNodePoolWrapper(nodePool), "n8", 1000, 1000), 289 }, 290 podList: nil, 291 tideNodePool: nodePool, 292 }, 293 wantReserveOnlineNodeCount: 2, 294 wantReserveOfflineNodeCount: 1, 295 wantReserveTideNodeCount: 1, 296 }, 297 } 298 for _, tt := range tests { 299 tt := tt 300 t1.Run(tt.name, func(t1 *testing.T) { 301 t1.Parallel() 302 303 controlCtx, err := katalystbase.GenerateFakeGenericContext(append(tt.args.nodeList, tt.args.podList...), []runtime.Object{tt.args.tideNodePool}) 304 if err != nil { 305 t1.Error(err) 306 } 307 t, err := NewTide(tt.args.ctx, controlCtx, nil, nil) 308 if err != nil { 309 t1.Error(err) 310 } 311 controlCtx.StartInformer(tt.args.ctx) 312 syncd := cache.WaitForCacheSync(tt.args.ctx.Done(), t.nodeListerSynced, t.tideListerSynced, t.podListerSynced) 313 assert.True(t1, syncd) 314 t.Reconcile(tt.args.ctx, tt.args.tideNodePool) 315 syncd = cache.WaitForCacheSync(tt.args.ctx.Done(), t.nodeListerSynced, t.tideListerSynced, t.podListerSynced) 316 assert.True(t1, syncd) 317 tideNodePool, err := t.client.InternalClient.TideV1alpha1().TideNodePools().Get(tt.args.ctx, tt.args.tideNodePool.Name, metav1.GetOptions{}) 318 if err != nil { 319 t1.Error(err) 320 } 321 assert.Equal(t1, len(tideNodePool.Status.ReserveNodes.OnlineNodes), tt.wantReserveOnlineNodeCount) 322 assert.Equal(t1, len(tideNodePool.Status.ReserveNodes.OfflineNodes), tt.wantReserveOfflineNodeCount) 323 assert.Equal(t1, len(tideNodePool.Status.TideNodes.Nodes), tt.wantReserveTideNodeCount) 324 }) 325 } 326 }