-
Notifications
You must be signed in to change notification settings - Fork 79
OCPBUGS-78953: Fix bug with PV not getting provisioned after deletion #595
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 |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| package e2e | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "testing" | ||
|
|
||
| "github.com/onsi/gomega" | ||
| framework "github.com/openshift/local-storage-operator/test/framework" | ||
| batchv1 "k8s.io/api/batch/v1" | ||
| corev1 "k8s.io/api/core/v1" | ||
| ) | ||
|
|
||
| // addNewUdevSymlink adds a new udev symlink on the node. The link will point to the same device as the currentLink. | ||
| func addNewUdevSymlink(t *testing.T, ctx *framework.TestCtx, nodeHostname string, currentLink, newLink string) { | ||
| t.Logf("adding new udev symlink %s -> %s on node %s", newLink, currentLink, nodeHostname) | ||
| matcher := gomega.NewWithT(t) | ||
|
|
||
| namespace, err := ctx.GetOperatorNamespace() | ||
| matcher.Expect(err).NotTo(gomega.HaveOccurred(), "could not determine namespace") | ||
|
|
||
| symlinkJob, err := newAddUdevSymlinkJob(nodeHostname, namespace, currentLink, newLink) | ||
| matcher.Expect(err).NotTo(gomega.HaveOccurred(), "could not create symlink job") | ||
| createOrReplaceJob(t, ctx, symlinkJob, fmt.Sprintf("creating symlink job on node: %q", nodeHostname)) | ||
|
|
||
| waitForJobCompletion(t, symlinkJob, fmt.Sprintf("waiting for check-symlink job to complete: %q", symlinkJob.GetName())) | ||
| symlinkJob.TypeMeta.Kind = "Job" | ||
| eventuallyDelete(t, false, symlinkJob) | ||
| t.Logf("added udev symlink %s -> %s on node %s", newLink, currentLink, nodeHostname) | ||
| } | ||
|
|
||
| // removeUdevSymlink removes a udev symlink on the node. It literally calls `rm -f $linkPattern` (in the root directory), | ||
| // so it can be used to remove multiple symlinks at once. | ||
| func removeUdevSymlink(t *testing.T, ctx *framework.TestCtx, nodeHostname string, linkPattern string) { | ||
| t.Logf("removing udev symlinks matching %s on node %s", linkPattern, nodeHostname) | ||
| matcher := gomega.NewWithT(t) | ||
|
|
||
| namespace, err := ctx.GetOperatorNamespace() | ||
| matcher.Expect(err).NotTo(gomega.HaveOccurred(), "could not determine namespace") | ||
|
|
||
| symlinkJob, err := newRemoveUdevSymlinksJob(nodeHostname, namespace, linkPattern) | ||
| matcher.Expect(err).NotTo(gomega.HaveOccurred(), "could not create symlink job") | ||
| createOrReplaceJob(t, ctx, symlinkJob, fmt.Sprintf("creating symlink job on node: %q", nodeHostname)) | ||
|
|
||
| waitForJobCompletion(t, symlinkJob, fmt.Sprintf("waiting for check-symlink job to complete: %q", symlinkJob.GetName())) | ||
| symlinkJob.TypeMeta.Kind = "Job" | ||
| eventuallyDelete(t, false, symlinkJob) | ||
| t.Logf("removed udev symlinks matching %s on node %s", linkPattern, nodeHostname) | ||
| } | ||
|
|
||
| func newAddUdevSymlinkJob(nodeHostname, namespace, currentUdevLink, newLink string) (*batchv1.Job, error) { | ||
| script := ` | ||
| set -eu | ||
| set -x | ||
|
|
||
| echo "adding new udev symlink $CURRENT_LINK -> $NEW_LINK" | ||
| if [[ ! -L "$CURRENT_LINK" ]]; then | ||
| echo "not a symlink: $CURRENT_LINK" | ||
| exit 1 | ||
| fi | ||
|
|
||
| DEVICE=$(readlink -f "$CURRENT_LINK") | ||
| ln -sfv "$DEVICE" "$NEW_LINK" | ||
| ` | ||
| return newNodeJob( | ||
| nodeHostname, | ||
| namespace, | ||
| fmt.Sprintf("add-udev-symlink-job-%s", nodeHostname), | ||
| "adds a new udev symlink", | ||
| []string{"/bin/bash", "-c", script}, | ||
| &NodeJobOptions{ | ||
| ContainerRestartPolicy: corev1.RestartPolicyNever, | ||
| Env: []corev1.EnvVar{ | ||
| {Name: "CURRENT_LINK", Value: currentUdevLink}, | ||
| {Name: "NEW_LINK", Value: newLink}, | ||
| }, | ||
| }) | ||
|
|
||
| } | ||
|
|
||
| func newRemoveUdevSymlinksJob(nodeHostname, namespace, pattern string) (*batchv1.Job, error) { | ||
| script := ` | ||
| set -eu | ||
| set -x | ||
|
|
||
| ls -la "$PATTERN" || echo "No matching files found" | ||
| rm -fv "$PATTERN" | ||
| ` | ||
| return newNodeJob( | ||
| nodeHostname, | ||
| namespace, | ||
| fmt.Sprintf("remove-udev-symlink-job-%s", nodeHostname), | ||
| "removes udev symlinks matching the pattern", | ||
| []string{"/bin/bash", "-c", script}, | ||
| &NodeJobOptions{ | ||
| ContainerRestartPolicy: corev1.RestartPolicyNever, | ||
| Env: []corev1.EnvVar{ | ||
| {Name: "PATTERN", Value: pattern}, | ||
| }, | ||
| }) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -159,6 +159,20 @@ func LocalVolumeTest(ctx *framework.Context, cleanupFuncs *[]cleanupFn) func(*te | |
| t.Logf("looking for %q annotation on pvs", provCommon.AnnProvisionedBy) | ||
| verifyProvisionerAnnotation(t, pvs, nodeList.Items) | ||
|
|
||
| t.Logf("Checking for recreation of PVs on symlink change") | ||
| // get first PV and change its symlink to verify if PV comes back when preferredSymlink changes | ||
| selectedPV := pvs[0] | ||
| nodeHostName := findNodeHostnameForPV(t, &selectedPV) | ||
| t.Logf("Using hostname %s", nodeHostName) | ||
| var currentSymlink string | ||
| if selectedDisk.id != "" { | ||
| currentSymlink = filepath.Join("/dev/disk/by-id", selectedDisk.id) | ||
| } else { | ||
| currentSymlink = filepath.Join("/dev", "name") | ||
| } | ||
| newPreferredTarget := "/dev/disk/by-id/scsi-1-local-storage-e2e-test" | ||
| addNewUdevSymlink(t, ctx, nodeHostName, currentSymlink, newPreferredTarget) | ||
|
Contributor
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. Is there an easy way how to test that the newly created PV many lines below actually used
Member
Author
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. The idea isn't here that the newly created will use new After my fix - the recreated PV will come up with same symlink it was using before. We aren't changing symlinks in this bug fix. |
||
|
|
||
| // verify deletion | ||
| for _, pv := range pvs { | ||
| eventuallyDelete(t, false, &pv) | ||
|
|
@@ -573,3 +587,22 @@ func deleteResource(obj client.Object, namespace, name string, client framework. | |
| }) | ||
| return waitErr | ||
| } | ||
|
|
||
| // findNodeHostnameForLVDL returns the node hostname where the LVDL's PV is scheduled, | ||
| // by inspecting the PV's spec.nodeAffinity (Required, LabelHostname). | ||
| func findNodeHostnameForPV(t *testing.T, pv *v1.PersistentVolume) string { | ||
| t.Helper() | ||
| pvName := pv.Name | ||
| if pv.Spec.NodeAffinity == nil || pv.Spec.NodeAffinity.Required == nil { | ||
| t.Fatalf("PV %s has no NodeAffinity.Required", pvName) | ||
| } | ||
| for _, term := range pv.Spec.NodeAffinity.Required.NodeSelectorTerms { | ||
| for _, expr := range term.MatchExpressions { | ||
| if expr.Key == corev1.LabelHostname && len(expr.Values) > 0 { | ||
| return expr.Values[0] | ||
| } | ||
| } | ||
| } | ||
| t.Fatalf("PV %s NodeAffinity has no %q expression", pvName, corev1.LabelHostname) | ||
| return "" | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.