mirror of https://github.com/k3s-io/k3s
Merge pull request #54539 from jamiehannaford/add-ha-feature-gate
Automatic merge from submit-queue (batch tested with PRs 54593, 54607, 54539, 54105). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. Add HA feature gate and minVersion validation **What this PR does / why we need it**: As we add more feature gates, there might be occasions where a feature is only available on newer releases of K8s. If a user makes a mistake, we should notify them as soon as possible in the init procedure and not them go down the path of hard-to-debug component issues. Specifically with HA, we ideally need the new `TaintNodesByCondition` (added in v1.8.0 but working in v1.9.0). **Which issue this PR fixes:** kubernetes/kubeadm#261 kubernetes/kubeadm#277 **Release note**: ```release-note Feature gates now check minimum versions ``` /cc @kubernetes/sig-cluster-lifecycle-pr-reviews @luxas @timothyscpull/6/head
commit
633ca56494
|
@ -231,6 +231,10 @@ func NewInit(cfgPath string, cfg *kubeadmapi.MasterConfiguration, skipPreFlight,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if err := features.ValidateVersion(features.InitFeatureGates, cfg.FeatureGates, cfg.KubernetesVersion); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fmt.Printf("[init] Using Kubernetes version: %s\n", cfg.KubernetesVersion)
|
||||
fmt.Printf("[init] Using Authorization modes: %v\n", cfg.AuthorizationModes)
|
||||
|
||||
|
|
|
@ -10,7 +10,10 @@ go_library(
|
|||
name = "go_default_library",
|
||||
srcs = ["features.go"],
|
||||
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/features",
|
||||
deps = ["//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library"],
|
||||
deps = [
|
||||
"//pkg/util/version:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
|
|
|
@ -23,21 +23,61 @@ import (
|
|||
"strings"
|
||||
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/kubernetes/pkg/util/version"
|
||||
)
|
||||
|
||||
const (
|
||||
// HighAvailability is alpha in v1.9
|
||||
HighAvailability = "HighAvailability"
|
||||
|
||||
// SelfHosting is beta in v1.8
|
||||
SelfHosting utilfeature.Feature = "SelfHosting"
|
||||
SelfHosting = "SelfHosting"
|
||||
|
||||
// StoreCertsInSecrets is alpha in v1.8
|
||||
StoreCertsInSecrets utilfeature.Feature = "StoreCertsInSecrets"
|
||||
StoreCertsInSecrets = "StoreCertsInSecrets"
|
||||
)
|
||||
|
||||
var v190 = version.MustParseSemantic("v1.9.0")
|
||||
|
||||
// InitFeatureGates are the default feature gates for the init command
|
||||
var InitFeatureGates = FeatureList{
|
||||
SelfHosting: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Beta}},
|
||||
StoreCertsInSecrets: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Alpha}},
|
||||
HighAvailability: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Alpha}, MinimumVersion: v190},
|
||||
}
|
||||
|
||||
// Feature represents a feature being gated
|
||||
type Feature struct {
|
||||
utilfeature.FeatureSpec
|
||||
MinimumVersion *version.Version
|
||||
}
|
||||
|
||||
// FeatureList represents a list of feature gates
|
||||
type FeatureList map[utilfeature.Feature]utilfeature.FeatureSpec
|
||||
type FeatureList map[string]Feature
|
||||
|
||||
// ValidateVersion ensures that a feature gate list is compatible with the chosen kubernetes version
|
||||
func ValidateVersion(allFeatures FeatureList, requestedFeatures map[string]bool, requestedVersion string) error {
|
||||
if requestedVersion == "" {
|
||||
return nil
|
||||
}
|
||||
parsedExpVersion, err := version.ParseSemantic(requestedVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error parsing version %s: %v", requestedVersion, err)
|
||||
}
|
||||
for k := range requestedFeatures {
|
||||
if minVersion := allFeatures[k].MinimumVersion; minVersion != nil {
|
||||
if !parsedExpVersion.AtLeast(minVersion) {
|
||||
return fmt.Errorf(
|
||||
"the requested kubernetes version (%s) is incompatible with the %s feature gate, which needs %s as a minimum",
|
||||
requestedVersion, k, minVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Enabled indicates whether a feature name has been enabled
|
||||
func Enabled(featureList map[string]bool, featureName utilfeature.Feature) bool {
|
||||
func Enabled(featureList map[string]bool, featureName string) bool {
|
||||
return featureList[string(featureName)]
|
||||
}
|
||||
|
||||
|
@ -61,12 +101,6 @@ func Keys(featureList FeatureList) []string {
|
|||
return list
|
||||
}
|
||||
|
||||
// InitFeatureGates are the default feature gates for the init command
|
||||
var InitFeatureGates = FeatureList{
|
||||
SelfHosting: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
StoreCertsInSecrets: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
}
|
||||
|
||||
// KnownFeatures returns a slice of strings describing the FeatureList features.
|
||||
func KnownFeatures(f *FeatureList) []string {
|
||||
var known []string
|
||||
|
|
|
@ -25,9 +25,9 @@ import (
|
|||
|
||||
func TestKnownFeatures(t *testing.T) {
|
||||
var someFeatures = FeatureList{
|
||||
"feature2": {Default: true, PreRelease: utilfeature.Alpha},
|
||||
"feature1": {Default: false, PreRelease: utilfeature.Beta},
|
||||
"feature3": {Default: false, PreRelease: utilfeature.GA},
|
||||
"feature2": {FeatureSpec: utilfeature.FeatureSpec{Default: true, PreRelease: utilfeature.Alpha}},
|
||||
"feature1": {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Beta}},
|
||||
"feature3": {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.GA}},
|
||||
}
|
||||
|
||||
r := KnownFeatures(&someFeatures)
|
||||
|
@ -55,8 +55,8 @@ func TestKnownFeatures(t *testing.T) {
|
|||
|
||||
func TestNewFeatureGate(t *testing.T) {
|
||||
var someFeatures = FeatureList{
|
||||
"feature1": {Default: false, PreRelease: utilfeature.Beta},
|
||||
"feature2": {Default: true, PreRelease: utilfeature.Alpha},
|
||||
"feature1": {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Beta}},
|
||||
"feature2": {FeatureSpec: utilfeature.FeatureSpec{Default: true, PreRelease: utilfeature.Alpha}},
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
|
@ -117,3 +117,42 @@ func TestNewFeatureGate(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateVersion(t *testing.T) {
|
||||
var someFeatures = FeatureList{
|
||||
"feature1": {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Beta}},
|
||||
"feature2": {FeatureSpec: utilfeature.FeatureSpec{Default: true, PreRelease: utilfeature.Alpha}, MinimumVersion: v190},
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
requestedVersion string
|
||||
requestedFeatures map[string]bool
|
||||
expectedError bool
|
||||
}{
|
||||
{ //no min version
|
||||
requestedFeatures: map[string]bool{"feature1": true},
|
||||
expectedError: false,
|
||||
},
|
||||
{ //min version but correct value given
|
||||
requestedFeatures: map[string]bool{"feature2": true},
|
||||
requestedVersion: "v1.9.0",
|
||||
expectedError: false,
|
||||
},
|
||||
{ //min version and incorrect value given
|
||||
requestedFeatures: map[string]bool{"feature2": true},
|
||||
requestedVersion: "v1.8.2",
|
||||
expectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
err := ValidateVersion(someFeatures, test.requestedFeatures, test.requestedVersion)
|
||||
if !test.expectedError && err != nil {
|
||||
t.Errorf("ValidateVersion failed when not expected: %v", err)
|
||||
continue
|
||||
} else if test.expectedError && err == nil {
|
||||
t.Error("ValidateVersion didn't failed when expected")
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue