Fix Node Number validation (#918)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user