Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions cmd/bridge/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ const (
// Well-known location of Prometheus service for OpenShift. This is only accessible in-cluster.
openshiftPrometheusHost = "prometheus-k8s.openshift-monitoring.svc:9091"

// The tenancy service port (9092) performs RBAC checks for namespace-specific queries.
openshiftPrometheusTenancyHost = "prometheus-k8s.openshift-monitoring.svc:9092"

// Well-known location of Alert Manager service for OpenShift. This is only accessible in-cluster.
openshiftAlertManagerHost = "alertmanager-main.openshift-monitoring.svc:9094"
)
Expand Down Expand Up @@ -262,6 +265,11 @@ func main() {
HeaderBlacklist: []string{"Cookie", "X-CSRFToken"},
Endpoint: &url.URL{Scheme: "https", Host: openshiftPrometheusHost, Path: "/api"},
}
srv.PrometheusTenancyProxyConfig = &proxy.Config{
TLSClientConfig: monitoringProxyTLSConfig,
HeaderBlacklist: []string{"Cookie", "X-CSRFToken"},
Endpoint: &url.URL{Scheme: "https", Host: openshiftPrometheusTenancyHost, Path: "/api"},
}
srv.AlertManagerProxyConfig = &proxy.Config{
TLSClientConfig: monitoringProxyTLSConfig,
HeaderBlacklist: []string{"Cookie", "X-CSRFToken"},
Expand Down
8 changes: 5 additions & 3 deletions frontend/public/components/build.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,18 @@ const BuildGraphs = requirePrometheus(({build}) => {
return null;
}

const namespace = build.metadata.namespace;

return <React.Fragment>
<div className="row">
<div className="col-md-4">
<Line title="Memory Usage" query={`pod_name:container_memory_usage_bytes:sum{pod_name='${podName}',container_name='',namespace='${build.metadata.namespace}'}`} />
<Line title="Memory Usage" namespace={namespace} query={`pod_name:container_memory_usage_bytes:sum{pod_name='${podName}',container_name='',namespace='${namespace}'}`} />
</div>
<div className="col-md-4">
<Line title="CPU Usage" query={`pod_name:container_cpu_usage:sum{pod_name='${podName}',container_name='',namespace='${build.metadata.namespace}'} * 1000`} />
<Line title="CPU Usage" namespace={namespace} query={`pod_name:container_cpu_usage:sum{pod_name='${podName}',container_name='',namespace='${namespace}'} * 1000`} />
</div>
<div className="col-md-4">
<Line title="Filesystem (bytes)" query={`pod_name:container_fs_usage_bytes:sum{pod_name='${podName}',container_name='',namespace='${build.metadata.namespace}'}`} />
<Line title="Filesystem (bytes)" namespace={namespace} query={`pod_name:container_fs_usage_bytes:sum{pod_name='${podName}',container_name='',namespace='${namespace}'}`} />
</div>
</div>

Expand Down
9 changes: 1 addition & 8 deletions frontend/public/components/cluster-overview.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import * as _ from 'lodash-es';
import * as React from 'react';
import * as classNames from 'classnames';
import { Helmet } from 'react-helmet';
import { Link } from 'react-router-dom';

import { EventsList } from './events';
import { SoftwareDetails } from './software-details';
Expand All @@ -20,15 +19,11 @@ import {
StatusBox,
} from './utils';


const DashboardLink = ({to, id}) => <Link id={id} className="co-external-link" target="_blank" to={to}>View Grafana Dashboard</Link>;

const Graphs = requirePrometheus(({namespace, isOpenShift}) => {
const Graphs = requirePrometheus(({namespace}) => {
return <React.Fragment>
<div className="group">
<div className="group__title">
<h2 className="h3">Health</h2>
{!isOpenShift && <DashboardLink id="qa_dashboard_k8s_health" to="/grafana/dashboard/db/kubernetes-cluster-health?orgId=1" />}
</div>
<div className="container-fluid group__body">
<Health namespace={namespace} />
Expand All @@ -38,7 +33,6 @@ const Graphs = requirePrometheus(({namespace, isOpenShift}) => {
<div className="group">
<div className="group__title">
<h2 className="h3">Control Plane Status</h2>
{!isOpenShift && <DashboardLink to="/grafana/dashboard/db/kubernetes-control-plane-status?orgId=1" />}
</div>
<div className="container-fluid group__body group__graphs">
<div className="row">
Expand All @@ -62,7 +56,6 @@ const Graphs = requirePrometheus(({namespace, isOpenShift}) => {
<div className="group">
<div className="group__title">
<h2 className="h3">Capacity Planning</h2>
{!isOpenShift && <DashboardLink to="/grafana/dashboard/db/kubernetes-capacity-planning?orgId=1" />}
</div>
<div className="container-fluid group__body group__graphs">
<div className="row">
Expand Down
10 changes: 6 additions & 4 deletions frontend/public/components/graphs/base.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as classNames from 'classnames';
import { coFetchJSON } from '../../co-fetch';
import { SafetyFirst } from '../safety-first';

import { prometheusBasePath } from './index';
import { prometheusBasePath, prometheusTenancyBasePath } from './index';
import { MonitoringRoutes } from '../../monitoring';

export class BaseGraph extends SafetyFirst {
Expand Down Expand Up @@ -44,13 +44,14 @@ export class BaseGraph extends SafetyFirst {
}];
}

const basePath = this.props.basePath || prometheusBasePath;
const basePath = this.props.basePath || (this.props.namespace ? prometheusTenancyBasePath : prometheusBasePath);
const pollInterval = timeSpan / 120 || 15000;
const stepSize = pollInterval / 1000;
const promises = queries.map(q => {
const nsParam = this.props.namespace ? `&namespace=${encodeURIComponent(this.props.namespace)}` : '';
const url = this.timeSpan
? `${basePath}/api/v1/query_range?query=${encodeURIComponent(q.query)}&start=${start / 1000}&end=${end / 1000}&step=${stepSize}`
: `${basePath}/api/v1/query?query=${encodeURIComponent(q.query)}`;
? `${basePath}/api/v1/query_range?query=${encodeURIComponent(q.query)}&start=${start / 1000}&end=${end / 1000}&step=${stepSize}${nsParam}`
: `${basePath}/api/v1/query?query=${encodeURIComponent(q.query)}${nsParam}`;
return coFetchJSON(url);
});
Promise.all(promises)
Expand Down Expand Up @@ -142,6 +143,7 @@ export class BaseGraph extends SafetyFirst {
}

BaseGraph.propTypes = {
namespace: PropTypes.string,
query: PropTypes.oneOfType([
PropTypes.string,
PropTypes.arrayOf(
Expand Down
36 changes: 7 additions & 29 deletions frontend/public/components/graphs/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import * as React from 'react';

import { AsyncComponent } from '../utils/async';

import { FLAGS, connectToFlags, flagPending } from '../../features';
import { FLAGS, connectToFlags } from '../../features';
export { Status, errorStatus } from './status';

export const prometheusBasePath = window.SERVER_FLAGS.prometheusBaseURL;
export const prometheusTenancyBasePath = window.SERVER_FLAGS.prometheusTenancyBaseURL;
export const alertManagerBasePath = window.SERVER_FLAGS.alertManagerBaseURL;

export const Bar = props => <AsyncComponent loader={() => import('./graph-loader').then(c => c.Bar)} {...props} />;
Expand All @@ -14,39 +15,16 @@ export const Line = props => <AsyncComponent loader={() => import('./graph-loade
export const Scalar = props => <AsyncComponent loader={() => import('./graph-loader').then(c => c.Scalar)} {...props} />;
export const Donut = props => <AsyncComponent loader={() => import('./graph-loader').then(c => c.Donut)} {...props} />;

const canAccessPrometheus = (openshiftFlag, prometheusFlag, canListNS) => {
if (flagPending(prometheusFlag) || flagPending(openshiftFlag)) {
// Wait for feature detection to complete before showing graphs so we don't show them, then hide them.
return false;
}

if (!prometheusFlag) {
return false;
}

if (!window.SERVER_FLAGS.prometheusBaseURL) {
// Proxy has not been set up.
return false;
}

if (!openshiftFlag) {
return true;
}

// In OpenShift, the user must be able to list namespaces to query Prometheus.
return canListNS;
};
const canAccessPrometheus = (prometheusFlag) => prometheusFlag && !!prometheusBasePath && !!prometheusTenancyBasePath;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to confirm, this will prevent all requirePrometheus() wrapped components from rendering unless both prometheusBasePath and prometheusTenancyBasePath are set. Is that what we want?

Copy link
Member Author

@spadgett spadgett Jan 27, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was trying to keep the client logic simple. Currently, the console serve sets both values together, so if one is set the other will always be, too. We assume the RBAC proxy will always be there in OpenShift 4.0. Let me know if that's not the case.


// HOC that will hide WrappedComponent when Prometheus isn't configured or the user doesn't have permission to query Prometheus.
/** @type {(WrappedComponent: React.SFC<P>) => React.ComponentType<P & {isOpenShift: boolean}>} */
export const requirePrometheus = WrappedComponent => connectToFlags(FLAGS.OPENSHIFT, FLAGS.PROMETHEUS, FLAGS.CAN_LIST_NS)(props => {
/** @type {(WrappedComponent: React.SFC<P>) => React.ComponentType<P>} */
export const requirePrometheus = WrappedComponent => connectToFlags(FLAGS.PROMETHEUS)(props => {
const { flags } = props;
const openshiftFlag = flags[FLAGS.OPENSHIFT];
const prometheusFlag = flags[FLAGS.PROMETHEUS];
const canListNS = flags[FLAGS.CAN_LIST_NS];
if (!canAccessPrometheus(openshiftFlag, prometheusFlag, canListNS)) {
if (!canAccessPrometheus(prometheusFlag)) {
return null;
}

return <WrappedComponent isOpenShift={openshiftFlag} {...props} />;
return <WrappedComponent {...props} />;
});
5 changes: 3 additions & 2 deletions frontend/public/components/namespace.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,15 +164,15 @@ class PullSecret extends SafetyFirst {

export const NamespaceLineCharts = ({ns}) => <div className="row">
<div className="col-sm-6 col-xs-12">
<Line title="CPU Usage" query={[
<Line title="CPU Usage" namespace={ns.metadata.name} query={[
{
name: 'Used',
query: `namespace:container_cpu_usage:sum{namespace='${ns.metadata.name}'}`,
},
]} />
</div>
<div className="col-sm-6 col-xs-12">
<Line title="Memory Usage" query={[
<Line title="Memory Usage" namespace={ns.metadata.name} query={[
{
name: 'Used',
query: `namespace:container_memory_usage_bytes:sum{namespace='${ns.metadata.name}'}`,
Expand All @@ -184,6 +184,7 @@ export const NamespaceLineCharts = ({ns}) => <div className="row">
export const TopPodsBarChart = ({ns}) => (
<Bar
title="Memory Usage by Pod (Top 10)"
namespace={ns.metadata.name}
query={`sort(topk(10, sum by (pod_name)(container_memory_usage_bytes{pod_name!="", namespace="${ns.metadata.name}"})))`}
humanize={humanizeMem}
metric="pod_name" />
Expand Down
7 changes: 3 additions & 4 deletions frontend/public/components/overview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { Toolbar, EmptyState } from 'patternfly-react';

import { coFetchJSON } from '../../co-fetch';
import { getBuildNumber } from '../../module/k8s/builds';
import { prometheusBasePath } from '../graphs';
import { prometheusTenancyBasePath } from '../graphs';
import { TextFilter } from '../factory';
import { UIActions, formatNamespacedRouteForResource } from '../../ui/ui-actions';
import {
Expand Down Expand Up @@ -504,8 +504,7 @@ class OverviewMainContent_ extends React.Component<OverviewMainContentProps, Ove
}

fetchMetrics = (): void => {
if (!prometheusBasePath) {
// Component is not mounted or proxy has not been set up.
if (!prometheusTenancyBasePath) {
return;
}

Expand All @@ -516,7 +515,7 @@ class OverviewMainContent_ extends React.Component<OverviewMainContentProps, Ove
};

const promises = _.map(queries, (query, name) => {
const url = `${prometheusBasePath}/api/v1/query?query=${encodeURIComponent(query)}`;
const url = `${prometheusTenancyBasePath}/api/v1/query?namespace=${namespace}&query=${encodeURIComponent(query)}`;
return coFetchJSON(url).then(({ data: {result} }) => {
const byPod: MetricValuesByPod = result.reduce((acc, { metric, value }) => {
acc[metric.pod_name] = Number(value[1]);
Expand Down
6 changes: 3 additions & 3 deletions frontend/public/components/pod.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,13 @@ export const PodContainerTable = ({heading, containers, pod}) => <React.Fragment
const PodGraphs = requirePrometheus(({pod}) => <React.Fragment>
<div className="row">
<div className="col-md-4">
<Line title="Memory Usage" query={`pod_name:container_memory_usage_bytes:sum{pod_name='${pod.metadata.name}',namespace='${pod.metadata.namespace}'}`} />
<Line title="Memory Usage" namespace={pod.metadata.namespace} query={`pod_name:container_memory_usage_bytes:sum{pod_name='${pod.metadata.name}',namespace='${pod.metadata.namespace}'}`} />
</div>
<div className="col-md-4">
<Line title="CPU Usage" query={`pod_name:container_cpu_usage:sum{pod_name='${pod.metadata.name}',namespace='${pod.metadata.namespace}'} * 1000`} />
<Line title="CPU Usage" namespace={pod.metadata.namespace} query={`pod_name:container_cpu_usage:sum{pod_name='${pod.metadata.name}',namespace='${pod.metadata.namespace}'} * 1000`} />
</div>
<div className="col-md-4">
<Line title="Filesystem (bytes)" query={`pod_name:container_fs_usage_bytes:sum{pod_name='${pod.metadata.name}',namespace='${pod.metadata.namespace}'}`} />
<Line title="Filesystem (bytes)" namespace={pod.metadata.namespace} query={`pod_name:container_fs_usage_bytes:sum{pod_name='${pod.metadata.name}',namespace='${pod.metadata.namespace}'}`} />
</div>
</div>

Expand Down
71 changes: 42 additions & 29 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,38 +23,40 @@ const (
indexPageTemplateName = "index.html"
tokenizerPageTemplateName = "tokener.html"

authLoginEndpoint = "/auth/login"
AuthLoginCallbackEndpoint = "/auth/callback"
AuthLoginSuccessEndpoint = "/"
AuthLoginErrorEndpoint = "/error"
authLogoutEndpoint = "/auth/logout"
k8sProxyEndpoint = "/api/kubernetes/"
prometheusProxyEndpoint = "/api/prometheus"
alertManagerProxyEndpoint = "/api/alertmanager"
authLoginEndpoint = "/auth/login"
AuthLoginCallbackEndpoint = "/auth/callback"
AuthLoginSuccessEndpoint = "/"
AuthLoginErrorEndpoint = "/error"
authLogoutEndpoint = "/auth/logout"
k8sProxyEndpoint = "/api/kubernetes/"
prometheusProxyEndpoint = "/api/prometheus"
prometheusTenancyProxyEndpoint = "/api/prometheus-tenancy"
alertManagerProxyEndpoint = "/api/alertmanager"
)

var (
plog = capnslog.NewPackageLogger("github.com/openshift/console", "server")
)

type jsGlobals struct {
ConsoleVersion string `json:"consoleVersion"`
AuthDisabled bool `json:"authDisabled"`
KubectlClientID string `json:"kubectlClientID"`
BasePath string `json:"basePath"`
LoginURL string `json:"loginURL"`
LoginSuccessURL string `json:"loginSuccessURL"`
LoginErrorURL string `json:"loginErrorURL"`
LogoutURL string `json:"logoutURL"`
LogoutRedirect string `json:"logoutRedirect"`
KubeAPIServerURL string `json:"kubeAPIServerURL"`
PrometheusBaseURL string `json:"prometheusBaseURL"`
AlertManagerBaseURL string `json:"alertManagerBaseURL"`
Branding string `json:"branding"`
DocumentationBaseURL string `json:"documentationBaseURL"`
ClusterName string `json:"clusterName"`
GoogleTagManagerID string `json:"googleTagManagerID"`
LoadTestFactor int `json:"loadTestFactor"`
ConsoleVersion string `json:"consoleVersion"`
AuthDisabled bool `json:"authDisabled"`
KubectlClientID string `json:"kubectlClientID"`
BasePath string `json:"basePath"`
LoginURL string `json:"loginURL"`
LoginSuccessURL string `json:"loginSuccessURL"`
LoginErrorURL string `json:"loginErrorURL"`
LogoutURL string `json:"logoutURL"`
LogoutRedirect string `json:"logoutRedirect"`
KubeAPIServerURL string `json:"kubeAPIServerURL"`
PrometheusBaseURL string `json:"prometheusBaseURL"`
PrometheusTenancyBaseURL string `json:"prometheusTenancyBaseURL"`
AlertManagerBaseURL string `json:"alertManagerBaseURL"`
Branding string `json:"branding"`
DocumentationBaseURL string `json:"documentationBaseURL"`
ClusterName string `json:"clusterName"`
GoogleTagManagerID string `json:"googleTagManagerID"`
LoadTestFactor int `json:"loadTestFactor"`
}

type Server struct {
Expand All @@ -75,17 +77,18 @@ type Server struct {
LoadTestFactor int
DexClient api.DexClient
// A client with the correct TLS setup for communicating with the API server.
K8sClient *http.Client
PrometheusProxyConfig *proxy.Config
AlertManagerProxyConfig *proxy.Config
K8sClient *http.Client
PrometheusProxyConfig *proxy.Config
PrometheusTenancyProxyConfig *proxy.Config
AlertManagerProxyConfig *proxy.Config
}

func (s *Server) authDisabled() bool {
return s.Auther == nil
}

func (s *Server) prometheusProxyEnabled() bool {
return s.PrometheusProxyConfig != nil
return s.PrometheusProxyConfig != nil && s.PrometheusTenancyProxyConfig != nil
}

func (s *Server) alertManagerProxyEnabled() bool {
Expand Down Expand Up @@ -189,6 +192,15 @@ func (s *Server) HTTPHandler() http.Handler {
prometheusProxy.ServeHTTP(w, r)
})),
)
prometheusTenancyProxyAPIPath := prometheusTenancyProxyEndpoint + "/api/"
prometheusTenancyProxy := proxy.NewProxy(s.PrometheusTenancyProxyConfig)
handle(prometheusTenancyProxyAPIPath, http.StripPrefix(
proxy.SingleJoiningSlash(s.BaseURL.Path, prometheusTenancyProxyAPIPath),
authHandlerWithUser(func(user *auth.User, w http.ResponseWriter, r *http.Request) {
r.Header.Set("Authorization", fmt.Sprintf("Bearer %s", user.Token))
prometheusTenancyProxy.ServeHTTP(w, r)
})),
)
}

if s.alertManagerProxyEnabled() {
Expand Down Expand Up @@ -251,6 +263,7 @@ func (s *Server) indexHandler(w http.ResponseWriter, r *http.Request) {

if s.prometheusProxyEnabled() {
jsg.PrometheusBaseURL = proxy.SingleJoiningSlash(s.BaseURL.Path, prometheusProxyEndpoint)
jsg.PrometheusTenancyBaseURL = proxy.SingleJoiningSlash(s.BaseURL.Path, prometheusTenancyProxyEndpoint)
}

if s.alertManagerProxyEnabled() {
Expand Down