Added affinity support to tenant add screen (#706)

This commit is contained in:
Alex
2021-04-26 20:40:09 -05:00
committed by GitHub
parent 0c90785ed0
commit 38f95e3b28
15 changed files with 305 additions and 29 deletions

View File

@@ -1,25 +1,25 @@
{
"files": {
"main.css": "/static/css/main.a19f3d53.chunk.css",
"main.js": "/static/js/main.b4a5fae7.chunk.js",
"main.js.map": "/static/js/main.b4a5fae7.chunk.js.map",
"main.js": "/static/js/main.8671089f.chunk.js",
"main.js.map": "/static/js/main.8671089f.chunk.js.map",
"runtime-main.js": "/static/js/runtime-main.f48e99e5.js",
"runtime-main.js.map": "/static/js/runtime-main.f48e99e5.js.map",
"static/css/2.32daf8f7.chunk.css": "/static/css/2.32daf8f7.chunk.css",
"static/js/2.e5e999f3.chunk.js": "/static/js/2.e5e999f3.chunk.js",
"static/js/2.e5e999f3.chunk.js.map": "/static/js/2.e5e999f3.chunk.js.map",
"static/js/2.095198e4.chunk.js": "/static/js/2.095198e4.chunk.js",
"static/js/2.095198e4.chunk.js.map": "/static/js/2.095198e4.chunk.js.map",
"index.html": "/index.html",
"static/css/2.32daf8f7.chunk.css.map": "/static/css/2.32daf8f7.chunk.css.map",
"static/css/main.a19f3d53.chunk.css.map": "/static/css/main.a19f3d53.chunk.css.map",
"static/js/2.e5e999f3.chunk.js.LICENSE.txt": "/static/js/2.e5e999f3.chunk.js.LICENSE.txt",
"static/js/2.095198e4.chunk.js.LICENSE.txt": "/static/js/2.095198e4.chunk.js.LICENSE.txt",
"static/media/minio_console_logo.0837460e.svg": "/static/media/minio_console_logo.0837460e.svg",
"static/media/minio_operator_logo.1312b7c9.svg": "/static/media/minio_operator_logo.1312b7c9.svg"
},
"entrypoints": [
"static/js/runtime-main.f48e99e5.js",
"static/css/2.32daf8f7.chunk.css",
"static/js/2.e5e999f3.chunk.js",
"static/js/2.095198e4.chunk.js",
"static/css/main.a19f3d53.chunk.css",
"static/js/main.b4a5fae7.chunk.js"
"static/js/main.8671089f.chunk.js"
]
}

View File

@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="MinIO Console"/><link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;500;700;900&display=swap" rel="stylesheet"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180x180.png"/><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"/><link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png"/><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/><link rel="manifest" href="/manifest.json"/><link rel="mask-icon" href="/safari-pinned-tab.svg" color="#3a4e54"/><title>MinIO Console</title><link href="/static/css/2.32daf8f7.chunk.css" rel="stylesheet"><link href="/static/css/main.a19f3d53.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,l,i=r[0],a=r[1],p=r[2],c=0,s=[];c<i.length;c++)l=i[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,p||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var a=t[i];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var i=this["webpackJsonpportal-ui"]=this["webpackJsonpportal-ui"]||[],a=i.push.bind(i);i.push=r,i=i.slice();for(var p=0;p<i.length;p++)r(i[p]);var f=a;t()}([])</script><script src="/static/js/2.e5e999f3.chunk.js"></script><script src="/static/js/main.b4a5fae7.chunk.js"></script></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="MinIO Console"/><link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;500;700;900&display=swap" rel="stylesheet"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180x180.png"/><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"/><link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png"/><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/><link rel="manifest" href="/manifest.json"/><link rel="mask-icon" href="/safari-pinned-tab.svg" color="#3a4e54"/><title>MinIO Console</title><link href="/static/css/2.32daf8f7.chunk.css" rel="stylesheet"><link href="/static/css/main.a19f3d53.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,l,i=r[0],a=r[1],p=r[2],c=0,s=[];c<i.length;c++)l=i[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,p||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var a=t[i];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var i=this["webpackJsonpportal-ui"]=this["webpackJsonpportal-ui"]||[],a=i.push.bind(i);i.push=r,i=i.slice();for(var p=0;p<i.length;p++)r(i[p]);var f=a;t()}([])</script><script src="/static/js/2.095198e4.chunk.js"></script><script src="/static/js/main.8671089f.chunk.js"></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -14,6 +14,8 @@
// 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 { ILabelKeyPair } from "../screens/Console/Tenants/types";
export interface ITenantsObject {
tenants: ITenant[];
}
@@ -70,6 +72,7 @@ export interface ITenantCreator {
image_registry?: ImageRegistry;
logSearchConfiguration?: LogSearchConfiguration;
prometheusConfiguration?: PrometheusConfiguration;
affinity?: AffinityConfiguration;
}
export interface ImageRegistry {
@@ -95,7 +98,8 @@ export interface ITenantUsage {
}
export interface IAffinityModel {
podAntiAffinity: IPodAntiAffinityModel;
podAntiAffinity?: IPodAntiAffinityModel;
podAffinity?: IPodAffinityModel;
}
export interface IPodAntiAffinityModel {
@@ -111,6 +115,19 @@ export interface IPodAffinityTermLabelSelector {
matchExpressions: IMatchExpressionItem[];
}
export interface IPodAffinityModel {
requiredDuringSchedulingIgnoredDuringExecution: IPodAffinityTerms[];
}
export interface IPodAffinityTerms {
labelSelector: IPodAffinityLabelsSelector;
topologyKey: string;
}
export interface IPodAffinityLabelsSelector {
matchLabels: object;
}
export interface IMatchExpressionItem {
key: string;
operator: string;
@@ -353,3 +370,8 @@ export interface PrometheusConfiguration {
storageClass: string;
storageSize: number;
}
export interface AffinityConfiguration {
affinityType: "default" | "nodeSelector" | "none";
nodeSelectorLabels?: ILabelKeyPair[];
}

View File

@@ -34,11 +34,11 @@ import { IAffinityModel, ITenantCreator } from "../../../../common/types";
import { KeyPair } from "../ListTenants/utils";
import { setModalErrorSnackMessage } from "../../../../actions";
import { getHardcodedAffinity } from "../TenantDetails/utils";
import { getDefaultAffinity, getNodeSelector } from "../TenantDetails/utils";
import CredentialsPrompt from "../../Common/CredentialsPrompt/CredentialsPrompt";
import NameTenant from "./Steps/NameTenant";
import { AppState } from "../../../../store";
import { ICertificatesItems, IFieldStore } from "../types";
import { ICertificatesItems, IFieldStore, ILabelKeyPair } from "../types";
import { updateAddField } from "../actions";
import Configure from "./Steps/Configure";
import IdentityProvider from "./Steps/IdentityProvider";
@@ -46,6 +46,7 @@ import Security from "./Steps/Security";
import Encryption from "./Steps/Encryption";
import TenantSize from "./Steps/TenantSize";
import Preview from "./Steps/Preview";
import Affinity from "./Steps/Affinity";
interface IAddTenantProps {
closeAndRefresh: (reloadData: boolean) => any;
@@ -162,14 +163,24 @@ const AddTenant = ({
const prometheusSelectedStorageClass =
fields.configure.prometheusSelectedStorageClass;
const prometheusVolumeSize = fields.configure.prometheusVolumeSize;
const affinityType = fields.affinity.podAffinity;
const affinityLabels = fields.affinity.affinityLabels;
if (addSending) {
const poolName = generatePoolName([]);
const hardCodedAffinity: IAffinityModel = getHardcodedAffinity(
tenantName,
poolName
);
let affinityObject = {};
switch (affinityType) {
case "default":
affinityObject = {
affinity: getDefaultAffinity(tenantName, poolName),
};
break;
case "nodeSelector":
affinityObject = { affinity: getNodeSelector(affinityLabels) };
break;
}
const erasureCode = ecParity.split(":")[1];
@@ -205,7 +216,7 @@ const AddTenant = ({
memory: memorySize.limit,
},
},
affinity: hardCodedAffinity,
...affinityObject,
},
],
erasureCodingParity: parseInt(erasureCode, 10),
@@ -536,6 +547,20 @@ const AddTenant = ({
},
],
},
{
label: "Pod Affinity",
advancedOnly: true,
componentRender: <Affinity />,
buttons: [
cancelButton,
{ label: "Back", type: "back", enabled: true },
{
label: "Next",
type: "next",
enabled: validPages.includes("affinity"),
},
],
},
{
label: "Identity Provider",
advancedOnly: true,

View File

@@ -0,0 +1,178 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 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, { useEffect, useState, useCallback, Fragment } from "react";
import { connect } from "react-redux";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { AppState } from "../../../../../store";
import { updateAddField, isPageValid } from "../../actions";
import { setModalErrorSnackMessage } from "../../../../../actions";
import {
modalBasic,
wizardCommon,
} from "../../../Common/FormComponents/common/styleLibrary";
import { Grid } from "@material-ui/core";
import {
commonFormValidation,
IValidation,
} from "../../../../../utils/validationFunctions";
import RadioGroupSelector from "../../../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
import QueryMultiSelector from "../../../Common/FormComponents/QueryMultiSelector/QueryMultiSelector";
interface IAffinityProps {
classes: any;
podAffinity: string;
affinityLabels: string;
setModalErrorSnackMessage: typeof setModalErrorSnackMessage;
updateAddField: typeof updateAddField;
isPageValid: typeof isPageValid;
}
const styles = (theme: Theme) =>
createStyles({
buttonContainer: {
textAlign: "right",
},
...modalBasic,
...wizardCommon,
});
const Affinity = ({
classes,
podAffinity,
affinityLabels,
setModalErrorSnackMessage,
updateAddField,
isPageValid,
}: IAffinityProps) => {
const [validationErrors, setValidationErrors] = useState<any>({});
// Common
const updateField = useCallback(
(field: string, value: any) => {
updateAddField("affinity", field, value);
},
[updateAddField]
);
// Validation
useEffect(() => {
let customAccountValidation: IValidation[] = [];
if (podAffinity === "nodeSelector") {
let valid = true;
const splittedLabels = affinityLabels.split("&");
if (splittedLabels.length === 1 && splittedLabels[0] === "") {
valid = false;
}
splittedLabels.forEach((item: string, index: number) => {
const splitItem = item.split("=");
if (splitItem.length !== 2) {
valid = false;
}
if (index + 1 !== splittedLabels.length) {
if (splitItem[0] === "" || splitItem[1] === "") {
valid = false;
}
}
});
customAccountValidation = [
...customAccountValidation,
{
fieldKey: "labels",
required: true,
value: affinityLabels,
customValidation: !valid,
customValidationMessage:
"You need to add at least one label key-pair",
},
];
}
const commonVal = commonFormValidation(customAccountValidation);
isPageValid("affinity", Object.keys(commonVal).length === 0);
setValidationErrors(commonVal);
}, [isPageValid, podAffinity, affinityLabels]);
return (
<Fragment>
<div className={classes.headerElement}>
<h3 className={classes.h3Section}>Pod Affinity</h3>
<span className={classes.descriptionText}>
Configure how pods will be assigned to nodes
</span>
</div>
<Grid item xs={12}>
<RadioGroupSelector
currentSelection={podAffinity}
id="affinity-options"
name="affinity-options"
label="Type"
onChange={(e) => {
updateField("podAffinity", e.target.value);
}}
selectorOptions={[
{ label: "None", value: "none" },
{ label: "Default (Pod Anti-afinnity)", value: "default" },
{ label: "Node Selector", value: "nodeSelector" },
]}
/>
MinIO supports multiple configurations for Pod Afinnity
</Grid>
{podAffinity === "nodeSelector" && (
<Fragment>
<br />
<Grid item xs={12}>
<QueryMultiSelector
name="labels"
label="Labels"
elements={affinityLabels}
onChange={(vl: string) => {
updateField("affinityLabels", vl);
}}
keyPlaceholder="Label Key"
valuePlaceholder="Label Value"
tooltip="Labels to be used in nodeSelector assignation. Invalid key-pairs will be ignored"
withBorder
/>
<span className={classes.error}>{validationErrors["labels"]}</span>
</Grid>
</Fragment>
)}
</Fragment>
);
};
const mapState = (state: AppState) => ({
podAffinity: state.tenants.createTenant.fields.affinity.podAffinity,
affinityLabels: state.tenants.createTenant.fields.affinity.affinityLabels,
});
const connector = connect(mapState, {
setModalErrorSnackMessage,
updateAddField,
isPageValid,
});
export default withStyles(styles)(connector(Affinity));

View File

@@ -10,7 +10,8 @@ import { Button, LinearProgress } from "@material-ui/core";
import api from "../../../../common/api";
import { IAddPoolRequest, ITenant } from "../ListTenants/types";
import { IAffinityModel } from "../../../../common/types";
import { getHardcodedAffinity } from "./utils";
import { getDefaultAffinity } from "./utils";
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
import { IQuotaElement, IQuotas, Opts } from "../ListTenants/utils";
@@ -121,7 +122,7 @@ const AddPoolModal = ({
const poolName = generatePoolName(tenant.pools);
const hardCodedAffinity: IAffinityModel = getHardcodedAffinity(
const defaultAffinity: IAffinityModel = getDefaultAffinity(
tenant.name,
poolName
);
@@ -135,7 +136,7 @@ const AddPoolModal = ({
storage_class_name: selectedStorageClass,
labels: null,
},
affinity: hardCodedAffinity,
affinity: defaultAffinity,
};
api

View File

@@ -14,9 +14,10 @@
// 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 { IAffinityModel } from "../../../../common/types";
import { ILabelKeyPair } from "../types";
export const getHardcodedAffinity = (tenantName: string, poolName: string) => {
const hardCodedAffinity: IAffinityModel = {
export const getDefaultAffinity = (tenantName: string, poolName: string) => {
const defaultAffinity: IAffinityModel = {
podAntiAffinity: {
requiredDuringSchedulingIgnoredDuringExecution: [
{
@@ -39,5 +40,35 @@ export const getHardcodedAffinity = (tenantName: string, poolName: string) => {
],
},
};
return hardCodedAffinity;
return defaultAffinity;
};
export const getNodeSelector = (labels: string) => {
// Labels in the form of key1=value1&key2=value2&key3=value3...
const splittedLabels = labels.split("&");
let labelItems: any = {};
splittedLabels.forEach((label: string) => {
const splitKeyValue = label.split("=");
if (splitKeyValue.length === 2) {
labelItems = {
...labelItems,
[`${splitKeyValue[0]}`]: splitKeyValue[1],
};
}
});
const nodeSelector: IAffinityModel = {
podAffinity: {
requiredDuringSchedulingIgnoredDuringExecution: [
{
labelSelector: {
matchLabels: { ...labelItems },
},
topologyKey: "kubernetes.io/hostname",
},
],
},
};
return nodeSelector;
};

View File

@@ -154,6 +154,10 @@ const initialState: ITenantState = {
},
limitSize: {},
},
affinity: {
affinityLabels: "",
podAffinity: "default",
},
},
certificates: {
minioCertificates: [
@@ -541,6 +545,10 @@ export function tenantsReducer(
},
limitSize: {},
},
affinity: {
affinityLabels: "",
podAffinity: "default",
},
},
certificates: {
minioCertificates: [

View File

@@ -80,6 +80,7 @@ export interface IFieldStore {
security: ISecurityFields;
encryption: IEncryptionFields;
tenantSize: ITenantSizeFields;
affinity: ITenantAffinity;
}
export interface INameTenantFields {
@@ -177,10 +178,20 @@ export interface ITenantSizeFields {
limitSize: any;
}
export interface ITenantAffinity {
podAffinity: "default" | "nodeSelector" | "none";
affinityLabels: string;
}
export interface ITenantState {
createTenant: ICreateTenant;
}
export interface ILabelKeyPair {
labelKey: string;
labelValue: string;
}
interface SetTenantWizardPage {
type: typeof ADD_TENANT_SET_CURRENT_PAGE;
page: number;