281 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			281 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Go
		
	
	
	
| package aurora
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"net/http"
 | |
| 	"net/url"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/influxdata/telegraf"
 | |
| 	"github.com/influxdata/telegraf/internal"
 | |
| 	"github.com/influxdata/telegraf/internal/tls"
 | |
| 	"github.com/influxdata/telegraf/plugins/inputs"
 | |
| )
 | |
| 
 | |
| type RoleType int
 | |
| 
 | |
| const (
 | |
| 	Unknown RoleType = iota
 | |
| 	Leader
 | |
| 	Follower
 | |
| )
 | |
| 
 | |
| func (r RoleType) String() string {
 | |
| 	switch r {
 | |
| 	case Leader:
 | |
| 		return "leader"
 | |
| 	case Follower:
 | |
| 		return "follower"
 | |
| 	default:
 | |
| 		return "unknown"
 | |
| 	}
 | |
| }
 | |
| 
 | |
| var (
 | |
| 	defaultTimeout = 5 * time.Second
 | |
| 	defaultRoles   = []string{"leader", "follower"}
 | |
| )
 | |
| 
 | |
| type Vars map[string]interface{}
 | |
| 
 | |
| type Aurora struct {
 | |
| 	Schedulers []string          `toml:"schedulers"`
 | |
| 	Roles      []string          `toml:"roles"`
 | |
| 	Timeout    internal.Duration `toml:"timeout"`
 | |
| 	Username   string            `toml:"username"`
 | |
| 	Password   string            `toml:"password"`
 | |
| 	tls.ClientConfig
 | |
| 
 | |
| 	client *http.Client
 | |
| 	urls   []*url.URL
 | |
| }
 | |
| 
 | |
| var sampleConfig = `
 | |
|   ## Schedulers are the base addresses of your Aurora Schedulers
 | |
|   schedulers = ["http://127.0.0.1:8081"]
 | |
| 
 | |
|   ## Set of role types to collect metrics from.
 | |
|   ##
 | |
|   ## The scheduler roles are checked each interval by contacting the
 | |
|   ## scheduler nodes; zookeeper is not contacted.
 | |
|   # roles = ["leader", "follower"]
 | |
| 
 | |
|   ## Timeout is the max time for total network operations.
 | |
|   # timeout = "5s"
 | |
| 
 | |
|   ## Username and password are sent using HTTP Basic Auth.
 | |
|   # username = "username"
 | |
|   # password = "pa$$word"
 | |
| 
 | |
|   ## Optional TLS Config
 | |
|   # tls_ca = "/etc/telegraf/ca.pem"
 | |
|   # tls_cert = "/etc/telegraf/cert.pem"
 | |
|   # tls_key = "/etc/telegraf/key.pem"
 | |
|   ## Use TLS but skip chain & host verification
 | |
|   # insecure_skip_verify = false
 | |
| `
 | |
| 
 | |
| func (a *Aurora) SampleConfig() string {
 | |
| 	return sampleConfig
 | |
| }
 | |
| 
 | |
| func (a *Aurora) Description() string {
 | |
| 	return "Gather metrics from Apache Aurora schedulers"
 | |
| }
 | |
| 
 | |
