Fix Node Number validation (#918)

Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
This commit is contained in:
Daniel Valdivia
2021-08-03 09:18:51 -07:00
committed by GitHub
parent 7e4d34958e
commit fcb74aee77
4 changed files with 44 additions and 41 deletions

View File

@@ -38,9 +38,10 @@ var (
errLicenseNotFound = errors.New("license not found") errLicenseNotFound = errors.New("license not found")
errAvoidSelfAccountDelete = errors.New("logged in user cannot be deleted by itself") errAvoidSelfAccountDelete = errors.New("logged in user cannot be deleted by itself")
errAccessDenied = errors.New("access denied") errAccessDenied = errors.New("access denied")
errTooFewNodes = errors.New("at least 4 nodes are required in cluster") errTooManyNodes = errors.New("cannot request more nodes than what is available in the cluster")
errTooFewSchedulableNodes = errors.New("at least 4 schedulable nodes are required in cluster") errTooFewNodes = errors.New("there are not enough nodes in the cluster to support this tenant")
errFewerThanFourNodes = errors.New("at least 4 nodes are required in request") errTooFewSchedulableNodes = errors.New("there is not enough schedulable nodes to satisfy this requirement")
errFewerThanFourNodes = errors.New("at least 4 nodes are required for a tenant")
) )
// prepareError receives an error object and parse it against k8sErrors, returns the right error code paired with a generic error message // prepareError receives an error object and parse it against k8sErrors, returns the right error code paired with a generic error message

View File

