Change:
1 ci-kubernetes-e2e-gci-gce 1 ci-cos-containerd-e2e-cos-gce in the master blocking test grid 1 ci-kubernetes-e2e-gci-gce-network-proxy-grpc 1 ci-kubernetes-e2e-gci-gce-proto Kubernetes e2e suite.[It] [sig-storage] CSI Mock workload info CSI PodInfoOnMount Update should be passed when update from false to true 12/14 code thaw after 1.29.0 release was merged at the time and is related to PodInfoOnMount. /cc /sig storage
This issue is currently awaiting triage. If a SIG or subproject determines this is a relevant issue, they will accept it by applying the label and provide further guidance. The label can be by org members by writing in a comment. <detailsInstructions for interacting with me using PR comments are available . If you have questions or suggestions related to my behavior, please file an issue against the repository. </details
/assign
testCase: podInfoOnMount should be passed when update from false to true kubelet log: csi-mockplugin-0-mock log: the function in the test case is not working as expected. expect log at Line 2, but actual log is at Line 3.
is called twice for the same volume sometime even though the volume is already published successfully. Not only this issue, but also other issues like I wonder if it is a potential bug or a normal behavior. cc what do you think?
good catch. I think that any test that depends on exact index of NodePublish call will be flaky, sometimes Kubelet has to retry NodePublish. It would be better to check for NodePublish parameter changes. For example, test should check there were 1 or more NodePublish calls with no pod info and then 1 or more NodePublish calls with pod info afterwards.

Discussion:
storageframework "k8s.io/kubernetes/test/e2e/storage/framework" "k8s.io/kubernetes/test/e2e/storage/testsuites" "k8s.io/kubernetes/test/e2e/storage/utils" <ins> "k8s.io/kubernetes/test/utils/format" </ins> imageutils "k8s.io/kubernetes/test/utils/image" )

