Skip to content

Conversation

@mrhapile
Copy link

Ⅰ. Describe what this PR does

This PR fixes failures in the CSI Fuse recovery logic caused by platform-specific
mount checks. The recovery path was returning unexpected errors on environments
where util/mount is not supported, which caused unit tests to fail.

The change ensures:

  • shouldRecover correctly handles non-existent and non-mounted paths
  • Platform-specific mount errors are treated gracefully
  • Existing CSI node logic remains unchanged
  • Fuse recovery behavior is consistent across environments

Ⅱ. Does this pull request fix one issue?

fixes #5224


Ⅲ. List the added test cases (unit test/integration test) if any, please explain if no tests are needed.

No new tests were added.

Existing unit tests under pkg/csi/recover now pass successfully after fixing
the recovery logic. The change strictly aligns the implementation with the
expected behavior already covered by existing test cases.


Ⅳ. Describe how to verify it

Run the following command locally:

go test ./pkg/csi/...

Copilot AI review requested due to automatic review settings January 26, 2026 05:28
@fluid-e2e-bot
Copy link

fluid-e2e-bot bot commented Jan 26, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign ronggu for approval by writing /assign @ronggu in a comment. For more information see:The Kubernetes Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@fluid-e2e-bot
Copy link

fluid-e2e-bot bot commented Jan 26, 2026

Hi @mrhapile. Thanks for your PR.

I'm waiting for a fluid-cloudnative member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @mrhapile, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request primarily resolves issues in the CSI Fuse recovery logic tests, which were failing due to platform-specific behaviors of the util/mount package. By enhancing the mocking strategy for mount.Interface and introducing platform-aware test skipping, the recovery path's unit tests now pass consistently across environments. Additionally, the node label patching mechanism has been upgraded to use JSONPatchType for more precise and flexible label modifications.

Highlights

  • Node Label Patching Refactor: The patchNodeWithLabel function in nodeserver.go has been refactored to utilize JSONPatchType for node label modifications, replacing the previous StrategicMergePatchType. This change includes proper JSON path construction with key escaping for ~ and / characters, and explicit add/remove operations for improved precision.
  • CSI Fuse Recovery Test Fixes: Failures in CSI Fuse recovery logic tests have been addressed by enhancing the mocking strategy for mount.Interface within recover_test.go. This ensures that shouldRecover tests correctly handle scenarios involving non-existent and non-mounted paths, and gracefully manage platform-specific mount errors, leading to more reliable test outcomes.
  • macOS Test Skipping for Inlined Functions: A BeforeEach hook has been introduced to conditionally skip shouldRecover tests on macOS. This is necessary because the mount.New function is inlined by the Go compiler on this platform, which prevents effective mocking without specific gcflags.
  • Improved Test Assertions: The recoverBrokenMount test now directly asserts on the fakeMounter.MountPoints to verify mount operations. This provides a more robust and direct validation of the expected behavior compared to the previous method of checking a mountCalled flag.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@sonarqubecloud
Copy link

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request primarily addresses test failures in the CSI recovery logic by mocking mount.Interface, which makes the tests platform-independent and more reliable. The test refactorings, such as removing time.Sleep and using the fake mounter's capabilities, are excellent improvements.

Additionally, the PR includes a significant refactoring in pkg/csi/plugins/nodeserver.go, switching from StrategicMergePatchType to JSONPatchType for patching node labels. This is a great change for robustness, and the implementation, including key escaping and cache invalidation, is well done. For better visibility, it would be beneficial to mention this refactoring in the PR description.

I have one suggestion regarding the use of context.TODO() in the newly modified patchNodeWithLabel function.

