Skip to content
Closed
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
55b3047
working WaitForPodCompleted and GetPodStatus
Jun 2, 2025
b27cda5
gitignore
Jun 6, 2025
a174a48
add test at eof time as well as when waitgroup completes
Jun 6, 2025
cb9a50b
remove bin
Jun 6, 2025
88414e8
working WaitForPodCompleted and GetPodStatus
Jun 2, 2025
64b7e79
gitignore
Jun 6, 2025
41a3d29
add test at eof time as well as when waitgroup completes
Jun 6, 2025
49afc98
remove bin
Jun 6, 2025
a6b1263
Merge branch 'capture-k8s-pod-diagnostics' of github.com:arrestle/rec…
Jun 10, 2025
2fb30e2
fix workunitbase.go by deleting and adding
Jun 10, 2025
362a739
add workunitbase.go
Jun 10, 2025
a5527cb
put mock back
Jun 10, 2025
f520d48
adding pod and container names
Jun 10, 2025
7c8926c
add pod reason and container name to failed pod containers
Jun 10, 2025
735f110
deactivate capturePodStatus until unit and integration tests are comp…
Jun 10, 2025
5dead7d
clean up error handling
Jun 11, 2025
d56c4c6
Merge branch 'ansible:devel' into capture-k8s-pod-diagnostics
Jun 11, 2025
3ecf827
lint
Jun 11, 2025
9b938c7
unit tests for CapturePodStatus
Jun 11, 2025
22939d4
unit tests
Jun 11, 2025
c528965
Update pkg/workceptor/kubernetes.go
Jun 12, 2025
c021f09
Fix test
Jun 12, 2025
bcbc220
cleanup linting
Jun 12, 2025
03d347a
pr comments
Jun 12, 2025
1f42903
lint
Jun 12, 2025
0f11125
feedback: only consider worker container, others tbd.
Jun 12, 2025
3a20984
Update pkg/workceptor/kubernetes.go
Jun 16, 2025
e0df53d
matoval review
Jun 16, 2025
c4db94b
merge to devel
Jun 17, 2025
30743b8
try to resolve merge conflict
Jun 17, 2025
9608c06
Merge branch 'devel' into capture-k8s-pod-diagnostics
Jun 17, 2025
bc6320c
pkg/
Jun 17, 2025
3f6de50
lint
Jun 17, 2025
0e37103
remove extraneous schema
Jun 18, 2025
f0c3698
Merge branch 'devel' into capture-k8s-pod-diagnostics
Jun 18, 2025
0db61e5
address review comments, add additional unit test
Jun 18, 2025
fd67a69
add unknown state
Jun 18, 2025
3a33387
Merge branch 'devel' into capture-k8s-pod-diagnostics
Jun 20, 2025
cc16392
Merge branch 'devel' into capture-k8s-pod-diagnostics
Jun 20, 2025
95e21e0
push todays changes
Jun 20, 2025
7424c77
Update watcher logic
lranjbar Jun 20, 2025
71c5e47
Fix GetPodStatus unit tests
lranjbar Jun 20, 2025
7002182
Fix WaitForPodCompleted tests
lranjbar Jun 20, 2025
677c2b3
Update test pods to be more realistic
lranjbar Jun 20, 2025
65604ab
Move new helper functions to new file pod.go
lranjbar Jun 23, 2025
c73562e
Merge branch 'devel' into AAP-46619-capture-k8s-pod-diagnostics
lranjbar Jun 23, 2025
568681c
Remove unused mocks in k8s.go
lranjbar Jun 23, 2025
78358bf
Merge branch 'devel' into capture-k8s-pod-diagnostics
lranjbar Jun 23, 2025
0c1f67d
Merge branch 'AAP-46619-capture-k8s-pod-diagnostics' into capture-k8s…
lranjbar Jun 23, 2025
1ad624c
Simplified pod status logic
lranjbar Jun 23, 2025
2fad163
revert accidental mock deletion
lranjbar Jun 23, 2025
6e02cf3
Fix some linting errors
lranjbar Jun 23, 2025
4ac5aca
Fix more linting errors
lranjbar Jun 24, 2025
6cb833e
Merge branch 'devel' into capture-k8s-pod-diagnostics
lranjbar Jun 24, 2025
b6c66a9
Fix more linting errors
lranjbar Jun 24, 2025
3f10768
Merge branch 'devel' into capture-k8s-pod-diagnostics
lranjbar Jun 27, 2025
5aaf190
Address review comments and fix broken unit test
lranjbar Jun 27, 2025
9c35093
add debug messages
Jun 27, 2025
3db8495
fix linting errors
lranjbar Jun 27, 2025
fd4a5c0
Merge branch 'AAP-46619-capture-k8s-pod-diagnostics' into capture-k8s…
lranjbar Jun 27, 2025
afca14c
fix linting errors
lranjbar Jun 27, 2025
0a7e38b
fix linting errors
lranjbar Jun 27, 2025
8bc7f36
fix linting errors
lranjbar Jun 27, 2025
e477eb8
add unit testing, add debug lines, add return for timeout
Jun 27, 2025
e5da583
Merge branch 'devel' into capture-k8s-pod-diagnostics
lranjbar Jun 30, 2025
bf86624
unit testing 95.7% remove CapturePodStatus for now
Jul 2, 2025
2bb31a5
lint
Jul 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,6 @@ venv
/vendor
pkg/services/.lock
pkg/workceptor/status
pkg/workceptor/status.lock
pkg/workceptor/status.lock
demo
**/__debug_*
Comment thread
arrestle marked this conversation as resolved.
150 changes: 149 additions & 1 deletion pkg/workceptor/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ type KubeAPIer interface {
UntilWithSync(context.Context, cache.ListerWatcher, runtime.Object, watch2.PreconditionFunc, ...watch2.ConditionFunc) (*watch.Event, error)
NewFakeNeverRateLimiter() flowcontrol.RateLimiter
NewFakeAlwaysRateLimiter() flowcontrol.RateLimiter

// Pod status helpers
GetPodStatus(pod *corev1.Pod) (bool, string, error)
Comment thread
lranjbar marked this conversation as resolved.
Outdated
WaitForPodCompleted(pod *corev1.Pod, clientset kubernetes.Interface, timeout time.Duration) (*corev1.Pod, error)
}

