feat: process group tag for groundwork output plugin (#10499)

Co-authored-by: Pavlo Sumkin <pavlo@bluesunrise.com>
This commit is contained in:
Pavlo Sumkin 2022-01-27 19:25:28 +02:00 committed by GitHub
parent a888d0233b
commit e4f040a2df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 114 additions and 22 deletions

View File

@ -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. ## The name of the tag that contains the hostname.
# resource_tag = "host" # resource_tag = "host"
## The name of the tag that contains the host group name.
# group_tag = "group"
``` ```
## List of tags used by the plugin ## 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. * 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". * 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. * message - to provide any message you want.

View File

@ -36,8 +36,16 @@ const sampleConfig = `
## The name of the tag that contains the hostname. ## The name of the tag that contains the hostname.
# resource_tag = "host" # 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 { type Groundwork struct {
Server string `toml:"url"` Server string `toml:"url"`
AgentID string `toml:"agent_id"` AgentID string `toml:"agent_id"`
@ -45,6 +53,7 @@ type Groundwork struct {
Password string `toml:"password"` Password string `toml:"password"`
DefaultHost string `toml:"default_host"` DefaultHost string `toml:"default_host"`
DefaultServiceState string `toml:"default_service_state"` DefaultServiceState string `toml:"default_service_state"`
GroupTag string `toml:"group_tag"`
ResourceTag string `toml:"resource_tag"` ResourceTag string `toml:"resource_tag"`
Log telegraf.Logger `toml:"-"` Log telegraf.Logger `toml:"-"`
client clients.GWClient client clients.GWClient
@ -123,14 +132,39 @@ func (g *Groundwork) Close() error {
} }
func (g *Groundwork) Write(metrics []telegraf.Metric) error { func (g *Groundwork) Write(metrics []telegraf.Metric) error {
groupMap := make(map[string][]transit.ResourceRef)
resourceToServicesMap := make(map[string][]transit.MonitoredService) resourceToServicesMap := make(map[string][]transit.MonitoredService)
for _, metric := range metrics { for _, metric := range metrics {
resource, service, err := g.parseMetric(metric) meta, service, err := g.parseMetric(metric)
if err != nil { if err != nil {
g.Log.Errorf("%v", err) g.Log.Errorf("%v", err)
continue continue
} }
resource := meta.resource
resourceToServicesMap[resource] = append(resourceToServicesMap[resource], *service) 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 var resources []transit.MonitoredResource
@ -163,7 +197,7 @@ func (g *Groundwork) Write(metrics []telegraf.Metric) error {
Version: transit.ModelVersion, Version: transit.ModelVersion,
}, },
Resources: resources, Resources: resources,
Groups: nil, Groups: groups,
}) })
if err != nil { if err != nil {
@ -185,6 +219,7 @@ func (g *Groundwork) Description() string {
func init() { func init() {
outputs.Add("groundwork", func() telegraf.Output { outputs.Add("groundwork", func() telegraf.Output {
return &Groundwork{ return &Groundwork{
GroupTag: "group",
ResourceTag: "host", ResourceTag: "host",
DefaultHost: "telegraf", DefaultHost: "telegraf",
DefaultServiceState: string(transit.ServiceOk), DefaultServiceState: string(transit.ServiceOk),
@ -192,7 +227,9 @@ func init() {
}) })
} }
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 resource := g.DefaultHost
if value, present := metric.GetTag(g.ResourceTag); present { if value, present := metric.GetTag(g.ResourceTag); present {
resource = value resource = value
@ -302,7 +339,7 @@ func (g *Groundwork) parseMetric(metric telegraf.Metric) (string, *transit.Monit
serviceObject.Status = serviceStatus serviceObject.Status = serviceStatus
} }
return resource, &serviceObject, nil return metricMeta{resource: resource, group: group}, &serviceObject, nil
} }
func validStatus(status string) bool { func validStatus(status string) bool {

View File

@ -20,33 +20,26 @@ const (
defaultHost = "telegraf" defaultHost = "telegraf"
) )
func TestWrite(t *testing.T) { func TestWriteWithDefaults(t *testing.T) {
// Generate test metric with default name to test Write logic // 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 // Simulate Groundwork server that should receive custom metrics
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body) body, err := ioutil.ReadAll(r.Body)
require.NoError(t, err) require.NoError(t, err)
// Decode body to use in assertations below // Decode body to use in assertions below
var obj groundworkObject var obj groundworkObject
err = json.Unmarshal(body, &obj) err = json.Unmarshal(body, &obj)
require.NoError(t, err) require.NoError(t, err)
// Check if server gets valid metrics object // Check if server gets valid metrics object
require.Equal(t, obj.Context.AgentID, defaultTestAgentID) require.Equal(t, defaultTestAgentID, obj.Context.AgentID)
require.Equal(t, obj.Resources[0].Name, defaultHost) require.Equal(t, defaultHost, obj.Resources[0].Name)
require.Equal( require.Equal(t, "IntMetric", obj.Resources[0].Services[0].Name)
t, require.Equal(t, int64(42), obj.Resources[0].Services[0].Metrics[0].Value.IntegerValue)
obj.Resources[0].Services[0].Name, require.Equal(t, 0, len(obj.Groups))
"Float",
)
require.Equal(
t,
obj.Resources[0].Services[0].Metrics[0].Value.DoubleValue,
1.0,
)
_, err = fmt.Fprintln(w, `OK`) _, err = fmt.Fprintln(w, `OK`)
require.NoError(t, err) require.NoError(t, err)
@ -55,7 +48,56 @@ func TestWrite(t *testing.T) {
i := Groundwork{ i := Groundwork{
Server: server.URL, Server: server.URL,
AgentID: defaultTestAgentID, 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")
// 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{ client: clients.GWClient{
AppName: "telegraf", AppName: "telegraf",
AppType: "TELEGRAF", AppType: "TELEGRAF",
@ -81,10 +123,18 @@ type groundworkObject struct {
Name string `json:"name"` Name string `json:"name"`
Metrics []struct { Metrics []struct {
Value struct { Value struct {
StringValue string `json:"stringValue"` DoubleValue float64 `json:"doubleValue"`
DoubleValue float64 `json:"doubleValue"` IntegerValue int64 `json:"integerValue"`
} `json:"value"` } `json:"value"`
} }
} `json:"services"` } `json:"services"`
} `json:"resources"` } `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"`
} }