Merge pull request #2672 from influxdata/feature/env
ENV variable TELEGRAF_SYSTEM_INTERVALpull/10616/head
commit
af4e525aaf
|
@ -647,3 +647,9 @@ type BuildStore interface {
|
|||
Get(context.Context) (BuildInfo, error)
|
||||
Update(context.Context, BuildInfo) error
|
||||
}
|
||||
|
||||
// Environement is the set of front-end exposed environment variables
|
||||
// that were set on the server
|
||||
type Environment struct {
|
||||
TelegrafSystemInterval time.Duration `json:"telegrafSystemInterval"`
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
)
|
||||
|
||||
type envResponse struct {
|
||||
Links selfLinks `json:"links"`
|
||||
TelegrafSystemInterval string `json:"telegrafSystemInterval"`
|
||||
}
|
||||
|
||||
func newEnvResponse(env chronograf.Environment) *envResponse {
|
||||
return &envResponse{
|
||||
Links: selfLinks{
|
||||
Self: "/chronograf/v1/env",
|
||||
},
|
||||
TelegrafSystemInterval: env.TelegrafSystemInterval.String(),
|
||||
}
|
||||
}
|
||||
|
||||
// Environment retrieves the global application configuration
|
||||
func (s *Service) Environment(w http.ResponseWriter, r *http.Request) {
|
||||
res := newEnvResponse(s.Env)
|
||||
encodeJSON(w, http.StatusOK, res, s.Logger)
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
"github.com/influxdata/chronograf/log"
|
||||
)
|
||||
|
||||
func TestEnvironment(t *testing.T) {
|
||||
type fields struct {
|
||||
Environment chronograf.Environment
|
||||
}
|
||||
type wants struct {
|
||||
statusCode int
|
||||
contentType string
|
||||
body string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "Get environment",
|
||||
fields: fields{
|
||||
Environment: chronograf.Environment{
|
||||
TelegrafSystemInterval: 1 * time.Minute,
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
statusCode: 200,
|
||||
contentType: "application/json",
|
||||
body: `{"links":{"self":"/chronograf/v1/env"},"telegrafSystemInterval":"1m0s"}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := &Service{
|
||||
Env: tt.fields.Environment,
|
||||
Logger: log.New(log.DebugLevel),
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("GET", "http://any.url", nil)
|
||||
|
||||
s.Environment(w, r)
|
||||
|
||||
resp := w.Result()
|
||||
content := resp.Header.Get("Content-Type")
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != tt.wants.statusCode {
|
||||
t.Errorf("%q. Config() = %v, want %v", tt.name, resp.StatusCode, tt.wants.statusCode)
|
||||
}
|
||||
if tt.wants.contentType != "" && content != tt.wants.contentType {
|
||||
t.Errorf("%q. Config() = %v, want %v", tt.name, content, tt.wants.contentType)
|
||||
}
|
||||
if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq {
|
||||
t.Errorf("%q. Config() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -242,6 +242,8 @@ func NewMux(opts MuxOpts, service Service) http.Handler {
|
|||
router.GET("/chronograf/v1/config/:section", EnsureSuperAdmin(service.ConfigSection))
|
||||
router.PUT("/chronograf/v1/config/:section", EnsureSuperAdmin(service.ReplaceConfigSection))
|
||||
|
||||
router.GET("/chronograf/v1/env", EnsureViewer(service.Environment))
|
||||
|
||||
allRoutes := &AllRoutes{
|
||||
Logger: opts.Logger,
|
||||
StatusFeed: opts.StatusFeedURL,
|
||||
|
|
|
@ -35,6 +35,7 @@ type getRoutesResponse struct {
|
|||
Mappings string `json:"mappings"` // Location of the application mappings endpoint
|
||||
Sources string `json:"sources"` // Location of the sources endpoint
|
||||
Me string `json:"me"` // Location of the me endpoint
|
||||
Environment string `json:"environment"` // Location of the environement endpoint
|
||||
Dashboards string `json:"dashboards"` // Location of the dashboards endpoint
|
||||
Config getConfigLinksResponse `json:"config"` // Location of the config endpoint and its various sections
|
||||
Auth []AuthRoute `json:"auth"` // Location of all auth routes.
|
||||
|
@ -67,6 +68,7 @@ func (a *AllRoutes) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
Users: "/chronograf/v1/users",
|
||||
Organizations: "/chronograf/v1/organizations",
|
||||
Me: "/chronograf/v1/me",
|
||||
Environment: "/chronograf/v1/env",
|
||||
Mappings: "/chronograf/v1/mappings",
|
||||
Dashboards: "/chronograf/v1/dashboards",
|
||||
Config: getConfigLinksResponse{
|
||||
|
|
|
@ -29,7 +29,7 @@ func TestAllRoutes(t *testing.T) {
|
|||
if err := json.Unmarshal(body, &routes); err != nil {
|
||||
t.Error("TestAllRoutes not able to unmarshal JSON response")
|
||||
}
|
||||
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[],"external":{"statusFeed":""}}
|
||||
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[],"external":{"statusFeed":""}}
|
||||
`
|
||||
if want != string(body) {
|
||||
t.Errorf("TestAllRoutes\nwanted\n*%s*\ngot\n*%s*", want, string(body))
|
||||
|
@ -67,7 +67,7 @@ func TestAllRoutesWithAuth(t *testing.T) {
|
|||
if err := json.Unmarshal(body, &routes); err != nil {
|
||||
t.Error("TestAllRoutesWithAuth not able to unmarshal JSON response")
|
||||
}
|
||||
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[{"name":"github","label":"GitHub","login":"/oauth/github/login","logout":"/oauth/github/logout","callback":"/oauth/github/callback"}],"logout":"/oauth/logout","external":{"statusFeed":""}}
|
||||
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[{"name":"github","label":"GitHub","login":"/oauth/github/login","logout":"/oauth/github/logout","callback":"/oauth/github/callback"}],"logout":"/oauth/logout","external":{"statusFeed":""}}
|
||||
`
|
||||
if want != string(body) {
|
||||
t.Errorf("TestAllRoutesWithAuth\nwanted\n*%s*\ngot\n*%s*", want, string(body))
|
||||
|
@ -100,7 +100,7 @@ func TestAllRoutesWithExternalLinks(t *testing.T) {
|
|||
if err := json.Unmarshal(body, &routes); err != nil {
|
||||
t.Error("TestAllRoutesWithExternalLinks not able to unmarshal JSON response")
|
||||
}
|
||||
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[],"external":{"statusFeed":"http://pineapple.life/feed.json","custom":[{"name":"cubeapple","url":"https://cube.apple"}]}}
|
||||
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[],"external":{"statusFeed":"http://pineapple.life/feed.json","custom":[{"name":"cubeapple","url":"https://cube.apple"}]}}
|
||||
`
|
||||
if want != string(body) {
|
||||
t.Errorf("TestAllRoutesWithExternalLinks\nwanted\n*%s*\ngot\n*%s*", want, string(body))
|
||||
|
|
|
@ -86,8 +86,9 @@ type Server struct {
|
|||
Auth0ClientSecret string `long:"auth0-client-secret" description:"Auth0 Client Secret for OAuth2 support" env:"AUTH0_CLIENT_SECRET"`
|
||||
Auth0Organizations []string `long:"auth0-organizations" description:"Auth0 organizations permitted to access Chronograf (comma separated)" env:"AUTH0_ORGS" env-delim:","`
|
||||
|
||||
StatusFeedURL string `long:"status-feed-url" description:"URL of a JSON Feed to display as a News Feed on the client Status page." default:"https://www.influxdata.com/feed/json" env:"STATUS_FEED_URL"`
|
||||
CustomLinks map[string]string `long:"custom-link" description:"Custom link to be added to the client User menu. Multiple links can be added by using multiple of the same flag with different 'name:url' values, or as an environment variable with comma-separated 'name:url' values. E.g. via flags: '--custom-link=InfluxData:https://www.influxdata.com --custom-link=Chronograf:https://github.com/influxdata/chronograf'. E.g. via environment variable: 'export CUSTOM_LINKS=InfluxData:https://www.influxdata.com,Chronograf:https://github.com/influxdata/chronograf'" env:"CUSTOM_LINKS" env-delim:","`
|
||||
StatusFeedURL string `long:"status-feed-url" description:"URL of a JSON Feed to display as a News Feed on the client Status page." default:"https://www.influxdata.com/feed/json" env:"STATUS_FEED_URL"`
|
||||
CustomLinks map[string]string `long:"custom-link" description:"Custom link to be added to the client User menu. Multiple links can be added by using multiple of the same flag with different 'name:url' values, or as an environment variable with comma-separated 'name:url' values. E.g. via flags: '--custom-link=InfluxData:https://www.influxdata.com --custom-link=Chronograf:https://github.com/influxdata/chronograf'. E.g. via environment variable: 'export CUSTOM_LINKS=InfluxData:https://www.influxdata.com,Chronograf:https://github.com/influxdata/chronograf'" env:"CUSTOM_LINKS" env-delim:","`
|
||||
TelegrafSystemInterval time.Duration `long:"telegraf-system-interval" default:"1m" description:"Duration used in the GROUP BY time interval for the hosts list" env:"TELEGRAF_SYSTEM_INTERVAL"`
|
||||
|
||||
ReportingDisabled bool `short:"r" long:"reporting-disabled" description:"Disable reporting of usage stats (os,arch,version,cluster_id,uptime) once every 24hr" env:"REPORTING_DISABLED"`
|
||||
LogLevel string `short:"l" long:"log-level" value-name:"choice" choice:"debug" choice:"info" choice:"error" default:"info" description:"Set the logging level" env:"LOG_LEVEL"`
|
||||
|
@ -325,6 +326,9 @@ func (s *Server) Serve(ctx context.Context) error {
|
|||
return err
|
||||
}
|
||||
service := openService(ctx, s.BuildInfo, s.BoltPath, s.newBuilders(logger), logger, s.useAuth())
|
||||
service.Env = chronograf.Environment{
|
||||
TelegrafSystemInterval: s.TelegrafSystemInterval,
|
||||
}
|
||||
if err := service.HandleNewSources(ctx, s.NewSources); err != nil {
|
||||
logger.
|
||||
WithField("component", "server").
|
||||
|
|
|
@ -15,6 +15,7 @@ type Service struct {
|
|||
TimeSeriesClient TimeSeriesClient
|
||||
Logger chronograf.Logger
|
||||
UseAuth bool
|
||||
Env chronograf.Environment
|
||||
Databases chronograf.Databases
|
||||
}
|
||||
|
||||
|
|
|
@ -2,15 +2,19 @@ import {proxy} from 'utils/queryUrlGenerator'
|
|||
import AJAX from 'utils/ajax'
|
||||
import _ from 'lodash'
|
||||
|
||||
export function getCpuAndLoadForHosts(proxyLink, telegrafDB) {
|
||||
export const getCpuAndLoadForHosts = (
|
||||
proxyLink,
|
||||
telegrafDB,
|
||||
telegrafSystemInterval
|
||||
) => {
|
||||
return proxy({
|
||||
source: proxyLink,
|
||||
query: `SELECT mean("usage_user") FROM cpu WHERE "cpu" = 'cpu-total' AND time > now() - 10m GROUP BY host;
|
||||
SELECT mean("load1") FROM "system" WHERE time > now() - 10m GROUP BY host;
|
||||
SELECT non_negative_derivative(mean(uptime)) AS deltaUptime FROM "system" WHERE time > now() - 10m GROUP BY host, time(1m) fill(0);
|
||||
SELECT non_negative_derivative(mean(uptime)) AS deltaUptime FROM "system" WHERE time > now() - ${telegrafSystemInterval} * 10 GROUP BY host, time(${telegrafSystemInterval}) fill(0);
|
||||
SELECT mean("Percent_Processor_Time") FROM win_cpu WHERE time > now() - 10m GROUP BY host;
|
||||
SELECT mean("Processor_Queue_Length") FROM win_system WHERE time > now() - 10s GROUP BY host;
|
||||
SELECT non_negative_derivative(mean("System_Up_Time")) AS winDeltaUptime FROM win_system WHERE time > now() - 10m GROUP BY host, time(1m) fill(0);
|
||||
SELECT non_negative_derivative(mean("System_Up_Time")) AS winDeltaUptime FROM win_system WHERE time > now() - ${telegrafSystemInterval} * 10 GROUP BY host, time(${telegrafSystemInterval}) fill(0);
|
||||
SHOW TAG VALUES WITH KEY = "host";`,
|
||||
db: telegrafDB,
|
||||
}).then(resp => {
|
||||
|
@ -116,7 +120,7 @@ export const getLayouts = () =>
|
|||
resource: 'layouts',
|
||||
})
|
||||
|
||||
export function getAppsForHosts(proxyLink, hosts, appLayouts, telegrafDB) {
|
||||
export const getAppsForHosts = (proxyLink, hosts, appLayouts, telegrafDB) => {
|
||||
const measurements = appLayouts.map(m => `^${m.measurement}$`).join('|')
|
||||
const measurementsToApps = _.zipObject(
|
||||
appLayouts.map(m => m.measurement),
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import React, {PropTypes, Component} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import _ from 'lodash'
|
||||
|
||||
import HostsTable from 'src/hosts/components/HostsTable'
|
||||
import SourceIndicator from 'shared/components/SourceIndicator'
|
||||
|
||||
import {getCpuAndLoadForHosts, getLayouts, getAppsForHosts} from '../apis'
|
||||
import {getEnv} from 'src/shared/apis/env'
|
||||
|
||||
class HostsPage extends Component {
|
||||
constructor(props) {
|
||||
|
@ -17,48 +19,72 @@ class HostsPage extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {source, addFlashMessage} = this.props
|
||||
Promise.all([
|
||||
getCpuAndLoadForHosts(source.links.proxy, source.telegraf),
|
||||
getLayouts(),
|
||||
new Promise(resolve => {
|
||||
this.setState({hostsLoading: true})
|
||||
resolve()
|
||||
}),
|
||||
])
|
||||
.then(([hosts, {data: {layouts}}]) => {
|
||||
this.setState({
|
||||
hosts,
|
||||
hostsLoading: false,
|
||||
})
|
||||
getAppsForHosts(source.links.proxy, hosts, layouts, source.telegraf)
|
||||
.then(newHosts => {
|
||||
this.setState({
|
||||
hosts: newHosts,
|
||||
hostsError: '',
|
||||
hostsLoading: false,
|
||||
})
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error)
|
||||
const reason = 'Unable to get apps for hosts'
|
||||
addFlashMessage({type: 'error', text: reason})
|
||||
this.setState({
|
||||
hostsError: reason,
|
||||
hostsLoading: false,
|
||||
})
|
||||
})
|
||||
async componentDidMount() {
|
||||
const {source, links, addFlashMessage} = this.props
|
||||
|
||||
const {telegrafSystemInterval} = await getEnv(links.environment)
|
||||
|
||||
const hostsError = 'Unable to get apps for hosts'
|
||||
let hosts, layouts
|
||||
|
||||
try {
|
||||
const [h, {data}] = await Promise.all([
|
||||
getCpuAndLoadForHosts(
|
||||
source.links.proxy,
|
||||
source.telegraf,
|
||||
telegrafSystemInterval
|
||||
),
|
||||
getLayouts(),
|
||||
new Promise(resolve => {
|
||||
this.setState({hostsLoading: true})
|
||||
resolve()
|
||||
}),
|
||||
])
|
||||
|
||||
hosts = h
|
||||
layouts = data.layouts
|
||||
|
||||
this.setState({
|
||||
hosts,
|
||||
hostsLoading: false,
|
||||
})
|
||||
.catch(reason => {
|
||||
this.setState({
|
||||
hostsError: reason.toString(),
|
||||
hostsLoading: false,
|
||||
})
|
||||
// TODO: this isn't reachable at the moment, because getCpuAndLoadForHosts doesn't fail when it should.
|
||||
// (like with a bogus proxy link). We should provide better messaging to the user in this catch after that's fixed.
|
||||
console.error(reason) // eslint-disable-line no-console
|
||||
} catch (error) {
|
||||
this.setState({
|
||||
hostsError: error.toString(),
|
||||
hostsLoading: false,
|
||||
})
|
||||
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
if (!hosts || !layouts) {
|
||||
addFlashMessage({type: 'error', text: hostsError})
|
||||
return this.setState({
|
||||
hostsError,
|
||||
hostsLoading: false,
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const newHosts = await getAppsForHosts(
|
||||
source.links.proxy,
|
||||
hosts,
|
||||
layouts,
|
||||
source.telegraf
|
||||
)
|
||||
this.setState({
|
||||
hosts: newHosts,
|
||||
hostsError: '',
|
||||
hostsLoading: false,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
addFlashMessage({type: 'error', text: hostsError})
|
||||
this.setState({
|
||||
hostsError,
|
||||
hostsLoading: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -97,6 +123,12 @@ class HostsPage extends Component {
|
|||
|
||||
const {func, shape, string} = PropTypes
|
||||
|
||||
const mapStateToProps = ({links}) => {
|
||||
return {
|
||||
links,
|
||||
}
|
||||
}
|
||||
|
||||
HostsPage.propTypes = {
|
||||
source: shape({
|
||||
id: string.isRequired,
|
||||
|
@ -107,7 +139,10 @@ HostsPage.propTypes = {
|
|||
}).isRequired,
|
||||
telegraf: string.isRequired,
|
||||
}),
|
||||
links: shape({
|
||||
environment: string.isRequired,
|
||||
}),
|
||||
addFlashMessage: func,
|
||||
}
|
||||
|
||||
export default HostsPage
|
||||
export default connect(mapStateToProps, null)(HostsPage)
|
||||
|
|
|
@ -61,13 +61,15 @@ export const getMeAsync = ({shouldResetMe = false} = {}) => async dispatch => {
|
|||
const {
|
||||
data: me,
|
||||
auth,
|
||||
logoutLink,
|
||||
external,
|
||||
users,
|
||||
organizations,
|
||||
meLink,
|
||||
config,
|
||||
external,
|
||||
logoutLink,
|
||||
organizations,
|
||||
environment,
|
||||
} = await getMeAJAX()
|
||||
|
||||
dispatch(
|
||||
meGetCompleted({
|
||||
me,
|
||||
|
@ -75,8 +77,16 @@ export const getMeAsync = ({shouldResetMe = false} = {}) => async dispatch => {
|
|||
logoutLink,
|
||||
})
|
||||
)
|
||||
|
||||
dispatch(
|
||||
linksReceived({external, users, organizations, me: meLink, config})
|
||||
linksReceived({
|
||||
external,
|
||||
users,
|
||||
organizations,
|
||||
me: meLink,
|
||||
config,
|
||||
environment,
|
||||
})
|
||||
) // TODO: put this before meGetCompleted... though for some reason it doesn't fire the first time then
|
||||
} catch (error) {
|
||||
dispatch(meGetFailed())
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import AJAX from 'src/utils/ajax'
|
||||
|
||||
const DEFAULT_ENVS = {
|
||||
telegrafSystemInterval: '1m',
|
||||
}
|
||||
|
||||
export const getEnv = async url => {
|
||||
try {
|
||||
const {data} = await AJAX({
|
||||
method: 'GET',
|
||||
url,
|
||||
})
|
||||
|
||||
return data
|
||||
} catch (error) {
|
||||
console.error('Error retreieving envs: ', error)
|
||||
return DEFAULT_ENVS
|
||||
}
|
||||
}
|
|
@ -18,7 +18,9 @@ const generateResponseWithLinks = (response, newLinks) => {
|
|||
organizations,
|
||||
me: meLink,
|
||||
config,
|
||||
environment,
|
||||
} = newLinks
|
||||
|
||||
return {
|
||||
...response,
|
||||
auth: {links: auth},
|
||||
|
@ -28,6 +30,7 @@ const generateResponseWithLinks = (response, newLinks) => {
|
|||
organizations,
|
||||
meLink,
|
||||
config,
|
||||
environment,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue