forked from influxdata/telegraf
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add new groundwork output plugin (influxdata#9891)
- Loading branch information
Vladislav
authored
Nov 30, 2021
1 parent
d9eb4d0
commit 27dea9b
Showing
7 changed files
with
429 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# GroundWork Output Plugin | ||
|
||
This plugin writes to a [GroundWork Monitor][1] instance. Plugin only supports GW8+ | ||
|
||
[1]: https://www.gwos.com/product/groundwork-monitor/ | ||
|
||
## Configuration | ||
|
||
```toml | ||
[[outputs.groundwork]] | ||
## URL of your groundwork instance. | ||
url = "https://groundwork.example.com" | ||
|
||
## Agent uuid for GroundWork API Server. | ||
agent_id = "" | ||
|
||
## Username and password to access GroundWork API. | ||
username = "" | ||
password = "" | ||
|
||
## Default display name for the host with services(metrics). | ||
# default_host = "telegraf" | ||
|
||
## Default service state. | ||
# default_service_state = "SERVICE_OK" | ||
|
||
## The name of the tag that contains the hostname. | ||
# resource_tag = "host" | ||
``` | ||
|
||
## List of tags used by the plugin | ||
|
||
* service - to define the name of the service you want to monitor. | ||
* status - to define the status of the service. | ||
* message - to provide any message you want. | ||
* unitType - to use in monitoring contexts(subset of The Unified Code for Units of Measure standard). Supported types: "1", "%cpu", "KB", "GB", "MB". | ||
* warning - to define warning threshold value. | ||
* critical - to define critical threshold value. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,289 @@ | ||
package groundwork | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"strconv" | ||
|
||
"github.com/gwos/tcg/sdk/clients" | ||
"github.com/gwos/tcg/sdk/transit" | ||
"github.com/hashicorp/go-uuid" | ||
|
||
"github.com/influxdata/telegraf" | ||
"github.com/influxdata/telegraf/plugins/outputs" | ||
) | ||
|
||
const sampleConfig = ` | ||
## URL of your groundwork instance. | ||
url = "https://groundwork.example.com" | ||
## Agent uuid for GroundWork API Server. | ||
agent_id = "" | ||
## Username and password to access GroundWork API. | ||
username = "" | ||
password = "" | ||
## Default display name for the host with services(metrics). | ||
# default_host = "telegraf" | ||
## Default service state. | ||
# default_service_state = "SERVICE_OK" | ||
## The name of the tag that contains the hostname. | ||
# resource_tag = "host" | ||
` | ||
|
||
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"` | ||
ResourceTag string `toml:"resource_tag"` | ||
Log telegraf.Logger `toml:"-"` | ||
client clients.GWClient | ||
} | ||
|
||
func (g *Groundwork) SampleConfig() string { | ||
return sampleConfig | ||
} | ||
|
||
func (g *Groundwork) Init() error { | ||
if g.Server == "" { | ||
return errors.New("no 'url' provided") | ||
} | ||
if g.AgentID == "" { | ||
return errors.New("no 'agent_id' provided") | ||
} | ||
if g.Username == "" { | ||
return errors.New("no 'username' provided") | ||
} | ||
if g.Password == "" { | ||
return errors.New("no 'password' provided") | ||
} | ||
if g.DefaultHost == "" { | ||
return errors.New("no 'default_host' provided") | ||
} | ||
if g.ResourceTag == "" { | ||
return errors.New("no 'resource_tag' provided") | ||
} | ||
if !validStatus(g.DefaultServiceState) { | ||
return errors.New("invalid 'default_service_state' provided") | ||
} | ||
|
||
g.client = clients.GWClient{ | ||
AppName: "telegraf", | ||
AppType: "TELEGRAF", | ||
GWConnection: &clients.GWConnection{ | ||
HostName: g.Server, | ||
UserName: g.Username, | ||
Password: g.Password, | ||
IsDynamicInventory: true, | ||
}, | ||
} | ||
return nil | ||
} | ||
|
||
func (g *Groundwork) Connect() error { | ||
err := g.client.Connect() | ||
if err != nil { | ||
return fmt.Errorf("could not log in: %v", err) | ||
} | ||
return nil | ||
} | ||
|
||
func (g *Groundwork) Close() error { | ||
err := g.client.Disconnect() | ||
if err != nil { | ||
return fmt.Errorf("could not log out: %v", err) | ||
} | ||
return nil | ||
} | ||
|
||
func (g *Groundwork) Write(metrics []telegraf.Metric) error { | ||
resourceToServicesMap := make(map[string][]transit.DynamicMonitoredService) | ||
for _, metric := range metrics { | ||
resource, service, err := g.parseMetric(metric) | ||
if err != nil { | ||
g.Log.Errorf("%v", err) | ||
continue | ||
} | ||
resourceToServicesMap[resource] = append(resourceToServicesMap[resource], *service) | ||
} | ||
|
||
var resources []transit.DynamicMonitoredResource | ||
for resourceName, services := range resourceToServicesMap { | ||
resources = append(resources, transit.DynamicMonitoredResource{ | ||
BaseResource: transit.BaseResource{ | ||
BaseTransitData: transit.BaseTransitData{ | ||
Name: resourceName, | ||
Type: transit.Host, | ||
}, | ||
}, | ||
Status: transit.HostUp, | ||
LastCheckTime: transit.NewTimestamp(), | ||
Services: services, | ||
}) | ||
} | ||
|
||
traceToken, err := uuid.GenerateUUID() | ||
if err != nil { | ||
return err | ||
} | ||
requestJSON, err := json.Marshal(transit.DynamicResourcesWithServicesRequest{ | ||
Context: &transit.TracerContext{ | ||
AppType: "TELEGRAF", | ||
AgentID: g.AgentID, | ||
TraceToken: traceToken, | ||
TimeStamp: transit.NewTimestamp(), | ||
Version: transit.ModelVersion, | ||
}, | ||
Resources: resources, | ||
Groups: nil, | ||
}) | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
_, err = g.client.SendResourcesWithMetrics(context.Background(), requestJSON) | ||
if err != nil { | ||
return fmt.Errorf("error while sending: %v", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (g *Groundwork) Description() string { | ||
return "Send telegraf metrics to GroundWork Monitor" | ||
} | ||
|
||
func init() { | ||
outputs.Add("groundwork", func() telegraf.Output { | ||
return &Groundwork{ | ||
ResourceTag: "host", | ||
DefaultHost: "telegraf", | ||
DefaultServiceState: string(transit.ServiceOk), | ||
} | ||
}) | ||
} | ||
|
||
func (g *Groundwork) parseMetric(metric telegraf.Metric) (string, *transit.DynamicMonitoredService, error) { | ||
resource := g.DefaultHost | ||
if value, present := metric.GetTag(g.ResourceTag); present { | ||
resource = value | ||
} | ||
|
||
service := metric.Name() | ||
if value, present := metric.GetTag("service"); present { | ||
service = value | ||
} | ||
|
||
status := g.DefaultServiceState | ||
value, statusPresent := metric.GetTag("status") | ||
if validStatus(value) { | ||
status = value | ||
} | ||
|
||
message, _ := metric.GetTag("message") | ||
|
||
unitType := string(transit.UnitCounter) | ||
if value, present := metric.GetTag("unitType"); present { | ||
unitType = value | ||
} | ||
|
||
var critical float64 | ||
value, criticalPresent := metric.GetTag("critical") | ||
if criticalPresent { | ||
if s, err := strconv.ParseFloat(value, 64); err == nil { | ||
critical = s | ||
} | ||
} | ||
|
||
var warning float64 | ||
value, warningPresent := metric.GetTag("warning") | ||
if warningPresent { | ||
if s, err := strconv.ParseFloat(value, 64); err == nil { | ||
warning = s | ||
} | ||
} | ||
|
||
lastCheckTime := transit.NewTimestamp() | ||
lastCheckTime.Time = metric.Time() | ||
serviceObject := transit.DynamicMonitoredService{ | ||
BaseTransitData: transit.BaseTransitData{ | ||
Name: service, | ||
Type: transit.Service, | ||
Owner: resource, | ||
}, | ||
Status: transit.MonitorStatus(status), | ||
LastCheckTime: lastCheckTime, | ||
LastPlugInOutput: message, | ||
Metrics: nil, | ||
} | ||
|
||
for _, value := range metric.FieldList() { | ||
var thresholds []transit.ThresholdValue | ||
if warningPresent { | ||
thresholds = append(thresholds, transit.ThresholdValue{ | ||
SampleType: transit.Warning, | ||
Label: value.Key + "_wn", | ||
Value: &transit.TypedValue{ | ||
ValueType: transit.DoubleType, | ||
DoubleValue: warning, | ||
}, | ||
}) | ||
} | ||
if criticalPresent { | ||
thresholds = append(thresholds, transit.ThresholdValue{ | ||
SampleType: transit.Critical, | ||
Label: value.Key + "_cr", | ||
Value: &transit.TypedValue{ | ||
ValueType: transit.DoubleType, | ||
DoubleValue: critical, | ||
}, | ||
}) | ||
} | ||
|
||
typedValue := new(transit.TypedValue) | ||
err := typedValue.FromInterface(value.Value) | ||
if err != nil { | ||
return "", nil, err | ||
} | ||
|
||
serviceObject.Metrics = append(serviceObject.Metrics, transit.TimeSeries{ | ||
MetricName: value.Key, | ||
SampleType: transit.Value, | ||
Interval: &transit.TimeInterval{ | ||
EndTime: lastCheckTime, | ||
}, | ||
Value: typedValue, | ||
Unit: transit.UnitType(unitType), | ||
Thresholds: &thresholds, | ||
}) | ||
} | ||
|
||
if !statusPresent { | ||
serviceStatus, err := transit.CalculateServiceStatus(&serviceObject.Metrics) | ||
if err != nil { | ||
g.Log.Infof("could not calculate service status, reverting to default_service_state: %v", err) | ||
serviceObject.Status = transit.MonitorStatus(g.DefaultServiceState) | ||
} | ||
serviceObject.Status = serviceStatus | ||
} | ||
|
||
return resource, &serviceObject, nil | ||
} | ||
|
||
func validStatus(status string) bool { | ||
switch transit.MonitorStatus(status) { | ||
case transit.ServiceOk, transit.ServiceWarning, transit.ServicePending, transit.ServiceScheduledCritical, | ||
transit.ServiceUnscheduledCritical, transit.ServiceUnknown: | ||
return true | ||
} | ||
return false | ||
} |
Oops, something went wrong.