Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disp work gatherer #281

Merged
merged 2 commits into from
Oct 23, 2023
Merged
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
142 changes: 142 additions & 0 deletions internal/factsengine/gatherers/dispwork.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package gatherers

import (
"encoding/json"
"fmt"
"path/filepath"
"regexp"
"strings"

log "github.com/sirupsen/logrus"
"github.com/spf13/afero"
"github.com/trento-project/agent/internal/core/sapsystem"
"github.com/trento-project/agent/pkg/factsengine/entities"
"github.com/trento-project/agent/pkg/utils"
)

const (
DispWorkGathererName = "disp+work"
)

// nolint:gochecknoglobals
var (
DispWorkFileSystemError = entities.FactGatheringError{
Type: "dispwork-file-system-error",
Message: "error reading the file system",
}

DispWorkCommandError = entities.FactGatheringError{
Type: "dispwork-command-error",
Message: "error running disp+work command",
}

DispWorkDecodingError = entities.FactGatheringError{
Type: "dispwork-decoding-error",
Message: "error decoding disp+work output",
}

// the names groups values are the values used to compose the resulting fact value map
entriesPatternCompiled = regexp.MustCompile("(?m)" +
"^kernel release\\s+(?P<kernel_release>.*)$|" +
"^compilation mode\\s+(?P<compilation_mode>.*)$|" +
"^patch number\\s+(?P<patch_number>.*)$")

groupedNames = entriesPatternCompiled.SubexpNames()[1:]
)

type DispWorkGatherer struct {
fs afero.Fs
executor utils.CommandExecutor
}

type dispWorkData struct {
CompilationMode string `json:"compilation_mode"`
KernelRelease string `json:"kernel_release"`
PatchNumber string `json:"patch_number"`
}

func NewDefaultDispWorkGatherer() *DispWorkGatherer {
return NewDispWorkGatherer(afero.NewOsFs(), utils.Executor{})
}

func NewDispWorkGatherer(fs afero.Fs, executor utils.CommandExecutor) *DispWorkGatherer {
return &DispWorkGatherer{
fs: fs,
executor: executor,
}
}

