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

feat: process group tag for groundwork output plugin #10499

Merged
merged 2 commits into from
Jan 27, 2022
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
5 changes: 5 additions & 0 deletions plugins/outputs/groundwork/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,15 @@ This plugin writes to a [GroundWork Monitor][1] instance. Plugin only supports G

## The name of the tag that contains the hostname.
# resource_tag = "host"

## The name of the tag that contains the host group name.
# group_tag = "group"
```

## List of tags used by the plugin

* group - to define the name of the group you want to monitor, can be changed with config.
* host - to define the name of the host you want to monitor, can be changed with config.
* service - to define the name of the service you want to monitor.
* status - to define the status of the service. Supported statuses: "SERVICE_OK", "SERVICE_WARNING", "SERVICE_UNSCHEDULED_CRITICAL", "SERVICE_PENDING", "SERVICE_SCHEDULED_CRITICAL", "SERVICE_UNKNOWN".
* message - to provide any message you want.
Expand Down
45 changes: 41 additions & 4 deletions plugins/outputs/groundwork/groundwork.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,24 @@ const sampleConfig = `

## The name of the tag that contains the hostname.
# resource_tag = "host"

## The name of the tag that contains the host group name.
# group_tag = "group"
`

type metricMeta struct {
group string
resource string
}

type Groundwork struct {
Server string `toml:"url"`
AgentID string `toml:"agent_id"`
Username string `toml:"username"`
Password string `toml:"password"`
DefaultHost string `toml:"default_host"`
DefaultServiceState string `toml:"default_service_state"`
GroupTag string `toml:"group_tag"`
ResourceTag string `toml:"resource_tag"`
Log telegraf.Logger `toml:"-"`
client clients.GWClient
Expand Down Expand Up @@ -123,14 +132,39 @@ func (g *Groundwork) Close() error {
}

func (g *Groundwork) Write(metrics []telegraf.Metric) error {
groupMap := make(map[string][]transit.ResourceRef)
resourceToServicesMap := make(map[string][]transit.MonitoredService)
for _, metric := range metrics {
resource, service, err := g.parseMetric(metric)
meta, service, err := g.parseMetric(metric)
if err != nil {
g.Log.Errorf("%v", err)
continue
}
resource := meta.resource
resourceToServicesMap[resource] = append(resourceToServicesMap[resource], *service)

group := meta.group
if len(group) != 0 {
resRef := transit.ResourceRef{
Name: resource,
Type: transit.ResourceTypeHost,
}
if refs, ok := groupMap[group]; ok {
refs = append(refs, resRef)
groupMap[group] = refs
} else {
groupMap[group] = []transit.ResourceRef{resRef}
}
}
}

groups := make([]transit.ResourceGroup, 0, len(groupMap))
for groupName, refs := range groupMap {
groups = append(groups, transit.ResourceGroup{
GroupName: groupName,
Resources: refs,
Type: transit.HostGroup,
})
}

var resources []transit.MonitoredResource
Expand Down Expand Up @@ -163,7 +197,7 @@ func (g *Groundwork) Write(metrics []telegraf.Metric) error {
Version: transit.ModelVersion,
},
Resources: resources,
Groups: nil,
Groups: groups,
})

if err != nil {
Expand All @@ -185,14 +219,17 @@ func (g *Groundwork) Description() string {
func init() {
outputs.Add("groundwork", func() telegraf.Output {
return &Groundwork{
GroupTag: "group",
ResourceTag: "host",
DefaultHost: "telegraf",
DefaultServiceState: string(transit.ServiceOk),
}
})
}

func (g *Groundwork) parseMetric(metric telegraf.Metric) (string, *transit.MonitoredService, error) {
func (g *Groundwork) parseMetric(metric telegraf.Metric) (metricMeta, *transit.MonitoredService, error) {
group, _ := metric.GetTag(g.GroupTag)

resource := g.DefaultHost
if value, present := metric.GetTag(g.ResourceTag); present {
resource = value
Expand Down Expand Up @@ -302,7 +339,7 @@ func (g *Groundwork) parseMetric(metric telegraf.Metric) (string, *transit.Monit
serviceObject.Status = serviceStatus
}

return resource, &serviceObject, nil
return metricMeta{resource: resource, group: group}, &serviceObject, nil
}

func validStatus(status string) bool {
Expand Down
86 changes: 68 additions & 18 deletions plugins/outputs/groundwork/groundwork_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,26 @@ const (
defaultHost = "telegraf"
)

func TestWrite(t *testing.T) {
func TestWriteWithDefaults(t *testing.T) {
// Generate test metric with default name to test Write logic
floatMetric := testutil.TestMetric(1.0, "Float")
intMetric := testutil.TestMetric(42, "IntMetric")

// Simulate Groundwork server that should receive custom metrics
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
require.NoError(t, err)

// Decode body to use in assertations below
// Decode body to use in assertions below
var obj groundworkObject
err = json.Unmarshal(body, &obj)
require.NoError(t, err)

// Check if server gets valid metrics object
require.Equal(t, obj.Context.AgentID, defaultTestAgentID)
require.Equal(t, obj.Resources[0].Name, defaultHost)
require.Equal(
t,
obj.Resources[0].Services[0].Name,
"Float",
)
require.Equal(
t,
obj.Resources[0].Services[0].Metrics[0].Value.DoubleValue,
1.0,
)
require.Equal(t, defaultTestAgentID, obj.Context.AgentID)
require.Equal(t, defaultHost, obj.Resources[0].Name)
require.Equal(t, "IntMetric", obj.Resources[0].Services[0].Name)
require.Equal(t, int64(42), obj.Resources[0].Services[0].Metrics[0].Value.IntegerValue)
require.Equal(t, 0, len(obj.Groups))

_, err = fmt.Fprintln(w, `OK`)
require.NoError(t, err)
Expand All @@ -55,7 +48,56 @@ func TestWrite(t *testing.T) {
i := Groundwork{
Server: server.URL,
AgentID: defaultTestAgentID,
DefaultHost: "telegraf",
DefaultHost: defaultHost,
client: clients.GWClient{
AppName: "telegraf",
AppType: "TELEGRAF",
GWConnection: &clients.GWConnection{
HostName: server.URL,
},
},
}

err := i.Write([]telegraf.Metric{intMetric})
require.NoError(t, err)

defer server.Close()
}

func TestWriteWithTags(t *testing.T) {
// Generate test metric with tags to test Write logic
floatMetric := testutil.TestMetric(1.0, "FloatMetric")
floatMetric.AddTag("host", "Host01")
floatMetric.AddTag("group", "Group01")
Hipska marked this conversation as resolved.
Show resolved Hide resolved

// Simulate Groundwork server that should receive custom metrics
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
require.NoError(t, err)

// Decode body to use in assertions below
var obj groundworkObject
err = json.Unmarshal(body, &obj)
require.NoError(t, err)

// Check if server gets valid metrics object
require.Equal(t, defaultTestAgentID, obj.Context.AgentID)
require.Equal(t, "Host01", obj.Resources[0].Name)
require.Equal(t, "FloatMetric", obj.Resources[0].Services[0].Name)
require.Equal(t, 1.0, obj.Resources[0].Services[0].Metrics[0].Value.DoubleValue)
require.Equal(t, "Group01", obj.Groups[0].GroupName)
require.Equal(t, "Host01", obj.Groups[0].Resources[0].Name)

_, err = fmt.Fprintln(w, `OK`)
require.NoError(t, err)
}))

i := Groundwork{
Server: server.URL,
AgentID: defaultTestAgentID,
DefaultHost: defaultHost,
GroupTag: "group",
ResourceTag: "host",
client: clients.GWClient{
AppName: "telegraf",
AppType: "TELEGRAF",
Expand All @@ -81,10 +123,18 @@ type groundworkObject struct {
Name string `json:"name"`
Metrics []struct {
Value struct {
StringValue string `json:"stringValue"`
DoubleValue float64 `json:"doubleValue"`
DoubleValue float64 `json:"doubleValue"`
IntegerValue int64 `json:"integerValue"`
} `json:"value"`
}
} `json:"services"`
} `json:"resources"`
Groups []struct {
Type string `json:"type"`
GroupName string `json:"groupName"`
Resources []struct {
Name string `json:"name"`
Type string `json:"type"`
} `json:"resources"`
} `json:"groups"`
}