From e4475bc03f2202c6810298749941aa7d79dfe254 Mon Sep 17 00:00:00 2001 From: AngelaBriel Date: Mon, 21 Oct 2024 11:52:07 +0200 Subject: [PATCH] Add new command 'saptune configure ' to change values in the saptune config file /etc/sysconfig/saptune. (jsc#TEAM-8703) Introduce the new command 'saptune configure reset' to reset the saptune configuration. It will revert the tuning (revert all) and reset the saptune configuration file back to the installation default. (jsc#SAPSOL-331) --- actions/actions.go | 9 ++- actions/configureacts.go | 165 +++++++++++++++++++++++++++++++++++++++ app/app.go | 18 +++-- main.go | 7 +- ospackage/man/saptune.8 | 36 ++++++++- sap/note/ini.go | 2 +- sap/note/note.go | 5 -- sap/note/parameter.go | 8 +- system/sysctl.go | 20 +++++ system/system.go | 51 +++++++++++- 10 files changed, 296 insertions(+), 25 deletions(-) create mode 100644 actions/configureacts.go diff --git a/actions/actions.go b/actions/actions.go index 5a41a5f3..08d8aa42 100644 --- a/actions/actions.go +++ b/actions/actions.go @@ -57,7 +57,7 @@ var RPMDate = "undef" var solutionSelector = system.GetSolutionSelector() // saptune configuration file -var saptuneSysconfig = "/etc/sysconfig/saptune" +var saptuneSysconfig = system.SaptuneConfigFile() // set colors for the table and list output // var setYellowText = "\033[38;5;220m" @@ -82,7 +82,7 @@ func SelectAction(writer io.Writer, stApp *app.App, saptuneVers string) { // check for test packages if RPMDate != "undef" { - system.NoticeLog("ATTENTION: You are running a test version (%s from %s) of saptune which is not supported for production use", RPMVersion, RPMDate) + system.NoticeLog("ATTENTION: You are running a test version (%s for SLES4SAP %d from %s) of saptune which is not supported for production use", RPMVersion, system.IfdefVers(), RPMDate) } switch system.CliArg(1) { @@ -94,6 +94,8 @@ func SelectAction(writer io.Writer, stApp *app.App, saptuneVers string) { NoteAction(writer, system.CliArg(2), system.CliArg(3), system.CliArg(4), stApp) case "solution": SolutionAction(writer, system.CliArg(2), system.CliArg(3), system.CliArg(4), stApp) + case "configure": + ConfigureAction(writer, system.CliArg(2), system.CliArgs(3), stApp) case "revert": RevertAction(writer, system.CliArg(2), stApp) case "staging": @@ -315,6 +317,9 @@ Staging control: saptune [--format FORMAT] [--force-color] staging ( status | enable | disable | is-enabled | list ) saptune [--format FORMAT] [--force-color] staging ( analysis | diff ) [ ( NOTEID | SOLUTIONNAME )... | all ] saptune [--format FORMAT] [--force-color] staging release [--force|--dry-run] [ ( NOTEID | SOLUTIONNAME )... | all ] +Config (re-)settings: + saptune [--format FORMAT] [--force-color] configure ( COLOR_SCHEME | SKIP_SYSCTL_FILES | IGNORE_RELOAD | DEBUG ) Value + saptune [--format FORMAT] [--force-color] configure ( reset | show ) Verify all applied Notes: saptune [--format FORMAT] [--force-color] verify applied Revert all parameters tuned by the SAP notes or solutions: diff --git a/actions/configureacts.go b/actions/configureacts.go new file mode 100644 index 00000000..c656b3ec --- /dev/null +++ b/actions/configureacts.go @@ -0,0 +1,165 @@ +package actions + +import ( + "fmt" + "github.com/SUSE/saptune/app" + "github.com/SUSE/saptune/system" + "github.com/SUSE/saptune/txtparser" + "io" + "os" + "strings" +) + +var mandatoryConfigKeys = []string{app.TuneForSolutionsKey, app.TuneForNotesKey, app.NoteApplyOrderKey, "SAPTUNE_VERSION", "STAGING", "COLOR_SCHEME", "SKIP_SYSCTL_FILES", "IGNORE_RELOAD"} +var changeableConfigKeys = []string{"COLOR_SCHEME", "SKIP_SYSCTL_FILES", "IGNORE_RELOAD", "DEBUG"} + +// MandKeyList returns a list of mandatory configuration parameter, which need +// to be available in the saptune configuration file +func MandKeyList() []string { + return mandatoryConfigKeys +} + +// ChangeKeyList returns a list of configuration parameter, which can be set +// or changed by the customer +func ChangeKeyList() []string { + return changeableConfigKeys +} + +// ConfigureAction changes entries in the main saptune configuration file +// Replaces the direct editing of the config file +// +// saptune configure STAGING -- not needed because of 'saptune staging enable' +func ConfigureAction(writer io.Writer, configEntry string, configVals []string, tuneApp *app.App) { + if len(configVals) == 0 && !(configEntry == "reset" || configEntry == "show") { + // missing value to be configured + PrintHelpAndExit(writer, 1) + } + switch configEntry { + case "COLOR_SCHEME": + ConfigureActionSetColorScheme(configVals[0]) + case "SKIP_SYSCTL_FILES": + ConfigureActionSetSkipSysctlFiles(configVals) + case "IGNORE_RELOAD": + ConfigureActionSetIgnoreReload(configVals[0]) + case "DEBUG": + ConfigureActionSetDebug(configVals[0]) + case "reset": + ConfigureActionReset(os.Stdin, writer, tuneApp) + case "show": + ConfigureActionShow(writer) + default: + PrintHelpAndExit(writer, 1) + } +} + +// ConfigureActionSetColorScheme sets the color scheme +func ConfigureActionSetColorScheme(configVal string) { + switch configVal { + case "", "full-green-zebra", "cmpl-green-zebra", "full-blue-zebra", "cmpl-blue-zebra", "full-red-noncmpl", "red-noncmpl", "full-yellow-noncmpl", "yellow-noncmpl": + writeConfigEntry("COLOR_SCHEME", configVal) + default: + system.ErrorExit("wrong value '%s' for config variable 'COLOR_SCHEME'. Please check.", configVal) + } +} + +// ConfigureActionSetIgnoreReload sets the variable IGNORE_RELOAD +func ConfigureActionSetIgnoreReload(configVal string) { + switch configVal { + case "yes", "no": + writeConfigEntry("IGNORE_RELOAD", configVal) + default: + system.ErrorExit("wrong value '%s' for config variable 'IGNORE_RELOAD'. Only 'yes' or 'no' supported. Please check.", configVal) + } +} + +// ConfigureActionSetDebug sets the variable DEBUG +func ConfigureActionSetDebug(configVal string) { + switch configVal { + case "on", "off": + writeConfigEntry("DEBUG", configVal) + default: + system.ErrorExit("wrong value '%s' for config variable 'DEBUG'. Only 'on' or 'off' supported. Please check.", configVal) + } +} + +// ConfigureActionSetSkipSysctlFiles sets the exclude list for the sysctl +// config warnings +func ConfigureActionSetSkipSysctlFiles(configVals []string) { + if configVals[0] == "" { + writeConfigEntry("SKIP_SYSCTL_FILES", configVals[0]) + system.ErrorExit("", 0) + } + confVals := configVals + if len(configVals) == 1 && strings.Contains(configVals[0], ",") { + confVals = strings.Split(configVals[0], ",") + } + confVal := "" + //for _, file := range configVals { + for _, file := range confVals { + file = strings.TrimSuffix(file, ",") + if system.IsValidSysctlLocations(file) { + if confVal == "" { + confVal = file + } else { + confVal = confVal + ", " + file + } + } else { + system.ErrorLog("wrong value '%s' for config variable 'SKIP_SYSCTL_FILES'. sysctl command will not search in this locaction. Skipping.", file) + } + } + if confVal != "" { + writeConfigEntry("SKIP_SYSCTL_FILES", confVal) + } else { + system.ErrorExit("wrong value(s) '%+v' for config variable 'SKIP_SYSCTL_FILES' provided. sysctl command will not search in this locaction(s). Exiting without changing saptune configuration. Please check.", configVals) + } +} + +// writeConfigEntry writes the changed config entry setting to the saptune +// config file +func writeConfigEntry(entry, val string) { + sconf, err := txtparser.ParseSysconfigFile(saptuneSysconfig, true) + if err != nil { + system.ErrorExit("Unable to read file '%s': '%v'\n", saptuneSysconfig, err, 128) + } + if val == "" { + system.NoticeLog("Reset '%s' to empty value", entry) + } else { + system.NoticeLog("Set '%s' to '%s'", entry, val) + } + sconf.Set(entry, val) + if err := os.WriteFile(saptuneSysconfig, []byte(sconf.ToText()), 0644); err != nil { + system.ErrorExit("'%s' could not be set to '%s'. - '%v'\n", entry, val, err) + } +} + +// ConfigureActionShow shows the content of the saptune configuration file +func ConfigureActionShow(writer io.Writer) { + cont, err := os.ReadFile(saptuneSysconfig) + if err != nil { + system.ErrorExit("Unable to read file '%s': '%v'\n", saptuneSysconfig, err, 128) + } + fmt.Fprintf(writer, "\nContent of saptune configuration file %s:\n\n%s\n", saptuneSysconfig, string(cont)) +} + +// ConfigureActionReset(writer) +func ConfigureActionReset(reader io.Reader, writer io.Writer, tuneApp *app.App) { + fmt.Fprintf(writer, "\nATTENTION: resetting the main saptune configuration.\nThis will reset the tuning of the system and remove/reset all saptune related configuration and runtime files.\n") + txtConfirm := fmt.Sprintf("Do you really want to reset the main saptune configuration?") + if readYesNo(txtConfirm, reader, writer) { + system.InfoLog("ATTENTION: Resetting main saptune configuration") + // revert all + if err := tuneApp.RevertAll(true); err != nil { + system.ErrorLog("Failed to revert notes: %v", err) + } + // remove saved_state files, if some left over + os.RemoveAll(system.SaptuneSectionDir) + os.RemoveAll(system.SaptuneParameterStateDir) + os.RemoveAll(system.SaptuneSavedStateDir) + + // set configuration file back to default/delivery + saptuneTemplate := system.SaptuneConfigTemplate() + if err := system.CopyFile(saptuneTemplate, saptuneSysconfig); err != nil { + system.ErrorLog("Failed to set saptune configuration file '%s' back to delivery state by copying the template file '%s'", saptuneSysconfig, saptuneTemplate) + } + } +} diff --git a/app/app.go b/app/app.go index 8728a75f..8a46f153 100644 --- a/app/app.go +++ b/app/app.go @@ -15,12 +15,11 @@ import ( "strings" ) -// define saptunes main configuration file and variables +// define saptunes main configuration variables const ( - SysconfigSaptuneFile = "/etc/sysconfig/saptune" - TuneForSolutionsKey = "TUNE_FOR_SOLUTIONS" - TuneForNotesKey = "TUNE_FOR_NOTES" - NoteApplyOrderKey = "NOTE_APPLY_ORDER" + TuneForSolutionsKey = "TUNE_FOR_SOLUTIONS" + TuneForNotesKey = "TUNE_FOR_NOTES" + NoteApplyOrderKey = "NOTE_APPLY_ORDER" ) // App defines the application configuration and serialised state information. @@ -34,6 +33,9 @@ type App struct { State *State // examine and manage serialised notes. } +// define saptunes main configuration file +var sysconfigSaptuneFile = system.SaptuneConfigFile() + // InitialiseApp load application configuration. Panic on error. func InitialiseApp(sysconfigPrefix, stateDirPrefix string, allNotes map[string]note.Note, allSolutions map[string]solution.Solution) (app *App) { app = &App{ @@ -42,7 +44,7 @@ func InitialiseApp(sysconfigPrefix, stateDirPrefix string, allNotes map[string]n AllNotes: allNotes, AllSolutions: allSolutions, } - sysconf, err := txtparser.ParseSysconfigFile(path.Join(app.SysconfigPrefix, SysconfigSaptuneFile), true) + sysconf, err := txtparser.ParseSysconfigFile(path.Join(app.SysconfigPrefix, sysconfigSaptuneFile), true) if err == nil { app.TuneForSolutions = sysconf.GetStringArray(TuneForSolutionsKey, []string{}) app.TuneForNotes = sysconf.GetStringArray(TuneForNotesKey, []string{}) @@ -91,14 +93,14 @@ func (app *App) PositionInNoteApplyOrder(noteID string) int { // SaveConfig save configuration to file /etc/sysconfig/saptune. func (app *App) SaveConfig() error { - sysconf, err := txtparser.ParseSysconfigFile(path.Join(app.SysconfigPrefix, SysconfigSaptuneFile), true) + sysconf, err := txtparser.ParseSysconfigFile(path.Join(app.SysconfigPrefix, sysconfigSaptuneFile), true) if err != nil { return err } sysconf.SetStrArray(TuneForSolutionsKey, app.TuneForSolutions) sysconf.SetStrArray(TuneForNotesKey, app.TuneForNotes) sysconf.SetStrArray(NoteApplyOrderKey, app.NoteApplyOrder) - return os.WriteFile(path.Join(app.SysconfigPrefix, SysconfigSaptuneFile), []byte(sysconf.ToText()), 0644) + return os.WriteFile(path.Join(app.SysconfigPrefix, sysconfigSaptuneFile), []byte(sysconf.ToText()), 0644) } // GetSortedSolutionEnabledNotes returns the number of all solution-enabled diff --git a/main.go b/main.go index fe227c36..36ff790c 100644 --- a/main.go +++ b/main.go @@ -36,7 +36,7 @@ func main() { } // get saptune version and log switches from saptune sysconfig file - SaptuneVersion = checkSaptuneConfigFile(os.Stderr, app.SysconfigSaptuneFile, logSwitch) + SaptuneVersion = checkSaptuneConfigFile(os.Stderr, system.SaptuneConfigFile(), logSwitch) arg1 := system.CliArg(1) if arg1 == "version" || system.IsFlagSet("version") { @@ -63,6 +63,7 @@ func main() { // now system.ErrorExit can write to log and os.Stderr. No longer extra // care is needed. system.InfoLog("saptune (%s) started with '%s'", actions.RPMVersion, strings.Join(os.Args, " ")) + system.InfoLog("build for '%d'", system.IfdefVers()) if arg1 == "lock" { if arg2 := system.CliArg(2); arg2 == "remove" { @@ -217,10 +218,10 @@ func checkWorkingArea() { // returns the saptune version and changes some log switches func checkSaptuneConfigFile(writer io.Writer, saptuneConf string, lswitch map[string]string) string { missingKey := []string{} - keyList := []string{app.TuneForSolutionsKey, app.TuneForNotesKey, app.NoteApplyOrderKey, "SAPTUNE_VERSION", "STAGING", "COLOR_SCHEME", "SKIP_SYSCTL_FILES", "IGNORE_RELOAD"} + keyList := actions.MandKeyList() sconf, err := txtparser.ParseSysconfigFile(saptuneConf, false) if err != nil { - fmt.Fprintf(writer, "Error: Unable to read file '%s': %v\n", saptuneConf, err) + fmt.Fprintf(writer, "Error: Checking saptune configuration file - Unable to read file '%s': %v\n", saptuneConf, err) system.ErrorExit("", 128) } // check, if all needed variables are available in the saptune diff --git a/ospackage/man/saptune.8 b/ospackage/man/saptune.8 index 9a6ef618..e18ac1f4 100644 --- a/ospackage/man/saptune.8 +++ b/ospackage/man/saptune.8 @@ -14,7 +14,7 @@ .\" * GNU General Public License for more details. .\" */ .\" -.TH saptune "8" "July 2024" "" "System optimization For SAP" +.TH saptune "8" "October 2024" "" "System optimization For SAP" .SH NAME saptune \- Comprehensive system optimization management for SAP solutions (\fBVersion 3\fP) @@ -63,6 +63,12 @@ rename SOLUTIONNAME NEWSOLUTIONNAME \fBsaptune\fP [--format FORMAT] [--force-color] \fBstaging\fP release [--force|--dry-run] [ ( NOTEID | SOLUTIONNAME )... | all ] +\fBsaptune\fP [--format FORMAT] [--force-color] \fBconfigure\fP +( COLOR_SCHEME | SKIP_SYSCTL_FILES | IGNORE_RELOAD | DEBUG ) Value + +\fBsaptune\fP [--format FORMAT] [--force-color] \fBconfigure\fP +( reset | show ) + \fBsaptune\fP [--format FORMAT] [--force-color] \fBverify\fP applied @@ -690,6 +696,29 @@ First the command will show an analysis of the objects going to be released to m Because the release is irreversible, the user has to confirm the action. +.SH CONFIGURE ACTIONS +Replaces the direct editing of the saptune configuration file /etc/sysconfig/saptune, which will be replaced by an intern configuration file and not be present in future versions. +.br +Not all of the former variables will be available as configure option, only those who should be changeable by the user. +.br +.SS +.TP +.B COLOR_SCHEME SCHEME +Default color scheme. See saptune verify --colorscheme SCHEME for available schemes. +.TP +.B SKIP_SYSCTL_FILES FILE[,FILE...] +Comma-separated list of sysctl config files or directories which should be excluded when checking if parameters handled by saptune are handled by sysctl as well. +Input is checked, if the given files are in a location which is searched by sysctl command. If not, the files will be skipped. +.TP +.B IGNORE_RELOAD yes||no +Controls behavior of systemctl reload saptune.service and the systemctl try-restart saptune.service during package installation. +.TP +.B reset +Reverts the tuning and reset the content of the saptune configuration file to the installation default. Asks for confirmation. +.TP +.B show +Shows the content of the saptune configuration file + .SH VERIFY ACTIONS .TP .B verify applied @@ -886,6 +915,11 @@ the saptune SAP Note or solution definitions, which are present in the Package A \fI/etc/sysconfig/saptune\fP .RS 4 the central saptune configuration file containing the information about the currently enabled notes and solutions, the order in which these notes are applied and the version of saptune currently used. +.br +ATTENTION: +the manual editing of this file will be \fBdeprecated\fP as the main configuration file will be (re)moved in the near future. +.br +Please use \fBsaptune configure\fP command instead of editing the file directly. .RE .PP \fI/etc/saptune/extra\fP diff --git a/sap/note/ini.go b/sap/note/ini.go index 645c1ac2..27c56b2e 100644 --- a/sap/note/ini.go +++ b/sap/note/ini.go @@ -534,7 +534,7 @@ func (vend INISettings) useOverrides(key, scheds, val string) (bool, string, str // till now for /sys parameter settings // like KSM, THP and /sys/block/*/queue func (vend INISettings) chkDoubles(key, info string) string { - paramFiles := system.GetFiles(SaptuneParameterStateDir) + paramFiles := system.GetFiles(system.SaptuneParameterStateDir) syskey := key inf := "" diff --git a/sap/note/note.go b/sap/note/note.go index d335c53b..67a7e3a7 100644 --- a/sap/note/note.go +++ b/sap/note/note.go @@ -21,11 +21,6 @@ import ( "strings" ) -// SaptuneParameterStateDir defines the directory where to store the -// parameter state files -// separated from the note state file directory -const SaptuneParameterStateDir = "/run/saptune/parameter" - // Note defines the structure and actions for a SAP Note // An SAP note consisting of a series of tunable parameters that can be // applied and reverted. diff --git a/sap/note/parameter.go b/sap/note/parameter.go index 29aed50d..124b1679 100644 --- a/sap/note/parameter.go +++ b/sap/note/parameter.go @@ -23,7 +23,7 @@ type ParameterNotes struct { // GetPathToParameter returns path to the serialised parameter state file. func GetPathToParameter(param string) string { - return path.Join(SaptuneParameterStateDir, param) + return path.Join(system.SaptuneParameterStateDir, param) } // IDInParameterList checks, if given noteID is already part of the @@ -39,11 +39,11 @@ func IDInParameterList(noteID string, list []ParameterNoteEntry) bool { // ListParams lists all stored parameter states. Return parameter names func ListParams() (ret []string, err error) { - if err = os.MkdirAll(SaptuneParameterStateDir, 0755); err != nil { + if err = os.MkdirAll(system.SaptuneParameterStateDir, 0755); err != nil { return } // List SaptuneParameterStateDir and collect parameter names from file names - dirContent, err := os.ReadDir(SaptuneParameterStateDir) + dirContent, err := os.ReadDir(system.SaptuneParameterStateDir) if os.IsNotExist(err) { return []string{}, nil } else if err != nil { @@ -140,7 +140,7 @@ func StoreParameter(param string, obj ParameterNotes, overwriteExisting bool) er if err != nil { return err } - if err = os.MkdirAll(SaptuneParameterStateDir, 0755); err != nil { + if err = os.MkdirAll(system.SaptuneParameterStateDir, 0755); err != nil { return err } if _, err := os.Stat(GetPathToParameter(param)); os.IsNotExist(err) || overwriteExisting { diff --git a/system/sysctl.go b/system/sysctl.go index c6be7b8e..bdf8e636 100644 --- a/system/sysctl.go +++ b/system/sysctl.go @@ -270,3 +270,23 @@ func IsPagecacheAvailable() bool { _, err := os.ReadFile(path.Join("/proc/sys", strings.Replace(SysctlPagecacheLimitMB, ".", "/", -1))) return err == nil } + +// IsValidSysctlLocations returns true, if the given string is a valid +// location for sysctl files +func IsValidSysctlLocations(location string) bool { + validLocation := false + sysctlFiles := make(map[string]string) + getSysctlFilelist(sysctlDirs, sysctlFiles, false) + if _, ok := sysctlFiles[location]; ok { + validLocation = true + } else { + for _, dir := range sysctlDirs { + dir = strings.TrimSuffix(dir, "/") + if dir == location || dir == filepath.Dir(location) { + validLocation = true + break + } + } + } + return validLocation +} diff --git a/system/system.go b/system/system.go index da00e212..8dd97000 100644 --- a/system/system.go +++ b/system/system.go @@ -9,15 +9,32 @@ import ( "reflect" "regexp" "runtime" + "strconv" "strings" "syscall" "time" "unicode" ) -// SaptuneSectionDir defines saptunes saved state directory +// SaptuneSectionDir defines saptunes saved state directory for section info const SaptuneSectionDir = "/run/saptune/sections" +// SaptuneSavedStateDir defines saptunes saved state directory for previous +// system values +const SaptuneSavedStateDir = "/run/saptune/saved_state" + +// SaptuneParameterStateDir defines the directory where to store the +// parameter state files +// separated from the note state file directory +const SaptuneParameterStateDir = "/run/saptune/parameter" + +// RPMBldVers is the version of the RPM build process (suse_version) +// defaults to '15' +// needs to be a string as replacement with -X during build does not work +// with int variables (or const) +// cannot set with -X: not a var of type string (type:int) +var RPMBldVers = "15" + // map to hold the current available systemd services var services map[string]string @@ -40,6 +57,38 @@ var InfoOut = InfoLog // DmiID is the path to the dmidecode representation in the /sys filesystem var DmiID = "/sys/class/dmi/id" +// IfdefVers returns the integer representation of the RPMBldVers variable +func IfdefVers() int { + intValue, _ := strconv.Atoi(RPMBldVers) + return intValue +} + +// SaptuneConfigFile returns the name of the saptune configuration file +// /etc/sysconfig/saptune in SLE12 and SLE15 +// /var/lib/saptune/config/saptune in SLE16 +func SaptuneConfigFile() string { + if IfdefVers() > 15 { + return "/var/lib/saptune/config/saptune" + } else { + return "/etc/sysconfig/saptune" + } +} + +// SaptuneConfigTemplate returns the name of the template file for the +// saptune configuration file +// /usr/share/fillup-templates/sysconfig.saptune in SLE12 and SLE15 +// /usr/share/saptune/saptuneTemplate.conf in SLE16 +func SaptuneConfigTemplate() string { + if IfdefVers() > 15 { + return "/usr/share/saptune/saptuneTemplate.conf" + } else { + if _, err := os.Stat("/var/adm/fillup-templates/sysconfig.saptune"); err == nil { + return "/var/adm/fillup-templates/sysconfig.saptune" + } + return "/usr/share/fillup-templates/sysconfig.saptune" + } +} + // IsUserRoot return true only if the current user is root. func IsUserRoot() bool { return os.Getuid() == 0