Merge pull request #1695 from influxdata/feature/persist_datasource_flag-1555

Add server flag to add new InfluxDb source and Kapacitor server
pull/10616/head
Jared Scheib 2017-07-07 14:51:51 -07:00 committed by GitHub
commit f988da6e7f
7 changed files with 204 additions and 2 deletions

View File

@ -9,6 +9,7 @@
1. [#1645](https://github.com/influxdata/chronograf/pull/1645): Add Auth0 as a supported OAuth2 provider
1. [#1660](https://github.com/influxdata/chronograf/pull/1660): Add ability to add custom links to User menu via server CLI or ENV vars
1. [#1674](https://github.com/influxdata/chronograf/pull/1674): Add support for organizations in Auth0
1. [#1695](https://github.com/influxdata/chronograf/pull/1695): Add server flag for adding new InfluxDb sources with Kapacitor servers
### UI Improvements
1. [#1644](https://github.com/influxdata/chronograf/pull/1644): Redesign Alerts History table to have sticky headers

View File

@ -625,3 +625,44 @@ type LayoutStore interface {
// Update the dashboard in the store.
Update(context.Context, Layout) error
}
// SourceAndKapacitor is used to parse any NewSources server flag arguments
type SourceAndKapacitor struct {
Source Source `json:"influxdb"`
Kapacitor Server `json:"kapacitor"`
}
// NewSources adds sources to BoltDb idempotently by name, as well as respective kapacitors
func NewSources(ctx context.Context, sourcesStore SourcesStore, serversStore ServersStore, srcsKaps []SourceAndKapacitor, logger Logger) error {
srcs, err := sourcesStore.All(ctx)
if err != nil {
return err
}
SourceLoop:
for _, srcKap := range srcsKaps {
for _, src := range srcs {
// If source already exists, do nothing
if src.Name == srcKap.Source.Name {
logger.
WithField("component", "server").
WithField("NewSources", src.Name).
Info("Source already exists")
continue SourceLoop
}
}
src, err := sourcesStore.Add(ctx, srcKap.Source)
if err != nil {
return err
}
srcKap.Kapacitor.SrcID = src.ID
_, err = serversStore.Add(ctx, srcKap.Kapacitor)
if err != nil {
return err
}
}
return nil
}

88
chronograf_test.go Normal file
View File

@ -0,0 +1,88 @@
package chronograf_test
import (
"context"
"reflect"
"testing"
"github.com/influxdata/chronograf"
"github.com/influxdata/chronograf/mocks"
)
func Test_NewSources(t *testing.T) {
t.Parallel()
srcsKaps := []chronograf.SourceAndKapacitor{
{
Source: chronograf.Source{
Default: true,
InsecureSkipVerify: false,
MetaURL: "http://metaurl.com",
Name: "Influx 1",
Password: "pass1",
Telegraf: "telegraf",
URL: "http://localhost:8086",
Username: "user1",
},
Kapacitor: chronograf.Server{
Active: true,
Name: "Kapa 1",
URL: "http://localhost:9092",
},
},
}
saboteurSrcsKaps := []chronograf.SourceAndKapacitor{
{
Source: chronograf.Source{
Name: "Influx 1",
},
Kapacitor: chronograf.Server{
Name: "Kapa Aspiring Saboteur",
},
},
}
ctx := context.Background()
srcs := []chronograf.Source{}
srcsStore := mocks.SourcesStore{
AllF: func(ctx context.Context) ([]chronograf.Source, error) {
return srcs, nil
},
AddF: func(ctx context.Context, src chronograf.Source) (chronograf.Source, error) {
srcs = append(srcs, src)
return src, nil
},
}
srvs := []chronograf.Server{}
srvsStore := mocks.ServersStore{
AddF: func(ctx context.Context, srv chronograf.Server) (chronograf.Server, error) {
srvs = append(srvs, srv)
return srv, nil
},
}
err := chronograf.NewSources(ctx, &srcsStore, &srvsStore, srcsKaps, &mocks.TestLogger{})
if err != nil {
t.Fatal("Expected no error when creating New Sources. Error:", err)
}
if len(srcs) != 1 {
t.Error("Expected one source in sourcesStore")
}
if len(srvs) != 1 {
t.Error("Expected one source in serversStore")
}
err = chronograf.NewSources(ctx, &srcsStore, &srvsStore, saboteurSrcsKaps, &mocks.TestLogger{})
if err != nil {
t.Fatal("Expected no error when creating New Sources. Error:", err)
}
if len(srcs) != 1 {
t.Error("Expected one source in sourcesStore")
}
if len(srvs) != 1 {
t.Error("Expected one source in serversStore")
}
if !reflect.DeepEqual(srcs[0], srcsKaps[0].Source) {
t.Error("Expected source in sourceStore to remain unchanged")
}
}

View File

@ -1,4 +1,4 @@
package server_test
package mocks
import (
"fmt"

38
mocks/servers.go Normal file
View File

@ -0,0 +1,38 @@
package mocks
import (
"context"
"github.com/influxdata/chronograf"
)
var _ chronograf.ServersStore = &ServersStore{}
// ServersStore mock allows all functions to be set for testing
type ServersStore struct {
AllF func(context.Context) ([]chronograf.Server, error)
AddF func(context.Context, chronograf.Server) (chronograf.Server, error)
DeleteF func(context.Context, chronograf.Server) error
GetF func(ctx context.Context, ID int) (chronograf.Server, error)
UpdateF func(context.Context, chronograf.Server) error
}
func (s *ServersStore) All(ctx context.Context) ([]chronograf.Server, error) {
return s.AllF(ctx)
}
func (s *ServersStore) Add(ctx context.Context, srv chronograf.Server) (chronograf.Server, error) {
return s.AddF(ctx, srv)
}
func (s *ServersStore) Delete(ctx context.Context, srv chronograf.Server) error {
return s.DeleteF(ctx, srv)
}
func (s *ServersStore) Get(ctx context.Context, id int) (chronograf.Server, error) {
return s.GetF(ctx, id)
}
func (s *ServersStore) Update(ctx context.Context, srv chronograf.Server) error {
return s.UpdateF(ctx, srv)
}

View File

@ -3,6 +3,7 @@ package server
import (
"context"
"crypto/tls"
"encoding/json"
"log"
"math/rand"
"net"
@ -50,6 +51,8 @@ type Server struct {
KapacitorUsername string `long:"kapacitor-username" description:"Username of your Kapacitor instance" env:"KAPACITOR_USERNAME"`
KapacitorPassword string `long:"kapacitor-password" description:"Password of your Kapacitor instance" env:"KAPACITOR_PASSWORD"`
NewSources string `long:"new-sources" description:"Config for adding a new InfluxDb source and Kapacitor server, in JSON as an array of objects, and surrounded by single quotes. E.g. --new-sources='[{\"influxdb\":{\"name\":\"Influx 1\",\"username\":\"user1\",\"password\":\"pass1\",\"url\":\"http://localhost:8086\",\"metaUrl\":\"http://metaurl.com\",\"insecureSkipVerify\":false,\"default\":true,\"telegraf\":\"telegraf\"},\"kapacitor\":{\"name\":\"Kapa 1\",\"url\":\"http://localhost:9092\",\"active\":true}}]'" env:"NEW_SOURCES"`
Develop bool `short:"d" long:"develop" description:"Run server in develop mode."`
BoltPath string `short:"b" long:"bolt-path" description:"Full path to boltDB file (/var/lib/chronograf/chronograf-v1.db)" env:"BOLT_PATH" default:"chronograf-v1.db"`
CannedPath string `short:"c" long:"canned-path" description:"Path to directory of pre-canned application layouts (/usr/share/chronograf/canned)" env:"CANNED_PATH" default:"canned"`
@ -298,6 +301,9 @@ func (s *Server) Serve(ctx context.Context) error {
return err
}
service := openService(ctx, s.BoltPath, layoutBuilder, sourcesBuilder, kapacitorBuilder, logger, s.useAuth())
go processNewSources(ctx, service, s.NewSources, logger)
basepath = s.Basepath
if basepath != "" && s.PrefixRoutes == false {
logger.
@ -431,6 +437,33 @@ func openService(ctx context.Context, boltPath string, lBuilder LayoutBuilder, s
}
}
// processNewSources parses and persists new sources passed in via server flag
func processNewSources(ctx context.Context, service Service, newSources string, logger chronograf.Logger) error {
if newSources == "" {
return nil
}
var srcsKaps []chronograf.SourceAndKapacitor
// On JSON unmarshal error, continue server process without new source and write error to log
if err := json.Unmarshal([]byte(newSources), &srcsKaps); err != nil {
logger.
WithField("component", "server").
WithField("NewSources", "invalid").
Error(err)
}
// Add any new sources and kapacitors as specified via server flag
if err := chronograf.NewSources(ctx, service.SourcesStore, service.ServersStore, srcsKaps, logger); err != nil {
// Continue with server run even if adding NewSources fails
logger.
WithField("component", "server").
WithField("NewSources", "invalid").
Error(err)
}
return nil
}
// reportUsageStats starts periodic server reporting.
func reportUsageStats(bi BuildInfo, logger chronograf.Logger) {
rand.Seed(time.Now().UTC().UnixNano())

View File

@ -7,6 +7,7 @@ import (
"net/http/httptest"
"testing"
"github.com/influxdata/chronograf/mocks"
"github.com/influxdata/chronograf/server"
)
@ -138,7 +139,7 @@ func Test_Server_Prefixer_NoPrefixingWithoutFlusther(t *testing.T) {
})
}
tl := &TestLogger{}
tl := &mocks.TestLogger{}
pfx := &server.URLPrefixer{
Prefix: "/hill",
Next: backend,