Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion cmd/generate-config/config/config-openapi-spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,12 @@
"type": "object",
"required": [
"customNoUpgrade",
"featureSet"
"featureSet",
"specialHandlingSupportExceptionRequired"
],
"properties": {
"customNoUpgrade": {
"description": "CustomNoUpgrade is used to enable/disable feature gates. When the enabled or disable lists are not empty, x- and y-stream upgrades will be blocked.\nUse this field exclusively for custom feature gates, unless you are certain that the feature gate is a SpecialHandlingSupportExceptionRequired feature.",
"type": "object",
"required": [
"disabled",
Expand All @@ -90,6 +92,28 @@
},
"featureSet": {
"type": "string"
},
"specialHandlingSupportExceptionRequired": {
"description": "SpecialHandlingSupportExceptionRequired allows for feature gates to be exempted from blocking x- and y-stream upgrades.",
"type": "object",
"required": [
"disabled",
"enabled"
],
"properties": {
"disabled": {
"type": "array",
"items": {
"type": "string"
}
},
"enabled": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
Expand Down
6 changes: 6 additions & 0 deletions docs/user/howto_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ apiServer:
disabled: []
enabled: []
featureSet: ""
specialHandlingSupportExceptionRequired:
disabled: []
enabled: []
namedCertificates:
- certPath: ""
keyPath: ""
Expand Down Expand Up @@ -168,6 +171,9 @@ apiServer:
disabled: []
enabled: []
featureSet: ""
specialHandlingSupportExceptionRequired:
disabled: []
enabled: []
namedCertificates:
- certPath: ""
keyPath: ""
Expand Down
74 changes: 42 additions & 32 deletions etcd/vendor/github.com/openshift/microshift/pkg/config/apiserver.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions packaging/microshift/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,16 @@ apiServer:
# profile is the OpenShift profile specifying a specific logging policy
profile: Default
featureGates:
# CustomNoUpgrade is used to enable/disable feature gates. When the enabled or disable lists are not empty, x- and y-stream upgrades will be blocked.
# Use this field exclusively for custom feature gates, unless you are certain that the feature gate is a SpecialHandlingSupportExceptionRequired feature.
customNoUpgrade:
disabled: []
enabled: []
featureSet: ""
# SpecialHandlingSupportExceptionRequired allows for feature gates to be exempted from blocking x- and y-stream upgrades.
specialHandlingSupportExceptionRequired:
disabled: []
enabled: []
# List of custom certificates used to secure requests to specific host names
namedCertificates:
- certPath: ""
Expand Down
94 changes: 60 additions & 34 deletions pkg/admin/prerun/featuregate_lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/openshift/microshift/pkg/config"
"github.com/openshift/microshift/pkg/util"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/klog/v2"
"sigs.k8s.io/yaml"
)
Expand All @@ -22,24 +23,26 @@ var (
// featureGateLockFile represents the structure of the lock file
// that tracks custom feature gate configuration and prevents changes/upgrades
type featureGateLockFile struct {
FeatureSet string `json:"featureSet"`
CustomNoUpgrade config.CustomNoUpgrade `json:"customNoUpgrade"`
Version versionMetadata `json:"version"`
FeatureSet string `json:"featureSet"`
CustomNoUpgrade config.EnableDisableFeatures `json:"customNoUpgrade"`
Version versionMetadata `json:"version"`
}

// FeatureGateLockManagement manages the feature gate lock file
// that prevents upgrades and config changes when custom feature gates are configured
func FeatureGateLockManagement(cfg *config.Config) error {
klog.InfoS("START feature gate lock management")
if err := featureGateLockManagement(cfg); err != nil {

fgCfg := &cfg.ApiServer.FeatureGates
if err := featureGateLockManagement(fgCfg); err != nil {
klog.ErrorS(err, "FAIL feature gate lock management")
return err
}
klog.InfoS("END feature gate lock management")
return nil
}

func featureGateLockManagement(cfg *config.Config) error {
func featureGateLockManagement(fgCfg *config.FeatureGates) error {
// If a lock file exists, it must be validated regardless of current config
// This prevents users from removing feature gates from config in order to block upgrades and configuration changes
lockExists, err := util.PathExists(featureGateLockFilePath)
Expand All @@ -48,18 +51,18 @@ func featureGateLockManagement(cfg *config.Config) error {
}
// Lock file exists - validate configuration
if lockExists {
return runValidationsChecks(cfg)
return runValidationsChecks(fgCfg)
}
// No lock file exists yet and custom feature gates are configured, so this is the first time configuring custom feature gates
if cfg.ApiServer.FeatureGates.FeatureSet != "" {
return createFeatureGateLockFile(cfg)
if fgCfg.FeatureSet != "" {
return createFeatureGateLockFile(fgCfg)
}
// No lock file and no custom feature gates - normal operation
return nil
}

// createFeatureGateLockFile creates the lock file with current configuration
func createFeatureGateLockFile(cfg *config.Config) error {
func createFeatureGateLockFile(fgCfg *config.FeatureGates) error {
klog.InfoS("Creating feature gate lock file - this cluster can no longer be upgraded",
"path", featureGateLockFilePath)

Expand All @@ -70,8 +73,8 @@ func createFeatureGateLockFile(cfg *config.Config) error {
}

lockFile := featureGateLockFile{
FeatureSet: cfg.ApiServer.FeatureGates.FeatureSet,
CustomNoUpgrade: cfg.ApiServer.FeatureGates.CustomNoUpgrade,
FeatureSet: fgCfg.FeatureSet,
CustomNoUpgrade: fgCfg.CustomNoUpgrade,
Version: currentVersion,
}

Expand All @@ -88,7 +91,7 @@ func createFeatureGateLockFile(cfg *config.Config) error {

// runValidationsChecks validates the feature gate lock file and the current configuration
// It returns an error if the configuration is invalid or if an x or y stream version upgrade has occurred.
func runValidationsChecks(cfg *config.Config) error {
func runValidationsChecks(fgCfg *config.FeatureGates) error {
klog.InfoS("Validating feature gate lock file", "path", featureGateLockFilePath)

lockFile, err := readFeatureGateLockFile(featureGateLockFilePath)
Expand All @@ -97,45 +100,68 @@ func runValidationsChecks(cfg *config.Config) error {
}

// Check if feature gate configuration has changed
if err := configValidationChecksPass(lockFile, cfg.ApiServer.FeatureGates); err != nil {
if err := configValidationChecksPass(lockFile, fgCfg); err != nil {
return fmt.Errorf("detected invalid changes in feature gate configuration: %w\n\n"+
"To restore MicroShift to a supported state, you must:\n"+
"1. Run: sudo microshift-cleanup-data --all\n"+
"2. Remove custom feature gates from /etc/microshift/config.yaml\n"+
"3. Restart MicroShift: sudo systemctl restart microshift", err)
}

// Check if version has changed (upgrade attempted)
currentExecutableVersion, err := getExecutableVersion()
if err != nil {
return fmt.Errorf("failed to get current executable version: %w", err)
}

if lockFile.Version.Major != currentExecutableVersion.Major || lockFile.Version.Minor != currentExecutableVersion.Minor {
return fmt.Errorf("version upgrade detected with custom feature gates: locked version %s, current version %s\n\n"+
"Upgrades are not supported when custom feature gates are configured.\n"+
"Custom feature gates (%s) were configured in version %s.\n"+
"To restore MicroShift to a supported state, you must:\n"+
"1. Roll back to version %s, OR\n"+
"2. Run: sudo microshift-cleanup-data --all\n"+
"3. Remove custom feature gates from /etc/microshift/config.yaml\n"+
"4. Restart MicroShift: sudo systemctl restart microshift",
lockFile.Version.String(), currentExecutableVersion.String(),
lockFile.FeatureSet, lockFile.Version.String(), lockFile.Version.String())
if err := upgradeChecksPass(lockFile, fgCfg); err != nil {
return err
}

klog.InfoS("Feature gate lock file validation successful")
return nil
}

func configValidationChecksPass(prev featureGateLockFile, current config.FeatureGates) error {
if prev.FeatureSet != "" && current.FeatureSet == "" {
func configValidationChecksPass(prev featureGateLockFile, fgCfg *config.FeatureGates) error {
if prev.FeatureSet != "" && fgCfg.FeatureSet == "" {
// Disallow changing from feature set to no feature set
return fmt.Errorf("cannot unset feature set. Previous config had feature set %q, current config has no feature set configured", prev.FeatureSet)
}
if prev.FeatureSet == config.FeatureSetCustomNoUpgrade && current.FeatureSet != config.FeatureSetCustomNoUpgrade {
if prev.FeatureSet == config.FeatureSetCustomNoUpgrade && fgCfg.FeatureSet != config.FeatureSetCustomNoUpgrade {
// Disallow changing from custom feature gates to any other feature set
return fmt.Errorf("cannot change CustomNoUpgrade feature set. Previous feature set was %q, current feature set is %q", prev.FeatureSet, current.FeatureSet)
return fmt.Errorf("cannot change CustomNoUpgrade feature set. Previous feature set was %q, current feature set is %q", prev.FeatureSet, fgCfg.FeatureSet)
}
return nil
}

func upgradeChecksPass(lockFile featureGateLockFile, fgCfg *config.FeatureGates) error {
currentExecutableVersion, err := getExecutableVersion()
lockedVersion := lockFile.Version
if err != nil {
return fmt.Errorf("failed to get current executable version: %w", err)
}

if lockedVersion.Major != currentExecutableVersion.Major || lockedVersion.Minor != currentExecutableVersion.Minor {
extractFeatureGatesWithoutExemptions := func(lhs []string, rhs []string) []string {
lhsSet := sets.New(lhs...)
rhsSet := sets.New(rhs...)
return lhsSet.Difference(rhsSet).UnsortedList()
}

// Extract feature gates that lack a special handling support exception.
customNoUpgradeEnabled := extractFeatureGatesWithoutExemptions(fgCfg.CustomNoUpgrade.Enabled, fgCfg.SpecialHandlingSupportExceptionRequired.Enabled)
customNoUpgradeDisabled := extractFeatureGatesWithoutExemptions(fgCfg.CustomNoUpgrade.Disabled, fgCfg.SpecialHandlingSupportExceptionRequired.Disabled)

// If there are any gates that lack a special handling support exception, return an error.
if len(customNoUpgradeEnabled) > 0 || len(customNoUpgradeDisabled) > 0 {
return fmt.Errorf("version upgrade detected with custom feature gates: locked version %s, current version %s\n\n"+
"Upgrades are not supported when custom feature gates are configured.\n"+
"Custom feature gates were configured in version %s.\n"+
"Gates Enabled: %s\n"+
"Gates Disabled: %s\n"+
"To restore MicroShift to a supported state, you must:\n"+
"1. Roll back to version %s, OR\n"+
"2. Run: sudo microshift-cleanup-data --all\n"+
"3. Remove custom feature gates from /etc/microshift/config.yaml\n"+
"4. Restart MicroShift: sudo systemctl restart microshift",
lockedVersion.String(), currentExecutableVersion.String(),
lockedVersion.String(), customNoUpgradeEnabled,
customNoUpgradeDisabled, lockedVersion.String())
}
}
return nil
}
Expand Down
Loading