diff --git a/internal/factsengine/gatherers/gatherer.go b/internal/factsengine/gatherers/gatherer.go index 6fcb9e43..43ff501f 100644 --- a/internal/factsengine/gatherers/gatherer.go +++ b/internal/factsengine/gatherers/gatherer.go @@ -20,5 +20,6 @@ func StandardGatherers() map[string]FactGatherer { SBDDumpGathererName: NewDefaultSBDDumpGatherer(), SapHostCtrlGathererName: NewDefaultSapHostCtrlGatherer(), VerifyPasswordGathererName: NewDefaultPasswordGatherer(), + SaptuneGathererName: NewDefaultSaptuneGatherer(), } } diff --git a/internal/factsengine/gatherers/saptune.go b/internal/factsengine/gatherers/saptune.go new file mode 100644 index 00000000..5951f6f2 --- /dev/null +++ b/internal/factsengine/gatherers/saptune.go @@ -0,0 +1,135 @@ +package gatherers + +import ( + "encoding/json" + + log "github.com/sirupsen/logrus" + "github.com/trento-project/agent/internal/core/saptune" + "github.com/trento-project/agent/pkg/factsengine/entities" + "github.com/trento-project/agent/pkg/utils" +) + +const ( + SaptuneGathererName = "saptune" +) + +// nolint:gochecknoglobals +var whitelistedArguments = map[string][]string{ + "status": {"status", "--non-compliance-check"}, + "solution-verify": {"solution", "verify"}, + "solution-list": {"solution", "list"}, + "note-verify": {"note", "verify"}, + "note-list": {"note", "list"}, +} + +// nolint:gochecknoglobals +var ( + SaptuneNotInstalled = entities.FactGatheringError{ + Type: "saptune-not-installed", + Message: "saptune is not installed", + } + + SaptuneVersionUnsupported = entities.FactGatheringError{ + Type: "saptune-version-not-supported", + Message: "currently installed version of saptune is not supported", + } + + SaptuneArgumentUnsupported = entities.FactGatheringError{ + Type: "saptune-unsupported-argument", + Message: "the requested argument is not currently supported", + } + + SaptuneMissingArgument = entities.FactGatheringError{ + Type: "saptune-missing-argument", + Message: "missing required argument", + } + + SaptuneCommandError = entities.FactGatheringError{ + Type: "saptune-cmd-error", + Message: "error executing saptune command", + } +) + +type SaptuneGatherer struct { + executor utils.CommandExecutor +} + +func NewDefaultSaptuneGatherer() *SaptuneGatherer { + return NewSaptuneGatherer(utils.Executor{}) +} + +func NewSaptuneGatherer(executor utils.CommandExecutor) *SaptuneGatherer { + return &SaptuneGatherer{ + executor: executor, + } +} + +func (s *SaptuneGatherer) Gather(factsRequests []entities.FactRequest) ([]entities.Fact, error) { + cachedFacts := make(map[string]entities.Fact) + + facts := []entities.Fact{} + log.Infof("Starting %s facts gathering process", SaptuneGathererName) + saptuneRetriever, err := saptune.NewSaptune(s.executor) + if err != nil { + return nil, SaptuneNotInstalled.Wrap(err.Error()) + } + + if !saptuneRetriever.IsJSONSupported { + return nil, &SaptuneVersionUnsupported + } + + for _, factReq := range factsRequests { + var fact entities.Fact + + internalArguments, ok := whitelistedArguments[factReq.Argument] + cachedFact, cacheHit := cachedFacts[factReq.Argument] + + switch { + case len(factReq.Argument) == 0: + log.Error(SaptuneMissingArgument.Message) + fact = entities.NewFactGatheredWithError(factReq, &SaptuneMissingArgument) + + case !ok: + gatheringError := SaptuneArgumentUnsupported.Wrap(factReq.Argument) + log.Error(gatheringError) + fact = entities.NewFactGatheredWithError(factReq, gatheringError) + + case cacheHit: + fact = entities.Fact{ + Name: factReq.Name, + CheckID: factReq.CheckID, + Value: cachedFact.Value, + Error: cachedFact.Error, + } + + default: + factValue, err := runCommand(&saptuneRetriever, internalArguments) + if err != nil { + gatheringError := SaptuneCommandError.Wrap(err.Error()) + log.Error(gatheringError) + fact = entities.NewFactGatheredWithError(factReq, gatheringError) + } else { + fact = entities.NewFactGatheredWithRequest(factReq, factValue) + } + cachedFacts[factReq.Argument] = fact + } + facts = append(facts, fact) + } + + log.Infof("Requested %s facts gathered", SaptuneGathererName) + return facts, nil +} + +func runCommand(saptuneRetriever *saptune.Saptune, arguments []string) (entities.FactValue, error) { + saptuneOutput, commandError := saptuneRetriever.RunCommandJSON(arguments...) + if commandError != nil { + return nil, commandError + } + + var jsonData interface{} + if err := json.Unmarshal(saptuneOutput, &jsonData); err != nil { + return nil, err + } + + return entities.NewFactValue(jsonData, entities.WithSnakeCaseKeys()) +} diff --git a/internal/factsengine/gatherers/saptune_test.go b/internal/factsengine/gatherers/saptune_test.go new file mode 100644 index 00000000..914785b2 --- /dev/null +++ b/internal/factsengine/gatherers/saptune_test.go @@ -0,0 +1,740 @@ +//nolint:dupl +package gatherers_test + +import ( + "errors" + "io" + "os" + "testing" + + "github.com/stretchr/testify/suite" + "github.com/trento-project/agent/internal/factsengine/gatherers" + "github.com/trento-project/agent/pkg/factsengine/entities" + utilsMocks "github.com/trento-project/agent/pkg/utils/mocks" + "github.com/trento-project/agent/test/helpers" +) + +type SaptuneTestSuite struct { + suite.Suite + mockExecutor *utilsMocks.CommandExecutor +} + +func TestSaptuneTestSuite(t *testing.T) { + suite.Run(t, new(SaptuneTestSuite)) +} + +func (suite *SaptuneTestSuite) SetupTest() { + suite.mockExecutor = new(utilsMocks.CommandExecutor) +} + +func (suite *SaptuneTestSuite) TestSaptuneGathererStatus() { + mockOutputFile, _ := os.Open(helpers.GetFixturePath("gatherers/saptune-status.output")) + mockOutput, _ := io.ReadAll(mockOutputFile) + suite.mockExecutor.On("Exec", "saptune", "--format", "json", "status", "--non-compliance-check").Return(mockOutput, nil) + suite.mockExecutor.On("Exec", "rpm", "-q", "--qf", "%{VERSION}", "saptune").Return( + []byte("3.1.0"), nil, + ) + c := gatherers.NewSaptuneGatherer(suite.mockExecutor) + + factRequests := []entities.FactRequest{ + { + Name: "saptune_status", + Gatherer: "saptune", + Argument: "status", + }, + } + + factResults, err := c.Gather(factRequests) + + expectedResults := []entities.Fact{ + { + Name: "saptune_status", + Value: &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "$schema": &entities.FactValueString{Value: "file:///usr/share/saptune/schemas/1.0/saptune_status.schema.json"}, + "publish_time": &entities.FactValueString{Value: "2023-09-15 15:15:14.599"}, + "argv": &entities.FactValueString{Value: "saptune --format json status"}, + "pid": &entities.FactValueInt{Value: 6593}, + "command": &entities.FactValueString{Value: "status"}, + "exit_code": &entities.FactValueInt{Value: 1}, + "result": &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "services": &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "saptune": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueString{Value: "disabled"}, + &entities.FactValueString{Value: "inactive"}, + }, + }, + "sapconf": &entities.FactValueList{Value: []entities.FactValue{}}, + "tuned": &entities.FactValueList{Value: []entities.FactValue{}}, + }, + }, + "systemd_system_state": &entities.FactValueString{Value: "degraded"}, + "tuning_state": &entities.FactValueString{Value: "compliant"}, + "virtualization": &entities.FactValueString{Value: "kvm"}, + "configured_version": &entities.FactValueString{Value: "3"}, + "package_version": &entities.FactValueString{Value: "3.1.0"}, + "solution_enabled": &entities.FactValueList{Value: []entities.FactValue{}}, + "notes_enabled_by_solution": &entities.FactValueList{Value: []entities.FactValue{}}, + "solution_applied": &entities.FactValueList{Value: []entities.FactValue{}}, + "notes_applied_by_solution": &entities.FactValueList{Value: []entities.FactValue{}}, + "notes_enabled_additionally": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueString{Value: "1410736"}, + }, + }, + "notes_enabled": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueString{Value: "1410736"}, + }, + }, + "notes_applied": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueString{Value: "1410736"}, + }, + }, + "staging": &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "staging_enabled": &entities.FactValueBool{Value: false}, + "notes_staged": &entities.FactValueList{Value: []entities.FactValue{}}, + "solutions_staged": &entities.FactValueList{Value: []entities.FactValue{}}, + }, + }, + "remember_message": &entities.FactValueString{Value: "This is a reminder"}, + }, + }, + "messages": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "priority": &entities.FactValueString{Value: "NOTICE"}, + "message": &entities.FactValueString{Value: "actions.go:85: ATTENTION: You are running a test version"}, + }, + }, + }, + }, + }, + }, + }, + } + + suite.NoError(err) + suite.ElementsMatch(expectedResults, factResults) +} + +func (suite *SaptuneTestSuite) TestSaptuneGathererNoteVerify() { + mockOutputFile, _ := os.Open(helpers.GetFixturePath("gatherers/saptune-note-verify.output")) + mockOutput, _ := io.ReadAll(mockOutputFile) + suite.mockExecutor.On("Exec", "saptune", "--format", "json", "note", "verify").Return(mockOutput, nil) + suite.mockExecutor.On("Exec", "rpm", "-q", "--qf", "%{VERSION}", "saptune").Return( + []byte("3.1.0"), nil, + ) + c := gatherers.NewSaptuneGatherer(suite.mockExecutor) + + factRequests := []entities.FactRequest{ + { + Name: "saptune_note_verify", + Gatherer: "saptune", + Argument: "note-verify", + }, + } + + factResults, err := c.Gather(factRequests) + + expectedResults := []entities.Fact{ + { + Name: "saptune_note_verify", + Value: &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "$schema": &entities.FactValueString{ + Value: "file:///usr/share/saptune/schemas/1.0/saptune_note_verify.schema.json", + }, + "publish_time": &entities.FactValueString{ + Value: "2023-04-24 15:49:43.399", + }, + "argv": &entities.FactValueString{ + Value: "saptune --format json note verify", + }, + "pid": &entities.FactValueInt{ + Value: 25202, + }, + "command": &entities.FactValueString{ + Value: "note verify", + }, + "exit_code": &entities.FactValueInt{ + Value: 1, + }, + "result": &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "verifications": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "note_id": &entities.FactValueString{Value: "1771258"}, + "note_version": &entities.FactValueString{Value: "6"}, + "parameter": &entities.FactValueString{Value: "LIMIT_@dba_hard_nofile"}, + "compliant": &entities.FactValueBool{Value: true}, + "expected_value": &entities.FactValueString{Value: "@dba hard nofile 1048576"}, + "actual_value": &entities.FactValueString{Value: "@dba hard nofile 1048576"}, + }, + }, + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "note_id": &entities.FactValueString{Value: "1771258"}, + "note_version": &entities.FactValueString{Value: "6"}, + "parameter": &entities.FactValueString{Value: "LIMIT_@dba_soft_nofile"}, + "compliant": &entities.FactValueBool{Value: true}, + "expected_value": &entities.FactValueString{Value: "@dba soft nofile 1048576"}, + "actual_value": &entities.FactValueString{Value: "@dba soft nofile 1048576"}, + }, + }, + }, + }, + "attentions": &entities.FactValueList{ + Value: []entities.FactValue{}, + }, + "notes_enabled": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueString{Value: "1771258"}, + }, + }, + "system_compliance": &entities.FactValueBool{Value: false}, + }, + }, + "messages": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "priority": &entities.FactValueString{Value: "NOTICE"}, + "message": &entities.FactValueString{Value: "actions.go:85 You are running a test version"}, + }, + }, + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "priority": &entities.FactValueString{Value: "WARNING"}, + "message": &entities.FactValueString{Value: "sysctl.go:73: Parameter 'kernel.shmmax' redefined "}, + }, + }, + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "priority": &entities.FactValueString{Value: "WARNING"}, + "message": &entities.FactValueString{Value: "sysctl.go:73: Parameter 'kernel.shmall' redefined"}, + }, + }, + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "priority": &entities.FactValueString{Value: "NOTICE"}, + "message": &entities.FactValueString{Value: "ini.go:308: block device related section settings detected"}, + }, + }, + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "priority": &entities.FactValueString{Value: "ERROR"}, + "message": &entities.FactValueString{Value: "system.go:148: The parameters have deviated from recommendations"}, + }, + }, + }, + }, + }, + }, + }, + } + + suite.NoError(err) + suite.ElementsMatch(expectedResults, factResults) +} + +func (suite *SaptuneTestSuite) TestSaptuneGathererSolutionVerify() { + mockOutputFile, _ := os.Open(helpers.GetFixturePath("gatherers/saptune-solution-verify.output")) + mockOutput, _ := io.ReadAll(mockOutputFile) + suite.mockExecutor.On("Exec", "saptune", "--format", "json", "solution", "verify").Return(mockOutput, nil) + suite.mockExecutor.On("Exec", "rpm", "-q", "--qf", "%{VERSION}", "saptune").Return( + []byte("3.1.0"), nil, + ) + c := gatherers.NewSaptuneGatherer(suite.mockExecutor) + + factRequests := []entities.FactRequest{ + { + Name: "saptune_solution_verify", + Gatherer: "saptune", + Argument: "solution-verify", + }, + } + + factResults, err := c.Gather(factRequests) + + expectedResults := []entities.Fact{ + { + Name: "saptune_solution_verify", + Value: &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "$schema": &entities.FactValueString{Value: "file:///usr/share/saptune/schemas/1.0/saptune_solution_verify.schema.json"}, + "publish_time": &entities.FactValueString{Value: "2023-04-27 17:17:23.743"}, + "argv": &entities.FactValueString{Value: "saptune --format json solution verify"}, + "pid": &entities.FactValueInt{Value: 2538}, + "command": &entities.FactValueString{Value: "solution verify"}, + "exit_code": &entities.FactValueInt{Value: 1}, + "result": &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "verifications": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "note_id": &entities.FactValueString{Value: "1771258"}, + "note_version": &entities.FactValueString{Value: "6"}, + "parameter": &entities.FactValueString{Value: "LIMIT_@dba_hard_nofile"}, + "compliant": &entities.FactValueBool{Value: true}, + "expected_value": &entities.FactValueString{Value: "@dba hard nofile 1048576"}, + "actual_value": &entities.FactValueString{Value: "@dba hard nofile 1048576"}, + }, + }, + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "note_id": &entities.FactValueString{Value: "1771258"}, + "note_version": &entities.FactValueString{Value: "6"}, + "parameter": &entities.FactValueString{Value: "LIMIT_@dba_soft_nofile"}, + "compliant": &entities.FactValueBool{Value: true}, + "expected_value": &entities.FactValueString{Value: "@dba soft nofile 1048576"}, + "actual_value": &entities.FactValueString{Value: "@dba soft nofile 1048576"}, + }, + }, + }, + }, + "attentions": &entities.FactValueList{ + Value: []entities.FactValue{}, + }, + "notes_enabled": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueString{Value: "1771258"}, + }, + }, + "system_compliance": &entities.FactValueBool{Value: false}, + }, + }, + "messages": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "priority": &entities.FactValueString{Value: "NOTICE"}, + "message": &entities.FactValueString{Value: "actions.go:85 You are running a test version"}, + }, + }, + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "priority": &entities.FactValueString{Value: "WARNING"}, + "message": &entities.FactValueString{Value: "sysctl.go:73: Parameter 'kernel.shmmax' redefined "}, + }, + }, + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "priority": &entities.FactValueString{Value: "WARNING"}, + "message": &entities.FactValueString{Value: "sysctl.go:73: Parameter 'kernel.shmall' redefined"}, + }, + }, + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "priority": &entities.FactValueString{Value: "NOTICE"}, + "message": &entities.FactValueString{Value: "ini.go:308: block device related section settings detected"}, + }, + }, + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "priority": &entities.FactValueString{Value: "ERROR"}, + "message": &entities.FactValueString{Value: "system.go:148: The parameters have deviated from recommendations"}, + }, + }, + }, + }, + }, + }, + }, + } + + suite.NoError(err) + suite.ElementsMatch(expectedResults, factResults) +} + +func (suite *SaptuneTestSuite) TestSaptuneGathererSolutionList() { + mockOutputFile, _ := os.Open(helpers.GetFixturePath("gatherers/saptune-solution-list.output")) + mockOutput, _ := io.ReadAll(mockOutputFile) + suite.mockExecutor.On("Exec", "saptune", "--format", "json", "solution", "list").Return(mockOutput, nil) + suite.mockExecutor.On("Exec", "rpm", "-q", "--qf", "%{VERSION}", "saptune").Return( + []byte("3.1.0"), nil, + ) + c := gatherers.NewSaptuneGatherer(suite.mockExecutor) + + factRequests := []entities.FactRequest{ + { + Name: "saptune_solution_list", + Gatherer: "saptune", + Argument: "solution-list", + }, + } + + factResults, err := c.Gather(factRequests) + + expectedResults := []entities.Fact{ + { + Name: "saptune_solution_list", + Value: &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "$schema": &entities.FactValueString{Value: "file:///usr/share/saptune/schemas/1.0/saptune_solution_list.schema.json"}, + "publish_time": &entities.FactValueString{Value: "2023-04-27 17:21:27.926"}, + "argv": &entities.FactValueString{Value: "saptune --format json solution list"}, + "pid": &entities.FactValueInt{Value: 2582}, + "command": &entities.FactValueString{Value: "solution list"}, + "exit_code": &entities.FactValueInt{Value: 0}, + "result": &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "solutions_available": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "solution_id": &entities.FactValueString{Value: "BOBJ"}, + "note_list": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueString{Value: "1771258"}, + }, + }, + "solution_enabled": &entities.FactValueBool{Value: false}, + "solution_override_exists": &entities.FactValueBool{Value: false}, + "custom_solution": &entities.FactValueBool{Value: false}, + "solution_deprecated": &entities.FactValueBool{Value: false}, + }, + }, + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "solution_id": &entities.FactValueString{Value: "DEMO"}, + "note_list": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueString{Value: "demo"}, + }, + }, + "solution_enabled": &entities.FactValueBool{Value: false}, + "solution_override_exists": &entities.FactValueBool{Value: false}, + "custom_solution": &entities.FactValueBool{Value: true}, + "solution_deprecated": &entities.FactValueBool{Value: false}, + }, + }, + }, + }, + "remember_message": &entities.FactValueString{Value: ""}, + }, + }, + "messages": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "priority": &entities.FactValueString{Value: "NOTICE"}, + "message": &entities.FactValueString{Value: "actions.go:85 You are running a test version"}, + }, + }, + }, + }, + }, + }, + }, + } + + suite.NoError(err) + suite.ElementsMatch(expectedResults, factResults) +} + +func (suite *SaptuneTestSuite) TestSaptuneGathererNoteList() { + mockOutputFile, _ := os.Open(helpers.GetFixturePath("gatherers/saptune-note-list.output")) + mockOutput, _ := io.ReadAll(mockOutputFile) + suite.mockExecutor.On("Exec", "saptune", "--format", "json", "note", "list").Return(mockOutput, nil) + suite.mockExecutor.On("Exec", "rpm", "-q", "--qf", "%{VERSION}", "saptune").Return( + []byte("3.1.0"), nil, + ) + c := gatherers.NewSaptuneGatherer(suite.mockExecutor) + + factRequests := []entities.FactRequest{ + { + Name: "saptune_note_list", + Gatherer: "saptune", + Argument: "note-list", + }, + } + + factResults, err := c.Gather(factRequests) + + expectedResults := []entities.Fact{ + { + Name: "saptune_note_list", + Value: &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "$schema": &entities.FactValueString{Value: "file:///usr/share/saptune/schemas/1.0/saptune_note_list.schema.json"}, + "publish_time": &entities.FactValueString{Value: "2023-04-27 17:28:53.073"}, + "argv": &entities.FactValueString{Value: "saptune --format json note list"}, + "pid": &entities.FactValueInt{Value: 2604}, + "command": &entities.FactValueString{Value: "note list"}, + "exit_code": &entities.FactValueInt{Value: 0}, + "result": &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "notes_available": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "note_id": &entities.FactValueString{Value: "1410736"}, + "note_description": &entities.FactValueString{Value: "TCP/IP: setting keepalive interval"}, + "note_reference": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueString{Value: "https://launchpad.support.sap.com/#/notes/1410736"}, + }, + }, + "note_version": &entities.FactValueString{Value: "6"}, + "note_release_date": &entities.FactValueString{Value: "13.01.2020"}, + "note_enabled_manually": &entities.FactValueBool{Value: false}, + "note_enabled_by_solution": &entities.FactValueBool{Value: false}, + "note_reverted_manually": &entities.FactValueBool{Value: false}, + "note_override_exists": &entities.FactValueBool{Value: false}, + "custom_note": &entities.FactValueBool{Value: false}, + }, + }, + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "note_id": &entities.FactValueString{Value: "1656250"}, + "note_description": &entities.FactValueString{Value: "SAP on AWS: prerequisites - only Linux"}, + "note_reference": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueString{Value: "https://launchpad.support.sap.com/#/notes/1656250"}, + }, + }, + "note_version": &entities.FactValueString{Value: "46"}, + "note_release_date": &entities.FactValueString{Value: "11.05.2022"}, + "note_enabled_manually": &entities.FactValueBool{Value: false}, + "note_enabled_by_solution": &entities.FactValueBool{Value: true}, + "note_reverted_manually": &entities.FactValueBool{Value: false}, + "note_override_exists": &entities.FactValueBool{Value: false}, + "custom_note": &entities.FactValueBool{Value: false}, + }, + }, + }, + }, + "notes_enabled": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueString{Value: "1656250"}, + }, + }, + "remember_message": &entities.FactValueString{Value: ""}, + }, + }, + "messages": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "priority": &entities.FactValueString{Value: "NOTICE"}, + "message": &entities.FactValueString{Value: "actions.go:85 You are running a test version"}, + }, + }, + }, + }, + }, + }, + }, + } + + suite.NoError(err) + suite.ElementsMatch(expectedResults, factResults) +} + +func (suite *SaptuneTestSuite) TestSaptuneGathererNoArgumentProvided() { + suite.mockExecutor.On("Exec", "rpm", "-q", "--qf", "%{VERSION}", "saptune").Return( + []byte("3.1.0"), nil, + ) + c := gatherers.NewSaptuneGatherer(suite.mockExecutor) + + factRequests := []entities.FactRequest{ + { + Name: "no_argument_fact", + Gatherer: "saptune", + }, + { + Name: "empty_argument_fact", + Gatherer: "saptune", + Argument: "", + }, + } + + factResults, err := c.Gather(factRequests) + + expectedResults := []entities.Fact{ + { + Name: "no_argument_fact", + Value: nil, + Error: &entities.FactGatheringError{ + Message: "missing required argument", + Type: "saptune-missing-argument", + }, + }, + { + Name: "empty_argument_fact", + Value: nil, + Error: &entities.FactGatheringError{ + Message: "missing required argument", + Type: "saptune-missing-argument", + }, + }, + } + + suite.NoError(err) + suite.ElementsMatch(expectedResults, factResults) +} + +func (suite *SaptuneTestSuite) TestSaptuneGathererUnsupportedArgument() { + suite.mockExecutor.On("Exec", "rpm", "-q", "--qf", "%{VERSION}", "saptune").Return( + []byte("3.1.0"), nil, + ) + c := gatherers.NewSaptuneGatherer(suite.mockExecutor) + + factRequests := []entities.FactRequest{ + { + Name: "unsupported_argument_fact", + Gatherer: "saptune", + Argument: "unsupported", + }, + } + + factResults, err := c.Gather(factRequests) + + expectedResults := []entities.Fact{ + { + Name: "unsupported_argument_fact", + Value: nil, + Error: &entities.FactGatheringError{ + Message: "the requested argument is not currently supported: unsupported", + Type: "saptune-unsupported-argument", + }, + }, + } + + suite.NoError(err) + suite.ElementsMatch(expectedResults, factResults) +} + +func (suite *SaptuneTestSuite) TestSaptuneGathererVersionUnsupported() { + suite.mockExecutor.On("Exec", "rpm", "-q", "--qf", "%{VERSION}", "saptune").Return( + []byte("2.0.0"), nil, + ) + c := gatherers.NewSaptuneGatherer(suite.mockExecutor) + + factRequests := []entities.FactRequest{ + { + Name: "saptune_status", + Gatherer: "saptune", + Argument: "status", + }, + } + + factResults, err := c.Gather(factRequests) + + expectedResults := []entities.Fact{} + + suite.EqualError(err, "fact gathering error: saptune-version-not-supported - currently installed version of saptune is not supported") + suite.ElementsMatch(expectedResults, factResults) +} + +func (suite *SaptuneTestSuite) TestSaptuneGathererNotInstalled() { + suite.mockExecutor.On("Exec", "rpm", "-q", "--qf", "%{VERSION}", "saptune").Return( + nil, errors.New("exit status 1"), + ) + c := gatherers.NewSaptuneGatherer(suite.mockExecutor) + + factRequests := []entities.FactRequest{ + { + Name: "saptune_status", + Gatherer: "saptune", + Argument: "status", + }, + } + + factResults, err := c.Gather(factRequests) + + expectedResults := []entities.Fact{} + + suite.EqualError(err, "fact gathering error: saptune-not-installed - saptune is not installed: could not determine saptune version: exit status 1") + suite.ElementsMatch(expectedResults, factResults) +} + +func (suite *SaptuneTestSuite) TestSaptuneGathererCommandError() { + suite.mockExecutor.On("Exec", "rpm", "-q", "--qf", "%{VERSION}", "saptune").Return( + []byte("3.1.0"), nil, + ) + suite.mockExecutor.On("Exec", "saptune", "--format", "json", "status", "--non-compliance-check").Return( + nil, errors.New("exit status 1"), + ) + c := gatherers.NewSaptuneGatherer(suite.mockExecutor) + + factRequests := []entities.FactRequest{ + { + Name: "saptune_status", + Gatherer: "saptune", + Argument: "status", + }, + } + + factResults, err := c.Gather(factRequests) + + expectedResults := []entities.Fact{ + { + Name: "saptune_status", + Value: nil, + Error: &entities.FactGatheringError{ + Message: "error executing saptune command: unexpected end of JSON input", + Type: "saptune-cmd-error", + }, + }, + } + + suite.NoError(err) + suite.ElementsMatch(expectedResults, factResults) +} + +func (suite *SaptuneTestSuite) TestSaptuneGathererCommandCaching() { + suite.mockExecutor.On("Exec", "rpm", "-q", "--qf", "%{VERSION}", "saptune").Return( + []byte("3.1.0"), nil, + ) + suite.mockExecutor.On("Exec", "saptune", "--format", "json", "status", "--non-compliance-check").Return([]byte("{\"some_json_key\": \"some_value\"}"), nil) + c := gatherers.NewSaptuneGatherer(suite.mockExecutor) + + factRequests := []entities.FactRequest{ + { + Name: "saptune_repeated_argument_1", + Gatherer: "saptune", + Argument: "status", + }, + { + Name: "saptune_repeated_argument_2", + Gatherer: "saptune", + Argument: "status", + }, + } + + factResults, err := c.Gather(factRequests) + + expectedResults := []entities.Fact{ + { + Name: "saptune_repeated_argument_1", + Value: &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "some_json_key": &entities.FactValueString{Value: "some_value"}, + }, + }, + }, + { + Name: "saptune_repeated_argument_2", + Value: &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "some_json_key": &entities.FactValueString{Value: "some_value"}, + }, + }, + }, + } + + suite.NoError(err) + suite.ElementsMatch(expectedResults, factResults) + suite.mockExecutor.AssertNumberOfCalls(suite.T(), "Exec", 2) // 1 for rpm, 1 for saptune +} diff --git a/test/fixtures/gatherers/saptune-note-list.output b/test/fixtures/gatherers/saptune-note-list.output new file mode 100644 index 00000000..b5e2f956 --- /dev/null +++ b/test/fixtures/gatherers/saptune-note-list.output @@ -0,0 +1,2 @@ + +{"$schema":"file:///usr/share/saptune/schemas/1.0/saptune_note_list.schema.json","publish time":"2023-04-27 17:28:53.073","argv":"saptune --format json note list","pid":2604,"command":"note list","exit code":0,"result":{"Notes available":[{"Note ID":"1410736","Note description":"TCP/IP: setting keepalive interval","Note reference":["https://launchpad.support.sap.com/#/notes/1410736"],"Note version":"6","Note release date":"13.01.2020","Note enabled manually":false,"Note enabled by Solution":false,"Note reverted manually":false,"Note override exists":false,"custom Note":false},{"Note ID":"1656250","Note description":"SAP on AWS: prerequisites - only Linux","Note reference":["https://launchpad.support.sap.com/#/notes/1656250"],"Note version":"46","Note release date":"11.05.2022","Note enabled manually":false,"Note enabled by Solution":true,"Note reverted manually":false,"Note override exists":false,"custom Note":false}],"Notes enabled":["1656250"],"remember message":""},"messages":[{"priority":"NOTICE","message":"actions.go:85 You are running a test version"}]} \ No newline at end of file diff --git a/test/fixtures/gatherers/saptune-note-verify.output b/test/fixtures/gatherers/saptune-note-verify.output new file mode 100644 index 00000000..ebe5be87 --- /dev/null +++ b/test/fixtures/gatherers/saptune-note-verify.output @@ -0,0 +1,2 @@ + +{"$schema":"file:///usr/share/saptune/schemas/1.0/saptune_note_verify.schema.json","publish time":"2023-04-24 15:49:43.399","argv":"saptune --format json note verify","pid":25202,"command":"note verify","exit code":1,"result":{"verifications":[{"Note ID":"1771258","Note version":"6","parameter":"LIMIT_@dba_hard_nofile","compliant":true,"expected value":"@dba hard nofile 1048576","actual value":"@dba hard nofile 1048576"},{"Note ID":"1771258","Note version":"6","parameter":"LIMIT_@dba_soft_nofile","compliant":true,"expected value":"@dba soft nofile 1048576","actual value":"@dba soft nofile 1048576"}],"attentions":[],"Notes enabled":["1771258"],"system compliance":false},"messages":[{"priority":"NOTICE","message":"actions.go:85 You are running a test version"},{"priority":"WARNING","message":"sysctl.go:73: Parameter 'kernel.shmmax' redefined "},{"priority":"WARNING","message":"sysctl.go:73: Parameter 'kernel.shmall' redefined"},{"priority":"NOTICE","message":"ini.go:308: block device related section settings detected"},{"priority":"ERROR","message":"system.go:148: The parameters have deviated from recommendations"}]} \ No newline at end of file diff --git a/test/fixtures/gatherers/saptune-solution-list.output b/test/fixtures/gatherers/saptune-solution-list.output new file mode 100644 index 00000000..5b8975ac --- /dev/null +++ b/test/fixtures/gatherers/saptune-solution-list.output @@ -0,0 +1,2 @@ + +{"$schema":"file:///usr/share/saptune/schemas/1.0/saptune_solution_list.schema.json","publish time":"2023-04-27 17:21:27.926","argv":"saptune --format json solution list","pid":2582,"command":"solution list","exit code":0,"result":{"Solutions available":[{"Solution ID":"BOBJ","Note list":["1771258"],"Solution enabled":false,"Solution override exists":false,"custom Solution":false,"Solution deprecated":false},{"Solution ID":"DEMO","Note list":["demo"],"Solution enabled":false,"Solution override exists":false,"custom Solution":true,"Solution deprecated":false}],"remember message":""},"messages":[{"priority":"NOTICE","message":"actions.go:85 You are running a test version"}]} \ No newline at end of file diff --git a/test/fixtures/gatherers/saptune-solution-verify.output b/test/fixtures/gatherers/saptune-solution-verify.output new file mode 100644 index 00000000..83ac8063 --- /dev/null +++ b/test/fixtures/gatherers/saptune-solution-verify.output @@ -0,0 +1,2 @@ + +{"$schema":"file:///usr/share/saptune/schemas/1.0/saptune_solution_verify.schema.json","publish time":"2023-04-27 17:17:23.743","argv":"saptune --format json solution verify","pid":2538,"command":"solution verify","exit code":1,"result":{"verifications":[{"Note ID":"1771258","Note version":"6","parameter":"LIMIT_@dba_hard_nofile","compliant":true,"expected value":"@dba hard nofile 1048576","actual value":"@dba hard nofile 1048576"},{"Note ID":"1771258","Note version":"6","parameter":"LIMIT_@dba_soft_nofile","compliant":true,"expected value":"@dba soft nofile 1048576","actual value":"@dba soft nofile 1048576"}],"attentions":[],"Notes enabled":["1771258"],"system compliance":false},"messages":[{"priority":"NOTICE","message":"actions.go:85 You are running a test version"},{"priority":"WARNING","message":"sysctl.go:73: Parameter 'kernel.shmmax' redefined "},{"priority":"WARNING","message":"sysctl.go:73: Parameter 'kernel.shmall' redefined"},{"priority":"NOTICE","message":"ini.go:308: block device related section settings detected"},{"priority":"ERROR","message":"system.go:148: The parameters have deviated from recommendations"}]} \ No newline at end of file diff --git a/test/fixtures/gatherers/saptune-status.output b/test/fixtures/gatherers/saptune-status.output new file mode 100644 index 00000000..a0388080 --- /dev/null +++ b/test/fixtures/gatherers/saptune-status.output @@ -0,0 +1,2 @@ + +{"$schema":"file:///usr/share/saptune/schemas/1.0/saptune_status.schema.json","publish time":"2023-09-15 15:15:14.599","argv":"saptune --format json status","pid":6593,"command":"status","exit code":1,"result":{"services":{"saptune":["disabled","inactive"],"sapconf":[],"tuned":[]},"systemd system state":"degraded","tuning state":"compliant","virtualization":"kvm","configured version":"3","package version":"3.1.0","Solution enabled":[],"Notes enabled by Solution":[],"Solution applied":[],"Notes applied by Solution":[],"Notes enabled additionally":["1410736"],"Notes enabled":["1410736"],"Notes applied":["1410736"],"staging":{"staging enabled":false,"Notes staged":[],"Solutions staged":[]},"remember message":"This is a reminder"},"messages":[{"priority":"NOTICE","message":"actions.go:85: ATTENTION: You are running a test version"}]} \ No newline at end of file