-
Notifications
You must be signed in to change notification settings - Fork 74
fix: add node affinity for node where PVC is mounted to workspace's backup Jobs #1677
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,7 @@ | |
| package storage | ||
|
|
||
| import ( | ||
| "context" | ||
| "errors" | ||
| "fmt" | ||
|
|
||
|
|
@@ -27,6 +28,7 @@ import ( | |
| k8sErrors "k8s.io/apimachinery/pkg/api/errors" | ||
| "k8s.io/apimachinery/pkg/api/resource" | ||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| "k8s.io/apimachinery/pkg/labels" | ||
| "k8s.io/apimachinery/pkg/types" | ||
| "sigs.k8s.io/controller-runtime/pkg/client" | ||
|
|
||
|
|
@@ -266,6 +268,60 @@ func getSharedPVCWorkspaceCount(namespace string, api sync.ClusterAPI) (total in | |
| return total, nil | ||
| } | ||
|
|
||
| // FindNodeForPVC lists DevWorkspace pods in the given namespace and returns the | ||
| // node name where a running pod mounts the specified PVC. Returns an empty | ||
| // string (and nil error) when no such pod is found. | ||
| func FindNodeForPVC(ctx context.Context, k8sClient client.Client, namespace, pvcName string) (string, error) { | ||
| labelSelector, err := labels.Parse(constants.DevWorkspaceIDLabel) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
| podList := &corev1.PodList{} | ||
| if err := k8sClient.List(ctx, podList, &client.ListOptions{ | ||
| Namespace: namespace, | ||
| LabelSelector: labelSelector, | ||
| }); err != nil { | ||
|
Comment on lines
+274
to
+283
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Files:"
git ls-files | grep -E '(^|/)(shared\.go|backupcronjob_controller\.go|cleanup\.go)$' || true
echo
echo "Relevant occurrences:"
rg -n "FindNodeForPVC|NonCachingClient|backupcronjob_controller\.go|cleanup\.go" -S .
echo
echo "Context shared.go around FindNodeForPVC:"
file=$(git ls-files | grep 'pkg/provision/storage/shared.go' | head -n1)
if [ -n "${file:-}" ]; then
wc -l "$file"
sed -n '240,310p' "$file" | cat -n
fi
echo
echo "Context backup cron job around call:"
file=$(git ls-files | grep 'controllers/backupcronjob/backupcronjob_controller.go' | head -n1)
if [ -n "${file:-}" ]; then
wc -l "$file"
sed -n '430,485p' "$file" | cat -n
fi
echo
echo "Context cleanup around call:"
file=$(git ls-files | grep 'pkg/provision/storage/cleanup.go' |头 n1 | sed 's/ / /;s/ */ /')
if [ -n "${file:-}" ]; then
wc -l "$file"
sed -n '90,145p' "$file" | cat -n
fiRepository: devfile/devworkspace-operator Length of output: 8245 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Backup caller and type fields:"
sed -n '48,70p' controllers/backupcronjob/backupcronjob_controller.go | cat -n
sed -n '445,468p' controllers/backupcronjob/backupcronjob_controller.go | cat -n
echo
echo "Cleanup callers:"
rg -n "FindNodeForPVC\\(" controllers pkg/provision/storage -S
sed -n '110,130p' controllers/workspace/cleanup.go | cat -n
sed -n '90,130p' pkg/provision/storage/cleanup.go | cat -n
echo
echo "ClusterAPI fields:"
rg -n "type ClusterAPI|NonCachingClient|Client.*ClusterAPI|Ctx.*ClusterAPI" -S pkg controllers -g '*.go' | head -n 80
sed -n '1,110p' pkg/provision/sync/cluster_api.go | cat -nRepository: devfile/devworkspace-operator Length of output: 8337 Use
Fresh pod data is needed to avoid pinning cleanup or backup Jobs to a stale node and causing ReadWriteOnce PV Multi-Attach failures. Pass each caller’s 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| return "", err | ||
| } | ||
| return getNodeNameWithPVC(podList, pvcName), nil | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
|
|
||
| // NodeAffinityForHostname returns an Affinity that pins a pod to the given node | ||
| // using the kubernetes.io/hostname label. Used by both cleanup and backup Jobs | ||
| // to avoid Multi-Attach errors with ReadWriteOnce PVCs on multi-node clusters. | ||
| func NodeAffinityForHostname(nodeName string) *corev1.Affinity { | ||
| return &corev1.Affinity{ | ||
| NodeAffinity: &corev1.NodeAffinity{ | ||
| RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ | ||
| NodeSelectorTerms: []corev1.NodeSelectorTerm{ | ||
| { | ||
| MatchExpressions: []corev1.NodeSelectorRequirement{ | ||
| { | ||
| Key: corev1.LabelHostname, | ||
| Operator: corev1.NodeSelectorOpIn, | ||
| Values: []string{nodeName}, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| func getNodeNameWithPVC(list *corev1.PodList, pvcName string) string { | ||
| for _, pod := range list.Items { | ||
| if pod.Status.Phase == corev1.PodRunning { | ||
| for _, volume := range pod.Spec.Volumes { | ||
| if volume.PersistentVolumeClaim != nil && volume.PersistentVolumeClaim.ClaimName == pvcName { | ||
| return pod.Spec.NodeName | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return "" | ||
| } | ||
|
|
||
| func checkPVCTerminating(name, namespace string, api sync.ClusterAPI) (bool, error) { | ||
| if name == "" { | ||
| // Should not happen | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.