| func (a *Aurora) Gather(acc telegraf.Accumulator) error {
 | |
| 	if a.client == nil {
 | |
| 		err := a.initialize()
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	ctx, cancel := context.WithTimeout(context.Background(), a.Timeout.Duration)
 | |
| 	defer cancel()
 | |
| 
 | |
| 	var wg sync.WaitGroup
 | |
| 	for _, u := range a.urls {
 | |
| 		wg.Add(1)
 | |
| 		go func(u *url.URL) {
 | |
| 			defer wg.Done()
 | |
| 			role, err := a.gatherRole(ctx, u)
 | |
| 			if err != nil {
 | |
| 				acc.AddError(fmt.Errorf("%s: %v", u, err))
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 			if !a.roleEnabled(role) {
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 			err = a.gatherScheduler(ctx, u, role, acc)
 | |
| 			if err != nil {
 | |
| 				acc.AddError(fmt.Errorf("%s: %v", u, err))
 | |
| 			}
 | |
| 		}(u)
 | |
| 	}
 | |
| 	wg.Wait()
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (a *Aurora) initialize() error {
 | |
| 	tlsCfg, err := a.ClientConfig.TLSConfig()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	client := &http.Client{
 | |
| 		Transport: &http.Transport{
 | |
| 			Proxy:           http.ProxyFromEnvironment,
 | |
| 			TLSClientConfig: tlsCfg,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	urls := make([]*url.URL, 0, len(a.Schedulers))
 | |
| 	for _, s := range a.Schedulers {
 | |
| 		loc, err := url.Parse(s)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		urls = append(urls, loc)
 | |
| 	}
 | |
| 
 | |
| 	if a.Timeout.Duration < time.Second {
 | |
| 		a.Timeout.Duration = defaultTimeout
 | |
| 	}
 | |
| 
 | |
| 	if len(a.Roles) == 0 {
 | |
| 		a.Roles = defaultRoles
 | |
| 	}
 | |
| 
 | |
| 	a.client = client
 | |
| 	a.urls = urls
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (a *Aurora) roleEnabled(role RoleType) bool {
 | |
| 	if len(a.Roles) == 0 {
 | |
| 		return true
 | |
| 	}
 | |
| 
 | |
| 	for _, v := range a.Roles {
 | |
| 		if role.String() == v {
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func (a *Aurora) gatherRole(ctx context.Context, origin *url.URL) (RoleType, error) {
 | |
| 	loc := *origin
 | |
| 	loc.Path = "leaderhealth"
 | |
| 	req, err := http.NewRequest("GET", loc.String(), nil)
 | |
| 	if err != nil {
 | |
| 		return Unknown, err
 | |
| 	}
 | |
| 
 | |
| 	if a.Username != "" || a.Password != "" {
 | |
| 		req.SetBasicAuth(a.Username, a.Password)
 | |
| 	}
 | |
| 	req.Header.Add("Accept", "text/plain")
 | |
| 
 | |
| 	resp, err := a.client.Do(req.WithContext(ctx))
 | |
| 	if err != nil {
 | |
| 		return Unknown, err
 | |
| 	}
 | |
| 	resp.Body.Close()
 | |
| 
 | |
| 	switch resp.StatusCode {
 | |
| 	case http.StatusOK:
 | |
| 		return Leader, nil
 | |
| 	case http.StatusBadGateway:
 | |
| 		fallthrough
 | |
| 	case http.StatusServiceUnavailable:
 | |
| 		return Follower, nil
 | |
| 	default:
 | |
| 		return Unknown, fmt.Errorf("%v", resp.Status)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (a *Aurora) gatherScheduler(
 | |
| 	ctx context.Context, origin *url.URL, role RoleType, acc telegraf.Accumulator,
 | |
| ) error {
 | |
| 	loc := *origin
 | |
| 	loc.Path = "vars.json"
 | |
| 	req, err := http.NewRequest("GET", loc.String(), nil)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if a.Username != "" || a.Password != "" {
 | |
| 		req.SetBasicAuth(a.Username, a.Password)
 | |
| 	}
 | |
| 	req.Header.Add("Accept", "application/json")
 | |
| 
 | |
| 	resp, err := a.client.Do(req.WithContext(ctx))
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer resp.Body.Close()
 | |
| 
 | |
| 	if resp.StatusCode != http.StatusOK {
 | |
| 		return fmt.Errorf("%v", resp.Status)
 | |
| 	}
 | |
| 
 | |
| 	var vars Vars
 | |
| 	decoder := json.NewDecoder(resp.Body)
 | |
| 	decoder.UseNumber()
 | |
| 	err = decoder.Decode(&vars)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("decoding response: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	var fields = make(map[string]interface{}, len(vars))
 | |
| 	for k, v := range vars {
 | |
| 		switch v := v.(type) {
 | |
| 		case json.Number:
 | |
| 			// Aurora encodes numbers as you would specify them as a literal,
 | |
| 			// use this to determine if a value is a float or int.
 | |
| 			if strings.ContainsAny(v.String(), ".eE") {
 | |
| 				fv, err := v.Float64()
 | |
| 				if err != nil {
 | |
| 					acc.AddError(err)
 | |
| 					continue
 | |
| 				}
 | |
| 				fields[k] = fv
 | |
| 			} else {
 | |
| 				fi, err := v.Int64()
 | |
| 				if err != nil {
 | |
| 					acc.AddError(err)
 | |
| 					continue
 | |
| 				}
 | |
| 				fields[k] = fi
 | |
| 			}
 | |
| 		default:
 | |
| 			continue
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	acc.AddFields("aurora",
 | |
| 		fields,
 | |
| 		map[string]string{
 | |
| 			"scheduler": origin.String(),
 | |
| 			"role":      role.String(),
 | |
| 		},
 | |
| 	)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func init() {
 | |
| 	inputs.Add("aurora", func() telegraf.Input {
 | |
| 		return &Aurora{}
 | |
| 	})
 | |
| }
 |