@@ -26,8 +26,6 @@ import (
"github.com/minio/console/cluster" "github.com/minio/console/cluster"
"errors"
"github.com/go-openapi/runtime/middleware" "github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models" "github.com/minio/console/models"
"github.com/minio/console/operatorapi/operations" "github.com/minio/console/operatorapi/operations"
@@ -56,8 +54,9 @@ func registerNodesHandlers(api *operations.OperatorAPI) {
// getMaxAllocatableMemory get max allocatable memory given a desired number of nodes // getMaxAllocatableMemory get max allocatable memory given a desired number of nodes
func getMaxAllocatableMemory(ctx context.Context, clientset v1.CoreV1Interface, numNodes int32) (*models.MaxAllocatableMemResponse, error) { func getMaxAllocatableMemory(ctx context.Context, clientset v1.CoreV1Interface, numNodes int32) (*models.MaxAllocatableMemResponse, error) {
// can't request less than 4 nodes
if numNodes < 4 { if numNodes < 4 {
return nil, errors.New("error NumNodes must be at least 4") return nil, errFewerThanFourNodes
} }
// get all nodes from cluster // get all nodes from cluster
@@ -65,16 +64,32 @@ func getMaxAllocatableMemory(ctx context.Context, clientset v1.CoreV1Interface,
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(nodes.Items) < int(numNodes) {
return nil, errTooFewNodes // requesting more nodes than are schedulable in the cluster
} schedulableNodes := len(nodes.Items)
activeNodes := 0 nonMasterNodes := len(nodes.Items)
for i := 0; i < len(nodes.Items); i++ { for _, node := range nodes.Items {
if !nodes.Items[i].Spec.Unschedulable { // check taints to check if node is schedulable
activeNodes++ for _, taint := range node.Spec.Taints {
if taint.Effect == corev1.TaintEffectNoSchedule {
schedulableNodes--
}
// check if the node is a master
if taint.Key == "node-role.kubernetes.io/master" {
nonMasterNodes--
}
} }
} }
if activeNodes < int(numNodes) { // requesting more nodes than schedulable and less than total number of workers
if int(numNodes) > schedulableNodes && int(numNodes) < nonMasterNodes {
return nil, errTooManyNodes
}
if nonMasterNodes < int(numNodes) {
return nil, errTooFewNodes
}
// not enough schedulable nodes
if schedulableNodes < int(numNodes) {
return nil, errTooFewSchedulableNodes return nil, errTooFewSchedulableNodes
} }

View File

@@ -224,16 +224,6 @@ export const calculateDistribution = (
}; };
} }
if (forcedNodes < 4) {
return {
error: "Number of nodes cannot be less than 4",
nodes: 0,
persistentVolumes: 0,
disks: 0,
pvSize: 0,
};
}
if (drivesPerServer <= 0) { if (drivesPerServer <= 0) {
return { return {
error: "Number of drives must be at least 1", error: "Number of drives must be at least 1",

View File

@@ -14,14 +14,14 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useState, useEffect, useCallback } from "react"; import React, { Fragment, useCallback, useEffect, useState } from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { AppState } from "../../../../../store"; import { AppState } from "../../../../../store";
import { updateAddField, isPageValid } from "../../actions"; import { isPageValid, updateAddField } from "../../actions";
import { import {
wizardCommon,
modalBasic, modalBasic,
wizardCommon,
} from "../../../Common/FormComponents/common/styleLibrary"; } from "../../../Common/FormComponents/common/styleLibrary";
import Grid from "@material-ui/core/Grid"; import Grid from "@material-ui/core/Grid";
import Table from "@material-ui/core/Table"; import Table from "@material-ui/core/Table";
@@ -29,11 +29,11 @@ import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell"; import TableCell from "@material-ui/core/TableCell";
import TableRow from "@material-ui/core/TableRow"; import TableRow from "@material-ui/core/TableRow";
import { import {
calculateDistribution,
erasureCodeCalc,
getBytes, getBytes,
k8sfactorForDropdown, k8sfactorForDropdown,
niceBytes, niceBytes,
calculateDistribution,
erasureCodeCalc,
setMemoryResource, setMemoryResource,
} from "../../../../../common/utils"; } from "../../../../../common/utils";
import { clearValidationError } from "../../utils"; import { clearValidationError } from "../../utils";
@@ -138,6 +138,7 @@ const TenantSize = ({
const getMaxAllocableMemory = (nodes: string) => { const getMaxAllocableMemory = (nodes: string) => {
if (nodes !== "" && !isNaN(parseInt(nodes))) { if (nodes !== "" && !isNaN(parseInt(nodes))) {
setNodeError("");
api api
.invoke( .invoke(
"GET", "GET",
@@ -147,10 +148,9 @@ const TenantSize = ({
const maxMemory = res.max_memory ? res.max_memory : 0; const maxMemory = res.max_memory ? res.max_memory : 0;
updateField("maxAllocableMemo", maxMemory); updateField("maxAllocableMemo", maxMemory);
}) })
.catch((err: any) => { .catch((err: ErrorResponseHandler) => {
setErrorFlag(true); setErrorFlag(true);
setNodeError(err.errorMessage); setNodeError(err.errorMessage);
console.error(err);
}); });
} }
}; };
@@ -230,13 +230,6 @@ const TenantSize = ({
useEffect(() => { useEffect(() => {
const parsedSize = getBytes(volumeSize, sizeFactor, true); const parsedSize = getBytes(volumeSize, sizeFactor, true);
const commonValidation = commonFormValidation([ const commonValidation = commonFormValidation([
{
fieldKey: "nodes",
required: true,
value: nodes,
customValidation: parseInt(nodes) < 4,
customValidationMessage: "Number of nodes cannot be less than 4",
},
{ {
fieldKey: "nodes", fieldKey: "nodes",
required: true, required: true,
@@ -297,7 +290,7 @@ const TenantSize = ({
selectedStorageClass, selectedStorageClass,
isPageValid, isPageValid,
errorFlag, errorFlag,
nodeError nodeError,
]); ]);
/* End Validation of pages */ /* End Validation of pages */
@@ -310,8 +303,12 @@ const TenantSize = ({
Please select the desired capacity Please select the desired capacity
</span> </span>
</div> </div>
<span className={classes.error}>{distribution.error}</span> {distribution.error !== "" && (
<span className={classes.error}>{memorySize.error}</span> <div className={classes.error}>{distribution.error}</div>
)}
{memorySize.error !== "" && (
<div className={classes.error}>{memorySize.error}</div>
)}
<Grid item xs={12}> <Grid item xs={12}>
<InputBoxWrapper <InputBoxWrapper
id="nodes" id="nodes"