Added tolerations selector to Add Tenant wizard (#1747)

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
Alex
2022-03-23 10:49:55 -07:00
committed by GitHub
parent 842c3dee5f
commit bef3897d0a
7 changed files with 470 additions and 8 deletions

View File

@@ -134,10 +134,21 @@ export interface IMatchExpressionItem {
values: string[];
}
export enum ITolerationEffect {
"NoSchedule" = "NoSchedule",
"PreferNoSchedule" = "PreferNoSchedule",
"NoExecute" = "NoExecute",
}
export enum ITolerationOperator {
"Equal" = "Equal",
"Exists" = "Exists",
}
export interface ITolerationModel {
effect: string;
effect: ITolerationEffect;
key: string;
operator: string;
operator: ITolerationOperator;
value?: string;
tolerationSeconds?: ITolerationSeconds;
}
@@ -194,6 +205,7 @@ export interface IGCPSecretManager {
endpoint?: string;
credentials?: IGCPCredentials;
}
export interface IGCPConfig {
secretmanager: IGCPSecretManager;
}
@@ -203,6 +215,7 @@ export interface IAzureCredentials {
client_id: string;
client_secret: string;
}
export interface IAzureKeyVault {
endpoint: string;
credentials?: IAzureCredentials;

View File

@@ -0,0 +1,222 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// 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/>.
import React from "react";
import {
ITolerationEffect,
ITolerationOperator,
} from "../../../../common/types";
import SelectWrapper, {
selectorTypes,
} from "../FormComponents/SelectWrapper/SelectWrapper";
import { Grid, SelectChangeEvent } from "@mui/material";
import InputBoxWrapper from "../FormComponents/InputBoxWrapper/InputBoxWrapper";
import InputUnitMenu from "../FormComponents/InputUnitMenu/InputUnitMenu";
import withStyles from "@mui/styles/withStyles";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
interface ITolerationSelector {
effect: ITolerationEffect;
onEffectChange: (value: ITolerationEffect) => void;
tolerationKey: string;
onTolerationKeyChange: (value: string) => void;
operator: ITolerationOperator;
onOperatorChange: (value: ITolerationOperator) => void;
value?: string;
onValueChange: (value: string) => void;
tolerationSeconds?: number;
onSecondsChange: (value: number) => void;
index: number;
classes: any;
}
const styles = (theme: Theme) =>
createStyles({
labelsStyle: {
fontSize: 18,
fontWeight: "bold",
color: "#AEAEAE",
display: "flex",
alignItems: "center",
justifyContent: "center",
maxWidth: 45,
marginRight: 10,
},
fieldsetStyle: {
border: "1px solid #EAEAEA",
borderRadius: 2,
padding: 10,
marginBottom: 15,
},
firstLevel: {
marginBottom: 10,
},
fieldContainer: {
marginRight: 10,
},
legendStyle: {
fontSize: 12,
color: "#696969",
fontWeight: "bold",
},
});
const TolerationSelector = ({
effect,
onEffectChange,
tolerationKey,
onTolerationKeyChange,
operator,
onOperatorChange,
value,
onValueChange,
tolerationSeconds,
onSecondsChange,
index,
classes,
}: ITolerationSelector) => {
const operatorOptions: selectorTypes[] = [];
const effectOptions: selectorTypes[] = [];
for (let operator in ITolerationOperator) {
operatorOptions.push({
value: operator,
label: operator,
});
}
for (let effect in ITolerationEffect) {
effectOptions.push({
value: effect,
label: effect,
});
}
return (
<Grid item xs={12}>
<fieldset className={classes.fieldsetStyle}>
<legend className={classes.legendStyle}>Toleration {index + 1}</legend>
<Grid container>
<Grid container className={classes.firstLevel}>
<Grid item xs className={classes.labelsStyle}>
If
</Grid>
<Grid item xs className={classes.fieldContainer}>
<InputBoxWrapper
id={`keyField-${index}`}
label={""}
name={`keyField-${index}`}
value={tolerationKey}
onChange={(e) => {
onTolerationKeyChange(e.target.value);
}}
index={index}
placeholder={"Toleration Key"}
/>
</Grid>
{ITolerationOperator[operator] === ITolerationOperator.Equal && (
<Grid item xs className={classes.labelsStyle}>
is
</Grid>
)}
<Grid item xs={1} className={classes.fieldContainer}>
<SelectWrapper
onChange={(e: SelectChangeEvent<string>) => {
onOperatorChange(
ITolerationOperator[e.target.value as ITolerationOperator]
);
}}
id={`operator-${index}`}
name="operator"
label={""}
value={ITolerationOperator[operator]}
options={operatorOptions}
/>
</Grid>
{ITolerationOperator[operator] === ITolerationOperator.Equal && (
<Grid item xs className={classes.labelsStyle}>
to
</Grid>
)}
{ITolerationOperator[operator] === ITolerationOperator.Equal && (
<Grid item xs className={classes.fieldContainer}>
<InputBoxWrapper
id={`valueField-${index}`}
label={""}
name={`valueField-${index}`}
value={value || ""}
onChange={(e) => {
onValueChange(e.target.value);
}}
index={index}
placeholder={"Toleration Value"}
/>
</Grid>
)}
</Grid>
<Grid container>
<Grid item xs className={classes.labelsStyle}>
then
</Grid>
<Grid item xs className={classes.fieldContainer}>
<SelectWrapper
onChange={(e: SelectChangeEvent<string>) => {
onEffectChange(
ITolerationEffect[e.target.value as ITolerationEffect]
);
}}
id={`effects-${index}`}
name="effects"
label={""}
value={ITolerationEffect[effect]}
options={effectOptions}
/>
</Grid>
<Grid item xs className={classes.labelsStyle}>
after
</Grid>
<Grid item xs className={classes.fieldContainer}>
<InputBoxWrapper
id={`seconds-${index}`}
label={""}
name={`seconds-${index}`}
value={tolerationSeconds?.toString() || "0"}
onChange={(e) => {
if (e.target.validity.valid) {
onSecondsChange(parseInt(e.target.value));
}
}}
index={index}
pattern={"[0-9]*"}
overlayObject={
<InputUnitMenu
id={`seconds-${index}`}
unitSelected={"seconds"}
unitsList={[{ label: "Seconds", value: "seconds" }]}
disabled={true}
/>
}
/>
</Grid>
</Grid>
</Grid>
</fieldset>
</Grid>
);
};
export default withStyles(styles)(TolerationSelector);

View File

@@ -33,7 +33,11 @@ import { generatePoolName, getBytes } from "../../../../common/utils";
import GenericWizard from "../../Common/GenericWizard/GenericWizard";
import { IWizardElement } from "../../Common/GenericWizard/types";
import { NewServiceAccount } from "../../Common/CredentialsPrompt/types";
import { ErrorResponseHandler, ITenantCreator } from "../../../../common/types";
import {
ErrorResponseHandler,
ITenantCreator,
ITolerationModel,
} from "../../../../common/types";
import { KeyPair } from "../ListTenants/utils";
import { setErrorSnackMessage } from "../../../../actions";
@@ -73,6 +77,7 @@ interface IAddTenantProps {
validPages: string[];
classes: any;
features?: string[];
tolerations: ITolerationModel[];
}
const styles = (theme: Theme) =>
@@ -95,6 +100,7 @@ const AddTenant = ({
setErrorSnackMessage,
resetAddTenantForm,
features,
tolerations,
}: IAddTenantProps) => {
// Modals
const [showNewCredentials, setShowNewCredentials] = useState<boolean>(false);
@@ -232,6 +238,10 @@ const AddTenant = ({
const kesReplicas = fields.encryption.replicas;
if (addSending) {
const tolerationValues = tolerations.filter(
(toleration) => toleration.key.trim() !== ""
);
const poolName = generatePoolName([]);
let affinityObject = {};
@@ -281,6 +291,7 @@ const AddTenant = ({
},
securityContext: tenantCustom ? tenantSecurityContext : null,
...affinityObject,
tolerations: tolerationValues,
},
],
erasureCodingParity: parseInt(erasureCode, 10),
@@ -847,6 +858,7 @@ const mapState = (state: AppState) => ({
selectedStorageClass:
state.tenants.createTenant.fields.nameTenant.selectedStorageClass,
features: state.console.session.features,
tolerations: state.tenants.createTenant.tolerations,
});
const connector = connect(mapState, {

View File

@@ -21,7 +21,14 @@ import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import { Grid, IconButton, Paper, SelectChangeEvent } from "@mui/material";
import { AppState } from "../../../../../store";
import { isPageValid, setKeyValuePairs, updateAddField } from "../../actions";
import {
addNewToleration,
isPageValid,
removeToleration,
setKeyValuePairs,
setTolerationInfo,
updateAddField,
} from "../../actions";
import { setModalErrorSnackMessage } from "../../../../../actions";
import {
modalBasic,
@@ -31,7 +38,10 @@ import {
commonFormValidation,
IValidation,
} from "../../../../../utils/validationFunctions";
import { ErrorResponseHandler } from "../../../../../common/types";
import {
ErrorResponseHandler,
ITolerationModel,
} from "../../../../../common/types";
import { LabelKeyPair } from "../../types";
import RadioGroupSelector from "../../../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
import FormSwitchWrapper from "../../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
@@ -40,6 +50,7 @@ import InputBoxWrapper from "../../../Common/FormComponents/InputBoxWrapper/Inpu
import AddIcon from "../../../../../icons/AddIcon";
import RemoveIcon from "../../../../../icons/RemoveIcon";
import SelectWrapper from "../../../Common/FormComponents/SelectWrapper/SelectWrapper";
import TolerationSelector from "../../../Common/TolerationSelector/TolerationSelector";
interface IAffinityProps {
classes: any;
@@ -47,16 +58,22 @@ interface IAffinityProps {
nodeSelectorLabels: string;
withPodAntiAffinity: boolean;
keyValuePairs: LabelKeyPair[];
tolerations: ITolerationModel[];
setModalErrorSnackMessage: typeof setModalErrorSnackMessage;
updateAddField: typeof updateAddField;
isPageValid: typeof isPageValid;
setKeyValuePairs: typeof setKeyValuePairs;
setTolerationInfo: typeof setTolerationInfo;
addNewToleration: typeof addNewToleration;
removeToleration: typeof removeToleration;
}
const styles = (theme: Theme) =>
createStyles({
overlayAction: {
marginLeft: 10,
display: "flex",
alignItems: "center",
"& svg": {
maxWidth: 15,
maxHeight: 15,
@@ -125,6 +142,10 @@ const Affinity = ({
keyValuePairs,
setKeyValuePairs,
isPageValid,
tolerations,
setTolerationInfo,
addNewToleration,
removeToleration,
}: IAffinityProps) => {
const [validationErrors, setValidationErrors] = useState<any>({});
const [loading, setLoading] = useState<boolean>(true);
@@ -223,6 +244,12 @@ const Affinity = ({
setValidationErrors(commonVal);
}, [isPageValid, podAffinity, nodeSelectorLabels]);
const updateToleration = (index: number, field: string, value: any) => {
const alterToleration = { ...tolerations[index], [field]: value };
setTolerationInfo(index, alterToleration);
};
return (
<Paper className={classes.paperWrapper}>
<div className={classes.headerElement}>
@@ -415,6 +442,72 @@ const Affinity = ({
</Grid>
</Fragment>
)}
<Grid item xs={12} className={classes.affinityConfigField}>
<Grid item className={classes.affinityFieldLabel}>
<h3>Tolerations</h3>
<span className={classes.error}>
{validationErrors["tolerations"]}
</span>
<Grid container>
{tolerations &&
tolerations.map((tol, i) => {
return (
<Grid
item
xs={12}
className={classes.affinityRow}
key={`affinity-keyVal-${i.toString()}`}
>
<TolerationSelector
effect={tol.effect}
onEffectChange={(value) => {
updateToleration(i, "effect", value);
}}
tolerationKey={tol.key}
onTolerationKeyChange={(value) => {
updateToleration(i, "key", value);
}}
operator={tol.operator}
onOperatorChange={(value) => {
updateToleration(i, "operator", value);
}}
value={tol.value}
onValueChange={(value) => {
updateToleration(i, "value", value);
}}
tolerationSeconds={tol.tolerationSeconds?.seconds || 0}
onSecondsChange={(value) => {
updateToleration(i, "tolerationSeconds", {
seconds: value,
});
}}
index={i}
/>
<div className={classes.overlayAction}>
<IconButton
size={"small"}
onClick={addNewToleration}
disabled={i !== tolerations.length - 1}
>
<AddIcon />
</IconButton>
</div>
<div className={classes.overlayAction}>
<IconButton
size={"small"}
onClick={() => removeToleration(i)}
disabled={tolerations.length <= 1}
>
<RemoveIcon />
</IconButton>
</div>
</Grid>
);
})}
</Grid>
</Grid>
</Grid>
</Paper>
);
};
@@ -427,6 +520,7 @@ const mapState = (state: AppState) => {
nodeSelectorLabels: createTenant.fields.affinity.nodeSelectorLabels,
withPodAntiAffinity: createTenant.fields.affinity.withPodAntiAffinity,
keyValuePairs: createTenant.nodeSelectorPairs,
tolerations: createTenant.tolerations,
};
};
@@ -435,6 +529,9 @@ const connector = connect(mapState, {
updateAddField,
isPageValid,
setKeyValuePairs,
setTolerationInfo,
addNewToleration,
removeToleration,
});
export default withStyles(styles)(connector(Affinity));

View File

@@ -44,8 +44,11 @@ import {
TENANT_DETAILS_SET_TAB,
TENANT_DETAILS_SET_TENANT,
ADD_TENANT_SET_KEY_PAIR_VALUE,
LabelKeyPair,
ADD_TENANT_SET_TOLERATION_VALUE,
ADD_TENANT_ADD_NEW_TOLERATION,
LabelKeyPair, ADD_TENANT_REMOVE_TOLERATION_ROW,
} from "./types";
import {ITolerationModel} from "../../../common/types";
// Basic actions
export const setWizardPage = (page: number) => {
@@ -295,3 +298,24 @@ export const setKeyValuePairs = (newArray: LabelKeyPair[]) => {
newArray,
};
};
export const setTolerationInfo = (index: number, tolerationValue: ITolerationModel) => {
return {
type: ADD_TENANT_SET_TOLERATION_VALUE,
index,
toleration: tolerationValue,
}
};
export const addNewToleration = () => {
return {
type: ADD_TENANT_ADD_NEW_TOLERATION,
}
};
export const removeToleration = (index: number) => {
return {
type: ADD_TENANT_REMOVE_TOLERATION_ROW,
index
}
};

View File

@@ -23,6 +23,7 @@ import {
ADD_TENANT_ADD_FILE_TO_CONSOLE_CA_KEYPAIR,
ADD_TENANT_ADD_FILE_TO_MINIO_KEYPAIR,
ADD_TENANT_ADD_MINIO_KEYPAIR,
ADD_TENANT_ADD_NEW_TOLERATION,
ADD_TENANT_DELETE_CA_KEYPAIR,
ADD_TENANT_DELETE_CONSOLE_CA_KEYPAIR,
ADD_TENANT_DELETE_MINIO_KEYPAIR,
@@ -31,13 +32,15 @@ import {
ADD_TENANT_ENCRYPTION_SERVER_CERT,
ADD_TENANT_ENCRYPTION_VAULT_CA,
ADD_TENANT_ENCRYPTION_VAULT_CERT,
ADD_TENANT_REMOVE_TOLERATION_ROW,
ADD_TENANT_RESET_FORM,
ADD_TENANT_SET_CURRENT_PAGE,
ADD_TENANT_SET_KEY_PAIR_VALUE,
ADD_TENANT_SET_LIMIT_SIZE,
ADD_TENANT_SET_PAGE_VALID,
ADD_TENANT_SET_STORAGE_TYPE,
ADD_TENANT_SET_STORAGE_CLASSES_LIST,
ADD_TENANT_SET_STORAGE_TYPE,
ADD_TENANT_SET_TOLERATION_VALUE,
ADD_TENANT_UPDATE_FIELD,
ITenantState,
TENANT_DETAILS_SET_CURRENT_TENANT,
@@ -49,6 +52,7 @@ import {
import { KeyPair } from "./ListTenants/utils";
import { getRandomString } from "./utils";
import { addTenantSetStorageTypeReducer } from "./reducers/add-tenant-reducer";
import { ITolerationEffect, ITolerationOperator } from "../../../common/types";
const initialState: ITenantState = {
createTenant: {
@@ -334,6 +338,15 @@ const initialState: ITenantState = {
},
},
nodeSelectorPairs: [{ key: "", value: "" }],
tolerations: [
{
key: "",
tolerationSeconds: { seconds: 0 },
value: "",
effect: ITolerationEffect.NoSchedule,
operator: ITolerationOperator.Equal,
},
],
},
tenantDetails: {
currentTenant: "",
@@ -886,6 +899,15 @@ export function tenantsReducer(
},
},
nodeSelectorPairs: [{ key: "", value: "" }],
tolerations: [
{
key: "",
tolerationSeconds: { seconds: 0 },
value: "",
effect: ITolerationEffect.NoSchedule,
operator: ITolerationOperator.Equal,
},
],
},
};
case ADD_TENANT_SET_KEY_PAIR_VALUE:
@@ -939,6 +961,51 @@ export function tenantsReducer(
...newTab,
},
};
case ADD_TENANT_SET_TOLERATION_VALUE:
const newSetTolerationValue = [...state.createTenant.tolerations];
newSetTolerationValue[action.index] = action.toleration;
return {
...state,
createTenant: {
...state.createTenant,
tolerations: [...newSetTolerationValue],
},
};
case ADD_TENANT_ADD_NEW_TOLERATION:
const newTolerationArray = [
...state.createTenant.tolerations,
{
key: "",
tolerationSeconds: { seconds: 0 },
value: "",
effect: ITolerationEffect.NoSchedule,
operator: ITolerationOperator.Equal,
},
];
return {
...state,
createTenant: {
...state.createTenant,
tolerations: [...newTolerationArray],
},
};
case ADD_TENANT_REMOVE_TOLERATION_ROW:
const cleanTolerationArray = state.createTenant.tolerations.filter(
(_, index) => index !== action.index
);
console.log("action", action.index, cleanTolerationArray)
return {
...state,
createTenant: {
...state.createTenant,
tolerations: [...cleanTolerationArray],
},
};
default:
return state;
}

View File

@@ -20,6 +20,7 @@ import {
IErasureCodeCalc,
IGCPConfig,
IGemaltoCredentials,
ITolerationModel,
} from "../../../common/types";
import { IResourcesSize, ITenant } from "./ListTenants/types";
import { KeyPair, Opts } from "./ListTenants/utils";
@@ -69,6 +70,12 @@ export const ADD_TENANT_ENCRYPTION_GEMALTO_CA =
// Affinity Node Selector KeyPairs
export const ADD_TENANT_SET_KEY_PAIR_VALUE = "ADD_TENANT/SET_KEY_PAIR_VALUE";
// Affinity Tolerations
export const ADD_TENANT_SET_TOLERATION_VALUE =
"ADD_TENANT/SET_TOLERATION_VALUE";
export const ADD_TENANT_ADD_NEW_TOLERATION = "ADD_TENANT/ADD_NEW_TOLERATION";
export const ADD_TENANT_REMOVE_TOLERATION_ROW = "ADD_TENANT/REMOVE_TOLERATION_ROW";
// Tenant Details
export const TENANT_DETAILS_SET_LOADING = "TENANT_DETAILS/SET_LOADING";
export const TENANT_DETAILS_SET_CURRENT_TENANT =
@@ -130,6 +137,7 @@ export interface IKeysecureConfiguration {
credentials: IGemaltoCredentials;
tls: IGemaltoTLS;
}
export interface IGemaltoConfiguration {
keysecure: IKeysecureConfiguration;
}
@@ -155,6 +163,7 @@ export interface ICreateTenant {
fields: IFieldStore;
certificates: ICertificatesItems;
nodeSelectorPairs: LabelKeyPair[];
tolerations: ITolerationModel[];
}
export interface ICertificatesItems {
@@ -520,6 +529,21 @@ interface SetTenantTab {
tab: string;
}
interface SetTolerationValue {
type: typeof ADD_TENANT_SET_TOLERATION_VALUE;
index: number;
toleration: ITolerationModel;
}
interface AddNewToleration {
type: typeof ADD_TENANT_ADD_NEW_TOLERATION;
}
interface RemoveTolerationRow {
type: typeof ADD_TENANT_REMOVE_TOLERATION_ROW;
index: number;
}
export type FieldsToHandle = INameTenantFields;
export type TenantsManagementTypes =
@@ -549,4 +573,7 @@ export type TenantsManagementTypes =
| SetLoadingTenant
| SetTenantName
| SetTenantDetails
| SetTenantTab;
| SetTenantTab
| SetTolerationValue
| AddNewToleration
| RemoveTolerationRow;