diff --git a/internal/core/sapsystem/sapcontrolapi/mocks/WebService.go b/internal/core/sapsystem/sapcontrolapi/mocks/WebService.go index 99af1ce5..37fd3a1d 100644 --- a/internal/core/sapsystem/sapcontrolapi/mocks/WebService.go +++ b/internal/core/sapsystem/sapcontrolapi/mocks/WebService.go @@ -81,6 +81,75 @@ func (_m *WebService) GetSystemInstanceList() (*sapcontrolapi.GetSystemInstanceL return r0, r1 } +// GetVersionInfo provides a mock function with given fields: +func (_m *WebService) GetVersionInfo() (*sapcontrolapi.GetVersionInfoResponse, error) { + ret := _m.Called() + + var r0 *sapcontrolapi.GetVersionInfoResponse + if rf, ok := ret.Get(0).(func() *sapcontrolapi.GetVersionInfoResponse); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*sapcontrolapi.GetVersionInfoResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// HACheckConfig provides a mock function with given fields: +func (_m *WebService) HACheckConfig() (*sapcontrolapi.HACheckConfigResponse, error) { + ret := _m.Called() + + var r0 *sapcontrolapi.HACheckConfigResponse + if rf, ok := ret.Get(0).(func() *sapcontrolapi.HACheckConfigResponse); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*sapcontrolapi.HACheckConfigResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// HAGetFailoverConfig provides a mock function with given fields: +func (_m *WebService) HAGetFailoverConfig() (*sapcontrolapi.HAGetFailoverConfigResponse, error) { + ret := _m.Called() + + var r0 *sapcontrolapi.HAGetFailoverConfigResponse + if rf, ok := ret.Get(0).(func() *sapcontrolapi.HAGetFailoverConfigResponse); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*sapcontrolapi.HAGetFailoverConfigResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + type NewWebServiceT interface { mock.TestingT Cleanup(func()) diff --git a/internal/core/sapsystem/sapcontrolapi/webservice.go b/internal/core/sapsystem/sapcontrolapi/webservice.go index 27eb51de..38ac0b4f 100644 --- a/internal/core/sapsystem/sapcontrolapi/webservice.go +++ b/internal/core/sapsystem/sapcontrolapi/webservice.go @@ -19,10 +19,15 @@ type WebService interface { GetInstanceProperties() (*GetInstancePropertiesResponse, error) GetProcessList() (*GetProcessListResponse, error) GetSystemInstanceList() (*GetSystemInstanceListResponse, error) + GetVersionInfo() (*GetVersionInfoResponse, error) + HACheckConfig() (*HACheckConfigResponse, error) + HAGetFailoverConfig() (*HAGetFailoverConfigResponse, error) } type STATECOLOR string type STATECOLOR_CODE int +type HAVerificationState string +type HACheckCategory string const ( STATECOLOR_GRAY STATECOLOR = "SAPControl-GRAY" @@ -30,6 +35,15 @@ const ( STATECOLOR_YELLOW STATECOLOR = "SAPControl-YELLOW" STATECOLOR_RED STATECOLOR = "SAPControl-RED" + HAVerificationStateSAPControlHASUCCESS HAVerificationState = "SAPControl-HA-SUCCESS" + HAVerificationStateSAPControlHAWARNING HAVerificationState = "SAPControl-HA-WARNING" + HAVerificationStateSAPControlHAERROR HAVerificationState = "SAPControl-HA-ERROR" + + HACheckCategorySAPControlSAPCONFIGURATION HACheckCategory = "SAPControl-SAP-CONFIGURATION" + HACheckCategorySAPControlSAPSTATE HACheckCategory = "SAPControl-SAP-STATE" + HACheckCategorySAPControlHACONFIGURATION HACheckCategory = "SAPControl-HA-CONFIGURATION" + HACheckCategorySAPControlHASTATE HACheckCategory = "SAPControl-HA-STATE" + // NOTE: This was just copy-pasted from sap_host_exporter, not used right now // see: https://github.com/SUSE/sap_host_exporter/blob/68bbf2f1b490ab0efaa2dd7b878b778f07fba2ab/lib/sapcontrol/webservice.go#L42 STATECOLOR_CODE_GRAY STATECOLOR_CODE = 1 @@ -65,6 +79,38 @@ type GetSystemInstanceListResponse struct { Instances []*SAPInstance `xml:"instance>item,omitempty" json:"instance>item,omitempty"` } +type GetVersionInfo struct { + XMLName xml.Name `xml:"urn:SAPControl GetVersionInfo"` +} + +type GetVersionInfoResponse struct { + XMLName xml.Name `xml:"urn:SAPControl GetVersionInfoResponse"` + InstanceVersions []*VersionInfo `xml:"version>item,omitempty" json:"version>item,omitempty"` +} + +type HACheckConfig struct { + XMLName xml.Name `xml:"urn:SAPControl HACheckConfig"` +} + +type HACheckConfigResponse struct { + XMLName xml.Name `xml:"urn:SAPControl HACheckConfigResponse"` + Checks []*HACheck `xml:"check>item,omitempty" json:"check>item,omitempty"` +} + +type HAGetFailoverConfig struct { + XMLName xml.Name `xml:"urn:SAPControl HAGetFailoverConfig"` +} + +type HAGetFailoverConfigResponse struct { + XMLName xml.Name `xml:"urn:SAPControl HAGetFailoverConfigResponse"` + HAActive bool `xml:"HAActive,omitempty" json:"HAActive,omitempty"` + HAProductVersion string `xml:"HAProductVersion,omitempty" json:"HAProductVersion,omitempty"` + HASAPInterfaceVersion string `xml:"HASAPInterfaceVersion,omitempty" json:"HASAPInterfaceVersion,omitempty"` + HADocumentation string `xml:"HADocumentation,omitempty" json:"HADocumentation,omitempty"` + HAActiveNode string `xml:"HAActiveNode,omitempty" json:"HAActiveNode,omitempty"` + HANodes *[]string `xml:"HANodes>item,omitempty" json:"HANodes>item,omitempty"` +} + type OSProcess struct { Name string `xml:"name,omitempty" json:"name,omitempty"` Description string `xml:"description,omitempty" json:"description,omitempty"` @@ -91,6 +137,19 @@ type SAPInstance struct { Dispstatus STATECOLOR `xml:"dispstatus,omitempty" json:"dispstatus,omitempty"` } +type VersionInfo struct { + Filename string `xml:"Filename,omitempty" json:"Filename,omitempty"` + VersionInfo string `xml:"VersionInfo,omitempty" json:"VersionInfo,omitempty"` + Time string `xml:"Time,omitempty" json:"Time,omitempty"` +} + +type HACheck struct { + State *HAVerificationState `xml:"state,omitempty" json:"state,omitempty"` + Category *HACheckCategory `xml:"category,omitempty" json:"category,omitempty"` + Description string `xml:"description,omitempty" json:"description,omitempty"` + Comment string `xml:"comment,omitempty" json:"comment,omitempty"` +} + type webService struct { client *soap.Client } @@ -163,3 +222,39 @@ func (s *webService) GetSystemInstanceList() (*GetSystemInstanceListResponse, er return response, nil } + +// GetVersionInfo returns a list version information for the most important files of the instance +func (s *webService) GetVersionInfo() (*GetVersionInfoResponse, error) { + request := &GetVersionInfo{} + response := &GetVersionInfoResponse{} + err := s.client.Call("''", request, response) + if err != nil { + return nil, err + } + + return response, nil +} + +// HACheckConfig checks high availability configurration and status of the system +func (s *webService) HACheckConfig() (*HACheckConfigResponse, error) { + request := &HACheckConfig{} + response := &HACheckConfigResponse{} + err := s.client.Call("''", request, &response) + if err != nil { + return nil, err + } + + return response, nil +} + +// HAGetFailoverConfig returns HA failover third party information +func (s *webService) HAGetFailoverConfig() (*HAGetFailoverConfigResponse, error) { + request := &HAGetFailoverConfig{} + response := &HAGetFailoverConfigResponse{} + err := s.client.Call("''", request, &response) + if err != nil { + return nil, err + } + + return response, nil +} diff --git a/internal/factsengine/gatherers/gatherer.go b/internal/factsengine/gatherers/gatherer.go index 5439d15f..bc1625f8 100644 --- a/internal/factsengine/gatherers/gatherer.go +++ b/internal/factsengine/gatherers/gatherer.go @@ -19,6 +19,7 @@ func StandardGatherers() map[string]FactGatherer { HostsFileGathererName: NewDefaultHostsFileGatherer(), PackageVersionGathererName: NewDefaultPackageVersionGatherer(), PasswdGathererName: NewDefaultPasswdGatherer(), + SapControlGathererName: NewDefaultSapControlGatherer(), SapHostCtrlGathererName: NewDefaultSapHostCtrlGatherer(), SapProfilesGathererName: NewDefaultSapProfilesGatherer(), SaptuneGathererName: NewDefaultSaptuneGatherer(), diff --git a/internal/factsengine/gatherers/sapcontrol.go b/internal/factsengine/gatherers/sapcontrol.go new file mode 100644 index 00000000..5ef270ee --- /dev/null +++ b/internal/factsengine/gatherers/sapcontrol.go @@ -0,0 +1,310 @@ +package gatherers + +import ( + "encoding/json" + "fmt" + "path/filepath" + "regexp" + + log "github.com/sirupsen/logrus" + "github.com/spf13/afero" + "github.com/trento-project/agent/internal/core/sapsystem" + "github.com/trento-project/agent/internal/core/sapsystem/sapcontrolapi" + "github.com/trento-project/agent/pkg/factsengine/entities" +) + +const ( + SapControlGathererName = "sapcontrol" +) + +// nolint:gochecknoglobals +var whitelistedSapControlArguments = map[string]func(sapcontrolapi.WebService) (interface{}, error){ + "GetProcessList": mapGetProcessList, + "GetSystemInstanceList": mapGetSystemInstanceList, + "GetVersionInfo": mapGetVersionInfo, + "HACheckConfig": mapHACheckConfig, + "HAGetFailoverConfig": mapHAGetFailoverConfig, +} + +// nolint:gochecknoglobals +var ( + SapcontrolFileSystemError = entities.FactGatheringError{ + Type: "sapcontrol-file-system-error", + Message: "error in the SAP file system", + } + + SapcontrolArgumentUnsupported = entities.FactGatheringError{ + Type: "sapcontrol-unsupported-argument", + Message: "the requested argument is not currently supported", + } + + SapcontrolMissingArgument = entities.FactGatheringError{ + Type: "sapcontrol-missing-argument", + Message: "missing required argument", + } + + SapcontrolWebmethodError = entities.FactGatheringError{ + Type: "sapcontrol-webmethod-error", + Message: "error executing sapcontrol webmethod", + } + + SapcontrolDecodingError = entities.FactGatheringError{ + Type: "sapcontrol-decoding-error", + Message: "error decoding sapcontrol output", + } + + versionInfoPatternCompiled = regexp.MustCompile("^(\\d+), patch (\\d+), changelist (\\d+), " + + "RKS compatibility level (\\d+), (.*), (.*)$") +) + +type versionInfo struct { + Filename string `json:"filename,omitempty"` + SapKernel string `json:"sap_kernel,omitempty"` + Patch string `json:"patch,omitempty"` + ChangeList string `json:"changelist,omitempty"` + RKSCompatibilityLevel string `json:"rks_compatibility_level,omitempty"` + Build string `json:"build,omitempty"` + Architecture string `json:"architecture,omitempty"` + Time string `json:"time,omitempty"` +} + +type failoverConfig struct { + HAActive bool `json:"ha_active"` + HAProductVersion string `json:"ha_product_version"` + HASAPInterfaceVersion string `json:"ha_sap_interface_version"` + HADocumentation string `json:"ha_documentation"` + HAActiveNode string `json:"ha_active_nodes"` + HANodes []string `json:"ha_nodes"` +} + +type SapControlMap map[string][]SapControlInstance + +type SapControlInstance struct { + Name string `json:"name"` + InstanceNr string `json:"instance_nr"` + Output interface{} `json:"output"` +} + +type SapControlGatherer struct { + webService sapcontrolapi.WebServiceConnector + fs afero.Fs +} + +func NewDefaultSapControlGatherer() *SapControlGatherer { + webService := sapcontrolapi.WebServiceUnix{} + fs := afero.NewOsFs() + return NewSapControlGatherer(webService, fs) +} + +func NewSapControlGatherer(webService sapcontrolapi.WebServiceConnector, fs afero.Fs) *SapControlGatherer { + return &SapControlGatherer{ + webService: webService, + fs: fs, + } +} + +func (s *SapControlGatherer) Gather(factsRequests []entities.FactRequest) ([]entities.Fact, error) { + cachedFacts := make(map[string]entities.Fact) + + log.Infof("Starting %s facts gathering process", SapControlGathererName) + facts := []entities.Fact{} + + foundSystems, err := initSystemsMap(s.fs) + if err != nil { + return nil, SapcontrolFileSystemError.Wrap(err.Error()) + } + + for _, factReq := range factsRequests { + if len(factReq.Argument) == 0 { + log.Error(SapcontrolMissingArgument.Error()) + facts = append(facts, entities.NewFactGatheredWithError(factReq, &SapcontrolMissingArgument)) + continue + } + + webmethod, ok := whitelistedSapControlArguments[factReq.Argument] + + if !ok { + gatheringError := SapcontrolArgumentUnsupported.Wrap(factReq.Argument) + log.Error(gatheringError) + facts = append(facts, entities.NewFactGatheredWithError(factReq, gatheringError)) + continue + } + + cachedFact, cacheHit := cachedFacts[factReq.Argument] + + if cacheHit { + facts = append(facts, entities.Fact{ + Name: factReq.Name, + CheckID: factReq.CheckID, + Value: cachedFact.Value, + Error: cachedFact.Error, + }) + continue + } + + sapControlMap := make(SapControlMap) + for sid, instances := range foundSystems { + sapControlInstance := []SapControlInstance{} + for _, instanceData := range instances { + instanceName, instanceNumber := instanceData[0], instanceData[1] + conn := s.webService.New(instanceNumber) + output, err := webmethod(conn) + if err != nil { + log.Error(SapcontrolWebmethodError. + Wrap(fmt.Sprintf("argument %s for %s/%s", factReq.Argument, sid, instanceName)). + Wrap(err.Error())) + continue + } + sapControlInstance = append(sapControlInstance, SapControlInstance{ + Name: instanceName, + InstanceNr: instanceNumber, + Output: output, + }) + sapControlMap[sid] = sapControlInstance + } + } + + var fact entities.Fact + + if factValue, err := outputToFactValue(sapControlMap); err != nil { + gatheringError := SapcontrolDecodingError. + Wrap(fmt.Sprintf("argument: %s", factReq.Argument)). + 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", SapControlGathererName) + + return facts, nil +} + +func initSystemsMap(fs afero.Fs) (map[string][][]string, error) { + foundSystems := make(map[string][][]string) + systems, err := sapsystem.FindSystems(fs) + if err != nil { + return nil, err + } + + for _, system := range systems { + sid := filepath.Base(system) + instances, err := sapsystem.FindInstances(fs, system) + if err != nil { + return nil, err + } + + foundSystems[sid] = instances + } + + return foundSystems, err +} + +func mapGetProcessList(conn sapcontrolapi.WebService) (interface{}, error) { + output, err := conn.GetProcessList() + if err != nil { + return nil, err + } + + return output.Processes, nil +} + +func mapGetSystemInstanceList(conn sapcontrolapi.WebService) (interface{}, error) { + output, err := conn.GetSystemInstanceList() + if err != nil { + return nil, err + } + + return output.Instances, nil +} + +func mapGetVersionInfo(conn sapcontrolapi.WebService) (interface{}, error) { + output, err := conn.GetVersionInfo() + if err != nil { + return nil, err + } + + versions := []versionInfo{} + + for _, version := range output.InstanceVersions { + fields := versionInfoPatternCompiled.FindStringSubmatch(version.VersionInfo) + if len(fields) != 7 { + return nil, fmt.Errorf("incorrect number of fields in line %s", version.VersionInfo) + } + + versions = append(versions, versionInfo{ + Filename: version.Filename, + SapKernel: fields[1], + Patch: fields[2], + ChangeList: fields[3], + RKSCompatibilityLevel: fields[4], + Build: fields[5], + Architecture: fields[6], + Time: version.Time, + }) + } + + return versions, nil +} + +func mapHACheckConfig(conn sapcontrolapi.WebService) (interface{}, error) { + output, err := conn.HACheckConfig() + if err != nil { + return nil, err + } + + return output.Checks, nil +} + +func mapHAGetFailoverConfig(conn sapcontrolapi.WebService) (interface{}, error) { + output, err := conn.HAGetFailoverConfig() + if err != nil { + return nil, err + } + + haNodes := []string{} + if output.HANodes != nil { + haNodes = *output.HANodes + } + + config := failoverConfig{ + HAActive: output.HAActive, + HAProductVersion: output.HAProductVersion, + HASAPInterfaceVersion: output.HASAPInterfaceVersion, + HADocumentation: output.HADocumentation, + HAActiveNode: output.HAActiveNode, + HANodes: haNodes, + } + + return config, nil +} + +func outputToFactValue(output interface{}) (*entities.FactValueMap, error) { + marshalled, err := json.Marshal(&output) + if err != nil { + return nil, err + } + + var unmarshalled map[string]interface{} + err = json.Unmarshal(marshalled, &unmarshalled) + if err != nil { + return nil, err + } + + // Trick to keep the SIDs as capital letter + result := &entities.FactValueMap{Value: make(map[string]entities.FactValue)} + for key, value := range unmarshalled { + factValue, err := entities.NewFactValue(value, entities.WithSnakeCaseKeys()) + if err != nil { + return nil, err + } + result.Value[key] = factValue + } + + return result, nil +} diff --git a/internal/factsengine/gatherers/sapcontrol_test.go b/internal/factsengine/gatherers/sapcontrol_test.go new file mode 100644 index 00000000..eb42fec0 --- /dev/null +++ b/internal/factsengine/gatherers/sapcontrol_test.go @@ -0,0 +1,619 @@ +package gatherers_test + +import ( + "fmt" + "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" + + sapcontrol "github.com/trento-project/agent/internal/core/sapsystem/sapcontrolapi" + sapControlMocks "github.com/trento-project/agent/internal/core/sapsystem/sapcontrolapi/mocks" +) + +type SapControlGathererSuite struct { + suite.Suite + testFS afero.Fs + webService *sapControlMocks.WebServiceConnector +} + +func TestSapControlGathererSuite(t *testing.T) { + suite.Run(t, new(SapControlGathererSuite)) +} + +func (suite *SapControlGathererSuite) SetupSuite() { + testFS := afero.NewMemMapFs() + err := testFS.MkdirAll("/usr/sap/PRD/ASCS00", 0644) + suite.NoError(err) + + suite.testFS = testFS +} + +func (suite *SapControlGathererSuite) SetupTest() { + suite.webService = new(sapControlMocks.WebServiceConnector) +} + +func (suite *SapControlGathererSuite) TestSapControlGathererArgumentErrors() { + gatherer := gatherers.NewSapControlGatherer(suite.webService, suite.testFS) + + fr := []entities.FactRequest{ + { + Name: "missing_argument", + Gatherer: "sapcontrol", + CheckID: "check1", + Argument: "", + }, + { + Name: "unsupported_argument", + Gatherer: "sapcontrol", + CheckID: "check1", + Argument: "Unsupported", + }, + } + + expectedFacts := []entities.Fact{ + { + Name: "missing_argument", + CheckID: "check1", + Value: nil, + Error: &entities.FactGatheringError{ + Message: "missing required argument", + Type: "sapcontrol-missing-argument", + }, + }, + { + Name: "unsupported_argument", + CheckID: "check1", + Value: nil, + Error: &entities.FactGatheringError{ + Message: "the requested argument is not currently supported: Unsupported", + Type: "sapcontrol-unsupported-argument", + }, + }, + } + + results, err := gatherer.Gather(fr) + suite.NoError(err) + suite.EqualValues(expectedFacts, results) +} + +func (suite *SapControlGathererSuite) TestSapControlGathererEmptyFileSystem() { + gatherer := gatherers.NewSapControlGatherer(suite.webService, afero.NewMemMapFs()) + + fr := []entities.FactRequest{{ + Name: "sapcontrol", + Gatherer: "sapcontrol", + CheckID: "check1", + Argument: "GetProcessList", + }} + + expectedFacts := []entities.Fact{ + { + Name: "sapcontrol", + CheckID: "check1", + Value: &entities.FactValueMap{ + Value: map[string]entities.FactValue{}, + }, + }, + } + + results, err := gatherer.Gather(fr) + suite.NoError(err) + suite.EqualValues(expectedFacts, results) +} + +func (suite *SapControlGathererSuite) TestSapControlGathererCacheHit() { + mockWebService := new(sapControlMocks.WebService) + mockWebService.On("GetProcessList").Return(&sapcontrol.GetProcessListResponse{ + Processes: []*sapcontrol.OSProcess{ + { + Name: "process1", + }, + { + Name: "process2", + }, + }, + }, nil) + + suite.webService.On("New", "00").Return(mockWebService).Once() + + gatherer := gatherers.NewSapControlGatherer(suite.webService, suite.testFS) + + fr := []entities.FactRequest{ + { + Name: "request1", + Gatherer: "sapcontrol", + CheckID: "check1", + Argument: "GetProcessList", + }, + { + Name: "request2", + Gatherer: "sapcontrol", + CheckID: "check1", + Argument: "GetProcessList", + }, + } + + expectedFacts := []entities.Fact{ + { + Name: "request1", + CheckID: "check1", + Value: &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "PRD": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "instance_nr": &entities.FactValueString{Value: "00"}, + "name": &entities.FactValueString{Value: "ASCS00"}, + "output": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "name": &entities.FactValueString{Value: "process1"}, + }, + }, + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "name": &entities.FactValueString{Value: "process2"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + Error: nil, + }, + { + Name: "request2", + CheckID: "check1", + Value: &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "PRD": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "instance_nr": &entities.FactValueString{Value: "00"}, + "name": &entities.FactValueString{Value: "ASCS00"}, + "output": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "name": &entities.FactValueString{Value: "process1"}, + }, + }, + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "name": &entities.FactValueString{Value: "process2"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + Error: nil, + }, + } + + results, err := gatherer.Gather(fr) + suite.NoError(err) + suite.EqualValues(expectedFacts, results) + suite.webService.AssertNumberOfCalls(suite.T(), "New", 1) + mockWebService.AssertNumberOfCalls(suite.T(), "GetProcessList", 1) +} + +func (suite *SapControlGathererSuite) TestSapControlGathererMultipleInstaces() { + testFS := afero.NewMemMapFs() + err := testFS.MkdirAll("/usr/sap/PRD/ASCS00", 0644) + suite.NoError(err) + err = testFS.MkdirAll("/usr/sap/PRD/ERS10", 0644) + suite.NoError(err) + err = testFS.MkdirAll("/usr/sap/QAS/D01", 0644) + suite.NoError(err) + err = testFS.MkdirAll("/usr/sap/QAS/D02", 0644) + suite.NoError(err) + + mockWebService := new(sapControlMocks.WebService) + mockWebService.On("GetProcessList").Return(&sapcontrol.GetProcessListResponse{ + Processes: []*sapcontrol.OSProcess{ + { + Name: "process1", + }, + { + Name: "process2", + }, + }, + }, nil) + + mockWebServiceError := new(sapControlMocks.WebService) + mockWebServiceError.On("GetProcessList").Return(nil, fmt.Errorf("some error")) + + suite.webService. + On("New", "00").Return(mockWebService). + On("New", "10").Return(mockWebService). + On("New", "01").Return(mockWebService). + On("New", "02").Return(mockWebServiceError) + + gatherer := gatherers.NewSapControlGatherer(suite.webService, testFS) + + fr := []entities.FactRequest{ + { + Name: "sapcontrol", + Gatherer: "sapcontrol", + CheckID: "check1", + Argument: "GetProcessList", + }, + } + + expectedFacts := []entities.Fact{ + { + Name: "sapcontrol", + CheckID: "check1", + Value: &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "PRD": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "instance_nr": &entities.FactValueString{Value: "00"}, + "name": &entities.FactValueString{Value: "ASCS00"}, + "output": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "name": &entities.FactValueString{Value: "process1"}, + }, + }, + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "name": &entities.FactValueString{Value: "process2"}, + }, + }, + }, + }, + }, + }, + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "instance_nr": &entities.FactValueString{Value: "10"}, + "name": &entities.FactValueString{Value: "ERS10"}, + "output": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "name": &entities.FactValueString{Value: "process1"}, + }, + }, + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "name": &entities.FactValueString{Value: "process2"}, + }, + }, + }, + }, + }, + }, + }, + }, + "QAS": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "instance_nr": &entities.FactValueString{Value: "01"}, + "name": &entities.FactValueString{Value: "D01"}, + "output": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "name": &entities.FactValueString{Value: "process1"}, + }, + }, + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "name": &entities.FactValueString{Value: "process2"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + Error: nil, + }, + } + + results, err := gatherer.Gather(fr) + suite.NoError(err) + suite.EqualValues(expectedFacts, results) +} + +func (suite *SapControlGathererSuite) TestSapControlGathererGetSystemInstanceList() { + mockWebService := new(sapControlMocks.WebService) + mockWebService.On("GetSystemInstanceList").Return(&sapcontrol.GetSystemInstanceListResponse{ + Instances: []*sapcontrol.SAPInstance{ + { + Hostname: "host1", + }, + { + Hostname: "host2", + }, + }, + }, nil) + + suite.webService.On("New", "00").Return(mockWebService) + + gatherer := gatherers.NewSapControlGatherer(suite.webService, suite.testFS) + + fr := []entities.FactRequest{ + { + Name: "sapcontrol", + Gatherer: "sapcontrol", + CheckID: "check1", + Argument: "GetSystemInstanceList", + }, + } + + expectedFacts := []entities.Fact{ + { + Name: "sapcontrol", + CheckID: "check1", + Value: &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "PRD": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "instance_nr": &entities.FactValueString{Value: "00"}, + "name": &entities.FactValueString{Value: "ASCS00"}, + "output": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "hostname": &entities.FactValueString{Value: "host1"}, + "instance_nr": &entities.FactValueInt{Value: 0}, + }, + }, + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "hostname": &entities.FactValueString{Value: "host2"}, + "instance_nr": &entities.FactValueInt{Value: 0}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + Error: nil, + }, + } + + results, err := gatherer.Gather(fr) + suite.NoError(err) + suite.EqualValues(expectedFacts, results) +} + +func (suite *SapControlGathererSuite) TestSapControlGathererGetVersionInfo() { + mockWebService := new(sapControlMocks.WebService) + mockWebService.On("GetVersionInfo").Return(&sapcontrol.GetVersionInfoResponse{ + InstanceVersions: []*sapcontrol.VersionInfo{ + { + Filename: "/usr/sap/NWP/ERS10/exe/sapstartsrv", + VersionInfo: "753, patch 900, changelist 2094654, RKS compatibility level 1, optU (Oct 16 2021, 00:03:15), linuxx86_64", + }, + { + Filename: "/usr/sap/NWP/ERS10/exe/enq_server", + VersionInfo: "755, patch 905, changelist 2094660, RKS compatibility level 2, optU (Oct 16 2021, 00:03:15), arch", + }, + }, + }, nil) + + suite.webService.On("New", "00").Return(mockWebService) + + gatherer := gatherers.NewSapControlGatherer(suite.webService, suite.testFS) + + fr := []entities.FactRequest{ + { + Name: "sapcontrol", + Gatherer: "sapcontrol", + CheckID: "check1", + Argument: "GetVersionInfo", + }, + } + + expectedFacts := []entities.Fact{ + { + Name: "sapcontrol", + CheckID: "check1", + Value: &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "PRD": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "instance_nr": &entities.FactValueString{Value: "00"}, + "name": &entities.FactValueString{Value: "ASCS00"}, + "output": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "filename": &entities.FactValueString{Value: "/usr/sap/NWP/ERS10/exe/sapstartsrv"}, + "sap_kernel": &entities.FactValueString{Value: "753"}, + "patch": &entities.FactValueString{Value: "900"}, + "changelist": &entities.FactValueString{Value: "2094654"}, + "rks_compatibility_level": &entities.FactValueString{Value: "1"}, + "build": &entities.FactValueString{Value: "optU (Oct 16 2021, 00:03:15)"}, + "architecture": &entities.FactValueString{Value: "linuxx86_64"}, + }, + }, + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "filename": &entities.FactValueString{Value: "/usr/sap/NWP/ERS10/exe/enq_server"}, + "sap_kernel": &entities.FactValueString{Value: "755"}, + "patch": &entities.FactValueString{Value: "905"}, + "changelist": &entities.FactValueString{Value: "2094660"}, + "rks_compatibility_level": &entities.FactValueString{Value: "2"}, + "build": &entities.FactValueString{Value: "optU (Oct 16 2021, 00:03:15)"}, + "architecture": &entities.FactValueString{Value: "arch"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + Error: nil, + }, + } + + results, err := gatherer.Gather(fr) + suite.NoError(err) + suite.EqualValues(expectedFacts, results) +} + +func (suite *SapControlGathererSuite) TestSapControlGathererHACheckConfig() { + mockWebService := new(sapControlMocks.WebService) + mockWebService.On("HACheckConfig").Return(&sapcontrol.HACheckConfigResponse{ + Checks: []*sapcontrol.HACheck{ + { + Description: "desc1", + }, + { + Description: "desc2", + }, + }, + }, nil) + + suite.webService.On("New", "00").Return(mockWebService) + + gatherer := gatherers.NewSapControlGatherer(suite.webService, suite.testFS) + + fr := []entities.FactRequest{ + { + Name: "sapcontrol", + Gatherer: "sapcontrol", + CheckID: "check1", + Argument: "HACheckConfig", + }, + } + + expectedFacts := []entities.Fact{ + { + Name: "sapcontrol", + CheckID: "check1", + Value: &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "PRD": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "instance_nr": &entities.FactValueString{Value: "00"}, + "name": &entities.FactValueString{Value: "ASCS00"}, + "output": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "description": &entities.FactValueString{Value: "desc1"}, + }, + }, + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "description": &entities.FactValueString{Value: "desc2"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + Error: nil, + }, + } + + results, err := gatherer.Gather(fr) + suite.NoError(err) + suite.EqualValues(expectedFacts, results) +} + +func (suite *SapControlGathererSuite) TestSapControlGathererHAGetFailoverConfig() { + mockWebService := new(sapControlMocks.WebService) + mockWebService.On("HAGetFailoverConfig").Return(&sapcontrol.HAGetFailoverConfigResponse{ + HAActive: false, + HANodes: &[]string{"node1"}, + }, nil) + + suite.webService.On("New", "00").Return(mockWebService) + + gatherer := gatherers.NewSapControlGatherer(suite.webService, suite.testFS) + + fr := []entities.FactRequest{ + { + Name: "sapcontrol", + Gatherer: "sapcontrol", + CheckID: "check1", + Argument: "HAGetFailoverConfig", + }, + } + + expectedFacts := []entities.Fact{ + { + Name: "sapcontrol", + CheckID: "check1", + Value: &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "PRD": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "instance_nr": &entities.FactValueString{Value: "00"}, + "name": &entities.FactValueString{Value: "ASCS00"}, + "output": &entities.FactValueMap{ + Value: map[string]entities.FactValue{ + "ha_active": &entities.FactValueBool{Value: false}, + "ha_product_version": &entities.FactValueString{Value: ""}, + "ha_sap_interface_version": &entities.FactValueString{Value: ""}, + "ha_documentation": &entities.FactValueString{Value: ""}, + "ha_active_nodes": &entities.FactValueString{Value: ""}, + "ha_nodes": &entities.FactValueList{ + Value: []entities.FactValue{ + &entities.FactValueString{Value: "node1"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + Error: nil, + }, + } + + results, err := gatherer.Gather(fr) + suite.NoError(err) + suite.EqualValues(expectedFacts, results) +}