influxdb/chronograf/enterprise/enterprise.go

226 lines
6.0 KiB
Go

package enterprise
import (
"container/ring"
"net/url"
"strings"
"context"
"github.com/influxdata/influxdb/v2/chronograf"
"github.com/influxdata/influxdb/v2/chronograf/influx"
)
var _ chronograf.TimeSeries = &Client{}
// Ctrl represents administrative controls over an Influx Enterprise cluster
type Ctrl interface {
ShowCluster(ctx context.Context) (*Cluster, error)
Users(ctx context.Context, name *string) (*Users, error)
User(ctx context.Context, name string) (*User, error)
CreateUser(ctx context.Context, name, passwd string) error
DeleteUser(ctx context.Context, name string) error
ChangePassword(ctx context.Context, name, passwd string) error
SetUserPerms(ctx context.Context, name string, perms Permissions) error
UserRoles(ctx context.Context) (map[string]Roles, error)
Roles(ctx context.Context, name *string) (*Roles, error)
Role(ctx context.Context, name string) (*Role, error)
CreateRole(ctx context.Context, name string) error
DeleteRole(ctx context.Context, name string) error
SetRolePerms(ctx context.Context, name string, perms Permissions) error
SetRoleUsers(ctx context.Context, name string, users []string) error
AddRoleUsers(ctx context.Context, name string, users []string) error
RemoveRoleUsers(ctx context.Context, name string, users []string) error
}
// Client is a device for retrieving time series data from an Influx Enterprise
// cluster. It is configured using the addresses of one or more meta node URLs.
// Data node URLs are retrieved automatically from the meta nodes and queries
// are appropriately load balanced across the cluster.
type Client struct {
Ctrl
UsersStore chronograf.UsersStore
RolesStore chronograf.RolesStore
Logger chronograf.Logger
dataNodes *ring.Ring
opened bool
}
// NewClientWithTimeSeries initializes a Client with a known set of TimeSeries.
func NewClientWithTimeSeries(lg chronograf.Logger, mu string, authorizer influx.Authorizer, tls, insecure bool, series ...chronograf.TimeSeries) (*Client, error) {
metaURL, err := parseMetaURL(mu, tls)
if err != nil {
return nil, err
}
ctrl := NewMetaClient(metaURL, insecure, authorizer)
c := &Client{
Ctrl: ctrl,
UsersStore: &UserStore{
Ctrl: ctrl,
Logger: lg,
},
RolesStore: &RolesStore{
Ctrl: ctrl,
Logger: lg,
},
}
c.dataNodes = ring.New(len(series))
for _, s := range series {
c.dataNodes.Value = s
c.dataNodes = c.dataNodes.Next()
}
return c, nil
}
// NewClientWithURL initializes an Enterprise client with a URL to a Meta Node.
// Acceptable URLs include host:port combinations as well as scheme://host:port
// varieties. TLS is used when the URL contains "https" or when the TLS
// parameter is set. authorizer will add the correct `Authorization` headers
// on the out-bound request.
func NewClientWithURL(mu string, authorizer influx.Authorizer, tls bool, insecure bool, lg chronograf.Logger) (*Client, error) {
metaURL, err := parseMetaURL(mu, tls)
if err != nil {
return nil, err
}
ctrl := NewMetaClient(metaURL, insecure, authorizer)
return &Client{
Ctrl: ctrl,
UsersStore: &UserStore{
Ctrl: ctrl,
Logger: lg,
},
RolesStore: &RolesStore{
Ctrl: ctrl,
Logger: lg,
},
Logger: lg,
}, nil
}
// Connect prepares a Client to process queries. It must be called prior to calling Query
func (c *Client) Connect(ctx context.Context, src *chronograf.Source) error {
c.opened = true
// return early if we already have dataNodes
if c.dataNodes != nil {
return nil
}
cluster, err := c.Ctrl.ShowCluster(ctx)
if err != nil {
return err
}
c.dataNodes = ring.New(len(cluster.DataNodes))
for _, dn := range cluster.DataNodes {
cl := &influx.Client{
Logger: c.Logger,
}
dataSrc := &chronograf.Source{}
*dataSrc = *src
dataSrc.URL = dn.HTTPAddr
if err := cl.Connect(ctx, dataSrc); err != nil {
continue
}
c.dataNodes.Value = cl
c.dataNodes = c.dataNodes.Next()
}
return nil
}
// Query retrieves timeseries information pertaining to a specified query. It
// can be cancelled by using a provided context.
func (c *Client) Query(ctx context.Context, q chronograf.Query) (chronograf.Response, error) {
if !c.opened {
return nil, chronograf.ErrUninitialized
}
return c.nextDataNode().Query(ctx, q)
}
// Write records points into a time series
func (c *Client) Write(ctx context.Context, points []chronograf.Point) error {
if !c.opened {
return chronograf.ErrUninitialized
}
return c.nextDataNode().Write(ctx, points)
}
// Users is the interface to the users within Influx Enterprise
func (c *Client) Users(context.Context) chronograf.UsersStore {
return c.UsersStore
}
// Roles provide a grouping of permissions given to a grouping of users
func (c *Client) Roles(ctx context.Context) (chronograf.RolesStore, error) {
return c.RolesStore, nil
}
// Permissions returns all Influx Enterprise permission strings
func (c *Client) Permissions(context.Context) chronograf.Permissions {
all := chronograf.Allowances{
"NoPermissions",
"ViewAdmin",
"ViewChronograf",
"CreateDatabase",
"CreateUserAndRole",
"AddRemoveNode",
"DropDatabase",
"DropData",
"ReadData",
"WriteData",
"Rebalance",
"ManageShard",
"ManageContinuousQuery",
"ManageQuery",
"ManageSubscription",
"Monitor",
"CopyShard",
"KapacitorAPI",
"KapacitorConfigAPI",
}
return chronograf.Permissions{
{
Scope: chronograf.AllScope,
Allowed: all,
},
{
Scope: chronograf.DBScope,
Allowed: all,
},
}
}
// nextDataNode retrieves the next available data node
func (c *Client) nextDataNode() chronograf.TimeSeries {
c.dataNodes = c.dataNodes.Next()
return c.dataNodes.Value.(chronograf.TimeSeries)
}
// parseMetaURL constructs a url from either a host:port combination or a
// scheme://host:port combo. The optional TLS parameter takes precedence over
// any TLS preference found in the provided URL
func parseMetaURL(mu string, tls bool) (metaURL *url.URL, err error) {
if strings.Contains(mu, "http") {
metaURL, err = url.Parse(mu)
} else {
metaURL = &url.URL{
Scheme: "http",
Host: mu,
}
}
if tls {
metaURL.Scheme = "https"
}
return
}