return cs.CoreV1().Pods(ns).Create(context.TODO(), pod, metav1.CreateOptions{}) } <del> func checkPodLogs(ctx context.Context, getCalls func(ctx context.Context) ([]drivers.MockCSICall, error), pod *v1.Pod, expectPodInfo, ephemeralVolume, csiInlineVolumesEnabled, csiServiceAccountTokenEnabled bool, expectedNumNodePublish int) error { </del> <ins> // checkNodePublishVolume goes through all calls to the mock driver and checks that at least one NodePublishVolume call had expected attributes. // If a matched call is found but it has unexpected attributes, checkNodePublishVolume skips it and continues searching. func checkNodePublishVolume(ctx context.Context, getCalls func(ctx context.Context) ([]drivers.MockCSICall, error), pod *v1.Pod, expectPodInfo, ephemeralVolume, csiInlineVolumesEnabled, csiServiceAccountTokenEnabled bool) error { </ins> expectedAttributes := map[string]string{} <ins> unexpectedAttributeKeys := sets.New[string]() </ins> if expectPodInfo { expectedAttributes["csi.storage.k8s.io/pod.name"] = pod.Name expectedAttributes["csi.storage.k8s.io/pod.namespace"] = pod.Namespace expectedAttributes["csi.storage.k8s.io/pod.uid"] = string(pod.UID) expectedAttributes["csi.storage.k8s.io/serviceAccount.name"] = "default" <ins> } else { unexpectedAttributeKeys.Insert("csi.storage.k8s.io/pod.name") unexpectedAttributeKeys.Insert("csi.storage.k8s.io/pod.namespace") unexpectedAttributeKeys.Insert("csi.storage.k8s.io/pod.uid") unexpectedAttributeKeys.Insert("csi.storage.k8s.io/serviceAccount.name") </ins> } if csiInlineVolumesEnabled { // This is only passed in 1.15 when the CSIInlineVolume feature gate is set. expectedAttributes["csi.storage.k8s.io/ephemeral"] = strconv.FormatBool(ephemeralVolume) <ins> } else { unexpectedAttributeKeys.Insert("csi.storage.k8s.io/ephemeral") </ins> } if csiServiceAccountTokenEnabled { expectedAttributes["csi.storage.k8s.io/serviceAccount.tokens"] = "<nonempty>" <ins> } else { unexpectedAttributeKeys.Insert("csi.storage.k8s.io/serviceAccount.tokens") </ins> } <del> // Find NodePublish in the GRPC calls. foundAttributes := sets.NewString() numNodePublishVolume := 0 numNodeUnpublishVolume := 0 </del> calls, err := getCalls(ctx) if err != nil { return err } <ins> var volumeContexts []map[string]string </ins> for _, call := range calls { <del> switch call.Method { case "NodePublishVolume": numNodePublishVolume++ if numNodePublishVolume == expectedNumNodePublish { // Check that NodePublish had expected attributes for last of expected volume for k, v := range expectedAttributes { vv, found := call.Request.VolumeContext[k] if found && (v == vv || (v == "<nonempty>" && len(vv) != 0)) { foundAttributes.Insert(k) framework.Logf("Found volume attribute %s: %s", k, vv) } } </del> <ins> if call.Method != "NodePublishVolume" { continue } volumeCtx := call.Request.VolumeContext // Check that NodePublish had expected attributes foundAttributes := sets.NewString() for k, v := range expectedAttributes { vv, found := volumeCtx[k] if found && (v == vv || (v == "<nonempty>" && len(vv) != 0)) { foundAttributes.Insert(k) </ins> } <del> case "NodeUnpublishVolume": framework.Logf("Found NodeUnpublishVolume: %+v", call) numNodeUnpublishVolume++ </del> } <del> } if numNodePublishVolume < expectedNumNodePublish { return fmt.Errorf("NodePublish should be called at least %d", expectedNumNodePublish) } </del> <ins> if foundAttributes.Len() != len(expectedAttributes) { framework.Logf("Skipping the NodePublishVolume call: expected attribute %+v, got %+v", format.Object(expectedAttributes, 1), format.Object(volumeCtx, 1)) continue } </ins> <del> if numNodeUnpublishVolume == 0 { return fmt.Errorf("NodeUnpublish was never called") </del> <ins> // Check that NodePublish had no unexpected attributes unexpectedAttributes := make(map[string]string) for k := range volumeCtx { if unexpectedAttributeKeys.Has(k) { unexpectedAttributes[k] = volumeCtx[k] } } if len(unexpectedAttributes) != 0 { framework.Logf("Skipping the NodePublishVolume call because it contains unexpected attributes %+v", format.Object(unexpectedAttributes, 1)) continue } return nil </ins> } <del> if foundAttributes.Len() != len(expectedAttributes) { return fmt.Errorf("number of found volume attributes does not match, expected %d, got %d", len(expectedAttributes), foundAttributes.Len()) </del> <ins> if len(volumeContexts) == 0 { return fmt.Errorf("NodePublishVolume was never called") </ins> } <del> return nil </del> <ins> return fmt.Errorf("NodePublishVolume was called %d times, but no call had expected attributes %s or calls have unwanted attributes key %+v", len(volumeContexts), format.Object(expectedAttributes, 1), unexpectedAttributeKeys.UnsortedList()) </ins> } // createFSGroupRequestPreHook creates a hook that records the fsGroup passed in

framework.ExpectNoError(err, "Failed to start pod: %v", err) // sleep to make sure RequiresRepublish triggers more than 1 NodePublishVolume <del> numNodePublishVolume := 1 </del> if test.deployCSIDriverObject && csiServiceAccountTokenEnabled { time.Sleep(5 * time.Second) <del> numNodePublishVolume = 2 </del> } ginkgo.By("Deleting the previously created pod")

framework.ExpectNoError(err, "while deleting") ginkgo.By("Checking CSI driver logs") <del> err = checkPodLogs(ctx, m.driver.GetCalls, pod, false, false, false, test.deployCSIDriverObject && csiServiceAccountTokenEnabled, numNodePublishVolume) </del> <ins> err = checkNodePublishVolume(ctx, m.driver.GetCalls, pod, false, false, false, test.deployCSIDriverObject && csiServiceAccountTokenEnabled) </ins> framework.ExpectNoError(err) }) }

