feat(pkg/features): added support for feature gate dependencies#652
feat(pkg/features): added support for feature gate dependencies#652akhilerm merged 9 commits intoopenebs-archive:developfrom jdkramhoft:develop
Conversation
Signed-off-by: Jens Daniel Kramhøft <jdkramhoft@gmail.com>
Signed-off-by: Jens Daniel Kramhøft <jdkramhoft@gmail.com>
z0marlin
left a comment
There was a problem hiding this comment.
Thanks for the PR @jdkramhoft :) Left a few comments.
| missingDependency := !fg[dependency] | ||
| if missingDependency { | ||
| fg[f] = false | ||
| return fmt.Errorf("Feature %s has unmet dependency %s - enable that first", feature, dependency) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
I have changed behavior to logging, disabling the flags and continuing.
| // 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) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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 Report
@@ 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
Continue to review full report at Codecov.
|
Signed-off-by: Jens Daniel Kramhøft <jdkramhoft@gmail.com>
| 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] | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
I have added a map for memoized values.
Signed-off-by: Jens Daniel Kramhøft <jdkramhoft@gmail.com>
|
Should be working nicely now - I can't see what Better Code Hub wants. |
|
|
||
| // 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 { |
There was a problem hiding this comment.
@z0marlin Can you take a look at this function.
| 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) |
There was a problem hiding this comment.
Can we break here, other wise we may set it to false multiple times for every unmet dependency
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>
z0marlin
left a comment
There was a problem hiding this comment.
LGTM. Thanks for working on this @jdkramhoft :)
|
|
||
| // 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 { |
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:
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:
<type>(<scope>): <subject>