Skip to content
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

Split the functionality in node/mounter into smaller packages #328

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

unexge
Copy link
Contributor

@unexge unexge commented Dec 27, 2024

Currently, node/mounter package contains lots of logic on top of mounting, and it makes harder to change/add new mounting logic for #279. This PR tries to address that by splitting the functionality in node/mounter into smaller packages.

Changes:


By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Comment on lines -94 to -99
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
tokenFile := os.Getenv(webIdentityTokenEnv)
if tokenFile != "" {
klog.Infof("Found AWS_WEB_IDENTITY_TOKEN_FILE, syncing token")
go tokenFileTender(ctx, tokenFile, "/csi/token")
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This logic has been moved to credentials_sts_web_identity.go. Since we enabled requiresRepublish as part of Pod-level identity support, Kubernetes will call NodePublishVolume periodically to update existing service account tokens before they expire, and the credential provider will be called as part of this method. Another reason for this change is that, this assumes a single location for service account tokens for Driver-level identity, but with containerization this won't be the case. See the note regarding credential provider in the original PR description for more context.

Comment on lines -83 to -85
// If the given file exists, `os.WriteFile` just truncates it without changing it's permissions,
// so we need to ensure it always has the correct permissions.
return os.Chmod(path, awsProfileFilePerm)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Comment on lines -171 to -201
// Moves a parameter optionName from the options list to MP's environment variable list. We need this as options are
// passed to the driver in a single field, but MP sometimes only supports config from environment variables.
// Returns an updated options and environment.
func moveOptionToEnvironmentVariables(optionName string, envName string, options []string, env []string) ([]string, []string) {
optionIdx := -1
for i, o := range options {
if strings.HasPrefix(o, optionName) {
optionIdx = i
break
}
}
if optionIdx != -1 {
// We can do replace here as we've just verified it has the right prefix
env = append(env, strings.Replace(options[optionIdx], optionName, envName, 1))
options = append(options[:optionIdx], options[optionIdx+1:]...)
}
return options, env
}

// method to add the user agent prefix to the Mountpoint headers
// https://github.com/awslabs/mountpoint-s3/pull/548
func addUserAgentToOptions(options []string, userAgent string) []string {
// first remove it from the options in case it's in there
for i := len(options) - 1; i >= 0; i-- {
if strings.Contains(options[i], userAgentPrefix) {
options = append(options[:i], options[i+1:]...)
}
}
// add the hard coded S3 CSI driver user agent string
return append(options, userAgentPrefix+"="+userAgent)
}
Copy link
Contributor Author

@unexge unexge Dec 27, 2024

Choose a reason for hiding this comment

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

The logic for moving --aws-max-attempts to env and adding user-agent to arguments has been moved to node.go as it's unrelated to mount mechanism and needs to be there for both SystemdMounter and PodMounter.

Comment on lines 69 to 80
volumeContext := req.GetVolumeContext()
if volumeContext[mounter.VolumeCtxAuthenticationSource] == mounter.AuthenticationSourcePod {
podID := volumeContext[mounter.VolumeCtxPodUID]
volumeID := req.GetVolumeId()
if podID != "" && volumeID != "" {
err := ns.credentialProvider.CleanupToken(volumeID, podID)
if err != nil {
klog.V(4).Infof("NodeStageVolume: Failed to cleanup token for pod/volume %s/%s: %v", podID, volumeID, err)
}
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This NodeStageVolume wasn't enabled at all – we need to add STAGE_UNSTAGE_VOLUME node capability to enable – and also this cleaning of tokens has been moved to Unmount method of mounter.

Comment on lines 225 to 238
targetPath, err := ParseTargetPath(target)
if err == nil {
if targetPath.VolumeID != volumeID {
klog.V(4).Infof("NodeUnpublishVolume: Volume ID from parsed target path differs from Volume ID passed: %s (parsed) != %s (passed)", targetPath.VolumeID, volumeID)
} else {
err := ns.credentialProvider.CleanupToken(targetPath.VolumeID, targetPath.PodID)
if err != nil {
klog.V(4).Infof("NodeUnpublishVolume: Failed to cleanup token for pod/volume %s/%s: %v", targetPath.PodID, volumeID, err)
}
}
} else {
klog.V(4).Infof("NodeUnpublishVolume: Failed to parse target path %s: %v", target, err)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This clean up logic has been moved to Unmount method of mounter.

@muddyfish
Copy link
Contributor

Is it possible to split this refactor our into many smaller PRs? It's quite hard to understand the context here with so many changes

@unexge
Copy link
Contributor Author

unexge commented Jan 6, 2025

Since each commit builds on top of each other, it would be hard to create lots of PRs and that's why I split them into commits and added description for each. Would it be possible to review it commit-by-commit?

unexge added a commit that referenced this pull request Jan 14, 2025
Splitted out of
#328.

---

By submitting this pull request, I confirm that you can use, modify,
copy, and redistribute this contribution, under the terms of your
choice.

Signed-off-by: Burak Varlı <[email protected]>
unexge added a commit that referenced this pull request Jan 14, 2025
…335)

Splitted out of
#328.

---

By submitting this pull request, I confirm that you can use, modify,
copy, and redistribute this contribution, under the terms of your
choice.

Signed-off-by: Burak Varlı <[email protected]>
unexge added a commit that referenced this pull request Jan 14, 2025
Splitted out of
#328.

This might not make sense since it's moved out of its original context,
but the original motivation was that these constants was spread out to
different packages and that was causing circular dependencies.

For example, if you need to use `VolumeCtxBucketName` in
`pkg/driver/node/mounter` package, you'd need to import
`pkg/driver/node` package which would cause a circular dependency as
`pkg/driver/node` imports `pkg/driver/node/mounter`. In situations like
that, it's best to extract common things into a leaf package that
doesn't import anything, which is what this PR does with
`pkg/driver/node/volumecontext` package.

It also makes finding these volume context keys easier by placing them
into a singular place.

---

By submitting this pull request, I confirm that you can use, modify,
copy, and redistribute this contribution, under the terms of your
choice.

---------

Signed-off-by: Burak Varlı <[email protected]>
}

// disallow options that don't make sense in CSI
switch arg {
Copy link
Contributor

Choose a reason for hiding this comment

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

Ideally this would be a function, but fine to keep as this hasn't changed


// SortedList returns ordered list of normalized arguments.
func (a *Args) SortedList() []string {
return sets.List(a.args)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we link to the docs here (https://pkg.go.dev/k8s.io/apimachinery/pkg/util/sets#List) to show this satisfies the 'is sorted' criteria?


// An Args represents arguments to be passed to Mountpoint during mount.
type Args struct {
args sets.Set[string]
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel a set is the wrong data structure here. Why aren't we using a dictionary/hashmap? Then we don't have to iterate over the entire data structure to see presence, and we don't have to do as much messing around with formatting

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure if Mountpoint currently uses such an argument but - dict/map is not a correct data structure to represent command line arguments IMO as you could have multiple values for same option, and also order of arguments might matter.

Like this find usage:

$ find . -name  "*file*" -type f -o \
         -name "*folder*" -type d

But that's being said the current implementation does not preserve original ordering and not sure if we need this behavior.

I didn't want to change original parsing function as that might cause some breaking changes if we do unintentional changes, but I think I added enough test cases now, and we should have enough confidence to make more refactor.

I think ideal data structure for this is: List[Tuple[Key, Optional[Value]]]

Copy link
Contributor

Choose a reason for hiding this comment

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

Either an ordered map or what you proposed sounds ideal to me as I don't think MP currently supports multiple arguments with the same name.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Address this in a separate PR: #349

)

// An Environment represents a list of environment variables.
type Environment = []string
Copy link
Contributor

Choose a reason for hiding this comment

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

This should probably be a dict or list of tuples internally

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes I think this should be map[string]string. Making it a map would cause lots of diffs in the tests though, they were already []string and due to way type aliases work in Go, we can just use []string in place of Environment. But if we make it a map, then we would need to update all call sites to use a proper type, which then would make this PR even bigger. I think it's best to do that as a follow-up PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds good to me

@@ -0,0 +1,34 @@
package regionprovider
Copy link
Contributor

Choose a reason for hiding this comment

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

Has any code here changed? The removed code isn't in this commit

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is no code change (other than function name) in this package (regionprovider/{imds, provider, provider_test}.go). It just moved out of now removed pkg/driver/node/mounter/credential_provider.go

// 4. Calling IMDS to detect region
//
// It returns [ErrUnknownRegion] if all of them fails.
func (p *Provider) SecurityTokenService(volumeContext map[string]string, args mountpoint.Args) (string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The name here doesn't describe the functionality. From what I understand, it either returns the region to use for STS or returns an error. Perhaps getSTSRegion?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So the usage of this function is like: regionProvider.SecurityTokenService(volumeCtx, args). That's why I didn't add region to name as we'd duplicate the information, and not sure if get adds any value.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think get adds value as otherwise it's not obvious what we're actually doing with STS. I'd be happy with regionProvider.getSTS(...)

github-merge-queue bot pushed a commit that referenced this pull request Jan 22, 2025
)

Previously, accessing and manipulating of Mountpoint arguments was
spread over to the codebase. This new mountpoint package contains all
the logic related to Mountpoint arguments.

Splitted out of
#328.

---

By submitting this pull request, I confirm that you can use, modify,
copy, and redistribute this contribution, under the terms of your
choice.

---------

Signed-off-by: Burak Varlı <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants