Skip to content

feat(pkg/features): added support for feature gate dependencies#652

Merged
akhilerm merged 9 commits intoopenebs-archive:developfrom
jdkramhoft:develop
Nov 16, 2021
Merged

feat(pkg/features): added support for feature gate dependencies#652
akhilerm merged 9 commits intoopenebs-archive:developfrom
jdkramhoft:develop

Conversation

@jdkramhoft
Copy link
Copy Markdown
Contributor

What this PR does?:
Features can now be added that are dependent on other features.
Attempting to set a feature flag to true if the dependency is not met will result in an error.

Does this PR require any upgrade changes?:
No, I don't think so.

If the changes in this PR are manually verified, list down the scenarios covered::
The unit tests included cover the scenarios:

  • Setting a feature flag to true when a dependency is met, and when it is unmet.
  • Setting a feature flag to true when only one of two dependencies are met.
  • Having multiple feature flags with the same dependencies

Any additional information for your reviewer? :
Mention if this PR is part of any design or a continuation of previous PRs
This supports dependent feature gates for later when UsePartTableUUID will be introduced.

Checklist:

Signed-off-by: Jens Daniel Kramhøft <jdkramhoft@gmail.com>
@akhilerm akhilerm requested review from akhilerm and z0marlin October 8, 2021 13:56
Signed-off-by: Jens Daniel Kramhøft <jdkramhoft@gmail.com>
Copy link
Copy Markdown
Contributor

@z0marlin z0marlin left a comment

Choose a reason for hiding this comment

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

Thanks for the PR @jdkramhoft :) Left a few comments.

Comment thread pkg/features/features.go Outdated
missingDependency := !fg[dependency]
if missingDependency {
fg[f] = false
return fmt.Errorf("Feature %s has unmet dependency %s - enable that first", feature, dependency)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

returning an error here would cause ndm to exit with failure. Is that what we want? Or do we want to just log, disable the flag and continue?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have changed behavior to logging, disabling the flags and continuing.

Comment thread pkg/features/features.go Outdated
Comment on lines +126 to +137
// check if the feature has dependencies
dependencies, hasDependencies := featureDependencies[f]
// if the feature is being set to true, we need to ensure dependencies are met
if hasDependencies && isEnabled {
for _, dependency := range dependencies {
missingDependency := !fg[dependency]
if missingDependency {
fg[f] = false
return fmt.Errorf("Feature %s has unmet dependency %s - enable that first", feature, dependency)
}
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This entire block should be out of the for loop. This is because the features aren't ordered. It is possible that a dependency for feature A appears in the slice after the feature A. Also, you might have to think of some recursive logic to resolve the dependencies if we want to support a case like A -> B -> C

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Oh! Good catch; Right now if someone later disabled a necessary dependency I wouldn't catch it.
I will move it to after the loop and validate the state then, logging and disabling flags.

Seems like I technically should check for cycles and then do a topological sort. Feels a bit heavyweight for only a few support flags. Can I assume there are no cycles of dependencies A -> B -> C -> A ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

yes, @jdkramhoft . cycles wont be there as we itself add the feature gates in the code. What we want to achieve is the user should not be enabling some features while dependent features are disabled.

Signed-off-by: Jens Daniel Kramhøft <jdkramhoft@gmail.com>
Signed-off-by: Jens Daniel Kramhøft <jdkramhoft@gmail.com>
Signed-off-by: Jens Daniel Kramhøft <jdkramhoft@gmail.com>
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Oct 12, 2021

Codecov Report

Merging #652 (d2e2af8) into develop (9fe3968) will increase coverage by 0.79%.
The diff coverage is 100.00%.

Impacted file tree graph

@@             Coverage Diff             @@
##           develop     #652      +/-   ##
===========================================
+ Coverage    46.09%   46.89%   +0.79%     
===========================================
  Files           78       78              
  Lines         3820     3834      +14     
===========================================
+ Hits          1761     1798      +37     
+ Misses        1901     1882      -19     
+ Partials       158      154       -4     
Impacted Files Coverage Δ
pkg/features/features.go 100.00% <100.00%> (ø)
cmd/ndm_daemonset/probe/udevprobe.go 50.19% <0.00%> (+1.97%) ⬆️
cmd/ndm_daemonset/probe/addhandler.go 75.42% <0.00%> (+4.09%) ⬆️
cmd/ndm_daemonset/probe/eventhandler.go 37.33% <0.00%> (+7.99%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 9fe3968...d2e2af8. Read the comment docs.

Comment thread pkg/features/features.go Outdated
Signed-off-by: Jens Daniel Kramhøft <jdkramhoft@gmail.com>
Comment thread pkg/features/features.go Outdated
Comment on lines +143 to +157
func ValidateDependencies(feature Feature, flags featureFlag) bool {
disabled := !flags[feature]
if disabled {
return false
}
dependencies := featureDependencies[feature]
for _, dependency := range dependencies {
missingDependency := !ValidateDependencies(dependency, flags)
if missingDependency {
flags[feature] = false
klog.Infof("Feature %v was set to false due to missing dependency %v", feature, dependency)
}
}
return flags[feature]
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This looks good. one thing you could do is to use another map for storing the computed values of the validated dependencies. This way you can store the results and save computation in case the ValidateDependencies function is called again for the same feature flag.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have added a map for memoized values.

Signed-off-by: Jens Daniel Kramhøft <jdkramhoft@gmail.com>
@jdkramhoft
Copy link
Copy Markdown
Contributor Author

Should be working nicely now - I can't see what Better Code Hub wants.

Comment thread pkg/features/features.go

// Ensures features are disabled if their dependencies are unmet
// Returns true if a feature is enabled after validation
func ValidateDependencies(feature Feature, flags featureFlag, memoizedValues featureFlag) bool {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@z0marlin Can you take a look at this function.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good

Comment thread pkg/features/features.go Outdated
Comment thread pkg/features/features.go
missingDependency := !ValidateDependencies(dependency, flags, memoizedValues)
if missingDependency {
flags[feature] = false
klog.Infof("Feature %v was set to false due to missing dependency %v", feature, dependency)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we break here, other wise we may set it to false multiple times for every unmet dependency

Copy link
Copy Markdown
Contributor

@akhilerm akhilerm left a comment

Choose a reason for hiding this comment

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

given a few comments.

jdkramhoft and others added 2 commits October 25, 2021 09:49
Co-authored-by: Akhil Mohan <akhilerm@gmail.com>
Signed-off-by: Jens Daniel Kramhøft <jdkramhoft@gmail.com>
Signed-off-by: Jens Daniel Kramhøft <jdkramhoft@gmail.com>
Copy link
Copy Markdown
Contributor

@z0marlin z0marlin left a comment

Choose a reason for hiding this comment

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

LGTM. Thanks for working on this @jdkramhoft :)

Comment thread pkg/features/features.go

// Ensures features are disabled if their dependencies are unmet
// Returns true if a feature is enabled after validation
func ValidateDependencies(feature Feature, flags featureFlag, memoizedValues featureFlag) bool {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good

@z0marlin z0marlin requested a review from akhilerm November 2, 2021 06:50
Copy link
Copy Markdown
Contributor

@akhilerm akhilerm left a comment

Choose a reason for hiding this comment

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

LGTM

@akhilerm akhilerm merged commit 1fcc2b6 into openebs-archive:develop Nov 16, 2021
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.

Enhance feature gate package to add support for dependent feature gates

6 participants