Parcourir la source

auto-detection and network requests

Alexander Belanger il y a 5 ans
Parent
commit
6eeae2d34f

+ 24 - 0
dashboard/src/main/home/cluster-dashboard/ClusterDashboard.tsx

@@ -11,6 +11,8 @@ import SortSelector from "./SortSelector";
 import ExpandedChart from "./expanded-chart/ExpandedChart";
 import { RouteComponentProps, withRouter } from "react-router";
 
+import api from "shared/api";
+
 type PropsType = RouteComponentProps & {
   currentCluster: ClusterType;
   setSidebar: (x: boolean) => void;
@@ -20,6 +22,7 @@ type StateType = {
   namespace: string;
   sortType: string;
   currentChart: ChartType | null;
+  isMetricsInstalled: boolean;
 };
 
 class ClusterDashboard extends Component<PropsType, StateType> {
@@ -29,8 +32,28 @@ class ClusterDashboard extends Component<PropsType, StateType> {
       ? localStorage.getItem("SortType")
       : "Newest",
     currentChart: null as ChartType | null,
+    isMetricsInstalled: false,
   };
 
+  componentDidMount() {
+    api
+      .getPrometheusIsInstalled(
+        "<token>",
+        {
+          cluster_id: this.context.currentCluster.id,
+        },
+        {
+          id: this.context.currentProject.id,
+        }
+      )
+      .then((res) => {
+        this.setState({ isMetricsInstalled: true });
+      })
+      .catch(() => {
+        this.setState({ isMetricsInstalled: false });
+      });
+  }
+
   componentDidUpdate(prevProps: PropsType) {
     localStorage.setItem("SortType", this.state.sortType);
     // Reset namespace filter and close expanded chart on cluster change
@@ -77,6 +100,7 @@ class ClusterDashboard extends Component<PropsType, StateType> {
           setCurrentChart={(x: ChartType | null) =>
             this.setState({ currentChart: x })
           }
+          isMetricsInstalled={this.state.isMetricsInstalled}
           setSidebar={setSidebar}
         />
       );

+ 10 - 1
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx

@@ -33,6 +33,7 @@ type PropsType = {
   currentCluster: ClusterType;
   setCurrentChart: (x: ChartType | null) => void;
   setSidebar: (x: boolean) => void;
+  isMetricsInstalled: boolean;
 };
 
 type StateType = {
@@ -364,7 +365,15 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
     // Append universal tabs
     tabOptions.push(
       { label: "Status", value: "status" },
-      { label: "Metrics", value: "metrics" },
+    );
+
+    if (this.props.isMetricsInstalled) {
+      tabOptions.push(
+        { label: "Metrics", value: "metrics" },
+      )
+    }
+
+    tabOptions.push(
       { label: "Chart Overview", value: "graph" }
     );
 

+ 25 - 0
dashboard/src/main/home/cluster-dashboard/expanded-chart/metrics/MetricsSection.tsx

@@ -43,6 +43,14 @@ type MetricsMemoryDataResponse = {
   }[],
 }[]
 
+type MetricsNetworkDataResponse = {
+  pod?: string,
+  results: {
+    date: number,
+    bytes: string,
+  }[],
+}[]
+
 const resolutions : { [range: string]: string } = {
   "1H": "15s",
   "6H": "15s",
@@ -196,6 +204,22 @@ export default class MetricsSection extends Component<PropsType, StateType> {
             }
           )
 
+          this.setState({ data: tData })
+        } else if (kind == "network") {
+          let data = res.data as MetricsNetworkDataResponse
+
+          let tData = data[0].results.map(
+            (d: {
+              date: number,
+              bytes: string,
+            }, i: number) => {
+              return {
+                date: d.date,
+                value: parseFloat(d.bytes) / (1024), // put units in Ki
+              }
+            }
+          )
+
           this.setState({ data: tData })
         }
       })
