github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/controllers/core/podlogstream/podlogstreamcontroller_test.go (about) 1 package podlogstream 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "strings" 8 "testing" 9 "time" 10 11 "github.com/jonboulle/clockwork" 12 "github.com/stretchr/testify/require" 13 14 "github.com/tilt-dev/tilt/internal/controllers/apicmp" 15 "github.com/tilt-dev/tilt/internal/timecmp" 16 "github.com/tilt-dev/tilt/pkg/apis" 17 18 "github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1" 19 20 "github.com/stretchr/testify/assert" 21 v1 "k8s.io/api/core/v1" 22 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 "k8s.io/apimachinery/pkg/types" 24 "sigs.k8s.io/controller-runtime/pkg/reconcile" 25 26 "github.com/tilt-dev/tilt/internal/container" 27 "github.com/tilt-dev/tilt/internal/controllers/fake" 28 "github.com/tilt-dev/tilt/internal/controllers/indexer" 29 "github.com/tilt-dev/tilt/internal/k8s" 30 "github.com/tilt-dev/tilt/internal/store" 31 "github.com/tilt-dev/tilt/internal/store/k8sconv" 32 "github.com/tilt-dev/tilt/internal/testutils/bufsync" 33 "github.com/tilt-dev/tilt/pkg/logger" 34 "github.com/tilt-dev/tilt/pkg/model" 35 ) 36 37 var podID = k8s.PodID("pod-id") 38 var cName = container.Name("cname") 39 var cID = container.ID("cid") 40 41 func TestLogs(t *testing.T) { 42 f := newPLMFixture(t) 43 44 f.kClient.SetLogsForPodContainer(podID, cName, "hello world!") 45 46 start := f.clock.Now() 47 48 pb := newPodBuilder(podID).addRunningContainer(cName, cID) 49 f.kClient.UpsertPod(pb.toPod()) 50 51 pls := plsFromPod("server", pb, start) 52 f.Create(pls) 53 54 f.triggerPodEvent(podID) 55 f.AssertOutputContains("hello world!") 56 f.AssertLogStartTime(start) 57 58 // Check to make sure that we're enqueuing pod changes as Reconcile() calls. 59 podNN := types.NamespacedName{Name: string(podID), Namespace: "default"} 60 streamNN := types.NamespacedName{Name: fmt.Sprintf("default-%s", podID)} 61 assert.Equal(t, []reconcile.Request{ 62 {NamespacedName: streamNN}, 63 }, f.plsc.podSource.indexer.EnqueueKey(indexer.Key{Name: podNN, GVK: podGVK})) 64 } 65 66 func TestLogCleanup(t *testing.T) { 67 f := newPLMFixture(t) 68 69 f.kClient.SetLogsForPodContainer(podID, cName, "hello world!") 70 71 start := f.clock.Now() 72 pb := newPodBuilder(podID).addRunningContainer(cName, cID) 73 f.kClient.UpsertPod(pb.toPod()) 74 75 pls := plsFromPod("server", pb, start) 76 f.Create(pls) 77 78 f.triggerPodEvent(podID) 79 f.AssertOutputContains("hello world!") 80 81 f.Delete(pls) 82 assert.Len(t, f.plsc.watches, 0) 83 84 // TODO(nick): Currently, namespace watches are never cleanedup, 85 // because the user might restart them again. 86 assert.Len(t, f.plsc.podSource.watchesByNamespace, 1) 87 } 88 89 func TestLogActions(t *testing.T) { 90 f := newPLMFixture(t) 91 92 f.kClient.SetLogsForPodContainer(podID, cName, "hello world!\ngoodbye world!\n") 93 94 pb := newPodBuilder(podID).addRunningContainer(cName, cID) 95 f.kClient.UpsertPod(pb.toPod()) 96 97 f.Create(plsFromPod("server", pb, time.Time{})) 98 99 f.triggerPodEvent(podID) 100 f.ConsumeLogActionsUntil("hello world!") 101 } 102 103 func TestLogsFailed(t *testing.T) { 104 f := newPLMFixture(t) 105 106 f.kClient.ContainerLogsError = fmt.Errorf("my-error") 107 108 pb := newPodBuilder(podID).addRunningContainer(cName, cID) 109 f.kClient.UpsertPod(pb.toPod()) 110 111 pls := plsFromPod("server", pb, time.Time{}) 112 f.Create(pls) 113 114 f.AssertOutputContains("Error streaming pod-id logs") 115 assert.Contains(t, f.out.String(), "my-error") 116 117 require.Eventually(t, 118 func() bool { 119 // Check to make sure the status has an error. 120 f.MustGet(f.KeyForObject(pls), pls) 121 return apicmp.DeepEqual(pls.Status, 122 PodLogStreamStatus{ 123 ContainerStatuses: []ContainerLogStreamStatus{ 124 { 125 Name: "cname", 126 Error: "my-error", 127 }, 128 }, 129 }) 130 }, 131 time.Second, 10*time.Millisecond, 132 "Expected error not present on PodLogStreamStatus: %v", pls, 133 ) 134 135 result := f.MustReconcile(f.KeyForObject(pls)) 136 assert.Equal(t, 2*time.Second, result.RequeueAfter) 137 138 f.clock.Advance(2 * time.Second) 139 140 assert.Eventually(f.t, func() bool { 141 result = f.MustReconcile(f.KeyForObject(pls)) 142 return result.RequeueAfter == 4*time.Second 143 }, time.Second, 5*time.Millisecond, "should re-stream and backoff again") 144 } 145 146 func TestLogsCanceledUnexpectedly(t *testing.T) { 147 f := newPLMFixture(t) 148 149 f.kClient.SetLogsForPodContainer(podID, cName, "hello world!\n") 150 151 pb := newPodBuilder(podID).addRunningContainer(cName, cID) 152 f.kClient.UpsertPod(pb.toPod()) 153 pls := plsFromPod("server", pb, time.Time{}) 154 f.Create(pls) 155 156 f.AssertOutputContains("hello world!\n") 157 158 // Wait until the previous log stream finishes. 159 assert.Eventually(f.t, func() bool { 160 f.MustGet(f.KeyForObject(pls), pls) 161 statuses := pls.Status.ContainerStatuses 162 if len(statuses) != 1 { 163 return false 164 } 165 return !statuses[0].Active 166 }, time.Second, 5*time.Millisecond) 167 168 // Set new logs, as if the pod restarted. 169 f.kClient.SetLogsForPodContainer(podID, cName, "goodbye world!\n") 170 f.triggerPodEvent(podID) 171 f.clock.Advance(10 * time.Second) 172 f.MustReconcile(types.NamespacedName{Name: pls.Name}) 173 f.AssertOutputContains("goodbye world!\n") 174 } 175 176 func TestMultiContainerLogs(t *testing.T) { 177 f := newPLMFixture(t) 178 179 f.kClient.SetLogsForPodContainer(podID, "cont1", "hello world!") 180 f.kClient.SetLogsForPodContainer(podID, "cont2", "goodbye world!") 181 182 pb := newPodBuilder(podID). 183 addRunningContainer("cont1", "cid1"). 184 addRunningContainer("cont2", "cid2") 185 f.kClient.UpsertPod(pb.toPod()) 186 f.Create(plsFromPod("server", pb, time.Time{})) 187 188 f.AssertOutputContains("hello world!") 189 f.AssertOutputContains("goodbye world!") 190 } 191 192 func TestContainerPrefixes(t *testing.T) { 193 f := newPLMFixture(t) 194 195 pID1 := k8s.PodID("pod1") 196 cNamePrefix1 := container.Name("yes-prefix-1") 197 cNamePrefix2 := container.Name("yes-prefix-2") 198 f.kClient.SetLogsForPodContainer(pID1, cNamePrefix1, "hello world!") 199 f.kClient.SetLogsForPodContainer(pID1, cNamePrefix2, "goodbye world!") 200 201 pID2 := k8s.PodID("pod2") 202 cNameNoPrefix := container.Name("no-prefix") 203 f.kClient.SetLogsForPodContainer(pID2, cNameNoPrefix, "hello jupiter!") 204 205 pbMultiC := newPodBuilder(pID1). 206 // Pod with multiple containers -- logs should be prefixed with container name 207 addRunningContainer(cNamePrefix1, "cid1"). 208 addRunningContainer(cNamePrefix2, "cid2") 209 f.kClient.UpsertPod(pbMultiC.toPod()) 210 211 f.Create(plsFromPod("multiContainer", pbMultiC, time.Time{})) 212 213 pbSingleC := newPodBuilder(pID2). 214 // Pod with just one container -- logs should NOT be prefixed with container name 215 addRunningContainer(cNameNoPrefix, "cid3") 216 f.kClient.UpsertPod(pbSingleC.toPod()) 217 218 f.Create(plsFromPod("singleContainer", pbSingleC, time.Time{})) 219 220 // Make sure we have expected logs 221 f.AssertOutputContains("hello world!") 222 f.AssertOutputContains("goodbye world!") 223 f.AssertOutputContains("hello jupiter!") 224 225 // Check for un/expected prefixes 226 f.AssertOutputContains(cNamePrefix1.String()) 227 f.AssertOutputContains(cNamePrefix2.String()) 228 f.AssertOutputDoesNotContain(cNameNoPrefix.String()) 229 } 230 231 func TestTerminatedContainerLogs(t *testing.T) { 232 f := newPLMFixture(t) 233 234 cName := container.Name("cName") 235 pb := newPodBuilder(podID).addTerminatedContainer(cName, "cID") 236 f.kClient.UpsertPod(pb.toPod()) 237 238 f.kClient.SetLogsForPodContainer(podID, cName, "hello world!") 239 240 f.Create(plsFromPod("server", pb, time.Time{})) 241 242 // Fire OnChange twice, because we used to have a bug where 243 // we'd immediately teardown the log watch on the terminated container. 244 f.triggerPodEvent(podID) 245 f.triggerPodEvent(podID) 246 247 f.AssertOutputContains("hello world!") 248 249 // Make sure that we don't try to re-stream after the terminated container 250 // closes the log stream. 251 f.kClient.SetLogsForPodContainer(podID, cName, "hello world!\ngoodbye world!\n") 252 253 f.triggerPodEvent(podID) 254 f.AssertOutputContains("hello world!") 255 f.AssertOutputDoesNotContain("goodbye world!") 256 } 257 258 // https://github.com/tilt-dev/tilt/issues/3908 259 func TestLogReconnection(t *testing.T) { 260 f := newPLMFixture(t) 261 cName := container.Name("cName") 262 pb := newPodBuilder(podID).addRunningContainer(cName, "cID") 263 f.kClient.UpsertPod(pb.toPod()) 264 265 reader, writer := io.Pipe() 266 defer func() { 267 require.NoError(t, writer.Close()) 268 }() 269 f.kClient.SetLogReaderForPodContainer(podID, cName, reader) 270 271 // Set up fake time 272 startTime := f.clock.Now() 273 f.Create(plsFromPod("server", pb, startTime)) 274 275 _, err := writer.Write([]byte("hello world!")) 276 require.NoError(t, err) 277 f.AssertOutputContains("hello world!") 278 f.AssertLogStartTime(startTime) 279 280 f.clock.Advance(20 * time.Second) 281 lastRead := f.clock.Now() 282 _, _ = writer.Write([]byte("hello world2!")) 283 f.AssertOutputContains("hello world2!") 284 285 // Simulate Kubernetes rotating the logs by creating a new pipe. 286 reader2, writer2 := io.Pipe() 287 defer func() { 288 require.NoError(t, writer2.Close()) 289 }() 290 f.kClient.SetLogReaderForPodContainer(podID, cName, reader2) 291 go func() { 292 _, _ = writer2.Write([]byte("goodbye world!")) 293 }() 294 f.AssertOutputDoesNotContain("goodbye world!") 295 296 f.clock.Advance(5 * time.Second) 297 f.AssertOutputDoesNotContain("goodbye world!") 298 299 f.clock.Advance(5 * time.Second) 300 f.AssertOutputDoesNotContain("goodbye world!") 301 f.AssertLogStartTime(startTime) 302 303 // simulate 15s since we last read a log; this triggers a reconnect 304 f.clock.Advance(15 * time.Second) 305 time.Sleep(20 * time.Millisecond) 306 assert.Error(t, f.kClient.LastPodLogContext.Err()) 307 require.NoError(t, writer.Close()) 308 309 f.AssertOutputContains("goodbye world!") 310 311 // Make sure the start time was adjusted for when the last read happened. 312 f.AssertLogStartTime(lastRead.Add(podLogReconnectGap)) 313 } 314 315 func TestInitContainerLogs(t *testing.T) { 316 f := newPLMFixture(t) 317 318 f.kClient.SetLogsForPodContainer(podID, "cont1", "hello world!") 319 320 cNameInit := container.Name("cNameInit") 321 cNameNormal := container.Name("cNameNormal") 322 pb := newPodBuilder(podID). 323 addTerminatedInitContainer(cNameInit, "cID-init"). 324 addRunningContainer(cNameNormal, "cID-normal") 325 f.kClient.UpsertPod(pb.toPod()) 326 327 f.kClient.SetLogsForPodContainer(podID, cNameInit, "init world!") 328 f.kClient.SetLogsForPodContainer(podID, cNameNormal, "hello world!") 329 330 f.Create(plsFromPod("server", pb, time.Time{})) 331 332 f.AssertOutputContains(cNameInit.String()) 333 f.AssertOutputContains("init world!") 334 f.AssertOutputDoesNotContain(cNameNormal.String()) 335 f.AssertOutputContains("hello world!") 336 } 337 338 func TestIgnoredContainerLogs(t *testing.T) { 339 f := newPLMFixture(t) 340 341 f.kClient.SetLogsForPodContainer(podID, "cont1", "hello world!") 342 343 istioInit := container.IstioInitContainerName 344 istioSidecar := container.IstioSidecarContainerName 345 cNormal := container.Name("cNameNormal") 346 pb := newPodBuilder(podID). 347 addTerminatedInitContainer(istioInit, "cID-init"). 348 addRunningContainer(istioSidecar, "cID-sidecar"). 349 addRunningContainer(cNormal, "cID-normal") 350 f.kClient.UpsertPod(pb.toPod()) 351 352 f.kClient.SetLogsForPodContainer(podID, istioInit, "init istio!") 353 f.kClient.SetLogsForPodContainer(podID, istioSidecar, "hello istio!") 354 f.kClient.SetLogsForPodContainer(podID, cNormal, "hello world!") 355 356 pls := plsFromPod("server", pb, time.Time{}) 357 pls.Spec.IgnoreContainers = []string{string(istioInit), string(istioSidecar)} 358 f.Create(pls) 359 360 f.AssertOutputDoesNotContain("istio") 361 f.AssertOutputContains("hello world!") 362 } 363 364 // Our old Fake Kubernetes client used to interact badly 365 // with the pod log stream reconciler, leading to an infinite 366 // loop in tests. 367 func TestInfiniteLoop(t *testing.T) { 368 f := newPLMFixture(t) 369 370 f.kClient.SetLogsForPodContainer(podID, "cont1", "hello world!") 371 372 pb := newPodBuilder(podID). 373 addRunningContainer("cNameNormal", "cID-normal") 374 f.kClient.UpsertPod(pb.toPod()) 375 376 pls := plsFromPod("server", pb, time.Time{}) 377 f.Create(pls) 378 379 nn := types.NamespacedName{Name: pls.Name} 380 f.MustReconcile(nn) 381 382 // Make sure this goes into an active state and stays there. 383 assert.Eventually(t, func() bool { 384 var pls v1alpha1.PodLogStream 385 f.MustGet(nn, &pls) 386 return len(pls.Status.ContainerStatuses) > 0 && pls.Status.ContainerStatuses[0].Active 387 }, 200*time.Millisecond, 10*time.Millisecond) 388 389 assert.Never(t, func() bool { 390 var pls v1alpha1.PodLogStream 391 f.MustGet(nn, &pls) 392 return len(pls.Status.ContainerStatuses) == 0 || !pls.Status.ContainerStatuses[0].Active 393 }, 200*time.Millisecond, 10*time.Millisecond) 394 395 _ = f.kClient.LastPodLogPipeWriter.CloseWithError(fmt.Errorf("manually closed")) 396 397 assert.Eventually(t, func() bool { 398 var pls v1alpha1.PodLogStream 399 f.MustGet(nn, &pls) 400 if len(pls.Status.ContainerStatuses) == 0 { 401 return false 402 } 403 cst := pls.Status.ContainerStatuses[0] 404 return !cst.Active && strings.Contains(cst.Error, "manually closed") 405 }, 200*time.Millisecond, 10*time.Millisecond) 406 } 407 408 func TestReconcilerIndexing(t *testing.T) { 409 f := newPLMFixture(t) 410 411 pls := plsFromPod("server", newPodBuilder(podID), f.clock.Now()) 412 pls.Namespace = "some-ns" 413 pls.Spec.Cluster = "my-cluster" 414 f.Create(pls) 415 416 ctx := context.Background() 417 reqs := f.plsc.indexer.Enqueue(ctx, &v1alpha1.Cluster{ 418 ObjectMeta: metav1.ObjectMeta{Namespace: "some-ns", Name: "my-cluster"}, 419 }) 420 assert.ElementsMatch(t, []reconcile.Request{ 421 {NamespacedName: types.NamespacedName{Namespace: "some-ns", Name: "default-pod-id"}}, 422 }, reqs) 423 } 424 425 func TestDeletionTimestamp(t *testing.T) { 426 f := newPLMFixture(t) 427 428 f.kClient.SetLogsForPodContainer(podID, cName, "hello world!") 429 430 start := f.clock.Now() 431 432 pb := newPodBuilder(podID).addRunningContainer(cName, cID).addDeletionTimestamp() 433 f.kClient.UpsertPod(pb.toPod()) 434 435 pls := plsFromPod("server", pb, start) 436 f.Create(pls) 437 438 f.triggerPodEvent(podID) 439 440 nn := types.NamespacedName{Name: pls.Name} 441 f.MustReconcile(nn) 442 443 f.AssertOutputContains("hello world!") 444 445 assert.Eventually(f.t, func() bool { 446 f.Get(nn, pls) 447 return len(pls.Status.ContainerStatuses) == 1 && !pls.Status.ContainerStatuses[0].Active 448 }, time.Second, 5*time.Millisecond, "should stream then stop") 449 450 // No log streams should be active. 451 assert.Equal(t, pls.Status, v1alpha1.PodLogStreamStatus{ 452 ContainerStatuses: []v1alpha1.ContainerLogStreamStatus{ 453 v1alpha1.ContainerLogStreamStatus{Name: "cname"}, 454 }, 455 }) 456 457 // The cname stream is closed forever. 458 assert.Len(t, f.plsc.hasClosedStream, 1) 459 } 460 461 func TestMissingPod(t *testing.T) { 462 f := newPLMFixture(t) 463 464 f.kClient.SetLogsForPodContainer(podID, cName, "hello world!") 465 466 start := f.clock.Now() 467 468 pb := newPodBuilder(podID).addRunningContainer(cName, cID) 469 pls := plsFromPod("server", pb, start) 470 nn := types.NamespacedName{Name: pls.Name} 471 result := f.Create(pls) 472 assert.Equal(t, time.Second, result.RequeueAfter) 473 474 result = f.MustReconcile(nn) 475 assert.Equal(t, 2*time.Second, result.RequeueAfter) 476 477 f.Get(nn, pls) 478 assert.Equal(t, "pod not found: default/pod-id", pls.Status.Error) 479 480 f.kClient.UpsertPod(pb.toPod()) 481 482 result = f.MustReconcile(nn) 483 assert.Equal(t, time.Duration(0), result.RequeueAfter) 484 485 f.AssertOutputContains("hello world!") 486 f.AssertLogStartTime(start) 487 } 488 489 func TestFailedToCreateLogWatcher(t *testing.T) { 490 f := newPLMFixture(t) 491 492 f.kClient.SetLogsForPodContainer(podID, cName, 493 "listening on 8080\nfailed to create fsnotify watcher: too many open files") 494 495 start := f.clock.Now() 496 497 pb := newPodBuilder(podID).addRunningContainer(cName, cID) 498 f.kClient.UpsertPod(pb.toPod()) 499 500 pls := plsFromPod("server", pb, start) 501 f.Create(pls) 502 503 f.triggerPodEvent(podID) 504 f.AssertOutputContains(`listening on 8080 505 failed to create fsnotify watcher: too many open files 506 Error streaming pod-id logs: failed to create fsnotify watcher: too many open files. Consider adjusting inotify limits: https://kind.sigs.k8s.io/docs/user/known-issues/#pod-errors-due-to-too-many-open-files 507 `) 508 } 509 510 type plmStore struct { 511 t testing.TB 512 *store.TestingStore 513 out *bufsync.ThreadSafeBuffer 514 } 515 516 func newPLMStore(t testing.TB, out *bufsync.ThreadSafeBuffer) *plmStore { 517 return &plmStore{ 518 t: t, 519 TestingStore: store.NewTestingStore(), 520 out: out, 521 } 522 } 523 524 func (s *plmStore) Dispatch(action store.Action) { 525 event, ok := action.(store.LogAction) 526 if !ok { 527 s.t.Errorf("Expected action type LogAction. Actual: %T", action) 528 } 529 530 _, err := s.out.Write(event.Message()) 531 if err != nil { 532 fmt.Printf("error writing event: %v\n", err) 533 } 534 } 535 536 type plmFixture struct { 537 *fake.ControllerFixture 538 t testing.TB 539 ctx context.Context 540 kClient *k8s.FakeK8sClient 541 plsc *Controller 542 out *bufsync.ThreadSafeBuffer 543 store *plmStore 544 clock clockwork.FakeClock 545 } 546 547 func newPLMFixture(t testing.TB) *plmFixture { 548 kClient := k8s.NewFakeK8sClient(t) 549 550 out := bufsync.NewThreadSafeBuffer() 551 ctx, cancel := context.WithCancel(context.Background()) 552 t.Cleanup(cancel) 553 ctx = logger.WithLogger(ctx, logger.NewTestLogger(out)) 554 555 cfb := fake.NewControllerFixtureBuilder(t) 556 557 clock := clockwork.NewFakeClock() 558 st := newPLMStore(t, out) 559 podSource := NewPodSource(ctx, kClient, cfb.Client.Scheme(), clock) 560 plsc := NewController(ctx, cfb.Client, cfb.Scheme(), st, kClient, podSource, clock) 561 562 return &plmFixture{ 563 t: t, 564 ControllerFixture: cfb.WithRequeuer(plsc.podSource).Build(plsc), 565 kClient: kClient, 566 plsc: plsc, 567 ctx: ctx, 568 out: out, 569 store: st, 570 clock: clock, 571 } 572 } 573 574 func (f *plmFixture) triggerPodEvent(podID k8s.PodID) { 575 podNN := types.NamespacedName{Name: string(podID), Namespace: "default"} 576 reqs := f.plsc.podSource.indexer.EnqueueKey(indexer.Key{Name: podNN, GVK: podGVK}) 577 for _, req := range reqs { 578 _, err := f.plsc.Reconcile(f.ctx, req) 579 assert.NoError(f.t, err) 580 } 581 } 582 583 func (f *plmFixture) ConsumeLogActionsUntil(expected string) { 584 start := time.Now() 585 for time.Since(start) < time.Second { 586 f.store.RLockState() 587 done := strings.Contains(f.out.String(), expected) 588 f.store.RUnlockState() 589 590 if done { 591 return 592 } 593 594 time.Sleep(10 * time.Millisecond) 595 } 596 597 f.t.Fatalf("Timeout. Collected output: %s", f.out.String()) 598 } 599 600 func (f *plmFixture) AssertOutputContains(s string) { 601 f.t.Helper() 602 f.out.AssertEventuallyContains(f.t, s, time.Second) 603 } 604 605 func (f *plmFixture) AssertOutputDoesNotContain(s string) { 606 time.Sleep(10 * time.Millisecond) 607 assert.NotContains(f.t, f.out.String(), s) 608 } 609 610 func (f *plmFixture) AssertLogStartTime(t time.Time) { 611 f.t.Helper() 612 613 // Truncate the time to match the behavior of metav1.Time 614 timecmp.AssertTimeEqual(f.t, t.Truncate(time.Second), f.kClient.LastPodLogStartTime) 615 } 616 617 type podBuilder v1.Pod 618 619 func newPodBuilder(id k8s.PodID) *podBuilder { 620 return (*podBuilder)(&v1.Pod{ 621 ObjectMeta: metav1.ObjectMeta{ 622 Name: string(id), 623 Namespace: "default", 624 }, 625 }) 626 } 627 628 func (pb *podBuilder) addDeletionTimestamp() *podBuilder { 629 now := metav1.Now() 630 pb.ObjectMeta.DeletionTimestamp = &now 631 return pb 632 } 633 634 func (pb *podBuilder) addRunningContainer(name container.Name, id container.ID) *podBuilder { 635 pb.Spec.Containers = append(pb.Spec.Containers, v1.Container{ 636 Name: string(name), 637 }) 638 pb.Status.ContainerStatuses = append(pb.Status.ContainerStatuses, v1.ContainerStatus{ 639 Name: string(name), 640 ContainerID: fmt.Sprintf("containerd://%s", id), 641 Image: fmt.Sprintf("image-%s", strings.ToLower(string(name))), 642 ImageID: fmt.Sprintf("image-%s", strings.ToLower(string(name))), 643 Ready: true, 644 State: v1.ContainerState{ 645 Running: &v1.ContainerStateRunning{ 646 StartedAt: metav1.Now(), 647 }, 648 }, 649 }) 650 return pb 651 } 652 653 func (pb *podBuilder) addRunningInitContainer(name container.Name, id container.ID) *podBuilder { 654 pb.Spec.InitContainers = append(pb.Spec.InitContainers, v1.Container{ 655 Name: string(name), 656 }) 657 pb.Status.InitContainerStatuses = append(pb.Status.InitContainerStatuses, v1.ContainerStatus{ 658 Name: string(name), 659 ContainerID: fmt.Sprintf("containerd://%s", id), 660 Image: fmt.Sprintf("image-%s", strings.ToLower(string(name))), 661 ImageID: fmt.Sprintf("image-%s", strings.ToLower(string(name))), 662 Ready: true, 663 State: v1.ContainerState{ 664 Running: &v1.ContainerStateRunning{ 665 StartedAt: metav1.Now(), 666 }, 667 }, 668 }) 669 return pb 670 } 671 672 func (pb *podBuilder) addTerminatedContainer(name container.Name, id container.ID) *podBuilder { 673 pb.addRunningContainer(name, id) 674 statuses := pb.Status.ContainerStatuses 675 statuses[len(statuses)-1].State.Running = nil 676 statuses[len(statuses)-1].State.Terminated = &v1.ContainerStateTerminated{ 677 StartedAt: metav1.Now(), 678 } 679 return pb 680 } 681 682 func (pb *podBuilder) addTerminatedInitContainer(name container.Name, id container.ID) *podBuilder { 683 pb.addRunningInitContainer(name, id) 684 statuses := pb.Status.InitContainerStatuses 685 statuses[len(statuses)-1].State.Running = nil 686 statuses[len(statuses)-1].State.Terminated = &v1.ContainerStateTerminated{ 687 StartedAt: metav1.Now(), 688 } 689 return pb 690 } 691 692 func (pb *podBuilder) toPod() *v1.Pod { 693 return (*v1.Pod)(pb) 694 } 695 696 func plsFromPod(mn model.ManifestName, pb *podBuilder, start time.Time) *v1alpha1.PodLogStream { 697 var sinceTime *metav1.Time 698 if !start.IsZero() { 699 t := apis.NewTime(start) 700 sinceTime = &t 701 } 702 return &v1alpha1.PodLogStream{ 703 ObjectMeta: metav1.ObjectMeta{ 704 Name: fmt.Sprintf("%s-%s", pb.Namespace, pb.Name), 705 Annotations: map[string]string{ 706 v1alpha1.AnnotationManifest: string(mn), 707 v1alpha1.AnnotationSpanID: string(k8sconv.SpanIDForPod(mn, k8s.PodID(pb.Name))), 708 }, 709 }, 710 Spec: PodLogStreamSpec{ 711 Namespace: pb.Namespace, 712 Pod: pb.Name, 713 SinceTime: sinceTime, 714 }, 715 } 716 }