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 @timothysc
pull/6/head
Kubernetes Submit Queue 2017-10-26 11:13:40 -07:00 committed by GitHub
commit 633ca56494
4 changed files with 96 additions and 16 deletions

View File

@ -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)

View File

@ -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(

View File

@ -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

View File

@ -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
}
}
}