k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/kubelet/container/testing/fake_runtime.go (about) 1 /* 2 Copyright 2015 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 testing 18 19 import ( 20 "context" 21 "io" 22 "net/url" 23 "reflect" 24 "sync" 25 "time" 26 27 v1 "k8s.io/api/core/v1" 28 "k8s.io/apimachinery/pkg/types" 29 "k8s.io/client-go/util/flowcontrol" 30 runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" 31 kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" 32 "k8s.io/kubernetes/pkg/volume" 33 ) 34 35 type TB interface { 36 Errorf(format string, args ...any) 37 } 38 39 type FakePod struct { 40 Pod *kubecontainer.Pod 41 NetnsPath string 42 } 43 44 // FakeRuntime is a fake container runtime for testing. 45 type FakeRuntime struct { 46 sync.Mutex 47 CalledFunctions []string 48 PodList []*FakePod 49 AllPodList []*FakePod 50 ImageList []kubecontainer.Image 51 ImageFsStats []*runtimeapi.FilesystemUsage 52 ContainerFsStats []*runtimeapi.FilesystemUsage 53 APIPodStatus v1.PodStatus 54 PodStatus kubecontainer.PodStatus 55 StartedPods []string 56 KilledPods []string 57 StartedContainers []string 58 KilledContainers []string 59 RuntimeStatus *kubecontainer.RuntimeStatus 60 VersionInfo string 61 APIVersionInfo string 62 RuntimeType string 63 Err error 64 InspectErr error 65 StatusErr error 66 // If BlockImagePulls is true, then all PullImage() calls will be blocked until 67 // UnblockImagePulls() is called. This is used to simulate image pull latency 68 // from container runtime. 69 BlockImagePulls bool 70 imagePullTokenBucket chan bool 71 T TB 72 } 73 74 const FakeHost = "localhost:12345" 75 76 type FakeStreamingRuntime struct { 77 *FakeRuntime 78 } 79 80 var _ kubecontainer.StreamingRuntime = &FakeStreamingRuntime{} 81 82 // FakeRuntime should implement Runtime. 83 var _ kubecontainer.Runtime = &FakeRuntime{} 84 85 type FakeVersion struct { 86 Version string 87 } 88 89 func (fv *FakeVersion) String() string { 90 return fv.Version 91 } 92 93 func (fv *FakeVersion) Compare(other string) (int, error) { 94 result := 0 95 if fv.Version > other { 96 result = 1 97 } else if fv.Version < other { 98 result = -1 99 } 100 return result, nil 101 } 102 103 type podsGetter interface { 104 GetPods(context.Context, bool) ([]*kubecontainer.Pod, error) 105 } 106 107 type FakeRuntimeCache struct { 108 getter podsGetter 109 } 110 111 func NewFakeRuntimeCache(getter podsGetter) kubecontainer.RuntimeCache { 112 return &FakeRuntimeCache{getter} 113 } 114 115 func (f *FakeRuntimeCache) GetPods(ctx context.Context) ([]*kubecontainer.Pod, error) { 116 return f.getter.GetPods(ctx, false) 117 } 118 119 func (f *FakeRuntimeCache) ForceUpdateIfOlder(context.Context, time.Time) error { 120 return nil 121 } 122 123 // UpdatePodCIDR fulfills the cri interface. 124 func (f *FakeRuntime) UpdatePodCIDR(_ context.Context, c string) error { 125 return nil 126 } 127 128 func (f *FakeRuntime) assertList(expect []string, test []string) bool { 129 if !reflect.DeepEqual(expect, test) { 130 f.T.Errorf("AssertList: expected %#v, got %#v", expect, test) 131 return false 132 } 133 return true 134 } 135 136 // AssertCalls test if the invoked functions are as expected. 137 func (f *FakeRuntime) AssertCalls(calls []string) bool { 138 f.Lock() 139 defer f.Unlock() 140 return f.assertList(calls, f.CalledFunctions) 141 } 142 143 // AssertCallCounts checks if a certain call is called for a certain of numbers 144 func (f *FakeRuntime) AssertCallCounts(funcName string, expectedCount int) bool { 145 f.Lock() 146 defer f.Unlock() 147 actualCount := 0 148 for _, c := range f.CalledFunctions { 149 if funcName == c { 150 actualCount += 1 151 } 152 } 153 if expectedCount != actualCount { 154 f.T.Errorf("AssertCallCounts: expected %s to be called %d times, but was actually called %d times.", funcName, expectedCount, actualCount) 155 return false 156 } 157 return true 158 } 159 160 func (f *FakeRuntime) AssertStartedPods(pods []string) bool { 161 f.Lock() 162 defer f.Unlock() 163 return f.assertList(pods, f.StartedPods) 164 } 165 166 func (f *FakeRuntime) AssertKilledPods(pods []string) bool { 167 f.Lock() 168 defer f.Unlock() 169 return f.assertList(pods, f.KilledPods) 170 } 171 172 func (f *FakeRuntime) AssertStartedContainers(containers []string) bool { 173 f.Lock() 174 defer f.Unlock() 175 return f.assertList(containers, f.StartedContainers) 176 } 177 178 func (f *FakeRuntime) AssertKilledContainers(containers []string) bool { 179 f.Lock() 180 defer f.Unlock() 181 return f.assertList(containers, f.KilledContainers) 182 } 183 184 func (f *FakeRuntime) Type() string { 185 return f.RuntimeType 186 } 187 188 func (f *FakeRuntime) Version(_ context.Context) (kubecontainer.Version, error) { 189 f.Lock() 190 defer f.Unlock() 191 192 f.CalledFunctions = append(f.CalledFunctions, "Version") 193 return &FakeVersion{Version: f.VersionInfo}, f.Err 194 } 195 196 func (f *FakeRuntime) APIVersion() (kubecontainer.Version, error) { 197 f.Lock() 198 defer f.Unlock() 199 200 f.CalledFunctions = append(f.CalledFunctions, "APIVersion") 201 return &FakeVersion{Version: f.APIVersionInfo}, f.Err 202 } 203 204 func (f *FakeRuntime) Status(_ context.Context) (*kubecontainer.RuntimeStatus, error) { 205 f.Lock() 206 defer f.Unlock() 207 208 f.CalledFunctions = append(f.CalledFunctions, "Status") 209 return f.RuntimeStatus, f.StatusErr 210 } 211 212 func (f *FakeRuntime) GetPods(_ context.Context, all bool) ([]*kubecontainer.Pod, error) { 213 f.Lock() 214 defer f.Unlock() 215 216 var pods []*kubecontainer.Pod 217 218 f.CalledFunctions = append(f.CalledFunctions, "GetPods") 219 if all { 220 for _, fakePod := range f.AllPodList { 221 pods = append(pods, fakePod.Pod) 222 } 223 } else { 224 for _, fakePod := range f.PodList { 225 pods = append(pods, fakePod.Pod) 226 } 227 } 228 return pods, f.Err 229 } 230 231 func (f *FakeRuntime) SyncPod(_ context.Context, pod *v1.Pod, _ *kubecontainer.PodStatus, _ []v1.Secret, backOff *flowcontrol.Backoff) (result kubecontainer.PodSyncResult) { 232 f.Lock() 233 defer f.Unlock() 234 235 f.CalledFunctions = append(f.CalledFunctions, "SyncPod") 236 f.StartedPods = append(f.StartedPods, string(pod.UID)) 237 for _, c := range pod.Spec.Containers { 238 f.StartedContainers = append(f.StartedContainers, c.Name) 239 } 240 // TODO(random-liu): Add SyncResult for starting and killing containers 241 if f.Err != nil { 242 result.Fail(f.Err) 243 } 244 return 245 } 246 247 func (f *FakeRuntime) KillPod(_ context.Context, pod *v1.Pod, runningPod kubecontainer.Pod, gracePeriodOverride *int64) error { 248 f.Lock() 249 defer f.Unlock() 250 251 f.CalledFunctions = append(f.CalledFunctions, "KillPod") 252 f.KilledPods = append(f.KilledPods, string(runningPod.ID)) 253 for _, c := range runningPod.Containers { 254 f.KilledContainers = append(f.KilledContainers, c.Name) 255 } 256 return f.Err 257 } 258 259 func (f *FakeRuntime) RunContainerInPod(container v1.Container, pod *v1.Pod, volumeMap map[string]volume.VolumePlugin) error { 260 f.Lock() 261 defer f.Unlock() 262 263 f.CalledFunctions = append(f.CalledFunctions, "RunContainerInPod") 264 f.StartedContainers = append(f.StartedContainers, container.Name) 265 266 pod.Spec.Containers = append(pod.Spec.Containers, container) 267 for _, c := range pod.Spec.Containers { 268 if c.Name == container.Name { // Container already in the pod. 269 return f.Err 270 } 271 } 272 pod.Spec.Containers = append(pod.Spec.Containers, container) 273 return f.Err 274 } 275 276 func (f *FakeRuntime) KillContainerInPod(container v1.Container, pod *v1.Pod) error { 277 f.Lock() 278 defer f.Unlock() 279 280 f.CalledFunctions = append(f.CalledFunctions, "KillContainerInPod") 281 f.KilledContainers = append(f.KilledContainers, container.Name) 282 return f.Err 283 } 284 285 func (f *FakeRuntime) GeneratePodStatus(event *runtimeapi.ContainerEventResponse) (*kubecontainer.PodStatus, error) { 286 f.Lock() 287 defer f.Unlock() 288 289 f.CalledFunctions = append(f.CalledFunctions, "GeneratePodStatus") 290 status := f.PodStatus 291 return &status, f.Err 292 } 293 294 func (f *FakeRuntime) GetPodStatus(_ context.Context, uid types.UID, name, namespace string) (*kubecontainer.PodStatus, error) { 295 f.Lock() 296 defer f.Unlock() 297 298 f.CalledFunctions = append(f.CalledFunctions, "GetPodStatus") 299 status := f.PodStatus 300 return &status, f.Err 301 } 302 303 func (f *FakeRuntime) GetContainerLogs(_ context.Context, pod *v1.Pod, containerID kubecontainer.ContainerID, logOptions *v1.PodLogOptions, stdout, stderr io.Writer) (err error) { 304 f.Lock() 305 defer f.Unlock() 306 307 f.CalledFunctions = append(f.CalledFunctions, "GetContainerLogs") 308 return f.Err 309 } 310 311 func (f *FakeRuntime) PullImage(ctx context.Context, image kubecontainer.ImageSpec, pullSecrets []v1.Secret, podSandboxConfig *runtimeapi.PodSandboxConfig) (string, error) { 312 f.Lock() 313 f.CalledFunctions = append(f.CalledFunctions, "PullImage") 314 if f.Err == nil { 315 i := kubecontainer.Image{ 316 ID: image.Image, 317 Spec: image, 318 } 319 f.ImageList = append(f.ImageList, i) 320 } 321 322 if !f.BlockImagePulls { 323 f.Unlock() 324 return image.Image, f.Err 325 } 326 327 retErr := f.Err 328 if f.imagePullTokenBucket == nil { 329 f.imagePullTokenBucket = make(chan bool, 1) 330 } 331 // Unlock before waiting for UnblockImagePulls calls, to avoid deadlock. 332 f.Unlock() 333 select { 334 case <-ctx.Done(): 335 case <-f.imagePullTokenBucket: 336 } 337 return image.Image, retErr 338 } 339 340 // UnblockImagePulls unblocks a certain number of image pulls, if BlockImagePulls is true. 341 func (f *FakeRuntime) UnblockImagePulls(count int) { 342 if f.imagePullTokenBucket != nil { 343 for i := 0; i < count; i++ { 344 select { 345 case f.imagePullTokenBucket <- true: 346 default: 347 } 348 } 349 } 350 } 351 352 func (f *FakeRuntime) GetImageRef(_ context.Context, image kubecontainer.ImageSpec) (string, error) { 353 f.Lock() 354 defer f.Unlock() 355 356 f.CalledFunctions = append(f.CalledFunctions, "GetImageRef") 357 for _, i := range f.ImageList { 358 if i.ID == image.Image { 359 return i.ID, nil 360 } 361 } 362 return "", f.InspectErr 363 } 364 365 func (f *FakeRuntime) GetImageSize(_ context.Context, image kubecontainer.ImageSpec) (uint64, error) { 366 f.Lock() 367 defer f.Unlock() 368 369 f.CalledFunctions = append(f.CalledFunctions, "GetImageSize") 370 return 0, f.Err 371 } 372 373 func (f *FakeRuntime) ListImages(_ context.Context) ([]kubecontainer.Image, error) { 374 f.Lock() 375 defer f.Unlock() 376 377 f.CalledFunctions = append(f.CalledFunctions, "ListImages") 378 return snapshot(f.ImageList), f.Err 379 } 380 381 func snapshot(imageList []kubecontainer.Image) []kubecontainer.Image { 382 result := make([]kubecontainer.Image, len(imageList)) 383 copy(result, imageList) 384 return result 385 } 386 387 func (f *FakeRuntime) RemoveImage(_ context.Context, image kubecontainer.ImageSpec) error { 388 f.Lock() 389 defer f.Unlock() 390 391 f.CalledFunctions = append(f.CalledFunctions, "RemoveImage") 392 index := 0 393 for i := range f.ImageList { 394 if f.ImageList[i].ID == image.Image { 395 index = i 396 break 397 } 398 } 399 f.ImageList = append(f.ImageList[:index], f.ImageList[index+1:]...) 400 401 return f.Err 402 } 403 404 func (f *FakeRuntime) GarbageCollect(_ context.Context, gcPolicy kubecontainer.GCPolicy, ready bool, evictNonDeletedPods bool) error { 405 f.Lock() 406 defer f.Unlock() 407 408 f.CalledFunctions = append(f.CalledFunctions, "GarbageCollect") 409 return f.Err 410 } 411 412 func (f *FakeRuntime) DeleteContainer(_ context.Context, containerID kubecontainer.ContainerID) error { 413 f.Lock() 414 defer f.Unlock() 415 416 f.CalledFunctions = append(f.CalledFunctions, "DeleteContainer") 417 return f.Err 418 } 419 420 func (f *FakeRuntime) CheckpointContainer(_ context.Context, options *runtimeapi.CheckpointContainerRequest) error { 421 f.Lock() 422 defer f.Unlock() 423 424 f.CalledFunctions = append(f.CalledFunctions, "CheckpointContainer") 425 return f.Err 426 } 427 428 func (f *FakeRuntime) ListMetricDescriptors(_ context.Context) ([]*runtimeapi.MetricDescriptor, error) { 429 f.Lock() 430 defer f.Unlock() 431 432 f.CalledFunctions = append(f.CalledFunctions, "ListMetricDescriptors") 433 return nil, f.Err 434 } 435 436 func (f *FakeRuntime) ListPodSandboxMetrics(_ context.Context) ([]*runtimeapi.PodSandboxMetrics, error) { 437 f.Lock() 438 defer f.Unlock() 439 440 f.CalledFunctions = append(f.CalledFunctions, "ListPodSandboxMetrics") 441 return nil, f.Err 442 } 443 444 // SetContainerFsStats sets the containerFsStats for dependency injection. 445 func (f *FakeRuntime) SetContainerFsStats(val []*runtimeapi.FilesystemUsage) { 446 f.ContainerFsStats = val 447 } 448 449 // SetImageFsStats sets the ImageFsStats for dependency injection. 450 func (f *FakeRuntime) SetImageFsStats(val []*runtimeapi.FilesystemUsage) { 451 f.ImageFsStats = val 452 } 453 454 func (f *FakeRuntime) ImageStats(_ context.Context) (*kubecontainer.ImageStats, error) { 455 f.Lock() 456 defer f.Unlock() 457 458 f.CalledFunctions = append(f.CalledFunctions, "ImageStats") 459 return nil, f.Err 460 } 461 462 // ImageFsInfo returns a ImageFsInfoResponse given the DI injected values of ImageFsStats 463 // and ContainerFsStats. 464 func (f *FakeRuntime) ImageFsInfo(_ context.Context) (*runtimeapi.ImageFsInfoResponse, error) { 465 f.Lock() 466 defer f.Unlock() 467 468 f.CalledFunctions = append(f.CalledFunctions, "ImageFsInfo") 469 resp := &runtimeapi.ImageFsInfoResponse{ 470 ImageFilesystems: f.ImageFsStats, 471 ContainerFilesystems: f.ContainerFsStats, 472 } 473 return resp, f.Err 474 } 475 476 func (f *FakeStreamingRuntime) GetExec(_ context.Context, id kubecontainer.ContainerID, cmd []string, stdin, stdout, stderr, tty bool) (*url.URL, error) { 477 f.Lock() 478 defer f.Unlock() 479 480 f.CalledFunctions = append(f.CalledFunctions, "GetExec") 481 return &url.URL{Host: FakeHost}, f.Err 482 } 483 484 func (f *FakeStreamingRuntime) GetAttach(_ context.Context, id kubecontainer.ContainerID, stdin, stdout, stderr, tty bool) (*url.URL, error) { 485 f.Lock() 486 defer f.Unlock() 487 488 f.CalledFunctions = append(f.CalledFunctions, "GetAttach") 489 return &url.URL{Host: FakeHost}, f.Err 490 } 491 492 func (f *FakeStreamingRuntime) GetPortForward(_ context.Context, podName, podNamespace string, podUID types.UID, ports []int32) (*url.URL, error) { 493 f.Lock() 494 defer f.Unlock() 495 496 f.CalledFunctions = append(f.CalledFunctions, "GetPortForward") 497 return &url.URL{Host: FakeHost}, f.Err 498 } 499 500 type FakeContainerCommandRunner struct { 501 // what to return 502 Stdout string 503 Err error 504 505 // actual values when invoked 506 ContainerID kubecontainer.ContainerID 507 Cmd []string 508 } 509 510 var _ kubecontainer.CommandRunner = &FakeContainerCommandRunner{} 511 512 func (f *FakeContainerCommandRunner) RunInContainer(_ context.Context, containerID kubecontainer.ContainerID, cmd []string, timeout time.Duration) ([]byte, error) { 513 // record invoked values 514 f.ContainerID = containerID 515 f.Cmd = cmd 516 517 return []byte(f.Stdout), f.Err 518 }