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 }