type KubeAPIWrapper struct{}
Expand All @@ -113,6 +117,12 @@ func (ku KubeAPIWrapper) Get(ctx context.Context, clientset *kubernetes.Clientse
}

func (ku KubeAPIWrapper) Create(ctx context.Context, clientset *kubernetes.Clientset, namespace string, pod *corev1.Pod, opts metav1.CreateOptions) (*corev1.Pod, error) {
if pod.Annotations == nil {
pod.Annotations = make(map[string]string)
}

// Add default system annotations
pod.Annotations["receptor.redhat.com/created-by"] = "automation-mesh"
Comment thread
lranjbar marked this conversation as resolved.
Outdated
return clientset.CoreV1().Pods(namespace).Create(ctx, pod, opts)
}

Expand Down Expand Up @@ -224,6 +234,105 @@ func podRunningAndReady(kw KubeUnit) func(event watch.Event) (bool, error) {
return inner
}

// podInfrastructureFailure checks if the pod has failed to start due to infrastructure issues.
func (ku KubeAPIWrapper) podInfrastructureSuccess(pod *corev1.Pod) (bool, string, error) {
Comment thread
arrestle marked this conversation as resolved.
Outdated
if pod == nil {
return false, "pod is nil", fmt.Errorf("pod is nil")
}

switch pod.Status.Phase {
case corev1.PodFailed:
for _, cs := range pod.Status.ContainerStatuses {
Comment thread
lranjbar marked this conversation as resolved.
Outdated
if cs.State.Terminated != nil {
switch cs.State.Terminated.ExitCode {
Comment thread
lranjbar marked this conversation as resolved.
Outdated
case 0:
break
default:
return false, cs.State.Terminated.Reason, nil
}
}
}
return true, "", nil
Comment thread
AaronH88 marked this conversation as resolved.
Outdated

case corev1.PodSucceeded:
return true, "", nil

case corev1.PodRunning, corev1.PodPending:
return true, fmt.Sprintf("pod phase: %s", pod.Status.Phase), fmt.Errorf("pod not in terminal state")
Comment thread
lranjbar marked this conversation as resolved.
Outdated

default:
return false, fmt.Sprintf("unknown phase: %s", pod.Status.Phase), fmt.Errorf("invalid pod phase")
Comment thread
arrestle marked this conversation as resolved.
Outdated
}
}

// PodApplicationSuccess checks if the pod has successfully completed its application logic.
func (ku KubeAPIWrapper) podApplicationSuccess(pod *corev1.Pod) (bool, string, error) {
if pod == nil {
return false, "pod is nil", fmt.Errorf("pod is nil")
}

for _, cs := range pod.Status.ContainerStatuses {
Comment thread
arrestle marked this conversation as resolved.
Outdated
if cs.State.Terminated == nil {
return true, "container has not terminated", nil
}
if cs.State.Terminated.ExitCode != 0 {
return false, fmt.Sprintf("container %s exited with code %d: %s", cs.Name, cs.State.Terminated.ExitCode, cs.State.Terminated.Reason), nil
}
}

return true, "", nil
}