f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged m := newMockDriverSetup(f) ginkgo.Context("CSI workload information using mock driver", func() { <del> var ( podInfoTrue  = true podInfoFalse = false ) </del> tests := []struct { name                   string <del> podInfoOnMount         *bool </del> <ins> podInfoOnMount         bool </ins> deployClusterRegistrar bool expectPodInfo          bool expectEphemeral        bool }{ { <del> name:                   "should not be passed when podInfoOnMount=nil", podInfoOnMount:         nil, deployClusterRegistrar: true, expectPodInfo:          false, expectEphemeral:        false, }, { </del> name:                   "should be passed when podInfoOnMount=true", <del> podInfoOnMount:         &podInfoTrue, </del> <ins> podInfoOnMount:         true, </ins> deployClusterRegistrar: true, expectPodInfo:          true, expectEphemeral:        false, }, { name:                   "contain ephemeral=true when using inline volume", <del> podInfoOnMount:         &podInfoTrue, </del> <ins> podInfoOnMount:         true, </ins> deployClusterRegistrar: true, expectPodInfo:          true, expectEphemeral:        true, }, { name:                   "should not be passed when podInfoOnMount=false", <del> podInfoOnMount:         &podInfoFalse, </del> <ins> podInfoOnMount:         false, </ins> deployClusterRegistrar: true, expectPodInfo:          false, expectEphemeral:        false,

ginkgo.It(t.name, func(ctx context.Context) { m.init(ctx, testParameters{ registerDriver: test.deployClusterRegistrar, <del> podInfo:        test.podInfoOnMount}) </del> <ins> podInfo:        &test.podInfoOnMount}) </ins> ginkgo.DeferCleanup(m.cleanup) <del> waitUntilPodInfoInLog(ctx, m, test.expectPodInfo, test.expectEphemeral, 1) </del> <ins> waitUntilPodInfoInLog(ctx, m, test.expectPodInfo, test.expectEphemeral) </ins> }) } }) ginkgo.Context("CSI PodInfoOnMount Update", func() { <del> var ( podInfoTrue  = true podInfoFalse = false ) </del> tests := []struct { name              string <del> oldPodInfoOnMount *bool newPodInfoOnMount *bool </del> <ins> oldPodInfoOnMount bool newPodInfoOnMount bool </ins> }{ { name:              "should not be passed when update from true to false", <del> oldPodInfoOnMount: &podInfoTrue, newPodInfoOnMount: &podInfoFalse, }, { name:              "should not be passed when update from true to nil", oldPodInfoOnMount: &podInfoTrue, newPodInfoOnMount: nil, </del> <ins> oldPodInfoOnMount: true, newPodInfoOnMount: false, </ins> }, { name:              "should be passed when update from false to true", <del> oldPodInfoOnMount: &podInfoFalse, newPodInfoOnMount: &podInfoTrue, }, { name:              "should be passed when update from nil to true", oldPodInfoOnMount: nil, newPodInfoOnMount: &podInfoTrue, </del> <ins> oldPodInfoOnMount: false, newPodInfoOnMount: true, </ins> }, } for _, t := range tests {

ginkgo.It(t.name, func(ctx context.Context) { m.init(ctx, testParameters{ registerDriver: true, <del> podInfo:        test.oldPodInfoOnMount}) </del> <ins> podInfo:        &test.oldPodInfoOnMount}) </ins> ginkgo.DeferCleanup(m.cleanup) <del> waitUntilPodInfoInLog(ctx, m, test.oldPodInfoOnMount != nil && *test.oldPodInfoOnMount, false, 1) m.update(utils.PatchCSIOptions{PodInfo: test.newPodInfoOnMount}) waitUntilPodInfoInLog(ctx, m, test.newPodInfoOnMount != nil && *test.newPodInfoOnMount, false, 2) </del> <ins> waitUntilPodInfoInLog(ctx, m, test.oldPodInfoOnMount, false) m.update(utils.PatchCSIOptions{PodInfo: &test.newPodInfoOnMount}) waitUntilPodInfoInLog(ctx, m, test.newPodInfoOnMount, false) </ins> }) } }) }) <del> func waitUntilPodInfoInLog(ctx context.Context, m *mockDriverSetup, expectPodInfo, expectEphemeral bool, expectedNumNodePublish int) { </del> <ins> func waitUntilPodInfoInLog(ctx context.Context, m *mockDriverSetup, expectPodInfo, expectEphemeral bool) { </ins> var err error utils.WaitUntil(framework.Poll, framework.PodStartTimeout, func() bool {

framework.ExpectNoError(err, "while deleting") ginkgo.By("Checking CSI driver logs") <del> err = checkPodLogs(ctx, m.driver.GetCalls, pod, expectPodInfo, expectEphemeral, csiInlineVolumesEnabled, false, expectedNumNodePublish) </del> <ins> err = checkNodePublishVolume(ctx, m.driver.GetCalls, pod, expectPodInfo, expectEphemeral, csiInlineVolumesEnabled, false) </ins> framework.ExpectNoError(err) })