From e374772fc6c1dc950e16aa1fe233a897d6c50306 Mon Sep 17 00:00:00 2001 From: jinapurapu <65002498+jinapurapu@users.noreply.github.com> Date: Fri, 14 Jan 2022 11:15:09 -0800 Subject: [PATCH] Added Prometheus monitoring CPU and memory selector. (#1387) * Added Prometheus monitoring CPU and memory selector. * Minor text fixes * Fixed memory units, improvements to get return * Updated logic to add storageClassName to get response * Minor fixes, removed warnings * Removed blank entry for empty storageclassname Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com> --- models/tenant_monitoring_info.go | 6 + operatorapi/embedded_spec.go | 12 ++ operatorapi/operator_tenants.go | 108 +++++++--- portal-ui/package.json | 200 +++++++++--------- .../Console/Tenants/ListTenants/types.ts | 3 + .../EditTenantMonitoringModal.tsx | 56 ++++- .../TenantDetails/TenantMonitoring.tsx | 22 +- swagger-operator.yml | 4 + 8 files changed, 275 insertions(+), 136 deletions(-) diff --git a/models/tenant_monitoring_info.go b/models/tenant_monitoring_info.go index c70fee882..1bca2ce8b 100644 --- a/models/tenant_monitoring_info.go +++ b/models/tenant_monitoring_info.go @@ -51,6 +51,12 @@ type TenantMonitoringInfo struct { // labels Labels []*Label `json:"labels"` + // monitoring CPU request + MonitoringCPURequest string `json:"monitoringCPURequest,omitempty"` + + // monitoring mem request + MonitoringMemRequest string `json:"monitoringMemRequest,omitempty"` + // node selector NodeSelector []*NodeSelector `json:"nodeSelector"` diff --git a/operatorapi/embedded_spec.go b/operatorapi/embedded_spec.go index f7e08ef40..c3ed68487 100644 --- a/operatorapi/embedded_spec.go +++ b/operatorapi/embedded_spec.go @@ -3360,6 +3360,12 @@ func init() { "$ref": "#/definitions/label" } }, + "monitoringCPURequest": { + "type": "string" + }, + "monitoringMemRequest": { + "type": "string" + }, "nodeSelector": { "type": "array", "items": { @@ -7562,6 +7568,12 @@ func init() { "$ref": "#/definitions/label" } }, + "monitoringCPURequest": { + "type": "string" + }, + "monitoringMemRequest": { + "type": "string" + }, "nodeSelector": { "type": "array", "items": { diff --git a/operatorapi/operator_tenants.go b/operatorapi/operator_tenants.go index e88d1c44c..02882aff8 100644 --- a/operatorapi/operator_tenants.go +++ b/operatorapi/operator_tenants.go @@ -2143,49 +2143,75 @@ func getTenantMonitoringResponse(session *models.Principal, params operator_api. return nil, prepareError(err) } - var storageClassName string monitoringInfo := &models.TenantMonitoringInfo{} - if minInst.Spec.Prometheus == nil { - monitoringInfo := &models.TenantMonitoringInfo{ - PrometheusEnabled: (false), - } + if minInst.Spec.Prometheus != nil { + monitoringInfo.PrometheusEnabled = true + } else { + monitoringInfo.PrometheusEnabled = false return monitoringInfo, nil } + var storageClassName string if minInst.Spec.Prometheus.StorageClassName != nil { storageClassName = *minInst.Spec.Prometheus.StorageClassName + monitoringInfo.StorageClassName = storageClassName } - mLabels := []*models.Label{} - for k, v := range minInst.Spec.Prometheus.Labels { - mLabels = append(mLabels, &models.Label{Key: k, Value: v}) - } - mAnnotations := []*models.Annotation{} - for k, v := range minInst.Spec.Prometheus.Annotations { - mAnnotations = append(mAnnotations, &models.Annotation{Key: k, Value: v}) - } - mNodeSelector := []*models.NodeSelector{} - for k, v := range minInst.Spec.Prometheus.NodeSelector { - mNodeSelector = append(mNodeSelector, &models.NodeSelector{Key: k, Value: v}) + var requestedCPU string + var requestedMem string + + if minInst.Spec.Prometheus.Resources.Requests != nil { + requestedCPUQ := minInst.Spec.Prometheus.Resources.Requests["cpu"] + requestedCPU = strconv.FormatInt(requestedCPUQ.Value(), 10) + requestedMemQ := minInst.Spec.Prometheus.Resources.Requests["memory"] + requestedMem = strconv.FormatInt(requestedMemQ.Value(), 10) + monitoringInfo.MonitoringCPURequest = requestedCPU + monitoringInfo.MonitoringMemRequest = requestedMem } - if minInst.Spec.Prometheus != nil { - monitoringInfo = &models.TenantMonitoringInfo{ - PrometheusEnabled: (true), - Annotations: mAnnotations, - DiskCapacityGB: strconv.Itoa(*minInst.Spec.Prometheus.DiskCapacityDB), - Image: minInst.Spec.Prometheus.Image, - InitImage: minInst.Spec.Prometheus.InitImage, - Labels: mLabels, - NodeSelector: mNodeSelector, - ServiceAccountName: minInst.Spec.Prometheus.ServiceAccountName, - SidecarImage: minInst.Spec.Prometheus.SideCarImage, - StorageClassName: storageClassName, + if len(minInst.Spec.Prometheus.Labels) != 0 && minInst.Spec.Prometheus.Labels != nil { + mLabels := []*models.Label{} + for k, v := range minInst.Spec.Prometheus.Labels { + mLabels = append(mLabels, &models.Label{Key: k, Value: v}) } - return monitoringInfo, nil + monitoringInfo.Labels = mLabels } + + if len(minInst.Spec.Prometheus.Annotations) != 0 && minInst.Spec.Prometheus.Annotations != nil { + mAnnotations := []*models.Annotation{} + for k, v := range minInst.Spec.Prometheus.Annotations { + mAnnotations = append(mAnnotations, &models.Annotation{Key: k, Value: v}) + } + monitoringInfo.Annotations = mAnnotations + } + + if len(minInst.Spec.Prometheus.NodeSelector) != 0 && minInst.Spec.Prometheus.NodeSelector != nil { + mNodeSelector := []*models.NodeSelector{} + for k, v := range minInst.Spec.Prometheus.NodeSelector { + mNodeSelector = append(mNodeSelector, &models.NodeSelector{Key: k, Value: v}) + } + monitoringInfo.NodeSelector = mNodeSelector + } + + if *minInst.Spec.Prometheus.DiskCapacityDB != 0 { + monitoringInfo.DiskCapacityGB = strconv.Itoa(*minInst.Spec.Prometheus.DiskCapacityDB) + } + if len(minInst.Spec.Prometheus.Image) != 0 { + monitoringInfo.Image = minInst.Spec.Prometheus.Image + } + if len(minInst.Spec.Prometheus.InitImage) != 0 { + monitoringInfo.InitImage = minInst.Spec.Prometheus.InitImage + } + if len(minInst.Spec.Prometheus.ServiceAccountName) != 0 { + monitoringInfo.ServiceAccountName = minInst.Spec.Prometheus.ServiceAccountName + } + if len(minInst.Spec.Prometheus.SideCarImage) != 0 { + monitoringInfo.SidecarImage = minInst.Spec.Prometheus.SideCarImage + } + return monitoringInfo, nil + } //sets tenant Prometheus monitoring cofiguration fields to values provided @@ -2245,18 +2271,34 @@ func setTenantMonitoringResponse(session *models.Principal, params operator_api. } } - var storageClassName string - if ¶ms.Data.StorageClassName != nil { - storageClassName = params.Data.StorageClassName + monitoringResourceRequest := make(corev1.ResourceList) + if ¶ms.Data.MonitoringCPURequest != nil { + + cpuQuantity, err := resource.ParseQuantity(params.Data.MonitoringCPURequest) + if err != nil { + return false, prepareError(err) + } + memQuantity, err := resource.ParseQuantity(params.Data.MonitoringMemRequest) + if err != nil { + return false, prepareError(err) + } + monitoringResourceRequest["cpu"] = cpuQuantity + monitoringResourceRequest["memory"] = memQuantity } + minTenant.Spec.Prometheus.Resources.Requests = monitoringResourceRequest minTenant.Spec.Prometheus.Labels = labels minTenant.Spec.Prometheus.Annotations = annotations minTenant.Spec.Prometheus.NodeSelector = nodeSelector minTenant.Spec.Prometheus.Image = params.Data.Image minTenant.Spec.Prometheus.SideCarImage = params.Data.SidecarImage minTenant.Spec.Prometheus.InitImage = params.Data.InitImage - minTenant.Spec.Prometheus.StorageClassName = &storageClassName + if params.Data.StorageClassName == "" { + minTenant.Spec.Prometheus.StorageClassName = nil + } else { + minTenant.Spec.Prometheus.StorageClassName = ¶ms.Data.StorageClassName + } + diskCapacityGB, err := strconv.Atoi(params.Data.DiskCapacityGB) if err == nil { *minTenant.Spec.Prometheus.DiskCapacityDB = diskCapacityGB diff --git a/portal-ui/package.json b/portal-ui/package.json index c59ea43a0..a1e395e6b 100644 --- a/portal-ui/package.json +++ b/portal-ui/package.json @@ -1,102 +1,102 @@ { - "name": "portal-ui", - "version": "0.1.0", - "homepage": ".", - "private": true, - "dependencies": { - "@codemirror/lang-json": "^0.19.1", - "@codemirror/legacy-modes": "^0.19.0", - "@codemirror/stream-parser": "^0.19.3", - "@date-io/moment": "1.x", - "@emotion/react": "^11.4.1", - "@emotion/styled": "^11.3.0", - "@hot-loader/react-dom": "17.0.1", - "@mui/icons-material": "^5.0.4", - "@mui/lab": "^5.0.0-alpha.30", - "@mui/material": "^5.0.4", - "@mui/styled-engine-sc": "^5.0.3", - "@mui/styles": "^5.0.1", - "@types/history": "^4.7.3", - "@types/jest": "24.0.23", - "@types/lodash": "^4.14.149", - "@types/node": "12.12.8", - "@types/react": "17.0.0", - "@types/react-copy-to-clipboard": "^4.3.0", - "@types/react-dom": "16.9.4", - "@types/react-grid-layout": "^1.1.1", - "@types/react-redux": "^7.1.5", - "@types/react-router": "^5.1.3", - "@types/react-router-dom": "^5.1.2", - "@types/react-virtualized": "^9.21.10", - "@types/superagent": "^4.1.12", - "@types/webpack-env": "^1.14.1", - "@types/websocket": "^1.0.0", - "@uiw/react-codemirror": "^4.3.2", - "ansi-to-react": "^6.0.5", - "chart.js": "^2.9.3", - "codemirror": "^5.52.2", - "history": "^4.10.1", - "local-storage-fallback": "^4.1.1", - "lodash": "^4.17.21", - "moment": "^2.29.1", - "react": "^17.0.2", - "react-async-hook": "^3.6.1", - "react-chartjs-2": "^2.9.0", - "react-copy-to-clipboard": "^5.0.2", - "react-dom": "17.0.1", - "react-dropzone": "^11.4.2", - "react-grid-layout": "^1.2.0", - "react-hot-loader": "^4.13.0", - "react-moment": "^1.1.1", - "react-redux": "^7.1.3", - "react-router-dom": "^5.1.2", - "react-virtualized": "^9.22.2", - "react-window": "^1.8.6", - "react-window-infinite-loader": "^1.0.7", - "recharts": "^2.1.1", - "redux": "^4.0.5", - "redux-thunk": "^2.3.0", - "styled-components": "^5.3.1", - "superagent": "^6.1.0", - "typeface-roboto": "^0.0.75", - "use-debounce": "^5.0.1", - "websocket": "^1.0.31" - }, - "scripts": { - "start": "PORT=5005 react-app-rewired start", - "build": "react-scripts build", - "test": "react-scripts test", - "eject": "react-scripts eject" - }, - "eslintConfig": { - "extends": "react-app" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "proxy": "http://localhost:9090/", - "devDependencies": { - "@types/react-window": "^1.8.5", - "@types/react-window-infinite-loader": "^1.0.5", - "@types/recharts": "^1.8.22", - "prettier": "2.3.2", - "react-app-rewire-hot-loader": "^2.0.1", - "react-app-rewired": "^2.1.6", - "react-scripts": "5.0.0", - "typescript": "^4.4.3" - }, - "resolutions": { - "ansi-regex": "^5.0.1", - "nth-check": "^2.0.1", - "postcss": "^8.2.13" - } + "name": "portal-ui", + "version": "0.1.0", + "homepage": ".", + "private": true, + "dependencies": { + "@codemirror/lang-json": "^0.19.1", + "@codemirror/legacy-modes": "^0.19.0", + "@codemirror/stream-parser": "^0.19.3", + "@date-io/moment": "1.x", + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "@hot-loader/react-dom": "17.0.1", + "@mui/icons-material": "^5.0.4", + "@mui/lab": "^5.0.0-alpha.30", + "@mui/material": "^5.0.4", + "@mui/styled-engine-sc": "^5.0.3", + "@mui/styles": "^5.0.1", + "@types/history": "^4.7.3", + "@types/jest": "24.0.23", + "@types/lodash": "^4.14.149", + "@types/node": "12.12.8", + "@types/react": "17.0.0", + "@types/react-copy-to-clipboard": "^4.3.0", + "@types/react-dom": "16.9.4", + "@types/react-grid-layout": "^1.1.1", + "@types/react-redux": "^7.1.5", + "@types/react-router": "^5.1.3", + "@types/react-router-dom": "^5.1.2", + "@types/react-virtualized": "^9.21.10", + "@types/superagent": "^4.1.12", + "@types/webpack-env": "^1.14.1", + "@types/websocket": "^1.0.0", + "@uiw/react-codemirror": "^4.3.2", + "ansi-to-react": "^6.0.5", + "chart.js": "^2.9.3", + "codemirror": "^5.52.2", + "history": "^4.10.1", + "local-storage-fallback": "^4.1.1", + "lodash": "^4.17.21", + "moment": "^2.29.1", + "react": "^17.0.2", + "react-async-hook": "^3.6.1", + "react-chartjs-2": "^2.9.0", + "react-copy-to-clipboard": "^5.0.2", + "react-dom": "17.0.1", + "react-dropzone": "^11.4.2", + "react-grid-layout": "^1.2.0", + "react-hot-loader": "^4.13.0", + "react-moment": "^1.1.1", + "react-redux": "^7.1.3", + "react-router-dom": "^5.1.2", + "react-virtualized": "^9.22.2", + "react-window": "^1.8.6", + "react-window-infinite-loader": "^1.0.7", + "recharts": "^2.1.1", + "redux": "^4.0.5", + "redux-thunk": "^2.3.0", + "styled-components": "^5.3.1", + "superagent": "^6.1.0", + "typeface-roboto": "^0.0.75", + "use-debounce": "^5.0.1", + "websocket": "^1.0.31" + }, + "scripts": { + "start": "PORT=5005 react-app-rewired start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": "react-app" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "proxy": "http://localhost:9090/", + "devDependencies": { + "@types/react-window": "^1.8.5", + "@types/react-window-infinite-loader": "^1.0.5", + "@types/recharts": "^1.8.22", + "prettier": "2.3.2", + "react-app-rewire-hot-loader": "^2.0.1", + "react-app-rewired": "^2.1.6", + "react-scripts": "5.0.0", + "typescript": "^4.4.3" + }, + "resolutions": { + "ansi-regex": "^5.0.1", + "nth-check": "^2.0.1", + "postcss": "^8.2.13" + } } diff --git a/portal-ui/src/screens/Console/Tenants/ListTenants/types.ts b/portal-ui/src/screens/Console/Tenants/ListTenants/types.ts index b3e9e3fe7..eaa1399b6 100644 --- a/portal-ui/src/screens/Console/Tenants/ListTenants/types.ts +++ b/portal-ui/src/screens/Console/Tenants/ListTenants/types.ts @@ -16,6 +16,7 @@ import { LicenseInfo } from "../../License/types"; import { IAffinityModel } from "../../../../common/types"; +import { NodeMaxAllocatableResources } from "../types"; export interface IEvent { namespace: string; @@ -141,6 +142,8 @@ export interface ITenantMonitoringStruct { diskCapacityGB: string; serviceAccountName: string; prometheusEnabled: boolean; + monitoringCPURequest: string; + monitoringMemRequest: string; } export interface IKeyValue { diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/EditTenantMonitoringModal.tsx b/portal-ui/src/screens/Console/Tenants/TenantDetails/EditTenantMonitoringModal.tsx index a37d6fce8..47d81ab59 100644 --- a/portal-ui/src/screens/Console/Tenants/TenantDetails/EditTenantMonitoringModal.tsx +++ b/portal-ui/src/screens/Console/Tenants/TenantDetails/EditTenantMonitoringModal.tsx @@ -33,6 +33,8 @@ interface IEditTenantMonitoringProps { tenantName: string; tenantNamespace: string; storageClassName: string; + cpuRequest: string; + memRequest: string; } const styles = (theme: Theme) => @@ -59,6 +61,8 @@ const EditTenantMonitoringModal = ({ storageClassName, tenantName, tenantNamespace, + cpuRequest, + memRequest, }: IEditTenantMonitoringProps) => { const [validationErrors, setValidationErrors] = useState({}); const [newLabels, setNewLabels] = useState( @@ -76,6 +80,10 @@ const EditTenantMonitoringModal = ({ const [newDiskCapacityGB, setNewDiskCapacityGB] = useState( diskCapacityGB.toString() ); + const [newCPURequest, setNewCPURequest] = useState(cpuRequest); + const [newMemRequest, setNewMemRequest] = useState( + Math.floor(parseInt(memRequest, 10) / 1000000000).toString() + ); const [newServiceAccountName, setNewServiceAccountName] = useState(serviceAccountName); const [newStorageClassName, setNewStorageClassName] = @@ -129,6 +137,20 @@ const EditTenantMonitoringModal = ({ pattern: /^[0-9]?(10)?$/, customPatternMessage: "Must be an integer between 0 and 10", }); + tenantMonitoringValidation.push({ + fieldKey: `newCPURequest`, + required: false, + value: newCPURequest as any as string, + pattern: /^[0-9]?(10)?$/, + customPatternMessage: "Must be an integer between 0 and 10", + }); + tenantMonitoringValidation.push({ + fieldKey: `newMemRequest`, + required: false, + value: newMemRequest as any as string, + pattern: /^[0-9]?(10)?$/, + customPatternMessage: "Must be an integer between 0 and 10", + }); tenantMonitoringValidation.push({ fieldKey: `serviceAccountName`, required: false, @@ -153,6 +175,8 @@ const EditTenantMonitoringModal = ({ newDiskCapacityGB, newServiceAccountName, newStorageClassName, + newCPURequest, + newMemRequest, setValidationErrors, ]); @@ -191,6 +215,8 @@ const EditTenantMonitoringModal = ({ diskCapacityGB: newDiskCapacityGB, serviceAccountName: newServiceAccountName, storageClassName: newStorageClassName, + monitoringCPURequest: newCPURequest, + monitoringMemRequest: newMemRequest + "Gi", } ) .then(() => { @@ -253,14 +279,40 @@ const EditTenantMonitoringModal = ({ label={""} placeholder={"Disk Capacity (GB)"} name={`diskCapacityGB`} - value={newDiskCapacityGB.toString()} + value={newDiskCapacityGB} onChange={(event: React.ChangeEvent) => { setNewDiskCapacityGB(event.target.value); }} key={`diskCapacityGB`} error={validationErrors[`diskCapacityGB`] || ""} /> -

Service Account

+

Prometheus CPU Request

+ ) => { + setNewCPURequest(event.target.value); + }} + key={`cpuRequest`} + error={validationErrors[`cpuRequest`] || ""} + /> +

Prometheus Memory Request (Gi)

+ ) => { + setNewMemRequest(event.target.value); + }} + key={`memRequest`} + error={validationErrors[`memRequest`] || ""} + /> +

Service Account Name

)} {confirmOpen && ( @@ -303,10 +306,27 @@ const TenantMonitoring = ({ )} + {monitoringInfo.monitoringCPURequest != null && ( + + CPU Request: + {monitoringInfo?.monitoringCPURequest} + + )} + {monitoringInfo.monitoringMemRequest != null && ( + + Memory Request: + + {niceBytes( + monitoringInfo?.monitoringMemRequest, + true + )} + + + )} {monitoringInfo.nodeSelector != null && monitoringInfo.nodeSelector.length > 0 && ( -

Node Seletor

+

Node Selector: