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 }