Merge branch 'master' into feature/1054-alert-history-spinner
# Conflicts: # CHANGELOG.mdpull/1130/head
commit
72f00e9802
|
@ -7,6 +7,7 @@
|
|||
### Features
|
||||
1. [#1112](https://github.com/influxdata/chronograf/pull/1112): Add ability to delete a dashboard
|
||||
1. [#1120](https://github.com/influxdata/chronograf/pull/1120): Allow users to update user passwords.
|
||||
1. [#1129](https://github.com/influxdata/chronograf/pull/1129): Allow InfluxDB and Kapacitor configuration via ENV vars or CLI options
|
||||
1. [#1130](https://github.com/influxdata/chronograf/pull/1130): Add loading spinner to Alert History page.
|
||||
|
||||
### UI Improvements
|
||||
|
|
|
@ -137,7 +137,7 @@ type Response interface {
|
|||
|
||||
// Source is connection information to a time-series data store.
|
||||
type Source struct {
|
||||
ID int `json:"id,omitempty,string"` // ID is the unique ID of the source
|
||||
ID int `json:"id,string"` // ID is the unique ID of the source
|
||||
Name string `json:"name"` // Name is the user-defined name for the source
|
||||
Type string `json:"type,omitempty"` // Type specifies which kinds of source (enterprise vs oss)
|
||||
Username string `json:"username,omitempty"` // Username is the username to connect to the source
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
package memdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
)
|
||||
|
||||
// Ensure KapacitorStore and MultiKapacitorStore implements chronograf.ServersStore.
|
||||
var _ chronograf.ServersStore = &KapacitorStore{}
|
||||
var _ chronograf.ServersStore = &MultiKapacitorStore{}
|
||||
|
||||
// KapacitorStore implements the chronograf.ServersStore interface, and keeps
|
||||
// an in-memory Kapacitor according to startup configuration
|
||||
type KapacitorStore struct {
|
||||
Kapacitor *chronograf.Server
|
||||
}
|
||||
|
||||
// All will return a slice containing a configured source
|
||||
func (store *KapacitorStore) All(ctx context.Context) ([]chronograf.Server, error) {
|
||||
if store.Kapacitor != nil {
|
||||
return []chronograf.Server{*store.Kapacitor}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Add does not have any effect
|
||||
func (store *KapacitorStore) Add(ctx context.Context, kap chronograf.Server) (chronograf.Server, error) {
|
||||
return chronograf.Server{}, fmt.Errorf("In-memory KapacitorStore does not support adding a Kapacitor")
|
||||
}
|
||||
|
||||
// Delete removes the in-memory configured Kapacitor if its ID matches what's provided
|
||||
func (store *KapacitorStore) Delete(ctx context.Context, kap chronograf.Server) error {
|
||||
if store.Kapacitor == nil || store.Kapacitor.ID != kap.ID {
|
||||
return fmt.Errorf("Unable to find Kapacitor with id %d", kap.ID)
|
||||
}
|
||||
store.Kapacitor = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns the in-memory Kapacitor if its ID matches what's provided
|
||||
func (store *KapacitorStore) Get(ctx context.Context, id int) (chronograf.Server, error) {
|
||||
if store.Kapacitor == nil || store.Kapacitor.ID != id {
|
||||
return chronograf.Server{}, fmt.Errorf("Unable to find Kapacitor with id %d", id)
|
||||
}
|
||||
return *store.Kapacitor, nil
|
||||
}
|
||||
|
||||
// Update overwrites the in-memory configured Kapacitor if its ID matches what's provided
|
||||
func (store *KapacitorStore) Update(ctx context.Context, kap chronograf.Server) error {
|
||||
if store.Kapacitor == nil || store.Kapacitor.ID != kap.ID {
|
||||
return fmt.Errorf("Unable to find Kapacitor with id %d", kap.ID)
|
||||
}
|
||||
store.Kapacitor = &kap
|
||||
return nil
|
||||
}
|
||||
|
||||
// MultiKapacitorStore implements the chronograf.ServersStore interface, and
|
||||
// delegates to all contained KapacitorStores
|
||||
type MultiKapacitorStore struct {
|
||||
Stores []chronograf.ServersStore
|
||||
}
|
||||
|
||||
// All concatenates the Kapacitors of all contained Stores
|
||||
func (multi *MultiKapacitorStore) All(ctx context.Context) ([]chronograf.Server, error) {
|
||||
all := []chronograf.Server{}
|
||||
kapSet := map[int]struct{}{}
|
||||
|
||||
ok := false
|
||||
var err error
|
||||
for _, store := range multi.Stores {
|
||||
var kaps []chronograf.Server
|
||||
kaps, err = store.All(ctx)
|
||||
if err != nil {
|
||||
// If this Store is unable to return an array of kapacitors, skip to the
|
||||
// next Store.
|
||||
continue
|
||||
}
|
||||
ok = true // We've received a response from at least one Store
|
||||
for _, kap := range kaps {
|
||||
// Enforce that the kapacitor has a unique ID
|
||||
// If the ID has been seen before, ignore the kapacitor
|
||||
if _, okay := kapSet[kap.ID]; !okay { // We have a new kapacitor
|
||||
kapSet[kap.ID] = struct{}{} // We just care that the ID is unique
|
||||
all = append(all, kap)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return nil, err
|
||||
}
|
||||
return all, nil
|
||||
}
|
||||
|
||||
// Add the kap to the first responsive Store
|
||||
func (multi *MultiKapacitorStore) Add(ctx context.Context, kap chronograf.Server) (chronograf.Server, error) {
|
||||
var err error
|
||||
for _, store := range multi.Stores {
|
||||
var k chronograf.Server
|
||||
k, err = store.Add(ctx, kap)
|
||||
if err == nil {
|
||||
return k, nil
|
||||
}
|
||||
}
|
||||
return chronograf.Server{}, nil
|
||||
}
|
||||
|
||||
// Delete delegates to all Stores, returns success if one Store is successful
|
||||
func (multi *MultiKapacitorStore) Delete(ctx context.Context, kap chronograf.Server) error {
|
||||
var err error
|
||||
for _, store := range multi.Stores {
|
||||
err = store.Delete(ctx, kap)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Get finds the Source by id among all contained Stores
|
||||
func (multi *MultiKapacitorStore) Get(ctx context.Context, id int) (chronograf.Server, error) {
|
||||
var err error
|
||||
for _, store := range multi.Stores {
|
||||
var k chronograf.Server
|
||||
k, err = store.Get(ctx, id)
|
||||
if err == nil {
|
||||
return k, nil
|
||||
}
|
||||
}
|
||||
return chronograf.Server{}, nil
|
||||
}
|
||||
|
||||
// Update the first responsive Store
|
||||
func (multi *MultiKapacitorStore) Update(ctx context.Context, kap chronograf.Server) error {
|
||||
var err error
|
||||
for _, store := range multi.Stores {
|
||||
err = store.Update(ctx, kap)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
package memdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
)
|
||||
|
||||
func TestInterfaceImplementation(t *testing.T) {
|
||||
var _ chronograf.ServersStore = &KapacitorStore{}
|
||||
var _ chronograf.ServersStore = &MultiKapacitorStore{}
|
||||
}
|
||||
|
||||
func TestKapacitorStoreAll(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
store := KapacitorStore{}
|
||||
kaps, err := store.All(ctx)
|
||||
if err != nil {
|
||||
t.Fatal("All should not throw an error with an empty Store")
|
||||
}
|
||||
if len(kaps) != 0 {
|
||||
t.Fatal("Store should be empty")
|
||||
}
|
||||
|
||||
store.Kapacitor = &chronograf.Server{}
|
||||
kaps, err = store.All(ctx)
|
||||
if err != nil {
|
||||
t.Fatal("All should not throw an error with an empty Store")
|
||||
}
|
||||
if len(kaps) != 1 {
|
||||
t.Fatal("Store should have 1 element")
|
||||
}
|
||||
}
|
||||
|
||||
func TestKapacitorStoreAdd(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
store := KapacitorStore{}
|
||||
_, err := store.Add(ctx, chronograf.Server{})
|
||||
if err == nil {
|
||||
t.Fatal("Store should not support adding another source")
|
||||
}
|
||||
}
|
||||
|
||||
func TestKapacitorStoreDelete(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
store := KapacitorStore{}
|
||||
err := store.Delete(ctx, chronograf.Server{})
|
||||
if err == nil {
|
||||
t.Fatal("Delete should not operate on an empty Store")
|
||||
}
|
||||
|
||||
store.Kapacitor = &chronograf.Server{
|
||||
ID: 9,
|
||||
}
|
||||
err = store.Delete(ctx, chronograf.Server{
|
||||
ID: 8,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("Delete should not remove elements with the wrong ID")
|
||||
}
|
||||
|
||||
err = store.Delete(ctx, chronograf.Server{
|
||||
ID: 9,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("Delete should remove an element with a matching ID")
|
||||
}
|
||||
}
|
||||
|
||||
func TestKapacitorStoreGet(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
store := KapacitorStore{}
|
||||
_, err := store.Get(ctx, 9)
|
||||
if err == nil {
|
||||
t.Fatal("Get should return an error for an empty Store")
|
||||
}
|
||||
|
||||
store.Kapacitor = &chronograf.Server{
|
||||
ID: 9,
|
||||
}
|
||||
_, err = store.Get(ctx, 8)
|
||||
if err == nil {
|
||||
t.Fatal("Get should return an error if it finds no matches")
|
||||
}
|
||||
|
||||
store.Kapacitor = &chronograf.Server{
|
||||
ID: 9,
|
||||
}
|
||||
kap, err := store.Get(ctx, 9)
|
||||
if err != nil || kap.ID != 9 {
|
||||
t.Fatal("Get should find the element with a matching ID")
|
||||
}
|
||||
}
|
||||
|
||||
func TestKapacitorStoreUpdate(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
store := KapacitorStore{}
|
||||
err := store.Update(ctx, chronograf.Server{})
|
||||
if err == nil {
|
||||
t.Fatal("Update fhouls return an error for an empty Store")
|
||||
}
|
||||
|
||||
store.Kapacitor = &chronograf.Server{
|
||||
ID: 9,
|
||||
}
|
||||
err = store.Update(ctx, chronograf.Server{
|
||||
ID: 8,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("Update should return an error if it finds no matches")
|
||||
}
|
||||
|
||||
store.Kapacitor = &chronograf.Server{
|
||||
ID: 9,
|
||||
}
|
||||
err = store.Update(ctx, chronograf.Server{
|
||||
ID: 9,
|
||||
URL: "http://crystal.pepsi.com",
|
||||
})
|
||||
if err != nil || store.Kapacitor.URL != "http://crystal.pepsi.com" {
|
||||
t.Fatal("Update should overwrite elements with matching IDs")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
package memdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
)
|
||||
|
||||
// Ensure MultiSourcesStore and SourcesStore implements chronograf.SourcesStore.
|
||||
var _ chronograf.SourcesStore = &SourcesStore{}
|
||||
var _ chronograf.SourcesStore = &MultiSourcesStore{}
|
||||
|
||||
// MultiSourcesStore delegates to the SourcesStores that compose it
|
||||
type MultiSourcesStore struct {
|
||||
Stores []chronograf.SourcesStore
|
||||
}
|
||||
|
||||
// All concatenates the Sources of all contained Stores
|
||||
func (multi *MultiSourcesStore) All(ctx context.Context) ([]chronograf.Source, error) {
|
||||
all := []chronograf.Source{}
|
||||
sourceSet := map[int]struct{}{}
|
||||
|
||||
ok := false
|
||||
var err error
|
||||
for _, store := range multi.Stores {
|
||||
var sources []chronograf.Source
|
||||
sources, err = store.All(ctx)
|
||||
if err != nil {
|
||||
// If this Store is unable to return an array of sources, skip to the
|
||||
// next Store.
|
||||
continue
|
||||
}
|
||||
ok = true // We've received a response from at least one Store
|
||||
for _, s := range sources {
|
||||
// Enforce that the source has a unique ID
|
||||
// If the source has been seen before, don't override what we already have
|
||||
if _, okay := sourceSet[s.ID]; !okay { // We have a new Source!
|
||||
sourceSet[s.ID] = struct{}{} // We just care that the ID is unique
|
||||
all = append(all, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return nil, err
|
||||
}
|
||||
return all, nil
|
||||
}
|
||||
|
||||
// Add the src to the first Store to respond successfully
|
||||
func (multi *MultiSourcesStore) Add(ctx context.Context, src chronograf.Source) (chronograf.Source, error) {
|
||||
var err error
|
||||
for _, store := range multi.Stores {
|
||||
var s chronograf.Source
|
||||
s, err = store.Add(ctx, src)
|
||||
if err == nil {
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
return chronograf.Source{}, nil
|
||||
}
|
||||
|
||||
// Delete delegates to all stores, returns success if one Store is successful
|
||||
func (multi *MultiSourcesStore) Delete(ctx context.Context, src chronograf.Source) error {
|
||||
var err error
|
||||
for _, store := range multi.Stores {
|
||||
err = store.Delete(ctx, src)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Get finds the Source by id among all contained Stores
|
||||
func (multi *MultiSourcesStore) Get(ctx context.Context, id int) (chronograf.Source, error) {
|
||||
var err error
|
||||
for _, store := range multi.Stores {
|
||||
var s chronograf.Source
|
||||
s, err = store.Get(ctx, id)
|
||||
if err == nil {
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
return chronograf.Source{}, err
|
||||
}
|
||||
|
||||
// Update the first store to return a successful response
|
||||
func (multi *MultiSourcesStore) Update(ctx context.Context, src chronograf.Source) error {
|
||||
var err error
|
||||
for _, store := range multi.Stores {
|
||||
err = store.Update(ctx, src)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// SourcesStore implements the chronograf.SourcesStore interface
|
||||
type SourcesStore struct {
|
||||
Source *chronograf.Source
|
||||
}
|
||||
|
||||
// Add does not have any effect
|
||||
func (store *SourcesStore) Add(ctx context.Context, src chronograf.Source) (chronograf.Source, error) {
|
||||
return chronograf.Source{}, fmt.Errorf("In-memory SourcesStore does not support adding a Source")
|
||||
}
|
||||
|
||||
// All will return a slice containing a configured source
|
||||
func (store *SourcesStore) All(ctx context.Context) ([]chronograf.Source, error) {
|
||||
if store.Source != nil {
|
||||
return []chronograf.Source{*store.Source}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Delete removes the SourcesStore.Soruce if it matches the provided Source
|
||||
func (store *SourcesStore) Delete(ctx context.Context, src chronograf.Source) error {
|
||||
if store.Source == nil || store.Source.ID != src.ID {
|
||||
return fmt.Errorf("Unable to find Source with id %d", src.ID)
|
||||
}
|
||||
store.Source = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns the configured source if the id matches
|
||||
func (store *SourcesStore) Get(ctx context.Context, id int) (chronograf.Source, error) {
|
||||
if store.Source == nil || store.Source.ID != id {
|
||||
return chronograf.Source{}, fmt.Errorf("Unable to find Source with id %d", id)
|
||||
}
|
||||
return *store.Source, nil
|
||||
}
|
||||
|
||||
// Update does nothing
|
||||
func (store *SourcesStore) Update(ctx context.Context, src chronograf.Source) error {
|
||||
if store.Source == nil || store.Source.ID != src.ID {
|
||||
return fmt.Errorf("Unable to find Source with id %d", src.ID)
|
||||
}
|
||||
store.Source = &src
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
package memdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
)
|
||||
|
||||
func TestSourcesStore(t *testing.T) {
|
||||
var _ chronograf.SourcesStore = &SourcesStore{}
|
||||
}
|
||||
|
||||
func TestSourcesStoreAdd(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
store := SourcesStore{}
|
||||
_, err := store.Add(ctx, chronograf.Source{})
|
||||
if err == nil {
|
||||
t.Fatal("Store should not support adding another source")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourcesStoreAll(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
store := SourcesStore{}
|
||||
srcs, err := store.All(ctx)
|
||||
if err != nil {
|
||||
t.Fatal("All should not throw an error with an empty Store")
|
||||
}
|
||||
if len(srcs) != 0 {
|
||||
t.Fatal("Store should be empty")
|
||||
}
|
||||
|
||||
store.Source = &chronograf.Source{}
|
||||
srcs, err = store.All(ctx)
|
||||
if err != nil {
|
||||
t.Fatal("All should not throw an error with an empty Store")
|
||||
}
|
||||
if len(srcs) != 1 {
|
||||
t.Fatal("Store should have 1 element")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourcesStoreDelete(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
store := SourcesStore{}
|
||||
err := store.Delete(ctx, chronograf.Source{})
|
||||
if err == nil {
|
||||
t.Fatal("Delete should not operate on an empty Store")
|
||||
}
|
||||
|
||||
store.Source = &chronograf.Source{
|
||||
ID: 9,
|
||||
}
|
||||
err = store.Delete(ctx, chronograf.Source{
|
||||
ID: 8,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("Delete should not remove elements with the wrong ID")
|
||||
}
|
||||
|
||||
err = store.Delete(ctx, chronograf.Source{
|
||||
ID: 9,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("Delete should remove an element with a matching ID")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourcesStoreGet(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
store := SourcesStore{}
|
||||
_, err := store.Get(ctx, 9)
|
||||
if err == nil {
|
||||
t.Fatal("Get should return an error for an empty Store")
|
||||
}
|
||||
|
||||
store.Source = &chronograf.Source{
|
||||
ID: 9,
|
||||
}
|
||||
_, err = store.Get(ctx, 8)
|
||||
if err == nil {
|
||||
t.Fatal("Get should return an error if it finds no matches")
|
||||
}
|
||||
|
||||
store.Source = &chronograf.Source{
|
||||
ID: 9,
|
||||
}
|
||||
src, err := store.Get(ctx, 9)
|
||||
if err != nil || src.ID != 9 {
|
||||
t.Fatal("Get should find the element with a matching ID")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourcesStoreUpdate(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
store := SourcesStore{}
|
||||
err := store.Update(ctx, chronograf.Source{})
|
||||
if err == nil {
|
||||
t.Fatal("Update should return an error for an empty Store")
|
||||
}
|
||||
|
||||
store.Source = &chronograf.Source{
|
||||
ID: 9,
|
||||
}
|
||||
err = store.Update(ctx, chronograf.Source{
|
||||
ID: 8,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("Update should return an error if it finds no matches")
|
||||
}
|
||||
|
||||
store.Source = &chronograf.Source{
|
||||
ID: 9,
|
||||
}
|
||||
err = store.Update(ctx, chronograf.Source{
|
||||
ID: 9,
|
||||
URL: "http://crystal.pepsi.com",
|
||||
})
|
||||
if err != nil || store.Source.URL != "http://crystal.pepsi.com" {
|
||||
t.Fatal("Update should overwrite elements with matching IDs")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"github.com/influxdata/chronograf"
|
||||
"github.com/influxdata/chronograf/canned"
|
||||
"github.com/influxdata/chronograf/layouts"
|
||||
"github.com/influxdata/chronograf/memdb"
|
||||
)
|
||||
|
||||
// LayoutBuilder is responsible for building Layouts
|
||||
type LayoutBuilder interface {
|
||||
Build(chronograf.LayoutStore) (*layouts.MultiLayoutStore, error)
|
||||
}
|
||||
|
||||
// MultiLayoutBuilder implements LayoutBuilder and will return a MultiLayoutStore
|
||||
type MultiLayoutBuilder struct {
|
||||
Logger chronograf.Logger
|
||||
UUID chronograf.ID
|
||||
CannedPath string
|
||||
}
|
||||
|
||||
// Build will construct a MultiLayoutStore of canned and db-backed personalized
|
||||
// layouts
|
||||
func (builder *MultiLayoutBuilder) Build(db chronograf.LayoutStore) (*layouts.MultiLayoutStore, error) {
|
||||
// These apps are those handled from a directory
|
||||
apps := canned.NewApps(builder.CannedPath, builder.UUID, builder.Logger)
|
||||
// These apps are statically compiled into chronograf
|
||||
binApps := &canned.BinLayoutStore{
|
||||
Logger: builder.Logger,
|
||||
}
|
||||
// Acts as a front-end to both the bolt layouts, filesystem layouts and binary statically compiled layouts.
|
||||
// The idea here is that these stores form a hierarchy in which each is tried sequentially until
|
||||
// the operation has success. So, the database is preferred over filesystem over binary data.
|
||||
layouts := &layouts.MultiLayoutStore{
|
||||
Stores: []chronograf.LayoutStore{
|
||||
db,
|
||||
apps,
|
||||
binApps,
|
||||
},
|
||||
}
|
||||
|
||||
return layouts, nil
|
||||
}
|
||||
|
||||
// SourcesBuilder builds a MultiSourceStore
|
||||
type SourcesBuilder interface {
|
||||
Build(chronograf.SourcesStore) (*memdb.MultiSourcesStore, error)
|
||||
}
|
||||
|
||||
// MultiSourceBuilder implements SourcesBuilder
|
||||
type MultiSourceBuilder struct {
|
||||
InfluxDBURL string
|
||||
InfluxDBUsername string
|
||||
InfluxDBPassword string
|
||||
}
|
||||
|
||||
// Build will return a MultiSourceStore
|
||||
func (fs *MultiSourceBuilder) Build(db chronograf.SourcesStore) (*memdb.MultiSourcesStore, error) {
|
||||
stores := []chronograf.SourcesStore{db}
|
||||
|
||||
if fs.InfluxDBURL != "" {
|
||||
influxStore := &memdb.SourcesStore{
|
||||
Source: &chronograf.Source{
|
||||
ID: 0,
|
||||
Name: fs.InfluxDBURL,
|
||||
Type: chronograf.InfluxDB,
|
||||
Username: fs.InfluxDBUsername,
|
||||
Password: fs.InfluxDBPassword,
|
||||
URL: fs.InfluxDBURL,
|
||||
Default: true,
|
||||
}}
|
||||
stores = append([]chronograf.SourcesStore{influxStore}, stores...)
|
||||
}
|
||||
sources := &memdb.MultiSourcesStore{
|
||||
Stores: stores,
|
||||
}
|
||||
|
||||
return sources, nil
|
||||
}
|
||||
|
||||
// KapacitorBuilder builds a KapacitorStore
|
||||
type KapacitorBuilder interface {
|
||||
Build(chronograf.ServersStore) (*memdb.MultiKapacitorStore, error)
|
||||
}
|
||||
|
||||
// MultiKapacitorBuilder implements KapacitorBuilder
|
||||
type MultiKapacitorBuilder struct {
|
||||
KapacitorURL string
|
||||
KapacitorUsername string
|
||||
KapacitorPassword string
|
||||
}
|
||||
|
||||
// Build will return a MultiKapacitorStore
|
||||
func (builder *MultiKapacitorBuilder) Build(db chronograf.ServersStore) (*memdb.MultiKapacitorStore, error) {
|
||||
stores := []chronograf.ServersStore{db}
|
||||
if builder.KapacitorURL != "" {
|
||||
memStore := &memdb.KapacitorStore{
|
||||
Kapacitor: &chronograf.Server{
|
||||
ID: 0,
|
||||
SrcID: 0,
|
||||
Name: builder.KapacitorURL,
|
||||
URL: builder.KapacitorURL,
|
||||
Username: builder.KapacitorUsername,
|
||||
Password: builder.KapacitorPassword,
|
||||
},
|
||||
}
|
||||
stores = append([]chronograf.ServersStore{memStore}, stores...)
|
||||
}
|
||||
kapacitors := &memdb.MultiKapacitorStore{
|
||||
Stores: stores,
|
||||
}
|
||||
return kapacitors, nil
|
||||
}
|
|
@ -14,15 +14,13 @@ import (
|
|||
|
||||
"github.com/influxdata/chronograf"
|
||||
"github.com/influxdata/chronograf/bolt"
|
||||
"github.com/influxdata/chronograf/canned"
|
||||
"github.com/influxdata/chronograf/layouts"
|
||||
"github.com/influxdata/chronograf/influx"
|
||||
clog "github.com/influxdata/chronograf/log"
|
||||
"github.com/influxdata/chronograf/oauth2"
|
||||
"github.com/influxdata/chronograf/uuid"
|
||||
client "github.com/influxdata/usage-client/v1"
|
||||
flags "github.com/jessevdk/go-flags"
|
||||
"github.com/tylerb/graceful"
|
||||
"github.com/influxdata/chronograf/influx"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -42,6 +40,14 @@ type Server struct {
|
|||
Cert flags.Filename `long:"cert" description:"Path to PEM encoded public key certificate. " env:"TLS_CERTIFICATE"`
|
||||
Key flags.Filename `long:"key" description:"Path to private key associated with given certificate. " env:"TLS_PRIVATE_KEY"`
|
||||
|
||||
InfluxDBURL string `long:"influxdb-url" description:"Location of your InfluxDB instance" env:"INFLUXDB_URL"`
|
||||
InfluxDBUsername string `long:"influxdb-username" description:"Username for your InfluxDB instance" env:"INFLUXDB_USERNAME"`
|
||||
InfluxDBPassword string `long:"influxdb-password" description:"Password for your InfluxDB instance" env:"INFLUXDB_PASSWORD"`
|
||||
|
||||
KapacitorURL string `long:"kapacitor-url" description:"Location of your Kapacitor instance" env:"KAPACITOR_URL"`
|
||||
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"`
|
||||
|
||||
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"`
|
||||
|
@ -180,7 +186,22 @@ func (s *Server) NewListener() (net.Listener, error) {
|
|||
// Serve starts and runs the chronograf server
|
||||
func (s *Server) Serve(ctx context.Context) error {
|
||||
logger := clog.New(clog.ParseLevel(s.LogLevel))
|
||||
service := openService(ctx, s.BoltPath, s.CannedPath, logger, s.useAuth())
|
||||
layoutBuilder := &MultiLayoutBuilder{
|
||||
Logger: logger,
|
||||
UUID: &uuid.V4{},
|
||||
CannedPath: s.CannedPath,
|
||||
}
|
||||
sourcesBuilder := &MultiSourceBuilder{
|
||||
InfluxDBURL: s.InfluxDBURL,
|
||||
InfluxDBUsername: s.InfluxDBUsername,
|
||||
InfluxDBPassword: s.InfluxDBPassword,
|
||||
}
|
||||
kapacitorBuilder := &MultiKapacitorBuilder{
|
||||
KapacitorURL: s.KapacitorURL,
|
||||
KapacitorUsername: s.KapacitorUsername,
|
||||
KapacitorPassword: s.KapacitorPassword,
|
||||
}
|
||||
service := openService(ctx, s.BoltPath, layoutBuilder, sourcesBuilder, kapacitorBuilder, logger, s.useAuth())
|
||||
basepath = s.Basepath
|
||||
|
||||
providerFuncs := []func(func(oauth2.Provider, oauth2.Mux)){}
|
||||
|
@ -256,7 +277,7 @@ func (s *Server) Serve(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func openService(ctx context.Context, boltPath, cannedPath string, logger chronograf.Logger, useAuth bool) Service {
|
||||
func openService(ctx context.Context, boltPath string, lBuilder LayoutBuilder, sBuilder SourcesBuilder, kapBuilder KapacitorBuilder, logger chronograf.Logger, useAuth bool) Service {
|
||||
db := bolt.NewClient()
|
||||
db.Path = boltPath
|
||||
if err := db.Open(ctx); err != nil {
|
||||
|
@ -266,28 +287,34 @@ func openService(ctx context.Context, boltPath, cannedPath string, logger chrono
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
// These apps are those handled from a directory
|
||||
apps := canned.NewApps(cannedPath, &uuid.V4{}, logger)
|
||||
// These apps are statically compiled into chronograf
|
||||
binApps := &canned.BinLayoutStore{
|
||||
Logger: logger,
|
||||
layouts, err := lBuilder.Build(db.LayoutStore)
|
||||
if err != nil {
|
||||
logger.
|
||||
WithField("component", "LayoutStore").
|
||||
Error("Unable to construct a MultiLayoutStore", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Acts as a front-end to both the bolt layouts, filesystem layouts and binary statically compiled layouts.
|
||||
// The idea here is that these stores form a hierarchy in which each is tried sequentially until
|
||||
// the operation has success. So, the database is preferred over filesystem over binary data.
|
||||
layouts := &layouts.MultiLayoutStore{
|
||||
Stores: []chronograf.LayoutStore{
|
||||
db.LayoutStore,
|
||||
apps,
|
||||
binApps,
|
||||
},
|
||||
sources, err := sBuilder.Build(db.SourcesStore)
|
||||
if err != nil {
|
||||
logger.
|
||||
WithField("component", "SourcesStore").
|
||||
Error("Unable to construct a MultiSourcesStore", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
kapacitors, err := kapBuilder.Build(db.ServersStore)
|
||||
if err != nil {
|
||||
logger.
|
||||
WithField("component", "KapacitorStore").
|
||||
Error("Unable to construct a MultiKapacitorStore", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return Service{
|
||||
TimeSeriesClient: &InfluxClient{},
|
||||
SourcesStore: db.SourcesStore,
|
||||
ServersStore: db.ServersStore,
|
||||
SourcesStore: sources,
|
||||
ServersStore: kapacitors,
|
||||
UsersStore: db.UsersStore,
|
||||
LayoutStore: layouts,
|
||||
DashboardsStore: db.DashboardsStore,
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package server
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestLayoutBuilder(t *testing.T) {
|
||||
var l LayoutBuilder = &MultiLayoutBuilder{}
|
||||
layout, err := l.Build(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("MultiLayoutBuilder can't build a MultiLayoutStore: %v", err)
|
||||
}
|
||||
|
||||
if layout == nil {
|
||||
t.Fatal("LayoutBuilder should have built a layout")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourcesStoresBuilder(t *testing.T) {
|
||||
var b SourcesBuilder = &MultiSourceBuilder{}
|
||||
sources, err := b.Build(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("MultiSourceBuilder can't build a MultiSourcesStore: %v", err)
|
||||
}
|
||||
if sources == nil {
|
||||
t.Fatal("SourcesBuilder should have built a MultiSourceStore")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue