SUSE/saptune

View on GitHub
actions/actions.go

Summary

Maintainability
A
0 mins
Test Coverage
C
70%
package actions

import (
    "bufio"
    "fmt"
    "github.com/SUSE/saptune/app"
    "github.com/SUSE/saptune/system"
    "io"
    "os"
    "strings"
)

// define constants and variables for the whole package
const (
    SaptuneService     = "saptune.service"
    SapconfService     = "sapconf.service"
    TunedService       = "tuned.service"
    exitSaptuneStopped = 1
    exitNotTuned       = 3
    exitNotCompliant   = 4
)

// PackageArea is the package area with all notes and solutions shipped by
// the current installed saptune rpm
var PackageArea = "/usr/share/saptune/"

// WorkingArea is the working directory
var WorkingArea = "/var/lib/saptune/working/"

// StagingArea is the staging area
var StagingArea = "/var/lib/saptune/staging/"

// StagingSheets is the staging directory of the latest notes
var StagingSheets = "/var/lib/saptune/staging/latest/"

// NoteTuningSheets is the working directory of available sap notes
var NoteTuningSheets = "/var/lib/saptune/working/notes/"

// OverrideTuningSheets is the directory for the override files
var OverrideTuningSheets = "/etc/saptune/override/"

// ExtraTuningSheets is a directory located on file system for external parties to place their tuning option files.
var ExtraTuningSheets = "/etc/saptune/extra/"

// SolutionSheets is the working directory of available sap solutions
var SolutionSheets = "/var/lib/saptune/working/sols/"

// RPMVersion is the package version from package build process
var RPMVersion = "undef"

// RPMDate is the date of package build
// only used in individual build test packages, but NOT in our official
// built and released packages (not possible because of 'reproducible' builds)
var RPMDate = "undef"

// solutionSelector used in solutionacts and stagingacts
var solutionSelector = system.GetSolutionSelector()

// saptune configuration file
var saptuneSysconfig = system.SaptuneConfigFile()

// set colors for the table and list output
// var setYellowText = "\033[38;5;220m"
// var setCyanText = "\033[36m"
// var setUnderlinedText = "\033[4m"
var setGreenText = "\033[32m"
var setRedText = "\033[31m"
var setYellowText = "\033[33m"
var setBlueText = "\033[34m"
var setBoldText = "\033[1m"
var resetBoldText = "\033[22m"
var setStrikeText = "\033[9m"
var resetTextColor = "\033[0m"
var dfltColorScheme = "full-red-noncmpl"

// SelectAction selects the chosen action depending on the first command line
// argument
func SelectAction(writer io.Writer, stApp *app.App, saptuneVers string) {
    // switch off color and highlighting, if Stdout is not a terminal
    switchOffColor()
    system.JnotSupportedYet()

    // check for test packages
    if RPMDate != "undef" {
        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) {
    case "daemon":
        DaemonAction(writer, system.CliArg(2), saptuneVers, stApp)
    case "service":
        ServiceAction(writer, system.CliArg(2), saptuneVers, stApp)
    case "note":
        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":
        StagingAction(system.CliArg(2), system.CliArgs(3), stApp)
    case "status":
        ServiceAction(writer, "status", saptuneVers, stApp)
    case "verify":
        VerifyAction(writer, system.CliArg(2), stApp)
    default:
        PrintHelpAndExit(writer, 1)
    }
}

// VerifyAction verifies all applied Notes
func VerifyAction(writer io.Writer, actionName string, tuneApp *app.App) {
    if actionName != "applied" {
        PrintHelpAndExit(writer, 1)
    }
    VerifyAllParameters(writer, tuneApp)
}

// RevertAction Revert all notes and solutions
func RevertAction(writer io.Writer, actionName string, tuneApp *app.App) {
    if actionName != "all" {
        PrintHelpAndExit(writer, 1)
    }
    reportSuc := false
    if len(tuneApp.NoteApplyOrder) != 0 {
        reportSuc = true
        system.InfoLog("Reverting all notes and solutions, this may take some time...")
        fmt.Fprintf(writer, "Reverting all notes and solutions, this may take some time...\n")
    }
    if err := tuneApp.RevertAll(true); err != nil {
        system.ErrorExit("Failed to revert notes: %v", err)
    }
    if reportSuc {
        system.InfoLog("Parameters tuned by the notes and solutions have been successfully reverted.")
        fmt.Fprintf(writer, "Parameters tuned by the notes and solutions have been successfully reverted.\n")
    }
}

