agones.dev/agones@v1.54.0/pkg/util/runtime/features.go (about)

     1  // Copyright 2020 Google LLC All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package runtime
    16  
    17  import (
    18  	"net/url"
    19  	"strconv"
    20  	"sync"
    21  
    22  	"github.com/pkg/errors"
    23  	"github.com/spf13/pflag"
    24  	"github.com/spf13/viper"
    25  )
    26  
    27  const (
    28  	// FeatureGateFlag is a name of a command line flag, which turns on specific tests for FeatureGates
    29  	FeatureGateFlag = "feature-gates"
    30  
    31  	////////////////
    32  	// Stable features
    33  
    34  	// FeatureDisableResyncOnSDKServer is a feature flag to enable/disable resync on SDK server.
    35  	FeatureDisableResyncOnSDKServer Feature = "DisableResyncOnSDKServer"
    36  
    37  	// FeatureAutopilotPassthroughPort is a feature flag that enables/disables Passthrough Port Policy.
    38  	FeatureAutopilotPassthroughPort Feature = "AutopilotPassthroughPort"
    39  
    40  	////////////////
    41  	// Beta features
    42  
    43  	// FeatureCountsAndLists is a feature flag that enables counts and lists feature
    44  	// (a generic implenetation of the player tracking feature).
    45  	FeatureCountsAndLists Feature = "CountsAndLists"
    46  
    47  	// FeatureGKEAutopilotExtendedDurationPods enables the use of Extended Duration pods
    48  	// when Agones is running on Autopilot. Available on 1.28+ only.
    49  	FeatureGKEAutopilotExtendedDurationPods = "GKEAutopilotExtendedDurationPods"
    50  
    51  	// FeaturePortPolicyNone is a feature flag to allow setting Port Policy to None.
    52  	FeaturePortPolicyNone Feature = "PortPolicyNone"
    53  
    54  	// FeaturePortRanges is a feature flag to enable/disable specific port ranges.
    55  	FeaturePortRanges Feature = "PortRanges"
    56  
    57  	// FeatureRollingUpdateFix is a feature flag to enable/disable fleet controller fixes.
    58  	FeatureRollingUpdateFix Feature = "RollingUpdateFix"
    59  
    60  	// FeatureScheduledAutoscaler is a feature flag to enable/disable scheduled fleet autoscaling.
    61  	FeatureScheduledAutoscaler Feature = "ScheduledAutoscaler"
    62  
    63  	////////////////
    64  	// Alpha features
    65  
    66  	// FeatureFleetAutoscaleRequestMetaData is a feature flag that enables/disables fleet metadata on webhook autoscaler request.
    67  	FeatureFleetAutoscaleRequestMetaData Feature = "FleetAutoscaleRequestMetaData"
    68  
    69  	// FeaturePlayerAllocationFilter is a feature flag that enables the ability for Allocations to filter based on
    70  	// player capacity.
    71  	FeaturePlayerAllocationFilter Feature = "PlayerAllocationFilter"
    72  
    73  	// FeaturePlayerTracking is a feature flag to enable/disable player tracking features.
    74  	FeaturePlayerTracking Feature = "PlayerTracking"
    75  
    76  	// FeatureSidecarContainers is a feature flag to enable/disable k8s sidecar containers for the sdkserver
    77  	FeatureSidecarContainers = "SidecarContainers"
    78  
    79  	// FeatureWasmAutoscaler is a feature flag to enable/disable the script based autoscaler.
    80  	FeatureWasmAutoscaler Feature = "WasmAutoscaler"
    81  
    82  	////////////////
    83  	// Dev features
    84  
    85  	// FeatureProcessorAllocator is a feature flag to enable/disable the processor allocator feature.
    86  	FeatureProcessorAllocator = "ProcessorAllocator"
    87  
    88  	////////////////
    89  	// Example feature
    90  
    91  	// FeatureExample is an example feature gate flag, used for testing and demonstrative purposes
    92  	FeatureExample Feature = "Example"
    93  )
    94  
    95  var (
    96  	// featureDefaults is a map of all Feature Gates that are
    97  	// operational in Agones, and what their default configuration is.
    98  	// dev & alpha features are disabled by default; beta features are enabled.
    99  	//
   100  	// To add a new dev feature (an in progress feature, not tested in CI and not publicly documented):
   101  	// * add a const above
   102  	// * add it to `featureDefaults`
   103  	// * add it to install/helm/agones/defaultfeaturegates.yaml
   104  	// * note: you can add a new feature as an alpha feature if you're ready to test it in CI
   105  	//
   106  	// To promote a feature from dev->alpha:
   107  	// * add it to `ALPHA_FEATURE_GATES` in build/Makefile
   108  	// * add the inverse to the e2e-runner config in cloudbuild.yaml
   109  	// * add it to site/content/en/docs/Guides/feature-stages.md
   110  	// * add it to test/upgrade/versionMap.yaml
   111  	// * Ensure that the features in each file are organized categorically and alphabetically.
   112  	//
   113  	// To promote a feature from alpha->beta:
   114  	// * move from `false` to `true` in `featureDefaults`.
   115  	// * move from `false` to `true` in install/helm/agones/defaultfeaturegates.yaml
   116  	// * remove from `ALPHA_FEATURE_GATES` in build/Makefile
   117  	// * add to `BETA_FEATURE_GATES` in build/Makefile
   118  	// * invert in the e2e-runner config in cloudbuild.yaml
   119  	// * change the value in site/content/en/docs/Guides/feature-stages.md.
   120  	// * add it to test/upgrade/versionMap.yaml
   121  	// * Ensure that the features in each file are organized categorically and alphabetically.
   122  	//
   123  	// Feature Promotion: alpha->beta for SDK Functions
   124  	// * Move methods from alpha->beta files:
   125  	// 		- From proto/sdk/alpha/alpha.proto to proto/sdk/beta/beta.proto
   126  	//		- For each language-specific SDK (e.g., Go, C#, Rust):
   127  	//			- Move implementation files (e.g., alpha.go to beta.go)
   128  	//			- Move test files (e.g., alpha_test.go to beta_test.go)
   129  	//			- Note: Delete references to 'alpha' in the moved alpha methods.
   130  	// * Change all code and documentation references of alpha->beta:
   131  	//		- Proto Files: proto/sdk/sdk.proto `[Stage:Alpha]->[Stage:Beta]`
   132  	//		- SDK Implementations: Update in language-specific SDKs (e.g., sdks/go/sdk.go, sdks/csharp/sdk/AgonesSDK.cs).
   133  	//		- Examples & Tests: Adjust in files like examples/simple-game-server/main.go and language-specific test files.
   134  	// * Modify automation scripts in the build/build-sdk-images directory to support beta file generation.
   135  	// * Run `make gen-all-sdk-grpc` to generate the required files. If there are changes to the `proto/allocation/allocation.proto` run `make gen-allocation-grpc`.
   136  	// * Afterwards, execute the `make run-sdk-conformance-test-go` command and address any issues that arise.
   137  	// * NOTE: DO NOT EDIT any autogenerated code. `make gen-all-sdk-grpc` will take care of it.
   138  	//
   139  	// To promote a feature from beta->GA:
   140  	// * Remove all places consuming the feature gate and fold logic to true.
   141  	// * Consider cleanup - often folding a gate to true allows refactoring.
   142  	// * Remove from the e2e-runner config in cloudbuild.yaml.
   143  	// * Update site/content/en/docs/Guides/feature-stages.md to indicate the feature is Stable.
   144  	// * Remove from `BETA_FEATURE_GATES` in build/Makefile.
   145  	// * Move from 'Beta features' in pkg/util/runtime/features.go and
   146  	//   install/helm/agones/defaultfeaturegates.yaml to 'Stable features'.
   147  	//
   148  	// In each of these, keep the feature sorted by descending maturity then alphabetical
   149  	featureDefaults = map[Feature]bool{
   150  		// Stable features
   151  		// Note that stable features cannot be set to "false", and are here so that upgrades from a
   152  		// previous version with the feature flag do not fail on parsing an unknown flag.
   153  		FeatureDisableResyncOnSDKServer: true,
   154  		FeatureAutopilotPassthroughPort: true,
   155  
   156  		// Beta features
   157  		FeatureCountsAndLists:                   true,
   158  		FeatureGKEAutopilotExtendedDurationPods: true,
   159  		FeaturePortPolicyNone:                   true,
   160  		FeaturePortRanges:                       true,
   161  		FeatureRollingUpdateFix:                 true,
   162  		FeatureScheduledAutoscaler:              true,
   163  
   164  		// Alpha features
   165  		FeatureFleetAutoscaleRequestMetaData: false,
   166  		FeaturePlayerAllocationFilter:        false,
   167  		FeaturePlayerTracking:                false,
   168  		FeatureSidecarContainers:             false,
   169  		FeatureWasmAutoscaler:                false,
   170  
   171  		// Dev features
   172  		FeatureProcessorAllocator: false,
   173  
   174  		// Example feature
   175  		FeatureExample: false,
   176  	}
   177  
   178  	// featureGates is the storage of what features are enabled
   179  	// or disabled.
   180  	featureGates map[Feature]bool
   181  
   182  	// featureMutex ensures that updates to featureGates don't happen at the same time as reads.
   183  	// this is mostly to protect tests which can change gates in parallel.
   184  	featureMutex = sync.RWMutex{}
   185  
   186  	// FeatureTestMutex is a mutex to be shared between tests to ensure that a test that involves changing featureGates
   187  	// cannot accidentally run at the same time as another test that also changing feature flags.
   188  	FeatureTestMutex sync.Mutex
   189  )
   190  
   191  // Feature is a type for defining feature gates.
   192  type Feature string
   193  
   194  // FeaturesBindFlags does the Viper arguments configuration. Call before running pflag.Parse()
   195  func FeaturesBindFlags() {
   196  	viper.SetDefault(FeatureGateFlag, "")
   197  	pflag.String(FeatureGateFlag, viper.GetString(FeatureGateFlag), "Flag to pass in the url query list of feature flags to enable or disable")
   198  }
   199  
   200  // FeaturesBindEnv binds the environment variables, based on the flags provided.
   201  // call after viper.SetEnvKeyReplacer(...) if it is being set.
   202  func FeaturesBindEnv() error {
   203  	return viper.BindEnv(FeatureGateFlag)
   204  }
   205  
   206  // ParseFeaturesFromEnv will parse the feature flags from the Viper args
   207  // configured by FeaturesBindFlags() and FeaturesBindEnv()
   208  func ParseFeaturesFromEnv() error {
   209  	return ParseFeatures(viper.GetString(FeatureGateFlag))
   210  }
   211  
   212  // ParseFeatures parses the url encoded query string of features and stores the value
   213  // for later retrieval
   214  func ParseFeatures(queryString string) error {
   215  	featureMutex.Lock()
   216  	defer featureMutex.Unlock()
   217  
   218  	features := map[Feature]bool{}
   219  	// copy the defaults into this map
   220  	for k, v := range featureDefaults {
   221  		features[k] = v
   222  	}
   223  
   224  	values, err := url.ParseQuery(queryString)
   225  	if err != nil {
   226  		return errors.Wrap(err, "error parsing query string for feature gates")
   227  	}
   228  
   229  	for k := range values {
   230  		f := Feature(k)
   231  
   232  		if _, ok := featureDefaults[f]; !ok {
   233  			return errors.Errorf("Feature Gate %q is not a valid Feature Gate", f)
   234  		}
   235  
   236  		b, err := strconv.ParseBool(values.Get(k))
   237  		if err != nil {
   238  			return errors.Wrapf(err, "error parsing bool value from flag %s ", k)
   239  		}
   240  		features[f] = b
   241  	}
   242  
   243  	featureGates = features
   244  	return nil
   245  }
   246  
   247  // EnableAllFeatures turns on all feature flags.
   248  // This is useful for libraries/processes/tests that want to
   249  // enable all Alpha/Beta features without having to track all
   250  // the current feature flags.
   251  func EnableAllFeatures() {
   252  	featureMutex.Lock()
   253  	defer featureMutex.Unlock()
   254  
   255  	features := map[Feature]bool{}
   256  	// copy the defaults into this map
   257  	for k := range featureDefaults {
   258  		features[k] = true
   259  	}
   260  
   261  	featureGates = features
   262  }
   263  
   264  // FeatureEnabled returns if a Feature is enabled or not
   265  func FeatureEnabled(feature Feature) bool {
   266  	featureMutex.RLock()
   267  	defer featureMutex.RUnlock()
   268  	return featureGates[feature]
   269  }
   270  
   271  // EncodeFeatures returns the feature set as a URL encoded query string
   272  func EncodeFeatures() string {
   273  	values := url.Values{}
   274  	featureMutex.RLock()
   275  	defer featureMutex.RUnlock()
   276  
   277  	for k, v := range featureGates {
   278  		values.Add(string(k), strconv.FormatBool(v))
   279  	}
   280  	return values.Encode()
   281  }