@@ -438,53 +438,79 @@ func (ns *nodeServer) getNode() (node *corev1.Node, err error) {

func (ns *nodeServer) patchNodeWithLabel(node *corev1.Node, labelsToModify common.LabelsToModify) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

It's great that you've refactored this function to use JSONPatchType. To further improve it, consider passing a context.Context into this function instead of using context.TODO() in the Patch calls on lines 486 and 500. This allows for better control over cancellation and deadlines, aligning with Go's best practices for context propagation.

You would need to update the function signature and its callers (NodeStageVolume, prepareSessMgr, getCleanFuseFunc) to pass the context down from the top-level gRPC methods.

Suggested change
func (ns *nodeServer) patchNodeWithLabel(node *corev1.Node, labelsToModify common.LabelsToModify) error {
func (ns *nodeServer) patchNodeWithLabel(ctx context.Context, node *corev1.Node, labelsToModify common.LabelsToModify) error {

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses test failures in CSI fuse recovery logic and refactors the node label patching mechanism from StrategicMergePatch to JSONPatch. The changes aim to fix issue #5224 where node labels were not being applied correctly, potentially due to limitations with StrategicMergePatch when handling labels containing special characters like "/".

Changes:

  • Fixed shouldRecover tests by properly mocking mount.Interface to handle platform-specific mount checks
  • Refactored patchNodeWithLabel to use JSONPatch instead of StrategicMergePatch with proper RFC 6901 escaping
  • Added cache invalidation after successful node patches to prevent stale data issues

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 7 comments.

File Description
pkg/csi/recover/recover_test.go Updated test mocking approach to properly handle mount.New calls and fixed Start() test to run synchronously; added macOS skip for tests that cannot mock inlined functions
pkg/csi/plugins/nodeserver.go Refactored node labeling from StrategicMergePatch to JSONPatch with RFC 6901 escaping for special characters; added cache invalidation logic and early return optimization

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +471 to 473
if len(patches) == 0 {
return nil
}
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The early return when patches is empty is an optimization, but it changes behavior from the previous implementation. The old StrategicMergePatch approach would always make a patch request even with empty labels, which could serve as a validation that the node exists and is accessible.

While this optimization makes sense (no need to make an API call if there's nothing to change), it's worth verifying that no callers rely on the side effect of always making the API call (e.g., for validation purposes). The behavior change should be documented or the callers should be reviewed to ensure they don't depend on this side effect.

Copilot uses AI. Check for mistakes.
Comment on lines 439 to 514
func (ns *nodeServer) patchNodeWithLabel(node *corev1.Node, labelsToModify common.LabelsToModify) error {
labels := labelsToModify.GetLabels()
labelValuePair := map[string]interface{}{}
patches := make([]map[string]interface{}, 0, len(labels))

for _, labelToModify := range labels {
operationType := labelToModify.GetOperationType()
labelToModifyKey := labelToModify.GetLabelKey()
labelToModifyValue := labelToModify.GetLabelValue()
labelKey := labelToModify.GetLabelKey()
labelValue := labelToModify.GetLabelValue()

// JSONPatch requires escaping "/" as "~1"
// We must escape "~" first to avoid double escaping
escapedKey := strings.ReplaceAll(labelKey, "~", "~0")
escapedKey = strings.ReplaceAll(escapedKey, "/", "~1")
path := "/metadata/labels/" + escapedKey

switch operationType {
case common.AddLabel, common.UpdateLabel:
labelValuePair[labelToModifyKey] = labelToModifyValue
patches = append(patches, map[string]interface{}{
"op": "add",
"path": path,
"value": labelValue,
})
case common.DeleteLabel:
labelValuePair[labelToModifyKey] = nil
patches = append(patches, map[string]interface{}{
"op": "remove",
"path": path,
})
default:
err := fmt.Errorf("fail to update the label due to the wrong operation: %s", operationType)
return err
return fmt.Errorf("fail to update the label due to the wrong operation: %s", operationType)
}
}

metadata := map[string]interface{}{
"metadata": map[string]interface{}{
"labels": labelValuePair,
},
if len(patches) == 0 {
return nil
}

patchByteData, err := json.Marshal(metadata)
patchBytes, err := json.Marshal(patches)
if err != nil {
return err
}

var patchErr error
useNodeAuthorization := ns.nodeAuthorizedClient != nil
if useNodeAuthorization {
_, err = ns.nodeAuthorizedClient.CoreV1().Nodes().Patch(context.TODO(), node.Name, types.StrategicMergePatchType, patchByteData, metav1.PatchOptions{})
if err != nil {
return err
}
_, patchErr = ns.nodeAuthorizedClient.CoreV1().
Nodes().
Patch(
context.TODO(),
node.Name,
types.JSONPatchType,
patchBytes,
metav1.PatchOptions{},
)
} else {
nodeToPatch := &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: node.Name,
},
}
err = ns.client.Patch(context.TODO(), nodeToPatch, client.RawPatch(types.StrategicMergePatchType, patchByteData))
if err != nil {
return err
}

patchErr = ns.client.Patch(
context.TODO(),
nodeToPatch,
client.RawPatch(types.JSONPatchType, patchBytes),
)
}

return nil
if patchErr == nil {
// Invalidate cached node to ensure future patch operations (especially deletes)
// don't fail due to stale cache logic generating invalid JSONPatch remove ops.
// This forces a fresh fetch on the next usage.
ns.node = nil
}

return patchErr
}
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description states this change fixes CSI fuse recovery test failures, but the primary change is actually a major refactor of the node label patching mechanism from StrategicMergePatchType to JSONPatchType. This is a significant architectural change that directly relates to fixing issue #5224 about node labels not being applied.

The PR description should accurately describe both changes:

  1. Test fixes to handle platform-specific mount checks
  2. Node labeling mechanism refactored from StrategicMergePatch to JSONPatch to fix label application issues

This discrepancy between the description and the actual changes makes it difficult to understand the scope and purpose of the PR.

Copilot uses AI. Check for mistakes.
Comment on lines 439 to 514
func (ns *nodeServer) patchNodeWithLabel(node *corev1.Node, labelsToModify common.LabelsToModify) error {
labels := labelsToModify.GetLabels()
labelValuePair := map[string]interface{}{}
patches := make([]map[string]interface{}, 0, len(labels))

for _, labelToModify := range labels {
operationType := labelToModify.GetOperationType()
labelToModifyKey := labelToModify.GetLabelKey()
labelToModifyValue := labelToModify.GetLabelValue()
labelKey := labelToModify.GetLabelKey()
labelValue := labelToModify.GetLabelValue()

// JSONPatch requires escaping "/" as "~1"
// We must escape "~" first to avoid double escaping
escapedKey := strings.ReplaceAll(labelKey, "~", "~0")
escapedKey = strings.ReplaceAll(escapedKey, "/", "~1")
path := "/metadata/labels/" + escapedKey

switch operationType {
case common.AddLabel, common.UpdateLabel:
labelValuePair[labelToModifyKey] = labelToModifyValue
patches = append(patches, map[string]interface{}{
"op": "add",
"path": path,
"value": labelValue,
})
case common.DeleteLabel:
labelValuePair[labelToModifyKey] = nil
patches = append(patches, map[string]interface{}{
"op": "remove",
"path": path,
})
default:
err := fmt.Errorf("fail to update the label due to the wrong operation: %s", operationType)
return err
return fmt.Errorf("fail to update the label due to the wrong operation: %s", operationType)
}
}

metadata := map[string]interface{}{
"metadata": map[string]interface{}{
"labels": labelValuePair,
},
if len(patches) == 0 {
return nil
}

patchByteData, err := json.Marshal(metadata)
patchBytes, err := json.Marshal(patches)
if err != nil {
return err
}

var patchErr error
useNodeAuthorization := ns.nodeAuthorizedClient != nil
if useNodeAuthorization {
_, err = ns.nodeAuthorizedClient.CoreV1().Nodes().Patch(context.TODO(), node.Name, types.StrategicMergePatchType, patchByteData, metav1.PatchOptions{})
if err != nil {
return err
}
_, patchErr = ns.nodeAuthorizedClient.CoreV1().
Nodes().
Patch(
context.TODO(),
node.Name,
types.JSONPatchType,
patchBytes,
metav1.PatchOptions{},
)
} else {
nodeToPatch := &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: node.Name,
},
}
err = ns.client.Patch(context.TODO(), nodeToPatch, client.RawPatch(types.StrategicMergePatchType, patchByteData))
if err != nil {
return err
}

patchErr = ns.client.Patch(
context.TODO(),
nodeToPatch,
client.RawPatch(types.JSONPatchType, patchBytes),
)
}

return nil
if patchErr == nil {
// Invalidate cached node to ensure future patch operations (especially deletes)
// don't fail due to stale cache logic generating invalid JSONPatch remove ops.
// This forces a fresh fetch on the next usage.
ns.node = nil
}

return patchErr
}
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change from StrategicMergePatchType to JSONPatchType for node label patching creates an inconsistency in the codebase. The file pkg/utils/label.go still uses StrategicMergePatchType for the same purpose (see ChangeNodeLabelWithPatchMode and PatchLabels functions at pkg/utils/label.go:73-114).

This inconsistency means:

  1. The CSI plugin will use JSONPatch for node labeling
  2. Other parts of the system (e.g., dataset lifecycle management) will use StrategicMergePatch for node labeling

Both approaches should be unified to use the same patch mechanism for consistency, maintainability, and to avoid confusion. If JSONPatch is the correct approach (which appears to be the case given the RFC 6901 escaping requirements), then ChangeNodeLabelWithPatchMode in pkg/utils/label.go should also be updated to use JSONPatch.

Copilot uses AI. Check for mistakes.
Comment on lines 439 to 514
func (ns *nodeServer) patchNodeWithLabel(node *corev1.Node, labelsToModify common.LabelsToModify) error {
labels := labelsToModify.GetLabels()
labelValuePair := map[string]interface{}{}
patches := make([]map[string]interface{}, 0, len(labels))

for _, labelToModify := range labels {
operationType := labelToModify.GetOperationType()
labelToModifyKey := labelToModify.GetLabelKey()
labelToModifyValue := labelToModify.GetLabelValue()
labelKey := labelToModify.GetLabelKey()
labelValue := labelToModify.GetLabelValue()

// JSONPatch requires escaping "/" as "~1"
// We must escape "~" first to avoid double escaping
escapedKey := strings.ReplaceAll(labelKey, "~", "~0")
escapedKey = strings.ReplaceAll(escapedKey, "/", "~1")
path := "/metadata/labels/" + escapedKey

switch operationType {
case common.AddLabel, common.UpdateLabel:
labelValuePair[labelToModifyKey] = labelToModifyValue
patches = append(patches, map[string]interface{}{
"op": "add",
"path": path,
"value": labelValue,
})
case common.DeleteLabel:
labelValuePair[labelToModifyKey] = nil
patches = append(patches, map[string]interface{}{
"op": "remove",
"path": path,
})
default:
err := fmt.Errorf("fail to update the label due to the wrong operation: %s", operationType)
return err
return fmt.Errorf("fail to update the label due to the wrong operation: %s", operationType)
}
}

metadata := map[string]interface{}{
"metadata": map[string]interface{}{
"labels": labelValuePair,
},
if len(patches) == 0 {
return nil
}

patchByteData, err := json.Marshal(metadata)
patchBytes, err := json.Marshal(patches)
if err != nil {
return err
}

var patchErr error
useNodeAuthorization := ns.nodeAuthorizedClient != nil
if useNodeAuthorization {
_, err = ns.nodeAuthorizedClient.CoreV1().Nodes().Patch(context.TODO(), node.Name, types.StrategicMergePatchType, patchByteData, metav1.PatchOptions{})
if err != nil {
return err
}
_, patchErr = ns.nodeAuthorizedClient.CoreV1().
Nodes().
Patch(
context.TODO(),
node.Name,
types.JSONPatchType,
patchBytes,
metav1.PatchOptions{},
)
} else {
nodeToPatch := &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: node.Name,
},
}
err = ns.client.Patch(context.TODO(), nodeToPatch, client.RawPatch(types.StrategicMergePatchType, patchByteData))
if err != nil {
return err
}

patchErr = ns.client.Patch(
context.TODO(),
nodeToPatch,
client.RawPatch(types.JSONPatchType, patchBytes),
)
}

return nil
if patchErr == nil {
// Invalidate cached node to ensure future patch operations (especially deletes)
// don't fail due to stale cache logic generating invalid JSONPatch remove ops.
// This forces a fresh fetch on the next usage.
ns.node = nil
}

return patchErr
}
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no tests for the new patchNodeWithLabel implementation. The refactored code includes:

  1. JSONPatch format generation
  2. RFC 6901 escape sequence handling for label keys containing "/" and "~"
  3. Cache invalidation logic (line 510: ns.node = nil)
  4. Different error handling paths for node-authorized vs non-authorized clients

Critical behaviors like proper escaping of special characters and cache invalidation after successful patches should be covered by unit tests to prevent regressions. Consider adding tests that verify:

  • Labels with "/" are properly escaped to "~1"
  • Labels with "~" are properly escaped to "~0"
  • The node cache is cleared after successful patches
  • Add, remove, and update operations generate correct JSONPatch operations
  • Error handling works correctly for both client types

Copilot uses AI. Check for mistakes.
Comment on lines +462 to +465
patches = append(patches, map[string]interface{}{
"op": "remove",
"path": path,
})
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JSONPatch "remove" operation will fail if the label doesn't exist on the node. Unlike StrategicMergePatch which treats setting a field to nil as a remove operation that succeeds even if the field doesn't exist, JSONPatch "remove" operations are strict and will return an error if the path doesn't exist.

This could cause NodeUnstageVolume to fail when trying to delete a label that's already been removed or never existed. Consider checking if the label exists before adding it to the patches array, or handle the error case gracefully. For example:

case common.DeleteLabel:
    // Only add remove patch if label exists
    if _, exists := node.Labels[labelKey]; exists {
        patches = append(patches, map[string]interface{}{
            "op":   "remove",
            "path": path,
        })
    }
Suggested change
patches = append(patches, map[string]interface{}{
"op": "remove",
"path": path,
})
// JSONPatch "remove" fails if the path does not exist, so only add the patch
// when the label is currently present on the node.
if node != nil && node.Labels != nil {
if _, exists := node.Labels[labelKey]; exists {
patches = append(patches, map[string]interface{}{
"op": "remove",
"path": path,
})
}
}

Copilot uses AI. Check for mistakes.
Comment on lines 693 to +696

Describe("shouldRecover", func() {
BeforeEach(func() {
if runtime.GOOS == "darwin" {
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipping tests on macOS due to inlining issues is a workaround rather than a proper fix. The comment mentions that mount.New is inlined and cannot be mocked without -gcflags=all=-l, but this Skip approach means these tests will never run on macOS during development.

Consider either:

  1. Restructuring the code to make mount.New mockable through dependency injection (e.g., passing a mounter factory function)
  2. Adding build tags and test compilation flags to disable inlining for tests
  3. Documenting this limitation more prominently in the test file or repository documentation

The current approach silently skips important test coverage on macOS, which could hide platform-specific issues.

Suggested change
Describe("shouldRecover", func() {
BeforeEach(func() {
if runtime.GOOS == "darwin" {
// NOTE FOR macOS USERS:
// These tests patch mount.New from k8s.io/utils/mount. On macOS, the Go
// compiler currently inlines mount.New, which prevents gomonkey from
// patching it in tests unless inlining is disabled with:
//
// go test -gcflags=all=-l ./pkg/csi/recover
//
// Because disabling inlining by default would slow down all test builds,
// we instead skip this block on macOS. If you need to run it locally on
// macOS, re-run the tests with the flag above and remove or comment out
// the Skip below.
Describe("shouldRecover", func() {
BeforeEach(func() {
if runtime.GOOS == "darwin" {
fmt.Fprintln(GinkgoWriter, "Skipping shouldRecover tests on macOS: mount.New is inlined and cannot be mocked without -gcflags=all=-l. To run these tests on macOS, use: go test -gcflags=all=-l ./pkg/csi/recover")

Copilot uses AI. Check for mistakes.
Comment on lines +448 to +452
// JSONPatch requires escaping "/" as "~1"
// We must escape "~" first to avoid double escaping
escapedKey := strings.ReplaceAll(labelKey, "~", "~0")
escapedKey = strings.ReplaceAll(escapedKey, "/", "~1")
path := "/metadata/labels/" + escapedKey
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The order of escape operations is critical and correct here (escaping "~" before "/"), but the escaping logic could be more robust by using the same approach as the standard libraries. The vendor directory shows that gomodules.xyz/jsonpatch/v2 already has a proper RFC 6901 encoder (line 155: var rfc6901Encoder = strings.NewReplacer("~", "~0", "/", "~1")).

Consider either:

  1. Importing and using the existing rfc6901Encoder from the jsonpatch library
  2. Creating a shared utility function following the same pattern

This would reduce code duplication, improve maintainability, and ensure consistency with the library that will ultimately parse these patches.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] no node has fuse pod label even if csi plugin has added label in node

1 participant