SUSE/saptune

View on GitHub
main.go

Summary

Maintainability
A
2 hrs
Test Coverage
F
42%
package main

import (
    "fmt"
    "github.com/SUSE/saptune/actions"
    "github.com/SUSE/saptune/app"
    "github.com/SUSE/saptune/sap/note"
    "github.com/SUSE/saptune/sap/solution"
    "github.com/SUSE/saptune/system"
    "github.com/SUSE/saptune/txtparser"
    "io"
    "os"
    "os/exec"
    "strings"
)

// constant definitions
const (
    saptuneV1 = "/usr/sbin/saptune_v1"
    saptcheck = "/usr/sbin/saptune_check"
    logFile   = "/var/log/saptune/saptune.log"
)

var tuneApp *app.App                 // application configuration and tuning states
var tuningOptions note.TuningOptions // Collection of tuning options from SAP notes and 3rd party vendors.
// Switch to control log reaction
var logSwitch = map[string]string{"verbose": os.Getenv("SAPTUNE_VERBOSE"), "debug": os.Getenv("SAPTUNE_DEBUG"), "error": os.Getenv("SAPTUNE_ERROR")}

// SaptuneVersion is the saptune version from /etc/sysconfig/saptune
var SaptuneVersion = ""

func main() {
    system.InitOut(logSwitch)
    if !system.ChkCliSyntax() {
        actions.PrintHelpAndExit(os.Stdout, 1)
    }

    // get saptune version and log switches from saptune sysconfig file
    SaptuneVersion = checkSaptuneConfigFile(os.Stderr, system.SaptuneConfigFile(), logSwitch)

    arg1 := system.CliArg(1)
    if arg1 == "version" || system.IsFlagSet("version") {
        fmt.Printf("current active saptune version is '%s'\n", SaptuneVersion)
        system.Jcollect(SaptuneVersion)
        system.ErrorExit("", 0)
    }
    if arg1 == "help" || system.IsFlagSet("help") {
        system.JnotSupportedYet()
        actions.PrintHelpAndExit(os.Stdout, 0)
    }
    if arg1 == "" {
        actions.PrintHelpAndExit(os.Stdout, 1)
    }

    // All other actions require super user privilege
    if os.Geteuid() != 0 {
        fmt.Fprintf(os.Stderr, "Please run saptune with root privilege.\n")
        system.ErrorExit("", 1)
    }

    // activate logging
    system.LogInit(logFile, logSwitch)
    // 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" {
            system.JnotSupportedYet()
            system.ReleaseSaptuneLock()
            system.InfoLog("command line triggered remove of lock file '/run/.saptune.lock'\n")
            system.ErrorExit("", 0)
        } else {
            actions.PrintHelpAndExit(os.Stdout, 1)
        }
    }
    callSaptuneCheckScript(arg1)

    // only one instance of saptune should run
    // check and set saptune lock file
    system.SaptuneLock()
    defer system.ReleaseSaptuneLock()

    // cleanup runtime files
    system.CleanUpRun()
    // additional clear ignore flag for the sapconf/saptune service deadlock
    os.Remove("/run/.saptune.ignore")

    //check, running config exists
    checkWorkingArea()

    switch SaptuneVersion {
    case "1":
        cmd := exec.Command(saptuneV1, os.Args[1:]...)
        cmd.Stdin = os.Stdin
        cmd.Stdout = os.Stdout
        cmd.Stderr = os.Stderr
        err := cmd.Run()
        if err != nil {
            system.ErrorExit("command '%+s %+v' failed with error '%v'\n", saptuneV1, os.Args, err)
        } else {
            system.ErrorExit("", 0)
        }
    case "2", "3":
        break
    default:
        system.ErrorExit("Wrong saptune version in file '/etc/sysconfig/saptune': %s", SaptuneVersion, 128)
    }

    solutionSelector := system.GetSolutionSelector()
    archSolutions, exist := solution.AllSolutions[solutionSelector]
    system.AddGap(os.Stdout)
    if !exist {
        system.ErrorExit("The system architecture (%s) is not supported.", solutionSelector)
        return
    }
    // Initialise application configuration and tuning procedures
    tuningOptions = note.GetTuningOptions(actions.NoteTuningSheets, actions.ExtraTuningSheets)
    tuneApp = app.InitialiseApp("", "", tuningOptions, archSolutions)

    checkUpdateLeftOvers()
    if err := tuneApp.NoteSanityCheck(); err != nil {
        system.ErrorExit("Error during NoteSanityCheck - '%v'\n", err)
    }
    checkForTuned()
    actions.CheckOrphanedOverrides()
    actions.SelectAction(os.Stdout, tuneApp, SaptuneVersion)
    system.ErrorExit("", 0)
}

// checkUpdateLeftOvers checks for left over files from the migration of
// saptune version 1 to saptune version 2
func checkUpdateLeftOvers() {
    // check for the /etc/tuned/saptune/tuned.conf file created during
    // the package update from saptune v1 to saptune v2/3
    // give a Warning but go ahead tuning the system
    if system.CheckForPattern("/etc/tuned/saptune/tuned.conf", "#stv1tov2#") {
        system.WarningLog("found file '/etc/tuned/saptune/tuned.conf' left over from the migration of saptune version 1 to saptune version 3. Please check and remove this file as it may work against the settings of some SAP Notes. For more information refer to the man page saptune-migrate(7)")
    }

    if system.CliArg(1) == "configure" && system.CliArg(2) == "reset" {
        return
    }

    // check if old solution or notes are applied
    if tuneApp != nil && (len(tuneApp.NoteApplyOrder) == 0 && (len(tuneApp.TuneForNotes) != 0 || len(tuneApp.TuneForSolutions) != 0)) {
        system.ErrorExit("There are 'old' solutions or notes defined in file '/etc/sysconfig/saptune'. Seems there were some steps missed during the migration from saptune version 1 to version 3. Please check. Refer to saptune-migrate(7) for more information")
    }
}