@@ -360,6 +384,7 @@ export default class MetricsSection extends Component<PropsType, StateType> {
     let metricOptions = [
       { value: "cpu", label: "CPU Utilization (vCPUs)" },
       { value: "memory", label: "RAM Utilization (Mi)" },
+      { value: "network", label: "Network Received Bytes (Ki)" },
     ];
     return metricOptions.map(
       (option: { value: string; label: string }, i: number) => {

+ 10 - 0
dashboard/src/shared/api.tsx

@@ -401,6 +401,15 @@ const getProjects = baseApi<{}, { id: number }>("GET", (pathParams) => {
   return `/api/users/${pathParams.id}/projects`;
 });
 
+const getPrometheusIsInstalled = baseApi<
+{
+  cluster_id: number;
+},
+{ id: number }
+>("GET", (pathParams) => {
+return `/api/projects/${pathParams.id}/k8s/prometheus/detect`;
+});
+
 const getRegistryIntegrations = baseApi("GET", "/api/integrations/registry");
 
 const getReleaseToken = baseApi<
@@ -621,6 +630,7 @@ export default {
   getProjectRegistries,
   getProjectRepos,
   getProjects,
+  getPrometheusIsInstalled,
   getRegistryIntegrations,
   getReleaseToken,
   getRepoIntegrations,

+ 8 - 0
internal/kubernetes/prometheus/metrics.go

@@ -55,12 +55,17 @@ func QueryPrometheus(
 		query = fmt.Sprintf("rate(container_cpu_usage_seconds_total{%s}[5m])", podSelector)
 	} else if opts.Metric == "memory" {
 		query = fmt.Sprintf("container_memory_usage_bytes{%s}", podSelector)
+	} else if opts.Metric == "network" {
+		netPodSelector := fmt.Sprintf(`namespace="%s",pod=~"%s",container="POD"`, opts.Namespace, strings.Join(opts.PodList, "|"))
+		query = fmt.Sprintf("rate(container_network_receive_bytes_total{%s}[5m])", netPodSelector)
 	}
 
 	if opts.ShouldSum {
 		query = fmt.Sprintf("sum(%s)", query)
 	}
 
+	fmt.Println("QUERY IS", query)
+
 	queryParams := map[string]string{
 		"query": query,
 		"start": fmt.Sprintf("%d", opts.StartRange),
@@ -101,6 +106,7 @@ type promParsedSingletonQueryResult struct {
 	Date   interface{} `json:"date,omitempty"`
 	CPU    interface{} `json:"cpu,omitempty"`
 	Memory interface{} `json:"memory,omitempty"`
+	Bytes  interface{} `json:"bytes,omitempty"`
 }
 
 type promParsedSingletonQuery struct {
@@ -131,6 +137,8 @@ func parseQuery(rawQuery []byte, metric string) ([]byte, error) {
 				singletonResult.CPU = values[1]
 			} else if metric == "memory" {
 				singletonResult.Memory = values[1]
+			} else if metric == "network" {
+				singletonResult.Bytes = values[1]
 			}
 
 			singletonResults = append(singletonResults, *singletonResult)

+ 46 - 0
server/api/k8s_handler.go

@@ -325,6 +325,52 @@ func (app *App) HandleStreamControllerStatus(w http.ResponseWriter, r *http.Requ
 	}
 }
 
+// HandleDetectPrometheusInstalled detects a prometheus installation in the target cluster
+func (app *App) HandleDetectPrometheusInstalled(w http.ResponseWriter, r *http.Request) {
+	vals, err := url.ParseQuery(r.URL.RawQuery)
+
+	if err != nil {
+		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
+		return
+	}
+
+	// get the filter options
+	form := &forms.K8sForm{
+		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
+			Repo:              app.Repo,
+			DigitalOceanOAuth: app.DOConf,
+		},
+	}
+
+	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
+
+	// validate the form
+	if err := app.validator.Struct(form); err != nil {
+		app.handleErrorFormValidation(err, ErrK8sValidate, w)
+		return
+	}
+
+	// create a new agent
+	var agent *kubernetes.Agent
+
+	if app.ServerConf.IsTesting {
+		agent = app.TestAgents.K8sAgent
+	} else {
+		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
+	}
+
+	// detect prometheus service
+	_, found, err := prometheus.GetPrometheusService(agent.Clientset)
+
+	if !found {
+		http.NotFound(w, r)
+		return
+	}
+
+	w.WriteHeader(http.StatusOK)
+	return
+}
+
 func (app *App) HandleGetPodMetrics(w http.ResponseWriter, r *http.Request) {
 	vals, err := url.ParseQuery(r.URL.RawQuery)
 

+ 14 - 0
server/router/router.go

@@ -1050,6 +1050,20 @@ func New(a *api.App) *chi.Mux {
 			),
 		)
 
+		r.Method(
+			"GET",
+			"/projects/{project_id}/k8s/prometheus/detect",
+			auth.DoesUserHaveProjectAccess(
+				auth.DoesUserHaveClusterAccess(
+					requestlog.NewHandler(a.HandleDetectPrometheusInstalled, l),
+					mw.URLParam,
+					mw.QueryParam,
+				),
+				mw.URLParam,
+				mw.ReadAccess,
+			),
+		)
+
 		r.Method(
 			"GET",
 			"/projects/{project_id}/k8s/metrics",