func (ku KubeAPIWrapper) WaitForPodCompleted(pod *corev1.Pod, clientset kubernetes.Interface, timeout time.Duration) (*corev1.Pod, error) {
Comment thread
lranjbar marked this conversation as resolved.
Outdated
namespace := pod.Namespace
name := pod.Name
interval := 1 * time.Second
Comment thread
lranjbar marked this conversation as resolved.
Outdated
latestPod := pod

ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()

for {
select {
case <-ctx.Done():
return latestPod, fmt.Errorf("timeout: pod did not complete within %s", timeout)
Comment thread
arrestle marked this conversation as resolved.
Outdated
default:
var err error
latestPod, err = clientset.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return latestPod, err
}

switch latestPod.Status.Phase {
case corev1.PodSucceeded, corev1.PodFailed:
return latestPod, nil
}

time.Sleep(interval)
}
}
}

// GetPodStatus checks if the pod has successfully completed its application logic and infrastructure is healthy.
func (ku KubeAPIWrapper) GetPodStatus(pod *corev1.Pod) (bool, string, error) {

infraOK, reason, err := ku.podInfrastructureSuccess(pod)

if !infraOK {
return false, fmt.Sprintf("pod %s/%s infrastructure %s", pod.Namespace, pod.Name, reason), err
}

appOK, reason, err := ku.podApplicationSuccess(pod)
if err != nil {
return false, fmt.Sprintf("pod: %s/%s application %s", pod.Namespace, pod.Name, reason), err
}
if !appOK {
return false, reason, err
}

return true, "", nil
}

func GetTimeoutOpenLogstream(kw *KubeUnit) int {
// RECEPTOR_OPEN_LOGSTREAM_TIMEOUT
// default: 1
Expand Down Expand Up @@ -386,6 +495,8 @@ func (kw *KubeUnit) KubeLoggingWithReconnect(streamWait *sync.WaitGroup, stdout
podName,
)

_, _ = kw.capturePodStatus(kw.Pod, stdout.Size(), 10*time.Second) //TODO Do we have a configurable timeout already?

Comment thread
lranjbar marked this conversation as resolved.
return
}

Expand Down Expand Up @@ -902,12 +1013,49 @@ func (kw *KubeUnit) runWorkUsingLogger() {
return
}

// only transition from WorkStateRunning to WorkStateSucceeded if WorkStateFailed is set we do not override
pod, err := kw.KubeAPIWrapperInstance.Get(kw.GetContext(), kw.clientset, podNamespace, podName, metav1.GetOptions{})
if err != nil {
kw.GetWorkceptor().nc.GetLogger().Warning("Failed to retrieve pod for diagnostics: %v", err)
} else {
ok, _ := kw.capturePodStatus(pod, stdout.Size(), 10*time.Second)
if !ok {
// If the pod did not succeed, we already updated the status to WorkStateFailed
// and we can return early.
return
}
}

// Only transition to WorkStateSucceeded if the work unit is still running
// and has not already failed due to diagnostic or streaming errors.
// This ensures we don't override a previously set WorkStateFailed.
if kw.GetContext().Err() != context.Canceled && kw.Status().State == WorkStateRunning {
kw.UpdateBasicStatus(WorkStateSucceeded, "Finished", stdout.Size())
}
}

func (kw *KubeUnit) capturePodStatus(pod *corev1.Pod, stdoutSize int64, timeout time.Duration) (ok bool, err error) {

Comment thread
arrestle marked this conversation as resolved.
Outdated
pod, err = kw.KubeAPIWrapperInstance.WaitForPodCompleted(pod, kw.clientset, timeout)
if err != nil {
kw.GetWorkceptor().nc.GetLogger().Debug("Job complete and pod is still running: %v", err)
}

ok, reason, err := kw.KubeAPIWrapperInstance.GetPodStatus(pod)
if err != nil {
reason := fmt.Sprintf("Pod diagnostics failed: %v reason %s", err, reason)
Comment thread
PabloHiro marked this conversation as resolved.
Outdated
Comment thread
lranjbar marked this conversation as resolved.
Outdated
kw.GetWorkceptor().nc.GetLogger().Warning(reason)
Comment thread
arrestle marked this conversation as resolved.
Outdated
kw.UpdateBasicStatus(WorkStateFailed, reason, stdoutSize)
return false, err
Comment thread
arrestle marked this conversation as resolved.
Outdated
} else if !ok {
kw.GetWorkceptor().nc.GetLogger().Warning("Pod did not succeed: %s", reason)
kw.UpdateBasicStatus(WorkStateFailed, reason, stdoutSize)
return false, fmt.Errorf("pod did not succeed: %s", reason)
Comment thread
arrestle marked this conversation as resolved.
Outdated
Comment thread
arrestle marked this conversation as resolved.
Outdated
Comment thread
arrestle marked this conversation as resolved.
Outdated
}
kw.GetWorkceptor().nc.GetLogger().Debug("Pod completed successfully: %s", pod.Status.String())
return true, nil

}

func IsCompatibleK8S(kw *KubeUnit, versionStr string) bool {
semver, err := version.ParseSemantic(versionStr)
if err != nil {
Expand Down
32 changes: 32 additions & 0 deletions pkg/workceptor/mock_workceptor/kubernetes.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.