k8s.io/kubernetes@v1.29.3/pkg/kubelet/cm/cpumanager/state/state_checkpoint_test.go (about) 1 /* 2 Copyright 2018 The Kubernetes 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 state 18 19 import ( 20 "os" 21 "reflect" 22 "strings" 23 "testing" 24 25 "github.com/stretchr/testify/require" 26 "k8s.io/kubernetes/pkg/kubelet/checkpointmanager" 27 "k8s.io/kubernetes/pkg/kubelet/cm/containermap" 28 testutil "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/state/testing" 29 "k8s.io/utils/cpuset" 30 ) 31 32 const testingCheckpoint = "cpumanager_checkpoint_test" 33 34 func TestCheckpointStateRestore(t *testing.T) { 35 testCases := []struct { 36 description string 37 checkpointContent string 38 policyName string 39 initialContainers containermap.ContainerMap 40 expectedError string 41 expectedState *stateMemory 42 }{ 43 { 44 "Restore non-existing checkpoint", 45 "", 46 "none", 47 containermap.ContainerMap{}, 48 "", 49 &stateMemory{}, 50 }, 51 { 52 "Restore default cpu set", 53 `{ 54 "policyName": "none", 55 "defaultCPUSet": "4-6", 56 "entries": {}, 57 "checksum": 354655845 58 }`, 59 "none", 60 containermap.ContainerMap{}, 61 "", 62 &stateMemory{ 63 defaultCPUSet: cpuset.New(4, 5, 6), 64 }, 65 }, 66 { 67 "Restore valid checkpoint", 68 `{ 69 "policyName": "none", 70 "defaultCPUSet": "1-3", 71 "entries": { 72 "pod": { 73 "container1": "4-6", 74 "container2": "1-3" 75 } 76 }, 77 "checksum": 3610638499 78 }`, 79 "none", 80 containermap.ContainerMap{}, 81 "", 82 &stateMemory{ 83 assignments: ContainerCPUAssignments{ 84 "pod": map[string]cpuset.CPUSet{ 85 "container1": cpuset.New(4, 5, 6), 86 "container2": cpuset.New(1, 2, 3), 87 }, 88 }, 89 defaultCPUSet: cpuset.New(1, 2, 3), 90 }, 91 }, 92 { 93 "Restore checkpoint with invalid checksum", 94 `{ 95 "policyName": "none", 96 "defaultCPUSet": "4-6", 97 "entries": {}, 98 "checksum": 1337 99 }`, 100 "none", 101 containermap.ContainerMap{}, 102 "checkpoint is corrupted", 103 &stateMemory{}, 104 }, 105 { 106 "Restore checkpoint with invalid JSON", 107 `{`, 108 "none", 109 containermap.ContainerMap{}, 110 "unexpected end of JSON input", 111 &stateMemory{}, 112 }, 113 { 114 "Restore checkpoint with invalid policy name", 115 `{ 116 "policyName": "other", 117 "defaultCPUSet": "1-3", 118 "entries": {}, 119 "checksum": 1394507217 120 }`, 121 "none", 122 containermap.ContainerMap{}, 123 `configured policy "none" differs from state checkpoint policy "other"`, 124 &stateMemory{}, 125 }, 126 { 127 "Restore checkpoint with unparsable default cpu set", 128 `{ 129 "policyName": "none", 130 "defaultCPUSet": "1.3", 131 "entries": {}, 132 "checksum": 3021697696 133 }`, 134 "none", 135 containermap.ContainerMap{}, 136 `could not parse default cpu set "1.3": strconv.Atoi: parsing "1.3": invalid syntax`, 137 &stateMemory{}, 138 }, 139 { 140 "Restore checkpoint with unparsable assignment entry", 141 `{ 142 "policyName": "none", 143 "defaultCPUSet": "1-3", 144 "entries": { 145 "pod": { 146 "container1": "4-6", 147 "container2": "asd" 148 } 149 }, 150 "checksum": 962272150 151 }`, 152 "none", 153 containermap.ContainerMap{}, 154 `could not parse cpuset "asd" for container "container2" in pod "pod": strconv.Atoi: parsing "asd": invalid syntax`, 155 &stateMemory{}, 156 }, 157 { 158 "Restore checkpoint from checkpoint with v1 checksum", 159 `{ 160 "policyName": "none", 161 "defaultCPUSet": "1-3", 162 "checksum": 1694838852 163 }`, 164 "none", 165 containermap.ContainerMap{}, 166 "", 167 &stateMemory{ 168 defaultCPUSet: cpuset.New(1, 2, 3), 169 }, 170 }, 171 { 172 "Restore checkpoint with migration", 173 `{ 174 "policyName": "none", 175 "defaultCPUSet": "1-3", 176 "entries": { 177 "containerID1": "4-6", 178 "containerID2": "1-3" 179 }, 180 "checksum": 3680390589 181 }`, 182 "none", 183 func() containermap.ContainerMap { 184 cm := containermap.NewContainerMap() 185 cm.Add("pod", "container1", "containerID1") 186 cm.Add("pod", "container2", "containerID2") 187 return cm 188 }(), 189 "", 190 &stateMemory{ 191 assignments: ContainerCPUAssignments{ 192 "pod": map[string]cpuset.CPUSet{ 193 "container1": cpuset.New(4, 5, 6), 194 "container2": cpuset.New(1, 2, 3), 195 }, 196 }, 197 defaultCPUSet: cpuset.New(1, 2, 3), 198 }, 199 }, 200 } 201 202 // create temp dir 203 testingDir, err := os.MkdirTemp("", "cpumanager_state_test") 204 require.NoError(t, err) 205 defer os.RemoveAll(testingDir) 206 // create checkpoint manager for testing 207 cpm, err := checkpointmanager.NewCheckpointManager(testingDir) 208 require.NoErrorf(t, err, "could not create testing checkpoint manager: %v", err) 209 210 for _, tc := range testCases { 211 t.Run(tc.description, func(t *testing.T) { 212 // ensure there is no previous checkpoint 213 cpm.RemoveCheckpoint(testingCheckpoint) 214 215 // prepare checkpoint for testing 216 if strings.TrimSpace(tc.checkpointContent) != "" { 217 checkpoint := &testutil.MockCheckpoint{Content: tc.checkpointContent} 218 err = cpm.CreateCheckpoint(testingCheckpoint, checkpoint) 219 require.NoErrorf(t, err, "could not create testing checkpoint: %v", err) 220 } 221 222 restoredState, err := NewCheckpointState(testingDir, testingCheckpoint, tc.policyName, tc.initialContainers) 223 if strings.TrimSpace(tc.expectedError) == "" { 224 require.NoError(t, err) 225 } else { 226 require.Error(t, err) 227 require.Contains(t, err.Error(), "could not restore state from checkpoint") 228 require.Contains(t, err.Error(), tc.expectedError) 229 return 230 } 231 232 // compare state after restoration with the one expected 233 AssertStateEqual(t, restoredState, tc.expectedState) 234 }) 235 } 236 } 237 238 func TestCheckpointStateStore(t *testing.T) { 239 testCases := []struct { 240 description string 241 expectedState *stateMemory 242 }{ 243 { 244 "Store default cpu set", 245 &stateMemory{defaultCPUSet: cpuset.New(1, 2, 3)}, 246 }, 247 { 248 "Store assignments", 249 &stateMemory{ 250 assignments: map[string]map[string]cpuset.CPUSet{ 251 "pod": { 252 "container1": cpuset.New(1, 5, 8), 253 }, 254 }, 255 }, 256 }, 257 } 258 259 // create temp dir 260 testingDir, err := os.MkdirTemp("", "cpumanager_state_test") 261 if err != nil { 262 t.Fatal(err) 263 } 264 defer os.RemoveAll(testingDir) 265 266 cpm, err := checkpointmanager.NewCheckpointManager(testingDir) 267 if err != nil { 268 t.Fatalf("could not create testing checkpoint manager: %v", err) 269 } 270 271 for _, tc := range testCases { 272 t.Run(tc.description, func(t *testing.T) { 273 // ensure there is no previous checkpoint 274 cpm.RemoveCheckpoint(testingCheckpoint) 275 276 cs1, err := NewCheckpointState(testingDir, testingCheckpoint, "none", nil) 277 if err != nil { 278 t.Fatalf("could not create testing checkpointState instance: %v", err) 279 } 280 281 // set values of cs1 instance so they are stored in checkpoint and can be read by cs2 282 cs1.SetDefaultCPUSet(tc.expectedState.defaultCPUSet) 283 cs1.SetCPUAssignments(tc.expectedState.assignments) 284 285 // restore checkpoint with previously stored values 286 cs2, err := NewCheckpointState(testingDir, testingCheckpoint, "none", nil) 287 if err != nil { 288 t.Fatalf("could not create testing checkpointState instance: %v", err) 289 } 290 291 AssertStateEqual(t, cs2, tc.expectedState) 292 }) 293 } 294 } 295 296 func TestCheckpointStateHelpers(t *testing.T) { 297 testCases := []struct { 298 description string 299 defaultCPUset cpuset.CPUSet 300 assignments map[string]map[string]cpuset.CPUSet 301 }{ 302 { 303 description: "One container", 304 defaultCPUset: cpuset.New(0, 1, 2, 3, 4, 5, 6, 7, 8), 305 assignments: map[string]map[string]cpuset.CPUSet{ 306 "pod": { 307 "c1": cpuset.New(0, 1), 308 }, 309 }, 310 }, 311 { 312 description: "Two containers", 313 defaultCPUset: cpuset.New(0, 1, 2, 3, 4, 5, 6, 7, 8), 314 assignments: map[string]map[string]cpuset.CPUSet{ 315 "pod": { 316 "c1": cpuset.New(0, 1), 317 "c2": cpuset.New(2, 3, 4, 5), 318 }, 319 }, 320 }, 321 { 322 description: "Container without assigned cpus", 323 defaultCPUset: cpuset.New(0, 1, 2, 3, 4, 5, 6, 7, 8), 324 assignments: map[string]map[string]cpuset.CPUSet{ 325 "pod": { 326 "c1": cpuset.New(), 327 }, 328 }, 329 }, 330 } 331 332 // create temp dir 333 testingDir, err := os.MkdirTemp("", "cpumanager_state_test") 334 if err != nil { 335 t.Fatal(err) 336 } 337 defer os.RemoveAll(testingDir) 338 339 cpm, err := checkpointmanager.NewCheckpointManager(testingDir) 340 if err != nil { 341 t.Fatalf("could not create testing checkpoint manager: %v", err) 342 } 343 344 for _, tc := range testCases { 345 t.Run(tc.description, func(t *testing.T) { 346 // ensure there is no previous checkpoint 347 cpm.RemoveCheckpoint(testingCheckpoint) 348 349 state, err := NewCheckpointState(testingDir, testingCheckpoint, "none", nil) 350 if err != nil { 351 t.Fatalf("could not create testing checkpointState instance: %v", err) 352 } 353 state.SetDefaultCPUSet(tc.defaultCPUset) 354 355 for pod := range tc.assignments { 356 for container, set := range tc.assignments[pod] { 357 state.SetCPUSet(pod, container, set) 358 if cpus, _ := state.GetCPUSet(pod, container); !cpus.Equals(set) { 359 t.Fatalf("state inconsistent, got %q instead of %q", set, cpus) 360 } 361 362 state.Delete(pod, container) 363 if _, ok := state.GetCPUSet(pod, container); ok { 364 t.Fatal("deleted container still existing in state") 365 } 366 } 367 } 368 }) 369 } 370 } 371 372 func TestCheckpointStateClear(t *testing.T) { 373 testCases := []struct { 374 description string 375 defaultCPUset cpuset.CPUSet 376 assignments map[string]map[string]cpuset.CPUSet 377 }{ 378 { 379 "Valid state", 380 cpuset.New(1, 5, 10), 381 map[string]map[string]cpuset.CPUSet{ 382 "pod": { 383 "container1": cpuset.New(1, 4), 384 }, 385 }, 386 }, 387 } 388 389 for _, tc := range testCases { 390 t.Run(tc.description, func(t *testing.T) { 391 // create temp dir 392 testingDir, err := os.MkdirTemp("", "cpumanager_state_test") 393 if err != nil { 394 t.Fatal(err) 395 } 396 defer os.RemoveAll(testingDir) 397 398 state, err := NewCheckpointState(testingDir, testingCheckpoint, "none", nil) 399 if err != nil { 400 t.Fatalf("could not create testing checkpointState instance: %v", err) 401 } 402 403 state.SetDefaultCPUSet(tc.defaultCPUset) 404 state.SetCPUAssignments(tc.assignments) 405 406 state.ClearState() 407 if !cpuset.New().Equals(state.GetDefaultCPUSet()) { 408 t.Fatal("cleared state with non-empty default cpu set") 409 } 410 for pod := range tc.assignments { 411 for container := range tc.assignments[pod] { 412 if _, ok := state.GetCPUSet(pod, container); ok { 413 t.Fatalf("container %q in pod %q with non-default cpu set in cleared state", container, pod) 414 } 415 } 416 } 417 }) 418 } 419 } 420 421 func AssertStateEqual(t *testing.T, sf State, sm State) { 422 cpusetSf := sf.GetDefaultCPUSet() 423 cpusetSm := sm.GetDefaultCPUSet() 424 if !cpusetSf.Equals(cpusetSm) { 425 t.Errorf("State CPUSet mismatch. Have %v, want %v", cpusetSf, cpusetSm) 426 } 427 428 cpuassignmentSf := sf.GetCPUAssignments() 429 cpuassignmentSm := sm.GetCPUAssignments() 430 if !reflect.DeepEqual(cpuassignmentSf, cpuassignmentSm) { 431 t.Errorf("State CPU assignments mismatch. Have %s, want %s", cpuassignmentSf, cpuassignmentSm) 432 } 433 }