github.com/openebs/node-disk-manager@v1.9.1-0.20230225014141-4531f06ffa1e/pkg/features/features.go (about) 1 /* 2 Copyright 2020 The OpenEBS Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 /* 17 The contents of this package has its origins from the feature gate 18 implementation in kubernetes. 19 Refer : 20 https://github.com/kubernetes/component-base/tree/master/featuregate 21 https://github.com/kubernetes/kubernetes/tree/master/pkg/features 22 23 */ 24 25 package features 26 27 import ( 28 "fmt" 29 "strings" 30 31 "github.com/openebs/node-disk-manager/pkg/util" 32 33 "k8s.io/klog/v2" 34 ) 35 36 // Feature is a typed string for a given feature 37 type Feature string 38 39 const ( 40 // GPTBasedUUID feature flag is used to enable the 41 // blockdevice UUID algorithm mentioned in 42 // https://github.com/openebs/openebs/pull/2666 43 GPTBasedUUID Feature = "GPTBasedUUID" 44 45 // APIService feature flag starts the GRPC server which provides functionality to manage block devices 46 APIService Feature = "APIService" 47 48 UseOSDisk Feature = "UseOSDisk" 49 50 // ChangeDetection is used to enable detecting changes to 51 // blockdevice size, filesystem, and mount-points. 52 ChangeDetection Feature = "ChangeDetection" 53 54 // PartitionTableUUID feature flag is used to enable use a 55 // partition table uuid instead of create partition described in 56 // https://github.com/openebs/node-disk-manager/issues/621 . 57 // This feature must enabled with GPTBasedUUID. 58 PartitionTableUUID Feature = "PartitionTableUUID" 59 ) 60 61 // supportedFeatures is the list of supported features. This is used while parsing the 62 // feature flag given via command line 63 var supportedFeatures = []Feature{ 64 GPTBasedUUID, 65 APIService, 66 UseOSDisk, 67 ChangeDetection, 68 PartitionTableUUID, 69 } 70 71 // defaultFeatureGates is the default features that will be applied to the application 72 var defaultFeatureGates = map[Feature]bool{ 73 GPTBasedUUID: true, 74 APIService: false, 75 UseOSDisk: false, 76 ChangeDetection: false, 77 PartitionTableUUID: false, 78 } 79 80 var featureDependencies = map[Feature][]Feature{ 81 PartitionTableUUID: { 82 GPTBasedUUID, 83 }, 84 } 85 86 // featureFlag is a map representing the flag and its state 87 type featureFlag map[Feature]bool 88 89 // FeatureGates is the global feature gate that can be used to check if a feature flag is enabled 90 // or disabled 91 var FeatureGates = NewFeatureGate() 92 93 // NewFeatureGate gets a new map with the default feature gates for the application 94 func NewFeatureGate() featureFlag { 95 fg := make(featureFlag) 96 97 // set the default feature gates 98 for k, v := range defaultFeatureGates { 99 fg[k] = v 100 } 101 102 return fg 103 } 104 105 // IsEnabled returns true if the feature is enabled 106 func (fg featureFlag) IsEnabled(f Feature) bool { 107 return fg[f] 108 } 109 110 // SetFeatureFlag parses a slice of string and sets the feature flag. 111 func (fg featureFlag) SetFeatureFlag(features []string) error { 112 if len(features) == 0 { 113 klog.V(4).Info("No feature flags are set, default values will be used") 114 } 115 // iterate through each feature and set its state onto the featureFlag map 116 for _, feature := range features { 117 var f Feature 118 // by default if a feature gate is provided, it is enabled 119 isEnabled := true 120 // if the feature is specified in the format 121 // MyFeature=false, the string need to be parsed and 122 // corresponding state to be set on the feature 123 s := strings.Split(feature, "=") 124 f = Feature(s[0]) 125 // only if length after splitting =2, we need to check whether the 126 // feature is enabled or disabled 127 if len(s) == 2 { 128 isEnabled = util.CheckTruthy(s[1]) 129 } else if len(s) > 2 { 130 // if length > 2 , there is some error in the format specified 131 return fmt.Errorf("incorrect format. cannot parse feature %s", feature) 132 } 133 // check if the feature flag provided is available in the list of 134 // supported features 135 if !containsFeature(supportedFeatures, f) { 136 return fmt.Errorf("unknown feature flag %s", f) 137 } 138 fg[f] = isEnabled 139 } 140 141 // We make sure features are only turned on if their dependencies are met 142 // We memoize values to avoid computing the same dependency twice 143 memoizedValues := make(featureFlag) 144 for feature := range fg { 145 _ = ValidateDependencies(feature, fg, memoizedValues) 146 } 147 148 for k, v := range fg { 149 klog.Infof("Feature gate: %s, state: %s", k, util.StateStatus(v)) 150 } 151 152 return nil 153 } 154 155 // Ensures features are disabled if their dependencies are unmet 156 // Returns true if a feature is enabled after validation 157 func ValidateDependencies(feature Feature, flags featureFlag, memoizedValues featureFlag) bool { 158 if value, isMemoized := memoizedValues[feature]; isMemoized { 159 return value 160 } 161 if disabled := !flags[feature]; disabled { 162 return false 163 } 164 dependencies := featureDependencies[feature] 165 for _, dependency := range dependencies { 166 missingDependency := !ValidateDependencies(dependency, flags, memoizedValues) 167 if missingDependency { 168 flags[feature] = false 169 klog.Infof("Feature %v was set to false due to missing dependency %v", feature, dependency) 170 break 171 } 172 } 173 memoizedValues[feature] = flags[feature] 174 return flags[feature] 175 }