func (g *DispWorkGatherer) Gather(factsRequests []entities.FactRequest) ([]entities.Fact, error) {
facts := []entities.Fact{}
log.Infof("Starting %s facts gathering process", DispWorkGathererName)

systemPaths, err := sapsystem.FindSystems(g.fs)
if err != nil {
return nil, DispWorkFileSystemError.Wrap(err.Error())
}

dispWorkMap := make(map[string]dispWorkData)

for _, systemPath := range systemPaths {
sid := filepath.Base(systemPath)
sapUser := fmt.Sprintf("%sadm", strings.ToLower(sid))

dispWorkOutput, err := g.executor.Exec("su", "-", sapUser, "-c", "\"disp+work\"")
if err != nil {
gatheringError := DispWorkCommandError.Wrap(err.Error())
log.Error(gatheringError)
dispWorkMap[sid] = dispWorkData{} // fill with empty data
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want to abort on something like that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about that, but the fact that we might have multiple sap installations doesn't make it a good idea in my opinion.
If you have NWP and NWD installed, and one fails, you at least have the result for the other (and the failed one filled with empty strings, that at the end, would make the rhai test fail

continue
}

result := fillRegexpGroups(string(dispWorkOutput))

dispWorkMap[sid] = dispWorkData{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if we have no matches on the regexp, for example something could be missing or could be empty, we allow that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that cases the map is filled with empty strings. The FindAllStringSubmatch function returns a value, even an empty one for group, so combined with the SubexpNames you can fill all the groups, at least with empty values.
Actually, I added a test to cover that case XD

CompilationMode: result["compilation_mode"],
KernelRelease: result["kernel_release"],
PatchNumber: result["patch_number"],
}
}

factValue, err := dispWorkDataToFactValue(dispWorkMap)
if err != nil {
gatheringError := DispWorkDecodingError.Wrap(err.Error())
log.Error(gatheringError)
return nil, gatheringError
}

for _, factReq := range factsRequests {
facts = append(facts, entities.NewFactGatheredWithRequest(factReq, factValue))
}

log.Infof("Requested %s facts gathered", DispWorkGathererName)
return facts, nil
}

func fillRegexpGroups(output string) map[string]string {
result := make(map[string]string)
for _, match := range entriesPatternCompiled.FindAllStringSubmatch(output, -1) {
for i, name := range groupedNames {
if value, found := result[name]; found && value != "" {
continue
}
result[name] = match[i+1]
}
}
return result
}

func dispWorkDataToFactValue(data map[string]dispWorkData) (entities.FactValue, error) {
marshalled, err := json.Marshal(&data)
if err != nil {
return nil, err
}

var unmarshalled map[string]interface{}
err = json.Unmarshal(marshalled, &unmarshalled)
if err != nil {
return nil, err
}

return entities.NewFactValue(unmarshalled)
}
133 changes: 133 additions & 0 deletions internal/factsengine/gatherers/dispwork_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package gatherers_test

import (
"errors"
"io"
"os"
"testing"

"github.com/spf13/afero"
"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 DispWorkGathererTestSuite struct {
suite.Suite
fs afero.Fs
mockExecutor *utilsMocks.CommandExecutor
}

func TestDispWorkGathererSuite(t *testing.T) {
suite.Run(t, new(DispWorkGathererTestSuite))
}

func (suite *DispWorkGathererTestSuite) SetupTest() {
fs := afero.NewMemMapFs()
err := fs.MkdirAll("/usr/sap/PRD", 0644)
suite.NoError(err)
err = fs.MkdirAll("/usr/sap/QAS", 0644)
suite.NoError(err)
err = fs.MkdirAll("/usr/sap/QA2", 0644)
suite.NoError(err)
err = fs.MkdirAll("/usr/sap/DEV", 0644)
suite.NoError(err)

suite.fs = fs
suite.mockExecutor = new(utilsMocks.CommandExecutor)
}

func (suite *DispWorkGathererTestSuite) TestDispWorkGatheringSuccess() {
validOutputFile, _ := os.Open(helpers.GetFixturePath("gatherers/dispwork-valid.output"))
validOutput, _ := io.ReadAll(validOutputFile)
partialOutputFile, _ := os.Open(helpers.GetFixturePath("gatherers/dispwork-partial.output"))
partialOutput, _ := io.ReadAll(partialOutputFile)
unsortedOutputFile, _ := os.Open(helpers.GetFixturePath("gatherers/dispwork-unsorted.output"))
unsortedOutput, _ := io.ReadAll(unsortedOutputFile)
suite.mockExecutor.
On("Exec", "su", "-", "prdadm", "-c", "\"disp+work\"").
Return(validOutput, nil).
On("Exec", "su", "-", "qasadm", "-c", "\"disp+work\"").
Return(partialOutput, nil).
On("Exec", "su", "-", "qa2adm", "-c", "\"disp+work\"").
Return(unsortedOutput, nil).
On("Exec", "su", "-", "devadm", "-c", "\"disp+work\"").
Return(nil, errors.New("some error"))

g := gatherers.NewDispWorkGatherer(suite.fs, suite.mockExecutor)

fr := []entities.FactRequest{
{
Name: "dispwork",
CheckID: "check1",
Gatherer: "disp+work",
},
}

expectedResults := []entities.Fact{{
Name: "dispwork",
CheckID: "check1",
Value: &entities.FactValueMap{
Value: map[string]entities.FactValue{
"PRD": &entities.FactValueMap{
Value: map[string]entities.FactValue{
"compilation_mode": &entities.FactValueString{Value: "UNICODE"},
"kernel_release": &entities.FactValueString{Value: "753"},
"patch_number": &entities.FactValueString{Value: "900"},
},
},
"QAS": &entities.FactValueMap{
Value: map[string]entities.FactValue{
"compilation_mode": &entities.FactValueString{Value: ""},
"kernel_release": &entities.FactValueString{Value: "753"},
"patch_number": &entities.FactValueString{Value: ""},
},
},
"QA2": &entities.FactValueMap{
Value: map[string]entities.FactValue{
"compilation_mode": &entities.FactValueString{Value: "UNICODE"},
"kernel_release": &entities.FactValueString{Value: "753"},
"patch_number": &entities.FactValueString{Value: "900"},
},
},
"DEV": &entities.FactValueMap{
Value: map[string]entities.FactValue{
"compilation_mode": &entities.FactValueString{Value: ""},
"kernel_release": &entities.FactValueString{Value: ""},
"patch_number": &entities.FactValueString{Value: ""},
},
},
},
},
}}

result, err := g.Gather(fr)
suite.NoError(err)
suite.EqualValues(expectedResults, result)
}

func (suite *DispWorkGathererTestSuite) TestDispWorkGatheringEmptyFileSystem() {
g := gatherers.NewDispWorkGatherer(afero.NewMemMapFs(), suite.mockExecutor)

fr := []entities.FactRequest{
{
Name: "dispwork",
CheckID: "check1",
Gatherer: "disp+work",
},
}

expectedResults := []entities.Fact{{
Name: "dispwork",
CheckID: "check1",
Value: &entities.FactValueMap{
Value: map[string]entities.FactValue{},
},
}}

result, err := g.Gather(fr)
suite.NoError(err)
suite.EqualValues(expectedResults, result)
}
3 changes: 3 additions & 0 deletions internal/factsengine/gatherers/gatherer.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ func StandardGatherers() FactGatherersTree {
DirScanGathererName: map[string]FactGatherer{
"v1": NewDefaultDirScanGatherer(),
},
DispWorkGathererName: map[string]FactGatherer{
"v1": NewDefaultDispWorkGatherer(),
},
FstabGathererName: map[string]FactGatherer{
"v1": NewDefaultFstabGatherer(),
},
Expand Down
12 changes: 12 additions & 0 deletions test/fixtures/gatherers/dispwork-partial.output
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

--------------------
disp+work information
--------------------

kernel release 753

kernel make variant 753_REL

compiled on Linux GNU SLES-11 x86_64 cc4.8.5 use-pr211015 for linuxx86_64

compiled for 64 BIT
46 changes: 46 additions & 0 deletions test/fixtures/gatherers/dispwork-unsorted.output
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@

--------------------
disp+work information
--------------------

patch number 900

kernel make variant 753_REL

compilation mode UNICODE

compiled on Linux GNU SLES-11 x86_64 cc4.8.5 use-pr211015 for linuxx86_64

compiled for 64 BIT

kernel release 753

compile time Oct 16 2021 00:21:02

Wed Oct 18 13:22:52 2023
Loading DB library '/usr/sap/NWP/SYS/exe/run/dbhdbslib.so' ...
Library '/usr/sap/NWP/SYS/exe/run/dbhdbslib.so' loaded
Version of '/usr/sap/NWP/SYS/exe/run/dbhdbslib.so' is "753.02", patchlevel (0.900)

update level 0


source id 0.900

RKS compatibility level 1

DW_GUI compatibility level 900


---------------------
supported environment
---------------------

database (SAP, table SVERS) 740
750
751
752
769

operating system
Linux
45 changes: 45 additions & 0 deletions test/fixtures/gatherers/dispwork-valid.output
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@

--------------------
disp+work information
--------------------

kernel release 753

kernel make variant 753_REL

compiled on Linux GNU SLES-11 x86_64 cc4.8.5 use-pr211015 for linuxx86_64

compiled for 64 BIT

compilation mode UNICODE

compile time Oct 16 2021 00:21:02

Wed Oct 18 13:22:52 2023
Loading DB library '/usr/sap/NWP/SYS/exe/run/dbhdbslib.so' ...
Library '/usr/sap/NWP/SYS/exe/run/dbhdbslib.so' loaded
Version of '/usr/sap/NWP/SYS/exe/run/dbhdbslib.so' is "753.02", patchlevel (0.900)

update level 0

patch number 900

source id 0.900

RKS compatibility level 1

DW_GUI compatibility level 900


---------------------
supported environment
---------------------

database (SAP, table SVERS) 740
750
751
752
769

operating system
Linux