-
Notifications
You must be signed in to change notification settings - Fork 11
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
Disp work gatherer #281
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
continue | ||
} | ||
|
||
result := fillRegexpGroups(string(dispWorkOutput)) | ||
|
||
dispWorkMap[sid] = dispWorkData{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In that cases the map is filled with empty strings. The |
||
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) | ||
} |
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) | ||
} |
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 |
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 |
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
andNWD
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 therhai
test fail