Skip to content

Commit

Permalink
Add cibadmin gatherer based on dot access maps
Browse files Browse the repository at this point in the history
  • Loading branch information
arbulu89 committed Dec 2, 2022
1 parent 6bde26d commit 7a275da
Show file tree
Hide file tree
Showing 6 changed files with 274 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/trento-project/agent
go 1.18

require (
github.com/clbanning/mxj/v2 v2.5.7
github.com/coreos/go-systemd/v22 v22.5.0
github.com/google/uuid v1.3.0
github.com/hashicorp/go-envparse v0.1.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/clbanning/mxj/v2 v2.5.7 h1:7q5lvUpaPF/WOkqgIDiwjBJaznaLCCBd78pi8ZyAnE0=
github.com/clbanning/mxj/v2 v2.5.7/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
Expand Down
73 changes: 73 additions & 0 deletions internal/factsengine/gatherers/cibadmin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package gatherers

import (
log "github.com/sirupsen/logrus"
"github.com/trento-project/agent/pkg/factsengine/entities"
"github.com/trento-project/agent/pkg/utils"
)

const (
CibAdminGathererName = "cibadmin"
)

// nolint:gochecknoglobals
var (
CibAdminCommandError = entities.FactGatheringError{
Type: "cibadmin-command-error",
Message: "error running crm_mon command",
}

CibAdminDecodingError = entities.FactGatheringError{
Type: "cibadmin-decoding-error",
Message: "error decoding cibadmin output",
}
)

type CibAdminGatherer struct {
executor utils.CommandExecutor
}

func NewDefaultCibAdminGatherer() *CibAdminGatherer {
return NewCibAdminGatherer(utils.Executor{})
}

func NewCibAdminGatherer(executor utils.CommandExecutor) *CibAdminGatherer {
return &CibAdminGatherer{
executor: executor,
}
}

func (g *CibAdminGatherer) Gather(factsRequests []entities.FactRequest) ([]entities.Fact, error) {
log.Infof("Starting %s facts gathering process", CibAdminGathererName)

cibadmin, err := g.executor.Exec("cibadmin", "--query", "--local")
if err != nil {
return nil, CibAdminCommandError.Wrap(err.Error())
}

elementsToList := []string{"primitive", "clone", "master", "group",
"nvpair", "op", "rsc_location", "rsc_order", "rsc_colocation"}

factValueMap, err := parseXMLToFactValueMap(cibadmin, elementsToList)
if err != nil {
return nil, CibAdminDecodingError.Wrap(err.Error())
}

facts := []entities.Fact{}

for _, factReq := range factsRequests {
var fact entities.Fact

if value, err := factValueMap.GetValue(factReq.Argument); err == nil {
fact = entities.NewFactGatheredWithRequest(factReq, value)

} else {
log.Error(err)
fact = entities.NewFactGatheredWithError(factReq, err)
}
facts = append(facts, fact)
}

log.Infof("Requested %s facts gathered", CibAdminGathererName)
return facts, err
}
138 changes: 138 additions & 0 deletions internal/factsengine/gatherers/cibadmin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
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 CibAdminTestSuite struct {
suite.Suite
mockExecutor *utilsMocks.CommandExecutor
cibAdminOutput []byte
}

func TestCibAdminTestSuite(t *testing.T) {
suite.Run(t, new(CibAdminTestSuite))
}

func (suite *CibAdminTestSuite) SetupSuite() {
lFile, _ := os.Open(helpers.GetFixturePath("gatherers/cibadmin.xml"))
content, _ := io.ReadAll(lFile)

suite.cibAdminOutput = content
}

func (suite *CibAdminTestSuite) SetupTest() {
suite.mockExecutor = new(utilsMocks.CommandExecutor)
}

func (suite *CibAdminTestSuite) TestCibAdminGatherCmdNotFound() {
suite.mockExecutor.On("Exec", "cibadmin", "--query", "--local").Return(
suite.cibAdminOutput, errors.New("cibadmin not found"))

p := gatherers.NewCibAdminGatherer(suite.mockExecutor)

factRequests := []entities.FactRequest{
{
Name: "cib",
Gatherer: "cibadmin",
Argument: "cib",
CheckID: "check1",
},
}

_, err := p.Gather(factRequests)

suite.EqualError(err, "fact gathering error: cibadmin-command-error - "+
"error running crm_mon command: cibadmin not found")
}

func (suite *CibAdminTestSuite) TestCibAdminInvalidXML() {
suite.mockExecutor.On("Exec", "cibadmin", "--query", "--local").Return(
[]byte("invalid"), nil)

p := gatherers.NewCibAdminGatherer(suite.mockExecutor)

factRequests := []entities.FactRequest{
{
Name: "cib",
Gatherer: "cibadmin",
Argument: "cib",
CheckID: "check1",
},
}

_, err := p.Gather(factRequests)

suite.EqualError(err, "fact gathering error: cibadmin-decoding-error - "+
"error decoding cibadmin output: EOF")
}

func (suite *CibAdminTestSuite) TestCibAdminGather() {
suite.mockExecutor.On("Exec", "cibadmin", "--query", "--local").Return(
suite.cibAdminOutput, nil)

p := gatherers.NewCibAdminGatherer(suite.mockExecutor)

factRequests := []entities.FactRequest{
{
Name: "sid",
Gatherer: "cibadmin",
Argument: "cib.configuration.resources.master.0.primitive.0.instance_attributes.nvpair.0.value",
CheckID: "check1",
},
{
Name: "nvpair",
Gatherer: "cibadmin",
Argument: "cib.configuration.crm_config.cluster_property_set.nvpair.0",
CheckID: "check2",
},
{
Name: "not_found",
Gatherer: "cibadmin",
Argument: "cib.not_found.crm_config",
CheckID: "check3",
},
}

factResults, err := p.Gather(factRequests)

expectedResults := []entities.Fact{
{
Name: "sid",
Value: &entities.FactValueString{Value: "PRD"},
CheckID: "check1",
},
{
Name: "nvpair",
Value: &entities.FactValueMap{
Value: map[string]entities.FactValue{
"id": &entities.FactValueString{Value: "cib-bootstrap-options-have-watchdog"},
"name": &entities.FactValueString{Value: "have-watchdog"},
"value": &entities.FactValueBool{Value: true},
},
},
CheckID: "check2",
},
{
Name: "not_found",
Value: nil,
CheckID: "check3",
Error: &entities.FactGatheringError{
Type: "value-not-found",
Message: "error getting value: requested field value not found: " +
"cib.not_found.crm_config"},
},
}

suite.NoError(err)
suite.ElementsMatch(expectedResults, factResults)
}
1 change: 1 addition & 0 deletions internal/factsengine/gatherers/gatherer.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type FactGatherer interface {

func StandardGatherers() map[string]FactGatherer {
return map[string]FactGatherer{
CibAdminGathererName: NewDefaultCibAdminGatherer(),
CorosyncCmapCtlGathererName: NewDefaultCorosyncCmapctlGatherer(),
CorosyncConfGathererName: NewDefaultCorosyncConfGatherer(),
HostsFileGathererName: NewDefaultHostsFileGatherer(),
Expand Down
59 changes: 59 additions & 0 deletions internal/factsengine/gatherers/xml.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package gatherers

import (
"fmt"

"github.com/clbanning/mxj/v2"
"github.com/trento-project/agent/pkg/factsengine/entities"
)

func init() {
mxj.PrependAttrWithHyphen(false)
}

func parseXMLToFactValueMap(xmlContent []byte, elementsToList []string) (*entities.FactValueMap, error) {
mv, err := mxj.NewMapXml(xmlContent)
if err != nil {
return nil, err
}

for _, element := range elementsToList {
err = convertList(&mv, element)
if err != nil {
return nil, fmt.Errorf("error converting %s to list", element)
}
}

mapValue := map[string]interface{}(mv)
factValue := entities.ParseInterfaceFactValue(mapValue)
factValueMap, ok := factValue.(*entities.FactValueMap)
if !ok {
return nil, fmt.Errorf("error converting to FactValueMap")
}

return factValueMap, nil
}

// convertList converts given keys to list if only one value was present
// this is needed as many fields are lists even though they might have
// one element
func convertList(mv *mxj.Map, key string) error {
paths := mv.PathsForKey(key)
for _, path := range paths {
value, err := mv.ValuesForPath(path)
if err != nil {
return err
}

values := map[string]interface{}{
key: value,
}

_, err = mv.UpdateValuesForPath(values, path)
if err != nil {
return err
}
}

return nil
}

0 comments on commit 7a275da

Please sign in to comment.