// rememberMessage prints a reminder message
func rememberMessage(writer io.Writer) {
    active, err := system.SystemctlIsRunning(SaptuneService)
    if err != nil {
        system.ErrorExit("%v", err)
    }
    if !active {
        fmt.Fprintf(writer, "\nRemember: if you wish to automatically activate the solution's tuning options after a reboot, "+
            "you must enable and start saptune.service by running:"+
            "\n    saptune service enablestart\n")
    }
}

// VerifyAllParameters Verify that all system parameters do not deviate from any of the enabled solutions/notes.
func VerifyAllParameters(writer io.Writer, tuneApp *app.App) {
    result := system.JPNotes{
        Verifications: []system.JPNotesLine{},
        Attentions:    []system.JPNotesRemind{},
        NotesOrder:    []string{},
        SysCompliance: nil,
    }
    if len(tuneApp.NoteApplyOrder) == 0 {
        fmt.Fprintf(writer, "No notes or solutions enabled, nothing to verify.\n")
    } else {
        unsatisfiedNotes, comparisons, err := tuneApp.VerifyAll()
        if err != nil {
            system.Jcollect(result)
            system.ErrorExit("Failed to inspect the current system: %v", err)
        }
        PrintNoteFields(writer, "NONE", comparisons, true, &result)
        tuneApp.PrintNoteApplyOrder(writer)
        result.NotesOrder = tuneApp.NoteApplyOrder
        sysComp := len(unsatisfiedNotes) == 0
        result.SysCompliance = &sysComp
        if len(unsatisfiedNotes) == 0 {
            fmt.Fprintf(writer, "%s%sThe running system is currently well-tuned according to all of the enabled notes.%s%s\n", setGreenText, setBoldText, resetBoldText, resetTextColor)
        } else {
            system.Jcollect(result)
            system.ErrorExit("The parameters listed above have deviated from SAP/SUSE recommendations.", "colorPrint", setRedText, setBoldText, resetBoldText, resetTextColor)
        }
    }
    system.Jcollect(result)
}

// chkFileName returns the corresponding filename of a given definition file
// (note or solution)
// additional it returns a boolean value which is pointing out that
// the definition is a custom definition (extraDef = true) or an internal one
func chkFileName(defName, workingDir, extraDir string) (string, bool, error) {
    extraDef := false
    defType := "Note"
    if workingDir == SolutionSheets {
        defType = "Solution"
    }
    fileName := fmt.Sprintf("%s%s", workingDir, defName)
    _, err := os.Stat(fileName)
    if os.IsNotExist(err) {
        // Note/solution is NOT an internal Note/solution,
        // but may be a custom Note/solution
        chkName := defName
        if defType == "Note" {
            chkName = defName + ".conf"
        }
        if defName != "" {
            fileName = fmt.Sprintf("%s%s", extraDir, chkName)
        }
        if _, err = os.Stat(fileName); err == nil {
            extraDef = true
        }
    }
    return fileName, extraDef, err
}

// getFileName returns the corresponding filename of a given definition file
// (note or solution)
// additional it returns a boolean value which is pointing out that
// the definition is a custom definition (extraDef = true) or an internal one
func getFileName(defName, workingDir, extraDir string) (string, bool) {
    defType := "Note"
    if workingDir == SolutionSheets {
        defType = "Solution"
    }
    fileName, extraDef, err := chkFileName(defName, workingDir, extraDir)
    if os.IsNotExist(err) {
        system.ErrorExit("%s %s not found in %s or %s.", defType, defName, workingDir, extraDir)
    } else if err != nil {
        system.ErrorExit("Failed to read file '%s' - %v", fileName, err)
    }
    return fileName, extraDef
}

// getovFile returns the corresponding override filename of a given
// definition (Note or solution)
// additional it returns a boolean value which is pointing out if the
// override file already exists (overrideNote = true) or not
func getovFile(defName, OverrideTuningSheets string) (string, bool) {
    overrideNote := true
    ovFileName := fmt.Sprintf("%s%s", OverrideTuningSheets, defName)
    if _, err := os.Stat(ovFileName); os.IsNotExist(err) {
        overrideNote = false
    } else if err != nil {
        system.ErrorExit("Failed to read file '%s' - %v", ovFileName, err)
    }
    return ovFileName, overrideNote
}