// checkForTuned checks for enabled and/or running tuned and prints out
// a warning message
func checkForTuned() {
    active, _ := system.SystemctlIsRunning(actions.TunedService)
    enabled, _ := system.SystemctlIsEnabled(actions.TunedService)
    if enabled || active {
        system.WarningLog("ATTENTION: tuned service is active, so we may encounter conflicting tuning values")
    }
}

// callSaptuneCheckScript will simply call the saptune_check script
// it's done before the saptune lock is set, but after the check for
// running as root
func callSaptuneCheckScript(arg string) {
    if arg == "check" {
        var err error
        if system.GetFlagVal("format") == "json" {
            var cmdOut []byte
            cmdOut, err = exec.Command(saptcheck, "--json").CombinedOutput()
            system.Jcollect(cmdOut)
        } else {
            // call external scrip saptune_check
            cmd := exec.Command(saptcheck)
            cmd.Stdin = os.Stdin
            cmd.Stdout = os.Stdout
            cmd.Stderr = os.Stderr
            err = cmd.Run()
        }
        if err != nil {
            system.ErrorExit("command '%+s' failed with error '%v'\n", saptcheck, err)
        } else {
            system.ErrorExit("", 0)
        }
    }
}

// checkWorkingArea checks, if solution and note configs exist in the working
// area
// if not, copy the definition files from the package area into the working area
// Should be covered by package installation but better safe than sorry
func checkWorkingArea() {
    refresh := false
    files := map[string]string{"note": actions.NoteTuningSheets, "solution": actions.SolutionSheets}
    for obj, file := range files {
        if _, err := os.Stat(file); os.IsNotExist(err) {
            // missing working area /var/lib/saptune/working/{notes,sols}
            refresh = true
            fmt.Println()
            system.WarningLog("missing the %ss in the working area, so copy %s definitions from package area to working area", obj, obj)
            if err := os.MkdirAll(file, 0755); err != nil {
                system.ErrorExit("Problems creating directory '%s' - '%v'", file, err)
            }
            if obj == "solution" {
                obj = "sol"
            }
            // package area /usr/share/saptune/{notes,sols}
            packedObjs := fmt.Sprintf("%s%ss/", actions.PackageArea, obj)
            _, files := system.ListDir(packedObjs, "")
            for _, f := range files {
                src := fmt.Sprintf("%s%s", packedObjs, f)
                dest := fmt.Sprintf("%s%s", file, f)
                if err := system.CopyFile(src, dest); err != nil {
                    system.ErrorLog("Problems copying '%s' to '%s', continue with next file ...", src, dest)
                }
            }
        }
    }
    if refresh {
        // refresh
        solution.Refresh()
    }
}

// checkSaptuneConfigFile checks the config file /etc/sysconfig/saptune
// if it exists, if it contains all needed variables and for some variables
// checks, if the values is valid
// returns the saptune version and changes some log switches
func checkSaptuneConfigFile(writer io.Writer, saptuneConf string, lswitch map[string]string) string {
    if system.CliArg(1) == "configure" && system.CliArg(2) == "reset" {
        if lswitch["debug"] == "" {
            lswitch["debug"] = "off"
        }
        if lswitch["verbose"] == "" {
            lswitch["verbose"] = "on"
        }
        if lswitch["error"] == "" {
            lswitch["error"] = "on"
        }
        return "3"
    }

    missingKey := []string{}
    keyList := actions.MandKeyList()
    sconf, err := txtparser.ParseSysconfigFile(saptuneConf, false)
    if err != nil {
        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
    // config file
    for _, key := range keyList {
        if !sconf.IsKeyAvail(key) {
            missingKey = append(missingKey, key)
        }
    }
    if len(missingKey) != 0 {
        fmt.Fprintf(writer, "Error: File '%s' is broken. Missing variables '%s'\n", saptuneConf, strings.Join(missingKey, ", "))
        system.ErrorExit("", 128)
    }
    txtparser.GetSysctlExcludes(sconf.GetString("SKIP_SYSCTL_FILES", ""))
    stageVal := sconf.GetString("STAGING", "")
    if stageVal != "true" && stageVal != "false" {
        fmt.Fprintf(writer, "Error: Variable 'STAGING' from file '%s' contains a wrong value '%s'. Needs to be 'true' or 'false'\n", saptuneConf, stageVal)
        system.ErrorExit("", 128)
    }

    // set values read from the config file
    saptuneVers := sconf.GetString("SAPTUNE_VERSION", "")
    if saptuneVers != "1" && saptuneVers != "2" && saptuneVers != "3" {
        fmt.Fprintf(writer, "Error: Wrong saptune version in file '/etc/sysconfig/saptune': %s\n", saptuneVers)
        system.ErrorExit("", 128)
    }

    // Switch Debug on ("on") or off ("off" - default)
    // Switch verbose mode on ("on" - default) or off ("off")
    // Switch error mode on ("on" - default) or off ("off")
    // check, if DEBUG, ERROR or VERBOSE is set in /etc/sysconfig/saptune
    if lswitch["debug"] == "" {
        lswitch["debug"] = sconf.GetString("DEBUG", "off")
    }
    if lswitch["verbose"] == "" {
        lswitch["verbose"] = sconf.GetString("VERBOSE", "on")
    }
    if lswitch["error"] == "" {
        lswitch["error"] = sconf.GetString("ERROR", "on")
    }
    return saptuneVers
}