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  }