// readYesNo asks the user for yes/no answer.
// "y", "Y", "yes", "YES", and "Yes" following by "enter" count as confirmation
// "n", "N", "no", "NO", and "No" following by "enter" count as non-confirmation
func readYesNo(s string, in io.Reader, out io.Writer) bool {
    reader := bufio.NewReader(in)
    for {
        fmt.Fprintf(out, "%s [y/n]: ", s)
        response, err := reader.ReadString('\n')
        if err != nil {
            system.ErrorExit("Failed to read input: %v", err)
        }
        response = strings.ToLower(strings.TrimSpace(response))
        if response == "y" || response == "yes" {
            return true
        } else if response == "n" || response == "no" {
            return false
        }
    }
}

// renameDefFile will rename a definition file (Note or Solution) to an new name
func renameDefFile(fileName, newFileName string) {
    if err := os.Rename(fileName, newFileName); err != nil {
        system.ErrorExit("Failed to rename file '%s' to '%s' - %v", fileName, newFileName, err)
    } else {
        system.NoticeLog("File '%s' renamed successfully to '%s'", fileName, newFileName)
    }
}

// deleteDefFile will delete a definition file (Note or Solution)
func deleteDefFile(fileName string) {
    if err := os.Remove(fileName); err != nil {
        system.ErrorExit("Failed to remove file '%s' - %v", fileName, err)
    } else {
        system.NoticeLog("File '%s' removed successfully", fileName)
    }
}

// switchOffColor turns off color and highlighting, if Stdout is not a terminal
func switchOffColor() {
    // switch off color and highlighting, if Stdout is not a terminal
    // command line option --force-color will override the 'switch off'
    if !system.OutIsTerm(os.Stdout) && !system.IsFlagSet("force-color") {
        setGreenText = ""
        setRedText = ""
        setYellowText = ""
        setBlueText = ""
        setBoldText = ""
        resetBoldText = ""
        setStrikeText = ""
        resetTextColor = ""
    }
}

func cmdLineSyntax() string {
    return `saptune: Comprehensive system optimisation management for SAP solutions.
Daemon control:
  saptune [--format FORMAT] [--force-color] daemon ( start | stop | status [--non-compliance-check] ) ATTENTION: deprecated
  saptune [--format FORMAT] [--force-color] service ( start | stop | restart | takeover | enable | disable | enablestart | disablestop | status [--non-compliance-check] )
Tune system according to SAP and SUSE notes:
  saptune [--format FORMAT] [--force-color] note ( list | verify | revertall | enabled | applied )
  saptune [--format FORMAT] [--force-color] note ( apply | simulate | customise | create | edit | revert | show | delete ) NOTEID
  saptune [--format FORMAT] [--force-color] [--fun] note verify [--colorscheme SCHEME] [--show-non-compliant] [NOTEID|applied]
  saptune [--format FORMAT] [--force-color] note rename NOTEID NEWNOTEID
Tune system for all notes applicable to your SAP solution:
  saptune [--format FORMAT] [--force-color] solution ( list | verify | enabled | applied )
  saptune [--format FORMAT] [--force-color] solution ( apply | simulate | customise | create | edit | revert | show | delete ) SOLUTIONNAME
  saptune [--format FORMAT] [--force-color] solution change [--force] SOLUTIONNAME
  saptune [--format FORMAT] [--force-color] [--fun] solution verify [--colorscheme SCHEME] [--show-non-compliant] [SOLUTIONNAME]
  saptune [--format FORMAT] [--force-color] solution rename SOLUTIONNAME NEWSOLUTIONNAME
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:
  saptune [--format FORMAT] [--force-color] revert all
Remove the pending lock file from a former saptune call
  saptune [--format FORMAT] [--force-color] lock remove
Call external script '/usr/sbin/saptune_check'
  saptune [--format FORMAT] [--force-color] check
Print current saptune status:
  saptune [--format FORMAT] [--force-color] status [--non-compliance-check]
Print current saptune version:
  saptune [--format FORMAT] [--force-color] version
Print this message:
  saptune [--format FORMAT] [--force-color] help

Deprecation list:
  all 'saptune daemon' actions
  'saptune note simulate'
  'saptune solution simulate'
`
}

// PrintHelpAndExit prints the usage and exit
func PrintHelpAndExit(writer io.Writer, exitStatus int) {
    if system.GetFlagVal("format") == "json" {
        system.JInvalid(exitStatus)
    }
    fmt.Fprintf(writer, cmdLineSyntax())
    system.ErrorExit("", exitStatus)
}