Added initial AWS Marketplace support to operator console (#1347)

* Added initial AWS Marketplace support to operator console

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>

* Renamed interface

* Removed resources request in tenant request.

* Destructured map state in Tenant Size

* Resource Validations

Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>

* Removed ecparity set default option

Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
This commit is contained in:
Alex
2021-12-30 17:43:28 -07:00
committed by GitHub
parent 7410fdbcc9
commit 5373e1dc19
23 changed files with 1357 additions and 510 deletions

View File

@@ -37,6 +37,9 @@ import (
// swagger:model operatorSessionResponse
type OperatorSessionResponse struct {
// features
Features []string `json:"features"`
// operator
Operator bool `json:"operator,omitempty"`

View File

@@ -65,3 +65,8 @@ func getK8sSAToken() string {
}
return string(dat)
}
// Marketplace Mode Token
func getMPMode() string {
return env.Get(ConsoleMPMode, "")
}

View File

@@ -21,6 +21,7 @@ const (
ConsoleSubnetLicense = "CONSOLE_SUBNET_LICENSE"
ConsoleOperatorSAToken = "CONSOLE_OPERATOR_SA_TOKEN"
MinIOSubnetLicense = "MINIO_SUBNET_LICENSE"
ConsoleMPMode = "CONSOLE_OPERATOR_MARKETPLACE"
// Constants for prometheus annotations
prometheusPath = "prometheus.io/path"

View File

@@ -2361,6 +2361,12 @@ func init() {
"operatorSessionResponse": {
"type": "object",
"properties": {
"features": {
"type": "array",
"items": {
"type": "string"
}
},
"operator": {
"type": "boolean"
},
@@ -6230,6 +6236,12 @@ func init() {
"operatorSessionResponse": {
"type": "object",
"properties": {
"features": {
"type": "array",
"items": {
"type": "string"
}
},
"operator": {
"type": "boolean"
},

View File

@@ -17,6 +17,8 @@
package operatorapi
import (
"fmt"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
"github.com/minio/console/operatorapi/operations"
@@ -44,6 +46,19 @@ func getSessionResponse(session *models.Principal) (*models.OperatorSessionRespo
Status: models.OperatorSessionResponseStatusOk,
Operator: true,
Permissions: map[string][]string{},
Features: getListOfOperatorFeatures(),
}
return sessionResp, nil
}
// getListOfEnabledFeatures returns a list of features
func getListOfOperatorFeatures() []string {
features := []string{}
mpEnabled := getMPMode()
if mpEnabled != "" {
features = append(features, fmt.Sprintf("mp-mode-%s", mpEnabled))
}
return features
}

View File

@@ -1257,9 +1257,6 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
if tenantReq.LogSearchConfiguration.StorageClass != "" {
logSearchStorageClass = tenantReq.LogSearchConfiguration.StorageClass
}
if tenantReq.LogSearchConfiguration.StorageClass == "" && len(tenantReq.Pools) > 0 {
logSearchStorageClass = tenantReq.Pools[0].VolumeConfiguration.StorageClassName
}
if tenantReq.LogSearchConfiguration.Image != "" {
logSearchImage = tenantReq.LogSearchConfiguration.Image
}
@@ -1346,12 +1343,6 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
if tenantReq.PrometheusConfiguration.StorageClass != "" {
prometheusStorageClass = tenantReq.PrometheusConfiguration.StorageClass
}
// Default class name for prometheus
if tenantReq.PrometheusConfiguration.StorageClass == "" && len(tenantReq.Pools) > 0 {
prometheusStorageClass = tenantReq.Pools[0].VolumeConfiguration.StorageClassName
}
if tenantReq.PrometheusConfiguration.Image != "" {
prometheusImage = tenantReq.PrometheusConfiguration.Image
}

View File

@@ -37,6 +37,7 @@ test("From value and unit to a number of bytes for kubernetes", () => {
expect(getBytes("1", "Ki", true)).toBe("1024");
expect(getBytes("1", "Mi", true)).toBe("1048576");
expect(getBytes("1", "Gi", true)).toBe("1073741824");
expect(getBytes("7500", "Gi", true)).toBe("8053063680000");
});
test("Determine the amount of memory to use", () => {

View File

@@ -147,18 +147,18 @@ export interface ITolerationSeconds {
}
export interface IResourceModel {
requests: IResourceRequests;
requests?: IResourceRequests;
limits?: IResourceLimits;
}
export interface IResourceRequests {
memory: number;
cpu: number;
memory?: number;
cpu?: number;
}
export interface IResourceLimits {
memory: number;
cpu: number;
memory?: number;
cpu?: number;
}
export interface ITLSTenantConfiguration {
@@ -272,7 +272,7 @@ export interface IActiveDirectoryConfiguration {
}
export interface IStorageDistribution {
error: number;
error: number | string;
nodes: number;
persistentVolumes: number;
disks: number;

View File

@@ -15,10 +15,13 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import storage from "local-storage-fallback";
import get from "lodash/get";
import { ICapacity, IErasureCodeCalc, IStorageFactors } from "./types";
import {
ICapacity,
IErasureCodeCalc,
IStorageDistribution,
IStorageFactors,
} from "./types";
import { IPool } from "../screens/Console/Tenants/ListTenants/types";
import { AllocableResourcesResponse } from "../screens/Console/Tenants/types";
const minStReq = 1073741824; // Minimal Space required for MinIO
const minMemReq = 2147483648; // Minimal Memory required for MinIO in bytes
@@ -199,8 +202,7 @@ export const calculateDistribution = (
forcedNodes: number = 0,
limitSize: number = 0,
drivesPerServer: number = 0
) => {
let numberOfNodes = {};
): IStorageDistribution => {
const requestedSizeBytes = getBytes(
capacityToUse.value,
capacityToUse.unit,
@@ -227,7 +229,7 @@ export const calculateDistribution = (
};
}
numberOfNodes = calculateStorage(
let numberOfNodes = calculateStorage(
requestedSizeBytes,
forcedNodes,
limitSize,
@@ -242,7 +244,7 @@ const calculateStorage = (
forcedNodes: number,
limitSize: number,
drivesPerServer: number
) => {
): IStorageDistribution => {
// Size validation
const intReqBytes = parseInt(requestedBytes, 10);
const maxDiskSize = minStReq * 256; // 256 GiB
@@ -263,7 +265,7 @@ const structureCalc = (
maxDiskSize: number,
maxClusterSize: number,
disksPerNode: number = 0
) => {
): IStorageDistribution => {
if (
isNaN(nodes) ||
isNaN(desiredCapacity) ||
@@ -275,7 +277,7 @@ const structureCalc = (
nodes: 0,
persistentVolumes: 0,
disks: 0,
volumePerDisk: 0,
pvSize: 0,
}; // Invalid Data
}
@@ -316,7 +318,7 @@ const structureCalc = (
nodes: 0,
persistentVolumes: 0,
disks: 0,
volumePerDisk: 0,
pvSize: 0,
}; // Cannot allocate this server
}
}
@@ -328,7 +330,7 @@ const structureCalc = (
nodes: 0,
persistentVolumes: 0,
disks: 0,
volumePerDisk: 0,
pvSize: 0,
}; // Cannot allocate this volume size
}
@@ -501,7 +503,8 @@ export const getTimeFromTimestamp = (
export const calculateBytes = (
x: string,
showDecimals = false,
roundFloor = true
roundFloor = true,
k8sUnit = false
) => {
const bytes = parseInt(x, 10);
@@ -523,7 +526,7 @@ export const calculateBytes = (
// Get Unit parsed
const unitParsed = parseFloat(roundedUnit.toFixed(fractionDigits));
const finalUnit = units[i];
const finalUnit = k8sUnit ? k8sCalcUnits[i] : units[i];
return { total: unitParsed, unit: finalUnit };
};
@@ -596,113 +599,3 @@ export const decodeFileName = (text: string) => {
return text;
}
};
export const setResourcesValidation = (
memorySize: number,
cpusSelected: number,
maxAllocatableResources: AllocableResourcesResponse
) => {
const requestedSizeBytes = getBytes(memorySize.toString(10), "GB");
const memReqSize = parseInt(requestedSizeBytes, 10);
const minimalRequiredMemory = 2147483648; // Minimal required memory, 2Gi
const memoryExists = get(
maxAllocatableResources,
"min_allocatable_mem",
false
);
const cpuExists = get(maxAllocatableResources, "min_allocatable_cpu", false);
if (memoryExists === false) {
return {
error:
"No available memory for the selected number of nodes. Please try another combination.",
memoryRequest: 0,
memoryLimit: 0,
cpuRequest: 0,
cpuLimit: 0,
};
}
if (cpuExists === false) {
return {
error:
"No available CPUs for the selected number of nodes. Please try another combination",
memoryRequest: 0,
memoryLimit: 0,
cpuRequest: 0,
cpuLimit: 0,
};
}
if (memReqSize < minimalRequiredMemory) {
return {
error: "Memory size is set bellow minimum required",
memoryRequest: 0,
memoryLimit: 0,
cpuRequest: 0,
cpuLimit: 0,
};
}
if (cpusSelected < 1) {
return {
error: "CPU amount is set bellow minimum available",
memoryRequest: 0,
memoryLimit: 0,
cpuRequest: 0,
cpuLimit: 0,
};
}
if (
memReqSize <= maxAllocatableResources.mem_priority.max_allocatable_mem &&
cpusSelected > maxAllocatableResources.mem_priority.max_allocatable_cpu
) {
return {
error:
"It is not possible to allocate this amount of memory in all the CPUs",
memoryRequest: 0,
memoryLimit: 0,
cpuRequest: 0,
cpuLimit: 0,
};
}
if (
cpusSelected <= maxAllocatableResources.cpu_priority.max_allocatable_cpu &&
memReqSize > maxAllocatableResources.cpu_priority.max_allocatable_mem
) {
return {
error:
"It is not possible to allocate this amount of CPUs with the available memory",
memoryRequest: 0,
memoryLimit: 0,
cpuRequest: 0,
cpuLimit: 0,
};
}
if (
cpusSelected > maxAllocatableResources.cpu_priority.max_allocatable_cpu ||
memReqSize > maxAllocatableResources.mem_priority.max_allocatable_mem
) {
return {
error: "CPUs or Memory selected is beyond bounds",
memoryRequest: 0,
memoryLimit: 0,
cpuRequest: 0,
cpuLimit: 0,
};
}
return {
error: "",
memoryRequest: memReqSize,
memoryLimit: maxAllocatableResources.mem_priority.max_allocatable_mem,
cpuRequest: cpusSelected,
cpuLimit: maxAllocatableResources.cpu_priority.max_allocatable_cpu,
};
};

View File

@@ -39,7 +39,6 @@ import { KeyPair } from "../ListTenants/utils";
import { setErrorSnackMessage } from "../../../../actions";
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 { resetAddTenantForm, updateAddField } from "../actions";
@@ -53,6 +52,7 @@ import history from "../../../../history";
import Images from "./Steps/Images";
import PageLayout from "../../Common/Layout/PageLayout";
import BackLink from "../../../../common/BackLink";
import TenantResources from "./Steps/TenantResources/TenantResources";
interface IAddTenantProps {
setErrorSnackMessage: typeof setErrorSnackMessage;
@@ -174,7 +174,6 @@ const AddTenant = ({
const enableTLS = fields.security.enableTLS;
const ecParity = fields.tenantSize.ecParity;
const distribution = fields.tenantSize.distribution;
const resourcesSize = fields.tenantSize.resourcesSize;
const tenantCustom = fields.configure.tenantCustom;
const logSearchCustom = fields.configure.logSearchCustom;
const prometheusCustom = fields.configure.prometheusCustom;
@@ -253,16 +252,6 @@ const AddTenant = ({
size: distribution.pvSize,
storage_class_name: selectedStorageClass,
},
resources: {
requests: {
memory: resourcesSize.memoryRequest,
cpu: resourcesSize.cpuRequest,
},
limits: {
memory: resourcesSize.memoryRequest,
cpu: resourcesSize.cpuRequest,
},
},
securityContext: tenantCustom ? tenantSecurityContext : null,
...affinityObject,
},
@@ -270,6 +259,49 @@ const AddTenant = ({
erasureCodingParity: parseInt(erasureCode, 10),
};
// Set Resources
if (
fields.tenantSize.resourcesCPURequest !== "" ||
fields.tenantSize.resourcesCPULimit !== "" ||
fields.tenantSize.resourcesMemoryRequest !== "" ||
fields.tenantSize.resourcesMemoryLimit !== ""
) {
dataSend.pools[0].resources = {};
// requests
if (
fields.tenantSize.resourcesCPURequest !== "" ||
fields.tenantSize.resourcesMemoryRequest !== ""
) {
dataSend.pools[0].resources.requests = {};
if (fields.tenantSize.resourcesCPURequest !== "") {
dataSend.pools[0].resources.requests.cpu = parseInt(
fields.tenantSize.resourcesCPURequest
);
}
if (fields.tenantSize.resourcesMemoryRequest !== "") {
dataSend.pools[0].resources.requests.memory = parseInt(
fields.tenantSize.resourcesMemoryRequest
);
}
}
// limits
if (
fields.tenantSize.resourcesCPULimit !== "" ||
fields.tenantSize.resourcesMemoryLimit !== ""
) {
dataSend.pools[0].resources.limits = {};
if (fields.tenantSize.resourcesCPULimit !== "") {
dataSend.pools[0].resources.limits.cpu = parseInt(
fields.tenantSize.resourcesCPULimit
);
}
if (fields.tenantSize.resourcesMemoryLimit !== "") {
dataSend.pools[0].resources.limits.memory = parseInt(
fields.tenantSize.resourcesMemoryLimit
);
}
}
}
if (customDockerhub) {
dataSend = {
...dataSend,
@@ -285,7 +317,10 @@ const AddTenant = ({
dataSend = {
...dataSend,
logSearchConfiguration: {
storageClass: logSearchSelectedStorageClass,
storageClass:
logSearchSelectedStorageClass === "default"
? ""
: logSearchSelectedStorageClass,
storageSize: parseInt(logSearchVolumeSize),
image: logSearchImage,
postgres_image: logSearchPostgresImage,
@@ -309,7 +344,10 @@ const AddTenant = ({
dataSend = {
...dataSend,
prometheusConfiguration: {
storageClass: prometheusSelectedStorageClass,
storageClass:
prometheusSelectedStorageClass === "default"
? ""
: prometheusSelectedStorageClass,
storageSize: parseInt(prometheusVolumeSize),
image: prometheusImage,
sidecar_image: prometheusSidecarImage,
@@ -671,7 +709,7 @@ const AddTenant = ({
const wizardSteps: IWizardElement[] = [
{
label: "Setup",
componentRender: <NameTenant />,
componentRender: <TenantResources />,
buttons: [cancelButton, createButton],
},
{

View File

@@ -163,6 +163,11 @@ const Configure = ({
}: IConfigureProps) => {
const [validationErrors, setValidationErrors] = useState<any>({});
const configureSTClasses = [
{ label: "Default", value: "default" },
...storageClasses,
];
// Common
const updateField = useCallback(
(field: string, value: any) => {
@@ -450,20 +455,20 @@ const Configure = ({
]);
useEffect(() => {
// New default values is current selection is invalid
// New default values in current selection is invalid
if (storageClasses.length > 0) {
const filterPrometheus = storageClasses.filter(
(item: any) => item.value === prometheusSelectedStorageClass
);
if (filterPrometheus.length === 0) {
updateField("prometheusSelectedStorageClass", selectedStorageClass);
updateField("prometheusSelectedStorageClass", "default");
}
const filterLogSearch = storageClasses.filter(
(item: any) => item.value === logSearchSelectedStorageClass
);
if (filterLogSearch.length === 0) {
updateField("logSearchSelectedStorageClass", selectedStorageClass);
updateField("logSearchSelectedStorageClass", "default");
}
}
}, [
@@ -673,8 +678,8 @@ const Configure = ({
}}
label="Log Search Storage Class"
value={logSearchSelectedStorageClass}
options={storageClasses}
disabled={storageClasses.length < 1}
options={configureSTClasses}
disabled={configureSTClasses.length < 1}
/>
</Grid>
<Grid item xs={12}>
@@ -925,8 +930,8 @@ const Configure = ({
}}
label="Prometheus Storage Class"
value={prometheusSelectedStorageClass}
options={storageClasses}
disabled={storageClasses.length < 1}
options={configureSTClasses}
disabled={configureSTClasses.length < 1}
/>
</Grid>
<Grid item xs={12}>

View File

@@ -277,31 +277,6 @@ const Images = ({
logSearchVolumeSize,
]);
useEffect(() => {
// New default values is current selection is invalid
if (storageClasses.length > 0) {
const filterPrometheus = storageClasses.filter(
(item: any) => item.value === prometheusSelectedStorageClass
);
if (filterPrometheus.length === 0) {
updateField("prometheusSelectedStorageClass", selectedStorageClass);
}
const filterLogSearch = storageClasses.filter(
(item: any) => item.value === logSearchSelectedStorageClass
);
if (filterLogSearch.length === 0) {
updateField("logSearchSelectedStorageClass", selectedStorageClass);
}
}
}, [
logSearchSelectedStorageClass,
prometheusSelectedStorageClass,
selectedStorageClass,
storageClasses,
updateField,
]);
const cleanValidation = (fieldName: string) => {
setValidationErrors(clearValidationError(validationErrors, fieldName));
};

View File

@@ -35,12 +35,12 @@ import { IResourcesSize } from "../../ListTenants/types";
import { IErasureCodeCalc } from "../../../../../common/types";
import { Divider } from "@mui/material";
import { IntegrationConfiguration } from "./TenantResources/utils";
interface ISizePreviewProps {
classes: any;
updateAddField: typeof updateAddField;
isPageValid: typeof isPageValid;
advancedMode: boolean;
volumeSize: string;
sizeFactor: string;
drivesPerServer: string;
@@ -49,13 +49,13 @@ interface ISizePreviewProps {
ecParity: string;
ecParityChoices: Opts[];
cleanECChoices: string[];
maxAllocableMemo: number;
resourcesSize: IResourcesSize;
distribution: any;
ecParityCalc: IErasureCodeCalc;
limitSize: any;
selectedStorageClass: string;
cpuToUse: string;
integrationSelection: IntegrationConfiguration;
}
const styles = (theme: Theme) =>
@@ -76,7 +76,6 @@ const SizePreview = ({
classes,
updateAddField,
isPageValid,
advancedMode,
volumeSize,
sizeFactor,
drivesPerServer,
@@ -85,13 +84,13 @@ const SizePreview = ({
ecParity,
ecParityChoices,
cleanECChoices,
maxAllocableMemo,
resourcesSize,
distribution,
ecParityCalc,
limitSize,
selectedStorageClass,
cpuToUse,
integrationSelection,
}: ISizePreviewProps) => {
const usableInformation = ecParityCalc.storageFactors.find(
(element) => element.erasureCode === ecParity
@@ -109,34 +108,47 @@ const SizePreview = ({
{parseInt(nodes) > 0 ? nodes : "-"}
</TableCell>
</TableRow>
<TableRow>
<TableCell scope="row">Drives per Server</TableCell>
<TableCell align="right">
{distribution ? distribution.disks : "-"}
</TableCell>
</TableRow>
<TableRow>
<TableCell scope="row">Drive Capacity</TableCell>
<TableCell align="right">
{distribution ? niceBytes(distribution.pvSize) : "-"}
</TableCell>
</TableRow>
{integrationSelection.typeSelection === "" &&
integrationSelection.storageClass === "" && (
<Fragment>
<TableRow>
<TableCell scope="row">Drives per Server</TableCell>
<TableCell align="right">
{distribution ? distribution.disks : "-"}
</TableCell>
</TableRow>
<TableRow>
<TableCell scope="row">Drive Capacity</TableCell>
<TableCell align="right">
{distribution ? niceBytes(distribution.pvSize) : "-"}
</TableCell>
</TableRow>
</Fragment>
)}
<TableRow>
<TableCell scope="row">Total Volumes</TableCell>
<TableCell align="right">
{distribution ? distribution.persistentVolumes : "-"}
</TableCell>
</TableRow>
{!advancedMode && (
<TableRow>
<TableCell scope="row">Memory per Node</TableCell>
<TableCell align="right">{memoryNode} Gi</TableCell>
</TableRow>
)}
<TableRow>
<TableCell scope="row">CPU Selection</TableCell>
<TableCell align="right">{cpuToUse}</TableCell>
</TableRow>
{integrationSelection.typeSelection === "" &&
integrationSelection.storageClass === "" && (
<Fragment>
<TableRow>
<TableCell scope="row">Memory per Node</TableCell>
<TableCell align="right">{memoryNode} Gi</TableCell>
</TableRow>
<TableRow>
<TableCell style={{ borderBottom: 0 }} scope="row">
CPU Selection
</TableCell>
<TableCell style={{ borderBottom: 0 }} align="right">
{cpuToUse}
</TableCell>
</TableRow>
</Fragment>
)}
</TableBody>
</Table>
{ecParityCalc.error === 0 && usableInformation && (
@@ -184,12 +196,59 @@ const SizePreview = ({
</Table>
</Fragment>
)}
{integrationSelection.typeSelection !== "" &&
integrationSelection.storageClass !== "" && (
<Fragment>
<h4>Single Instance Configuration</h4>
<Divider />
<Table
className={classes.table}
aria-label="simple table"
size={"small"}
>
<TableBody>
<TableRow>
<TableCell scope="row">CPU</TableCell>
<TableCell align="right">
{integrationSelection.CPU !== 0
? integrationSelection.CPU
: "-"}
</TableCell>
</TableRow>
<TableRow>
<TableCell scope="row">Memory</TableCell>
<TableCell align="right">
{integrationSelection.memory !== 0
? `${integrationSelection.memory} Gi`
: "-"}
</TableCell>
</TableRow>
<TableRow>
<TableCell scope="row">Drives per Server</TableCell>
<TableCell align="right">
{integrationSelection.drivesPerServer !== 0
? `${integrationSelection.drivesPerServer}`
: "-"}
</TableCell>
</TableRow>
<TableRow>
<TableCell style={{ borderBottom: 0 }} scope="row">
Drive Size
</TableCell>
<TableCell style={{ borderBottom: 0 }} align="right">
{integrationSelection.driveSize.driveSize}
{integrationSelection.driveSize.sizeUnit}
</TableCell>
</TableRow>
</TableBody>
</Table>
</Fragment>
)}
</div>
);
};
const mapState = (state: AppState) => ({
advancedMode: state.tenants.createTenant.advancedModeOn,
volumeSize: state.tenants.createTenant.fields.tenantSize.volumeSize,
sizeFactor: state.tenants.createTenant.fields.tenantSize.sizeFactor,
drivesPerServer: state.tenants.createTenant.fields.tenantSize.drivesPerServer,
@@ -198,8 +257,6 @@ const mapState = (state: AppState) => ({
ecParity: state.tenants.createTenant.fields.tenantSize.ecParity,
ecParityChoices: state.tenants.createTenant.fields.tenantSize.ecParityChoices,
cleanECChoices: state.tenants.createTenant.fields.tenantSize.cleanECChoices,
maxAllocableMemo:
state.tenants.createTenant.fields.tenantSize.maxAllocableMemo,
resourcesSize: state.tenants.createTenant.fields.tenantSize.resourcesSize,
distribution: state.tenants.createTenant.fields.tenantSize.distribution,
ecParityCalc: state.tenants.createTenant.fields.tenantSize.ecParityCalc,
@@ -207,6 +264,8 @@ const mapState = (state: AppState) => ({
selectedStorageClass:
state.tenants.createTenant.fields.nameTenant.selectedStorageClass,
cpuToUse: state.tenants.createTenant.fields.tenantSize.cpuToUse,
integrationSelection:
state.tenants.createTenant.fields.tenantSize.integrationSelection,
});
const connector = connect(mapState, {

View File

@@ -32,32 +32,33 @@ import {
formFieldStyles,
modalBasic,
wizardCommon,
} from "../../../Common/FormComponents/common/styleLibrary";
import { setModalErrorSnackMessage } from "../../../../../actions";
} from "../../../../Common/FormComponents/common/styleLibrary";
import { setModalErrorSnackMessage } from "../../../../../../actions";
import {
isPageValid,
setLimitSize,
setStorageClassesList,
updateAddField,
} from "../../actions";
} from "../../../actions";
import {
getLimitSizes,
IQuotaElement,
IQuotas,
Opts,
} from "../../ListTenants/utils";
import { AppState } from "../../../../../store";
import { commonFormValidation } from "../../../../../utils/validationFunctions";
import { clearValidationError } from "../../utils";
import { ErrorResponseHandler } from "../../../../../common/types";
import api from "../../../../../common/api";
import InputBoxWrapper from "../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import SelectWrapper from "../../../Common/FormComponents/SelectWrapper/SelectWrapper";
import AddIcon from "../../../../../icons/AddIcon";
import AddNamespaceModal from "./helpers/AddNamespaceModal";
import SizePreview from "./SizePreview";
} from "../../../ListTenants/utils";
import { AppState } from "../../../../../../store";
import { commonFormValidation } from "../../../../../../utils/validationFunctions";
import { clearValidationError } from "../../../utils";
import { ErrorResponseHandler } from "../../../../../../common/types";
import api from "../../../../../../common/api";
import InputBoxWrapper from "../../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import SelectWrapper from "../../../../Common/FormComponents/SelectWrapper/SelectWrapper";
import AddIcon from "../../../../../../icons/AddIcon";
import AddNamespaceModal from "../helpers/AddNamespaceModal";
import SizePreview from "../SizePreview";
import TenantSize from "./TenantSize";
import { Paper, SelectChangeEvent } from "@mui/material";
import { IMkEnvs, mkPanelConfigurations } from "./utils";
const styles = (theme: Theme) =>
createStyles({
@@ -73,7 +74,7 @@ const styles = (theme: Theme) =>
...wizardCommon,
});
interface INameTenantScreen {
interface INameTenantMainScreen {
classes: any;
storageClasses: Opts[];
setModalErrorSnackMessage: typeof setModalErrorSnackMessage;
@@ -84,20 +85,24 @@ interface INameTenantScreen {
tenantName: string;
namespace: string;
selectedStorageClass: string;
selectedStorageType: string;
formToRender?: IMkEnvs;
}
const NameTenant = ({
const NameTenantMain = ({
classes,
storageClasses,
tenantName,
namespace,
selectedStorageClass,
selectedStorageType,
formToRender = IMkEnvs.default,
updateAddField,
setStorageClassesList,
setLimitSize,
isPageValid,
setModalErrorSnackMessage,
}: INameTenantScreen) => {
}: INameTenantMainScreen) => {
const [validationErrors, setValidationErrors] = useState<any>({});
const [emptyNamespace, setEmptyNamespace] = useState<boolean>(true);
const [loadingNamespaceInfo, setLoadingNamespaceInfo] =
@@ -230,7 +235,8 @@ const NameTenant = ({
const isValid =
!("tenant-name" in commonValidation) &&
!("namespace" in commonValidation) &&
storageClasses.length > 0;
((formToRender === IMkEnvs.default && storageClasses.length > 0) ||
(formToRender !== IMkEnvs.default && selectedStorageType !== ""));
isPageValid("nameTenant", isValid);
@@ -242,6 +248,8 @@ const NameTenant = ({
isPageValid,
emptyNamespace,
loadingNamespaceInfo,
selectedStorageType,
formToRender,
]);
const frmValidationCleanup = (fieldName: string) => {
@@ -271,11 +279,11 @@ const NameTenant = ({
)}
<Grid container>
<Grid item xs={8} md={9}>
<Paper className={classes.paperWrapper}>
<Paper className={classes.paperWrapper} sx={{ minHeight: 550 }}>
<Grid container>
<Grid item xs={12}>
<div className={classes.headerElement}>
<h3 className={classes.h3Section}>Name Tenant</h3>
<h3 className={classes.h3Section}>Name</h3>
<span className={classes.descriptionText}>
How would you like to name this new tenant?
</span>
@@ -311,23 +319,57 @@ const NameTenant = ({
required
/>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<SelectWrapper
id="storage_class"
name="storage_class"
onChange={(e: SelectChangeEvent<string>) => {
updateField(
"selectedStorageClass",
e.target.value as string
);
}}
label="Storage Class"
value={selectedStorageClass}
options={storageClasses}
disabled={storageClasses.length < 1}
/>
</Grid>
<TenantSize />
{formToRender === IMkEnvs.default ? (
<Grid item xs={12} className={classes.formFieldRow}>
<SelectWrapper
id="storage_class"
name="storage_class"
onChange={(e: SelectChangeEvent<string>) => {
updateField(
"selectedStorageClass",
e.target.value as string
);
}}
label="Storage Class"
value={selectedStorageClass}
options={storageClasses}
disabled={storageClasses.length < 1}
/>
</Grid>
) : (
<Grid item xs={12} className={classes.formFieldRow}>
<SelectWrapper
id="storage_type"
name="storage_type"
onChange={(e: SelectChangeEvent<string>) => {
updateField(
"selectedStorageType",
e.target.value as string
);
}}
label={get(
mkPanelConfigurations,
`${formToRender}.variantSelectorLabel`,
"Storage Type"
)}
value={selectedStorageType}
options={get(
mkPanelConfigurations,
`${formToRender}.variantSelectorValues`,
[]
)}
/>
</Grid>
)}
{formToRender === IMkEnvs.default ? (
<TenantSize />
) : (
get(
mkPanelConfigurations,
`${formToRender}.sizingComponent`,
null
)
)}
</Grid>
</Paper>
</Grid>
@@ -346,6 +388,8 @@ const mapState = (state: AppState) => ({
namespace: state.tenants.createTenant.fields.nameTenant.namespace,
selectedStorageClass:
state.tenants.createTenant.fields.nameTenant.selectedStorageClass,
selectedStorageType:
state.tenants.createTenant.fields.nameTenant.selectedStorageType,
storageClasses: state.tenants.createTenant.storageClasses,
});
@@ -357,4 +401,4 @@ const connector = connect(mapState, {
isPageValid,
});
export default withStyles(styles)(connector(NameTenant));
export default withStyles(styles)(connector(NameTenantMain));

View File

@@ -0,0 +1,64 @@
// 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 } from "react";
import { connect } from "react-redux";
import get from "lodash/get";
import NameTenantMain from "./NameTenantMain";
import { IMkEnvs, resourcesConfigurations } from "./utils";
import { AppState } from "../../../../../../store";
interface ITenantResources {
features?: string[];
}
const TenantResources = ({ features }: ITenantResources) => {
const [formRender, setFormRender] = useState<IMkEnvs | null>(null);
useEffect(() => {
let setConfiguration = IMkEnvs.default;
if (features && features.length !== 0) {
const possibleVariables = Object.keys(resourcesConfigurations);
possibleVariables.forEach((element) => {
if (features.includes(element)) {
setConfiguration = get(
resourcesConfigurations,
element,
IMkEnvs.default
);
}
});
}
setFormRender(setConfiguration);
}, [features]);
if (formRender === null) {
return null;
}
return <NameTenantMain formToRender={formRender} />;
};
const mapState = (state: AppState) => ({
features: state.console.session.features,
});
const connector = connect(mapState, null);
export default connector(TenantResources);

View File

@@ -20,14 +20,13 @@ import { Theme } from "@mui/material/styles";
import { SelectChangeEvent } from "@mui/material";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import get from "lodash/get";
import { AppState } from "../../../../../store";
import { isPageValid, updateAddField } from "../../actions";
import { AppState } from "../../../../../../store";
import { isPageValid, updateAddField } from "../../../actions";
import {
formFieldStyles,
modalBasic,
wizardCommon,
} from "../../../Common/FormComponents/common/styleLibrary";
} from "../../../../Common/FormComponents/common/styleLibrary";
import Grid from "@mui/material/Grid";
import {
calculateDistribution,
@@ -35,23 +34,21 @@ import {
getBytes,
k8sfactorForDropdown,
niceBytes,
setResourcesValidation,
} from "../../../../../common/utils";
import { clearValidationError } from "../../utils";
import { ecListTransform, Opts } from "../../ListTenants/utils";
import { IResourcesSize } from "../../ListTenants/types";
import { AllocableResourcesResponse } from "../../types";
import { ICapacity, IErasureCodeCalc } from "../../../../../common/types";
import { commonFormValidation } from "../../../../../utils/validationFunctions";
import api from "../../../../../common/api";
import InputBoxWrapper from "../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import SelectWrapper from "../../../Common/FormComponents/SelectWrapper/SelectWrapper";
} from "../../../../../../common/utils";
import { clearValidationError } from "../../../utils";
import { ecListTransform, Opts } from "../../../ListTenants/utils";
import { IResourcesSize } from "../../../ListTenants/types";
import { ICapacity, IErasureCodeCalc } from "../../../../../../common/types";
import { commonFormValidation } from "../../../../../../utils/validationFunctions";
import api from "../../../../../../common/api";
import InputBoxWrapper from "../../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import SelectWrapper from "../../../../Common/FormComponents/SelectWrapper/SelectWrapper";
import TenantSizeResources from "./TenantSizeResources";
interface ITenantSizeProps {
classes: any;
updateAddField: typeof updateAddField;
isPageValid: typeof isPageValid;
advancedMode: boolean;
volumeSize: string;
sizeFactor: string;
drivesPerServer: string;
@@ -60,16 +57,11 @@ interface ITenantSizeProps {
ecParity: string;
ecParityChoices: Opts[];
cleanECChoices: string[];
maxAllocableMemo: number;
resourcesSize: IResourcesSize;
distribution: any;
ecParityCalc: IErasureCodeCalc;
limitSize: any;
selectedStorageClass: string;
cpuToUse: string;
maxAllocatableResources: AllocableResourcesResponse;
maxCPUsUse: string;
maxMemorySize: string;
}
const styles = (theme: Theme) =>
@@ -98,7 +90,6 @@ const TenantSize = ({
classes,
updateAddField,
isPageValid,
advancedMode,
volumeSize,
sizeFactor,
drivesPerServer,
@@ -107,16 +98,11 @@ const TenantSize = ({
ecParity,
ecParityChoices,
cleanECChoices,
maxAllocableMemo,
resourcesSize,
distribution,
ecParityCalc,
limitSize,
cpuToUse,
selectedStorageClass,
maxAllocatableResources,
maxCPUsUse,
maxMemorySize,
}: ITenantSizeProps) => {
const [validationErrors, setValidationErrors] = useState<any>({});
const [errorFlag, setErrorFlag] = useState<boolean>(false);
@@ -138,23 +124,6 @@ const TenantSize = ({
// Storage Quotas
const validateResourcesSize = useCallback(() => {
const memSize = memoryNode || "0";
const cpusSelected = cpuToUse;
const resourcesSize = setResourcesValidation(
parseInt(memSize),
parseInt(cpusSelected),
maxAllocatableResources
);
updateField("resourcesSize", resourcesSize);
}, [memoryNode, cpuToUse, maxAllocatableResources, updateField]);
useEffect(() => {
validateResourcesSize();
}, [memoryNode, cpuToUse, validateResourcesSize]);
useEffect(() => {
if (ecParityChoices.length > 0 && distribution.error === "") {
const ecCodeValidated = erasureCodeCalc(
@@ -165,9 +134,11 @@ const TenantSize = ({
);
updateField("ecParityCalc", ecCodeValidated);
updateField("ecParity", ecCodeValidated.defaultEC);
if (!cleanECChoices.includes(ecParity) || ecParity === "") {
updateField("ecParity", ecCodeValidated.defaultEC);
}
}
}, [ecParityChoices.length, distribution, cleanECChoices, updateField]);
}, [ecParity, ecParityChoices.length, distribution, cleanECChoices, updateField]);
/*End debounce functions*/
/*Calculate Allocation*/
@@ -192,74 +163,7 @@ const TenantSize = ({
updateField("distribution", distrCalculate);
setErrorFlag(false);
setNodeError("");
// Get allocatable Resources
api
.invoke("GET", `api/v1/cluster/allocatable-resources?num_nodes=${nodes}`)
.then((res: AllocableResourcesResponse) => {
updateField("maxAllocatableResources", res);
const maxAllocatableResources = res;
const memoryExists = get(
maxAllocatableResources,
"min_allocatable_mem",
false
);
const cpuExists = get(
maxAllocatableResources,
"min_allocatable_cpu",
false
);
if (memoryExists === false || cpuExists === false) {
updateField("cpuToUse", 0);
updateField("maxMemorySize", "0");
updateField("maxCPUsUse", "0");
validateResourcesSize();
return;
}
// We default to Best CPU Configuration
updateField(
"maxMemorySize",
res.mem_priority.max_allocatable_mem.toString()
);
updateField(
"maxCPUsUse",
res.cpu_priority.max_allocatable_cpu.toString()
);
updateField("maxAllocableMemo", res.mem_priority.max_allocatable_mem);
const cpuInt = parseInt(cpuToUse);
const maxAlocatableCPU = get(
maxAllocatableResources,
"cpu_priority.max_allocatable_cpu",
0
);
if (cpuInt === 0 && cpuInt !== maxAlocatableCPU) {
updateField("cpuToUse", maxAlocatableCPU);
} else if (cpuInt > maxAlocatableCPU) {
updateField("cpuToUse", maxAlocatableCPU);
}
// We reset error states
validateResourcesSize();
})
.catch((err: any) => {
updateField("maxAllocableMemo", 0);
updateField("cpuToUse", "0");
setErrorFlag(true);
setNodeError(err.errorMessage);
console.error(err);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [nodes, volumeSize, sizeFactor, updateField]);
}, [nodes, volumeSize, sizeFactor, updateField, drivesPerServer]);
/*Calculate Allocation End*/
@@ -288,13 +192,6 @@ const TenantSize = ({
true
)}`,
},
{
fieldKey: "memory_per_node",
required: true,
value: memoryNode,
customValidation: parseInt(memoryNode) < 2,
customValidationMessage: "Memory size must be greater than 2Gi",
},
{
fieldKey: "drivesps",
required: true,
@@ -308,13 +205,9 @@ const TenantSize = ({
"tenantSize",
!("nodes" in commonValidation) &&
!("volume_size" in commonValidation) &&
!("memory_per_node" in commonValidation) &&
!("drivesps" in commonValidation) &&
distribution.error === "" &&
ecParityCalc.error === 0 &&
resourcesSize.error === "" &&
parseInt(cpuToUse) <= parseInt(maxCPUsUse) &&
parseInt(cpuToUse) > 0 &&
ecParity !== ""
);
@@ -329,8 +222,6 @@ const TenantSize = ({
resourcesSize,
limitSize,
selectedStorageClass,
cpuToUse,
maxCPUsUse,
isPageValid,
errorFlag,
nodeError,
@@ -365,7 +256,7 @@ const TenantSize = ({
<Fragment>
<Grid item xs={12}>
<div className={classes.headerElement}>
<h3 className={classes.h3Section}>Tenant Size</h3>
<h3 className={classes.h3Section}>Capacity</h3>
<span className={classes.descriptionText}>
Please select the desired capacity
</span>
@@ -376,11 +267,6 @@ const TenantSize = ({
<div className={classes.error}>{distribution.error}</div>
</Grid>
)}
{resourcesSize.error !== "" && (
<Grid item xs={12}>
<div className={classes.error}>{resourcesSize.error}</div>
</Grid>
)}
<Grid item xs={12} className={classes.formFieldRow}>
<InputBoxWrapper
id="nodes"
@@ -410,7 +296,7 @@ const TenantSize = ({
cleanValidation("drivesps");
}
}}
label="Number of Drives per Server"
label="Drives per Server"
value={drivesPerServer}
disabled={selectedStorageClass === ""}
min="1"
@@ -456,49 +342,6 @@ const TenantSize = ({
</div>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<InputBoxWrapper
label={"CPU Selection"}
id={"cpuToUse"}
name={"cpuToUse"}
onChange={(e) => {
if (e.target.validity.valid) {
updateField("cpuToUse", e.target.value);
}
}}
value={cpuToUse}
disabled={selectedStorageClass === ""}
min="1"
max={maxCPUsUse}
error={
parseInt(cpuToUse) > parseInt(maxCPUsUse) ||
parseInt(cpuToUse) <= 0 ||
isNaN(parseInt(cpuToUse))
? "Invalid CPU Configuration"
: ""
}
pattern={"[0-9]*"}
/>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<InputBoxWrapper
type="number"
id="memory_per_node"
name="memory_per_node"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
updateField("memoryNode", e.target.value);
cleanValidation("memory_per_node");
}}
label="Memory per Node [Gi]"
value={memoryNode}
disabled={selectedStorageClass === ""}
required
error={validationErrors["memory_per_node"] || ""}
min="2"
max={maxMemorySize}
/>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<SelectWrapper
id="ec_parity"
@@ -516,12 +359,13 @@ const TenantSize = ({
usable capacity in the cluster
</span>
</Grid>
<TenantSizeResources />
</Fragment>
);
};
const mapState = (state: AppState) => ({
advancedMode: state.tenants.createTenant.advancedModeOn,
volumeSize: state.tenants.createTenant.fields.tenantSize.volumeSize,
sizeFactor: state.tenants.createTenant.fields.tenantSize.sizeFactor,
drivesPerServer: state.tenants.createTenant.fields.tenantSize.drivesPerServer,
@@ -530,19 +374,13 @@ const mapState = (state: AppState) => ({
ecParity: state.tenants.createTenant.fields.tenantSize.ecParity,
ecParityChoices: state.tenants.createTenant.fields.tenantSize.ecParityChoices,
cleanECChoices: state.tenants.createTenant.fields.tenantSize.cleanECChoices,
maxAllocableMemo:
state.tenants.createTenant.fields.tenantSize.maxAllocableMemo,
resourcesSize: state.tenants.createTenant.fields.tenantSize.resourcesSize,
distribution: state.tenants.createTenant.fields.tenantSize.distribution,
ecParityCalc: state.tenants.createTenant.fields.tenantSize.ecParityCalc,
limitSize: state.tenants.createTenant.limitSize,
selectedStorageClass:
state.tenants.createTenant.fields.nameTenant.selectedStorageClass,
cpuToUse: state.tenants.createTenant.fields.tenantSize.cpuToUse,
maxAllocatableResources:
state.tenants.createTenant.fields.tenantSize.maxAllocatableResources,
maxCPUsUse: state.tenants.createTenant.fields.tenantSize.maxCPUsUse,
maxMemorySize: state.tenants.createTenant.fields.tenantSize.maxMemorySize,
});
const connector = connect(mapState, {

View File

@@ -0,0 +1,385 @@
// 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, { Fragment, useCallback, useEffect, useState } from "react";
import { connect } from "react-redux";
import { Theme } from "@mui/material/styles";
import { SelectChangeEvent } from "@mui/material";
import get from "lodash/get";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import { AppState } from "../../../../../../store";
import { isPageValid, updateAddField } from "../../../actions";
import {
formFieldStyles,
modalBasic,
wizardCommon,
} from "../../../../Common/FormComponents/common/styleLibrary";
import Grid from "@mui/material/Grid";
import { erasureCodeCalc, getBytes } from "../../../../../../common/utils";
import { clearValidationError } from "../../../utils";
import { ecListTransform, Opts } from "../../../ListTenants/utils";
import { IResourcesSize } from "../../../ListTenants/types";
import {
IErasureCodeCalc,
IStorageDistribution,
} from "../../../../../../common/types";
import { commonFormValidation } from "../../../../../../utils/validationFunctions";
import api from "../../../../../../common/api";
import InputBoxWrapper from "../../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import SelectWrapper from "../../../../Common/FormComponents/SelectWrapper/SelectWrapper";
import {
IMkEnvs,
IntegrationConfiguration,
mkPanelConfigurations,
} from "./utils";
interface ITenantSizeAWSProps {
classes: any;
updateAddField: typeof updateAddField;
isPageValid: typeof isPageValid;
volumeSize: string;
sizeFactor: string;
drivesPerServer: string;
nodes: string;
memoryNode: string;
ecParity: string;
ecParityChoices: Opts[];
cleanECChoices: string[];
resourcesSize: IResourcesSize;
distribution: any;
ecParityCalc: IErasureCodeCalc;
limitSize: any;
selectedStorageType: string;
cpuToUse: string;
maxCPUsUse: string;
formToRender?: IMkEnvs;
integrationSelection: IntegrationConfiguration;
}
const styles = (theme: Theme) =>
createStyles({
compositeFieldContainer: {
display: "flex",
alignItems: "center",
},
compositeAddOn: {
marginLeft: 10,
"& div": {
marginBottom: 0,
},
"@media (max-width: 900px)": {
"& div": {
marginTop: 5,
},
},
},
...formFieldStyles,
...modalBasic,
...wizardCommon,
});
const TenantSizeMK = ({
classes,
updateAddField,
isPageValid,
volumeSize,
sizeFactor,
drivesPerServer,
nodes,
memoryNode,
ecParity,
ecParityChoices,
cleanECChoices,
resourcesSize,
distribution,
ecParityCalc,
limitSize,
cpuToUse,
selectedStorageType,
maxCPUsUse,
formToRender,
integrationSelection,
}: ITenantSizeAWSProps) => {
const [validationErrors, setValidationErrors] = useState<any>({});
// Common
const updateField = useCallback(
(field: string, value: any) => {
updateAddField("tenantSize", field, value);
},
[updateAddField]
);
const updateMainField = useCallback(
(field: string, value: string) => {
updateAddField("nameTenant", field, value);
},
[updateAddField]
);
const cleanValidation = (fieldName: string) => {
setValidationErrors(clearValidationError(validationErrors, fieldName));
};
/*Debounce functions*/
// Storage Quotas
useEffect(() => {
if (ecParityChoices.length > 0 && distribution.error === "") {
const ecCodeValidated = erasureCodeCalc(
cleanECChoices,
distribution.persistentVolumes,
distribution.pvSize,
distribution.nodes
);
updateField("ecParityCalc", ecCodeValidated);
if (!cleanECChoices.includes(ecParity) || ecParity === "") {
updateField("ecParity", ecCodeValidated.defaultEC);
}
}
}, [ecParity, ecParityChoices, distribution, cleanECChoices, updateField]);
/*End debounce functions*/
/*Set location Storage Types*/
useEffect(() => {
if (formToRender !== undefined && parseInt(nodes) >= 4) {
const setConfigs = mkPanelConfigurations[formToRender];
const keyCount = Object.keys(setConfigs).length;
//Configuration is filled
if (keyCount > 0) {
const configs: IntegrationConfiguration[] = get(
setConfigs,
"configurations",
[]
);
const mainSelection = configs.find(
(item) => item.typeSelection === selectedStorageType
);
if (mainSelection) {
updateField("integrationSelection", mainSelection);
updateMainField("selectedStorageClass", mainSelection.storageClass);
let pvSize = parseInt(
getBytes(
mainSelection.driveSize.driveSize,
mainSelection.driveSize.sizeUnit,
true
),
10
);
const distrCalculate: IStorageDistribution = {
pvSize,
nodes: parseInt(nodes),
disks: mainSelection.drivesPerServer,
persistentVolumes: mainSelection.drivesPerServer * parseInt(nodes),
error: "",
};
updateField("distribution", distrCalculate);
// apply requests, half of the available resources
updateField(
"resourcesCPURequest",
Math.max(1, mainSelection.CPU / 2)
);
updateField(
"resourcesMemoryRequest",
Math.max(2, mainSelection.memory / 2)
);
}
}
}
}, [nodes, selectedStorageType, formToRender, updateField, updateMainField]);
/*Calculate Allocation End*/
/* Validations of pages */
useEffect(() => {
const commonValidation = commonFormValidation([
{
fieldKey: "nodes",
required: true,
value: nodes,
customValidation: parseInt(nodes) < 4,
customValidationMessage: "Al least 4 servers must be selected",
},
]);
isPageValid(
"tenantSize",
!("nodes" in commonValidation) &&
distribution.error === "" &&
ecParityCalc.error === 0 &&
resourcesSize.error === "" &&
ecParity !== "" &&
parseInt(nodes) >= 4
);
setValidationErrors(commonValidation);
}, [
nodes,
volumeSize,
sizeFactor,
memoryNode,
distribution,
ecParityCalc,
resourcesSize,
limitSize,
selectedStorageType,
cpuToUse,
maxCPUsUse,
isPageValid,
drivesPerServer,
ecParity,
]);
useEffect(() => {
if (integrationSelection.drivesPerServer !== 0) {
// Get EC Value
if (nodes.trim() !== "") {
api
.invoke(
"GET",
`api/v1/get-parity/${nodes}/${integrationSelection.drivesPerServer}`
)
.then((ecList: string[]) => {
updateField("ecParityChoices", ecListTransform(ecList));
updateField("cleanECChoices", ecList);
})
.catch((err: any) => {
updateField("ecparityChoices", []);
isPageValid("tenantSize", false);
updateField("ecParity", "");
});
}
}
}, [integrationSelection, nodes, isPageValid, updateField]);
/* End Validation of pages */
return (
<Fragment>
<Grid item xs={12}>
<div className={classes.headerElement}>
<h3 className={classes.h3Section}>Tenant Size</h3>
<span className={classes.descriptionText}>
Please select the desired capacity
</span>
</div>
</Grid>
{distribution.error !== "" && (
<Grid item xs={12}>
<div className={classes.error}>{distribution.error}</div>
</Grid>
)}
{resourcesSize.error !== "" && (
<Grid item xs={12}>
<div className={classes.error}>{resourcesSize.error}</div>
</Grid>
)}
<Grid item xs={12} className={classes.formFieldRow}>
<InputBoxWrapper
id="nodes"
name="nodes"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.validity.valid) {
updateField("nodes", e.target.value);
cleanValidation("nodes");
}
}}
label="Number of Servers"
disabled={selectedStorageType === ""}
value={nodes}
min="4"
required
error={validationErrors["nodes"] || ""}
pattern={"[0-9]*"}
/>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<SelectWrapper
id="ec_parity"
name="ec_parity"
onChange={(e: SelectChangeEvent<string>) => {
updateField("ecParity", e.target.value as string);
}}
label="Erasure Code Parity"
disabled={selectedStorageType === ""}
value={ecParity}
options={ecParityChoices}
/>
<span className={classes.descriptionText}>
Please select the desired parity. This setting will change the max
usable capacity in the cluster
</span>
</Grid>
</Fragment>
);
};
const mapState = (state: AppState) => () => {
const createTenant = state.tenants.createTenant;
const {
memoryNode,
ecParityChoices,
distribution,
cleanECChoices,
sizeFactor,
ecParity,
cpuToUse,
integrationSelection,
resourcesSize,
drivesPerServer,
maxCPUsUse,
ecParityCalc,
volumeSize,
nodes,
} = createTenant.fields.tenantSize;
return {
volumeSize,
sizeFactor,
drivesPerServer,
nodes,
memoryNode,
ecParity,
ecParityChoices,
cleanECChoices,
resourcesSize,
distribution,
ecParityCalc,
cpuToUse,
maxCPUsUse,
integrationSelection,
limitSize: createTenant.limitSize,
selectedStorageType: createTenant.fields.nameTenant.selectedStorageType,
};
};
const connector = connect(mapState, {
updateAddField,
isPageValid,
});
export default withStyles(styles)(connector(TenantSizeMK));

View File

@@ -0,0 +1,400 @@
// 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, { Fragment, useCallback, useEffect } from "react";
import { connect } from "react-redux";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import get from "lodash/get";
import { AppState } from "../../../../../../store";
import { isPageValid, updateAddField } from "../../../actions";
import {
formFieldStyles,
modalBasic,
wizardCommon,
} from "../../../../Common/FormComponents/common/styleLibrary";
import Grid from "@mui/material/Grid";
import { IResourcesSize } from "../../../ListTenants/types";
import { AllocableResourcesResponse } from "../../../types";
import api from "../../../../../../common/api";
import InputBoxWrapper from "../../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import FormSwitchWrapper from "../../../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
import { floor } from "lodash";
interface ITenantSizeResourcesProps {
classes: any;
updateAddField: typeof updateAddField;
isPageValid: typeof isPageValid;
nodes: string;
resourcesSize: IResourcesSize;
selectedStorageClass: string;
maxAllocatableResources: AllocableResourcesResponse;
maxCPUsUse: string;
maxMemorySize: string;
resourcesSpecifyLimit: boolean;
resourcesCPURequestError: string;
resourcesCPURequest: string;
resourcesCPULimitError: string;
resourcesCPULimit: string;
resourcesMemoryRequestError: string;
resourcesMemoryRequest: string;
resourcesMemoryLimitError: string;
resourcesMemoryLimit: string;
}
const styles = (theme: Theme) =>
createStyles({
compositeFieldContainer: {
display: "flex",
alignItems: "center",
},
compositeAddOn: {
marginLeft: 10,
"& div": {
marginBottom: 0,
},
"@media (max-width: 900px)": {
"& div": {
marginTop: 5,
},
},
},
...formFieldStyles,
...modalBasic,
...wizardCommon,
});
const TenantSizeResources = ({
classes,
updateAddField,
isPageValid,
nodes,
resourcesSize,
selectedStorageClass,
maxAllocatableResources,
maxCPUsUse,
maxMemorySize,
resourcesSpecifyLimit,
resourcesCPURequestError,
resourcesCPURequest,
resourcesCPULimitError,
resourcesCPULimit,
resourcesMemoryRequestError,
resourcesMemoryRequest,
resourcesMemoryLimitError,
resourcesMemoryLimit,
}: ITenantSizeResourcesProps) => {
// Common
const updateField = useCallback(
(field: string, value: any) => {
updateAddField("tenantSize", field, value);
},
[updateAddField]
);
/*Debounce functions*/
useEffect(() => {
isPageValid(
"tenantSize",
resourcesMemoryRequestError === "" &&
resourcesMemoryLimitError === "" &&
resourcesCPURequestError === "" &&
resourcesCPULimitError === ""
);
}, [
isPageValid,
resourcesMemoryRequestError,
resourcesMemoryLimitError,
resourcesCPURequestError,
resourcesCPULimitError,
]);
/*End debounce functions*/
/*Calculate Allocation*/
useEffect(() => {
// Get allocatable Resources
api
.invoke("GET", `api/v1/cluster/allocatable-resources?num_nodes=${nodes}`)
.then((res: AllocableResourcesResponse) => {
updateField("maxAllocatableResources", res);
const maxAllocatableResources = res;
const memoryExists = get(
maxAllocatableResources,
"min_allocatable_mem",
false
);
const cpuExists = get(
maxAllocatableResources,
"min_allocatable_cpu",
false
);
if (memoryExists === false || cpuExists === false) {
updateField("cpuToUse", 0);
updateField("maxMemorySize", "");
updateField("maxCPUsUse", "");
return;
}
const maxMemory = floor(
res.mem_priority.max_allocatable_mem / 1024 / 1024 / 1024
);
// We default to Best CPU Configuration
updateField("maxMemorySize", maxMemory.toString());
updateField(
"maxCPUsUse",
res.cpu_priority.max_allocatable_cpu.toString()
);
const maxAllocatableCPU = get(
maxAllocatableResources,
"cpu_priority.max_allocatable_cpu",
0
);
const baseCpuUse = Math.max(1, floor(maxAllocatableCPU / 2));
updateField("resourcesCPURequest", baseCpuUse);
const baseMemoryUse = Math.max(2, floor(maxMemory / 2));
updateField("resourcesMemoryRequest", baseMemoryUse);
})
.catch((err: any) => {
updateField("maxMemorySize", 0);
updateField("resourcesCPURequest", "");
console.error(err);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [nodes, updateField]);
/*Calculate Allocation End*/
return (
<Fragment>
<Grid item xs={12}>
<div className={classes.headerElement}>
<h3 className={classes.h3Section}>Resources</h3>
<span className={classes.descriptionText}>
You may specify the amount of CPU and Memory that MinIO servers
should reserve on each node.
</span>
</div>
</Grid>
{resourcesSize.error !== "" && (
<Grid item xs={12}>
<div className={classes.error}>{resourcesSize.error}</div>
</Grid>
)}
<Grid item xs={12} className={classes.formFieldRow}>
<InputBoxWrapper
label={"CPU Request"}
id={"resourcesCPURequest"}
name={"resourcesCPURequest"}
onChange={(e) => {
let value = parseInt(e.target.value);
if (e.target.value === "") {
updateField("resourcesCPURequestError", "");
} else if (isNaN(value)) {
updateField("resourcesCPURequestError", "Invalid number");
} else if (value > parseInt(maxCPUsUse)) {
updateField(
"resourcesCPURequestError",
`Request exceeds available cores (${maxCPUsUse})`
);
} else if (e.target.validity.valid) {
updateField("resourcesCPURequestError", "");
} else {
updateField("resourcesCPURequestError", "Invalid configuration");
}
updateField("resourcesCPURequest", e.target.value);
}}
value={resourcesCPURequest}
disabled={selectedStorageClass === ""}
max={maxCPUsUse}
error={resourcesCPURequestError}
pattern={"[0-9]*"}
/>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<InputBoxWrapper
id="resourcesMemoryRequest"
name="resourcesMemoryRequest"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
let value = parseInt(e.target.value);
if (e.target.value === "") {
updateField("resourcesMemoryRequestError", "");
} else if (isNaN(value)) {
updateField("resourcesMemoryRequestError", "Invalid number");
} else if (value > parseInt(maxMemorySize)) {
updateField(
"resourcesMemoryRequestError",
`Request exceeds available memory across ${nodes} nodes (${maxMemorySize}Gi)`
);
} else if (value < 2) {
updateField(
"resourcesMemoryRequestError",
"At least 2Gi must be requested"
);
} else if (e.target.validity.valid) {
updateField("resourcesMemoryRequestError", "");
} else {
updateField(
"resourcesMemoryRequestError",
"Invalid configuration"
);
}
updateField("resourcesMemoryRequest", e.target.value);
}}
label="Memory Request [Gi]"
value={resourcesMemoryRequest}
disabled={selectedStorageClass === ""}
error={resourcesMemoryRequestError}
pattern={"[0-9]*"}
/>
</Grid>
<Grid item xs={12}>
<FormSwitchWrapper
value="resourcesSpecifyLimit"
id="resourcesSpecifyLimit"
name="resourcesSpecifyLimit"
checked={resourcesSpecifyLimit}
onChange={(e) => {
const targetD = e.target;
const checked = targetD.checked;
updateField("resourcesSpecifyLimit", checked);
}}
label={"Specify Limit"}
/>
</Grid>
{resourcesSpecifyLimit && (
<Fragment>
<Grid item xs={12} className={classes.formFieldRow}>
<InputBoxWrapper
label={"CPU Limit"}
id={"resourcesCPULimit"}
name={"resourcesCPULimit"}
onChange={(e) => {
let value = parseInt(e.target.value);
if (e.target.value === "") {
updateField("resourcesCPULimitError", "");
} else if (isNaN(value)) {
updateField("resourcesCPULimitError", "Invalid number");
} else if (e.target.validity.valid) {
updateField("resourcesCPULimitError", "");
} else {
updateField(
"resourcesCPULimitError",
"Invalid configuration"
);
}
updateField("resourcesCPULimit", e.target.value);
}}
value={resourcesCPULimit}
disabled={selectedStorageClass === ""}
max={maxCPUsUse}
error={resourcesCPULimitError}
pattern={"[0-9]*"}
/>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<InputBoxWrapper
id="resourcesMemoryLimit"
name="resourcesMemoryLimit"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
let value = parseInt(e.target.value);
console.log("value", value);
if (e.target.value === "") {
updateField("resourcesMemoryLimitError", "");
} else if (isNaN(value)) {
updateField("resourcesMemoryLimitError", "Invalid number");
} else if (e.target.validity.valid) {
updateField("resourcesMemoryLimitError", "");
} else {
updateField(
"resourcesMemoryLimitError",
"Invalid configuration"
);
}
updateField("resourcesMemoryLimit", e.target.value);
}}
label="Memory Limit [Gi]"
value={resourcesMemoryLimit}
disabled={selectedStorageClass === ""}
error={resourcesMemoryLimitError}
pattern={"[0-9]*"}
/>
</Grid>
</Fragment>
)}
</Fragment>
);
};
const mapState = (state: AppState) => ({
nodes: state.tenants.createTenant.fields.tenantSize.nodes,
resourcesSize: state.tenants.createTenant.fields.tenantSize.resourcesSize,
selectedStorageClass:
state.tenants.createTenant.fields.nameTenant.selectedStorageClass,
maxAllocatableResources:
state.tenants.createTenant.fields.tenantSize.maxAllocatableResources,
maxCPUsUse: state.tenants.createTenant.fields.tenantSize.maxCPUsUse,
maxMemorySize: state.tenants.createTenant.fields.tenantSize.maxMemorySize,
resourcesSpecifyLimit:
state.tenants.createTenant.fields.tenantSize.resourcesSpecifyLimit,
resourcesCPURequestError:
state.tenants.createTenant.fields.tenantSize.resourcesCPURequestError,
resourcesCPURequest:
state.tenants.createTenant.fields.tenantSize.resourcesCPURequest,
resourcesCPULimitError:
state.tenants.createTenant.fields.tenantSize.resourcesCPULimitError,
resourcesCPULimit:
state.tenants.createTenant.fields.tenantSize.resourcesCPULimit,
resourcesMemoryRequestError:
state.tenants.createTenant.fields.tenantSize.resourcesMemoryRequestError,
resourcesMemoryRequest:
state.tenants.createTenant.fields.tenantSize.resourcesMemoryRequest,
resourcesMemoryLimitError:
state.tenants.createTenant.fields.tenantSize.resourcesMemoryLimitError,
resourcesMemoryLimit:
state.tenants.createTenant.fields.tenantSize.resourcesMemoryLimit,
});
const connector = connect(mapState, {
updateAddField,
isPageValid,
});
export default withStyles(styles)(connector(TenantSizeResources));

View File

@@ -0,0 +1,84 @@
// 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 from "react";
import { Opts } from "../../../ListTenants/utils";
import TenantSizeMK from "./TenantSizeMK";
export enum IMkEnvs {
"aws",
"azure",
"gcs",
"default",
undefined,
}
export interface IDriveSizing {
driveSize: string;
sizeUnit: string;
}
export interface IntegrationConfiguration {
typeSelection: string;
storageClass: string;
CPU: number;
memory: number;
drivesPerServer: number;
driveSize: IDriveSizing;
}
export const AWSStorageTypes: Opts[] = [
{ label: "NVME", value: "nvme" },
{ label: "HDD", value: "hdd" },
];
export const resourcesConfigurations = {
"mp-mode-aws": IMkEnvs.aws,
"mp-mode-azure": IMkEnvs.azure,
"mp-mode-gcs": IMkEnvs.gcs,
};
export const AWSConfigurations: IntegrationConfiguration[] = [
{
typeSelection: "nvme",
storageClass: "nvme-i3en-12xlarge",
CPU: 48,
memory: 384,
driveSize: { driveSize: "7500", sizeUnit: "Gi" },
drivesPerServer: 4,
},
{
typeSelection: "hdd",
storageClass: "hdd-d3en-12xlarge",
CPU: 8,
memory: 32,
driveSize: { driveSize: "12.7", sizeUnit: "Ti" },
drivesPerServer: 4,
},
];
export const mkPanelConfigurations = {
[IMkEnvs.aws]: {
variantSelectorLabel: "Storage Type",
variantSelectorValues: AWSStorageTypes,
configurations: AWSConfigurations,
sizingComponent: <TenantSizeMK formToRender={IMkEnvs.aws} />,
},
[IMkEnvs.azure]: {},
[IMkEnvs.gcs]: {},
[IMkEnvs.default]: {},
[IMkEnvs.undefined]: {},
};

View File

@@ -17,32 +17,31 @@
import { ITenant } from "./ListTenants/types";
import { Opts } from "./ListTenants/utils";
import {
ADD_TENANT_SET_ADVANCED_MODE,
ADD_TENANT_ADD_CA_KEYPAIR,
ADD_TENANT_ADD_CONSOLE_CA_KEYPAIR,
ADD_TENANT_ADD_CONSOLE_CERT,
ADD_TENANT_ADD_FILE_TO_CA_KEYPAIR,
ADD_TENANT_ADD_FILE_TO_CONSOLE_CA_KEYPAIR,
ADD_TENANT_ADD_FILE_TO_MINIO_KEYPAIR,
ADD_TENANT_ADD_MINIO_KEYPAIR,
ADD_TENANT_DELETE_CA_KEYPAIR,
ADD_TENANT_DELETE_CONSOLE_CA_KEYPAIR,
ADD_TENANT_DELETE_MINIO_KEYPAIR,
ADD_TENANT_ENCRYPTION_CLIENT_CERT,
ADD_TENANT_ENCRYPTION_GEMALTO_CA,
ADD_TENANT_ENCRYPTION_SERVER_CERT,
ADD_TENANT_ENCRYPTION_VAULT_CA,
ADD_TENANT_ENCRYPTION_VAULT_CERT,
ADD_TENANT_RESET_FORM,
ADD_TENANT_SET_CURRENT_PAGE,
ADD_TENANT_UPDATE_FIELD,
ADD_TENANT_SET_LIMIT_SIZE,
ADD_TENANT_SET_PAGE_VALID,
ADD_TENANT_SET_STORAGE_CLASSES_LIST,
ADD_TENANT_SET_LIMIT_SIZE,
ADD_TENANT_ADD_CA_KEYPAIR,
ADD_TENANT_DELETE_CA_KEYPAIR,
ADD_TENANT_ADD_FILE_TO_CA_KEYPAIR,
ADD_TENANT_ADD_MINIO_KEYPAIR,
ADD_TENANT_DELETE_MINIO_KEYPAIR,
ADD_TENANT_ADD_FILE_TO_MINIO_KEYPAIR,
ADD_TENANT_ADD_CONSOLE_CERT,
ADD_TENANT_ADD_CONSOLE_CA_KEYPAIR,
ADD_TENANT_DELETE_CONSOLE_CA_KEYPAIR,
ADD_TENANT_ADD_FILE_TO_CONSOLE_CA_KEYPAIR,
ADD_TENANT_ENCRYPTION_SERVER_CERT,
ADD_TENANT_ENCRYPTION_CLIENT_CERT,
ADD_TENANT_ENCRYPTION_VAULT_CERT,
ADD_TENANT_ENCRYPTION_VAULT_CA,
ADD_TENANT_ENCRYPTION_GEMALTO_CA,
ADD_TENANT_RESET_FORM,
TENANT_DETAILS_SET_LOADING,
TENANT_DETAILS_SET_TENANT,
ADD_TENANT_UPDATE_FIELD,
TENANT_DETAILS_SET_CURRENT_TENANT,
TENANT_DETAILS_SET_LOADING,
TENANT_DETAILS_SET_TAB,
TENANT_DETAILS_SET_TENANT,
} from "./types";
// Basic actions
@@ -53,13 +52,6 @@ export const setWizardPage = (page: number) => {
};
};
export const setAdvancedMode = (state: boolean) => {
return {
type: ADD_TENANT_SET_ADVANCED_MODE,
state,
};
};
export const updateAddField = (
pageName: string,
fieldName: string,

View File

@@ -16,34 +16,33 @@
import has from "lodash/has";
import get from "lodash/get";
import {
TenantsManagementTypes,
ITenantState,
ADD_TENANT_ADD_CA_KEYPAIR,
ADD_TENANT_ADD_CONSOLE_CA_KEYPAIR,
ADD_TENANT_ADD_CONSOLE_CERT,
ADD_TENANT_ADD_FILE_TO_CA_KEYPAIR,
ADD_TENANT_ADD_FILE_TO_CONSOLE_CA_KEYPAIR,
ADD_TENANT_ADD_FILE_TO_MINIO_KEYPAIR,
ADD_TENANT_ADD_MINIO_KEYPAIR,
ADD_TENANT_DELETE_CA_KEYPAIR,
ADD_TENANT_DELETE_CONSOLE_CA_KEYPAIR,
ADD_TENANT_DELETE_MINIO_KEYPAIR,
ADD_TENANT_ENCRYPTION_CLIENT_CERT,
ADD_TENANT_ENCRYPTION_GEMALTO_CA,
ADD_TENANT_ENCRYPTION_SERVER_CERT,
ADD_TENANT_ENCRYPTION_VAULT_CA,
ADD_TENANT_ENCRYPTION_VAULT_CERT,
ADD_TENANT_RESET_FORM,
ADD_TENANT_SET_CURRENT_PAGE,
ADD_TENANT_SET_ADVANCED_MODE,
ADD_TENANT_UPDATE_FIELD,
ADD_TENANT_SET_LIMIT_SIZE,
ADD_TENANT_SET_PAGE_VALID,
ADD_TENANT_SET_STORAGE_CLASSES_LIST,
ADD_TENANT_ADD_MINIO_KEYPAIR,
ADD_TENANT_DELETE_MINIO_KEYPAIR,
ADD_TENANT_ADD_CA_KEYPAIR,
ADD_TENANT_ADD_FILE_TO_CA_KEYPAIR,
ADD_TENANT_DELETE_CA_KEYPAIR,
ADD_TENANT_ADD_CONSOLE_CERT,
ADD_TENANT_ADD_CONSOLE_CA_KEYPAIR,
ADD_TENANT_ADD_FILE_TO_CONSOLE_CA_KEYPAIR,
ADD_TENANT_DELETE_CONSOLE_CA_KEYPAIR,
ADD_TENANT_ADD_FILE_TO_MINIO_KEYPAIR,
ADD_TENANT_ENCRYPTION_SERVER_CERT,
ADD_TENANT_ENCRYPTION_CLIENT_CERT,
ADD_TENANT_ENCRYPTION_VAULT_CERT,
ADD_TENANT_ENCRYPTION_VAULT_CA,
ADD_TENANT_ENCRYPTION_GEMALTO_CA,
ADD_TENANT_RESET_FORM,
ADD_TENANT_SET_LIMIT_SIZE,
TENANT_DETAILS_SET_LOADING,
ADD_TENANT_UPDATE_FIELD,
ITenantState,
TENANT_DETAILS_SET_CURRENT_TENANT,
TENANT_DETAILS_SET_TENANT,
TENANT_DETAILS_SET_LOADING,
TENANT_DETAILS_SET_TAB,
TENANT_DETAILS_SET_TENANT,
TenantsManagementTypes,
} from "./types";
import { KeyPair } from "./ListTenants/utils";
import { getRandomString } from "./utils";
@@ -61,7 +60,6 @@ const initialState: ITenantState = {
"security",
"encryption",
],
advancedModeOn: false,
storageClasses: [],
limitSize: {},
fields: {
@@ -69,6 +67,7 @@ const initialState: ITenantState = {
tenantName: "",
namespace: "",
selectedStorageClass: "",
selectedStorageType: "",
},
configure: {
customImage: true,
@@ -90,8 +89,8 @@ const initialState: ITenantState = {
logSearchPostgresInitImage: "",
prometheusVolumeSize: "5",
prometheusSizeFactor: "Gi",
logSearchSelectedStorageClass: "",
prometheusSelectedStorageClass: "",
logSearchSelectedStorageClass: "default",
prometheusSelectedStorageClass: "default",
prometheusImage: "",
prometheusSidecarImage: "",
prometheusInitImage: "",
@@ -200,8 +199,17 @@ const initialState: ITenantState = {
ecParity: "",
ecParityChoices: [],
cleanECChoices: [],
maxAllocableMemo: 0,
cpuToUse: "0",
// resource request
resourcesSpecifyLimit: false,
resourcesCPURequestError: "",
resourcesCPURequest: "",
resourcesCPULimitError: "",
resourcesCPULimit: "",
resourcesMemoryRequestError: "",
resourcesMemoryRequest: "",
resourcesMemoryLimitError: "",
resourcesMemoryLimit: "",
resourcesSize: {
error: "",
memoryRequest: 0,
@@ -214,7 +222,6 @@ const initialState: ITenantState = {
nodes: 0,
persistentVolumes: 0,
disks: 0,
volumePerDisk: 0,
},
ecParityCalc: {
error: 0,
@@ -239,6 +246,14 @@ const initialState: ITenantState = {
},
maxCPUsUse: "0",
maxMemorySize: "0",
integrationSelection: {
driveSize: { driveSize: "0", sizeUnit: "B" },
CPU: 0,
typeSelection: "",
memory: 0,
drivesPerServer: 0,
storageClass: "",
},
},
affinity: {
nodeSelectorLabels: "",
@@ -338,10 +353,7 @@ export function tenantsReducer(
newState.createTenant.page = action.page;
return { ...newState };
case ADD_TENANT_SET_ADVANCED_MODE:
newState.createTenant.advancedModeOn = action.state;
return { ...newState };
case ADD_TENANT_UPDATE_FIELD:
if (
has(newState.createTenant.fields, `${action.pageName}.${action.field}`)
@@ -599,7 +611,6 @@ export function tenantsReducer(
"security",
"encryption",
],
advancedModeOn: false,
storageClasses: [],
limitSize: {},
fields: {
@@ -607,6 +618,7 @@ export function tenantsReducer(
tenantName: "",
namespace: "",
selectedStorageClass: "",
selectedStorageType: "",
},
configure: {
customImage: false,
@@ -622,14 +634,14 @@ export function tenantsReducer(
prometheusCustom: false,
logSearchVolumeSize: "5",
logSearchSizeFactor: "Gi",
logSearchSelectedStorageClass: "",
logSearchSelectedStorageClass: "default",
logSearchImage: "",
kesImage: "",
logSearchPostgresImage: "",
logSearchPostgresInitImage: "",
prometheusVolumeSize: "5",
prometheusSizeFactor: "Gi",
prometheusSelectedStorageClass: "",
prometheusSelectedStorageClass: "default",
prometheusImage: "",
prometheusSidecarImage: "",
prometheusInitImage: "",
@@ -738,13 +750,11 @@ export function tenantsReducer(
ecParity: "",
ecParityChoices: [],
cleanECChoices: [],
maxAllocableMemo: 0,
distribution: {
error: "",
nodes: 0,
persistentVolumes: 0,
disks: 0,
volumePerDisk: 0,
},
ecParityCalc: {
error: 0,
@@ -756,6 +766,16 @@ export function tenantsReducer(
},
limitSize: {},
cpuToUse: "0",
// resource request
resourcesSpecifyLimit: false,
resourcesCPURequestError: "",
resourcesCPURequest: "",
resourcesCPULimitError: "",
resourcesCPULimit: "",
resourcesMemoryRequestError: "",
resourcesMemoryRequest: "",
resourcesMemoryLimitError: "",
resourcesMemoryLimit: "",
resourcesSize: {
error: "",
memoryRequest: 0,
@@ -777,6 +797,14 @@ export function tenantsReducer(
},
maxCPUsUse: "0",
maxMemorySize: "0",
integrationSelection: {
driveSize: { driveSize: "0", sizeUnit: "B" },
CPU: 0,
typeSelection: "",
memory: 0,
drivesPerServer: 0,
storageClass: "",
},
},
affinity: {
nodeSelectorLabels: "",

View File

@@ -15,8 +15,9 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import { IErasureCodeCalc } from "../../../common/types";
import { ITenant, IResourcesSize } from "./ListTenants/types";
import { IResourcesSize, ITenant } from "./ListTenants/types";
import { KeyPair, Opts } from "./ListTenants/utils";
import { IntegrationConfiguration } from "./AddTenant/Steps/TenantResources/utils";
export const ADD_TENANT_SET_CURRENT_PAGE = "ADD_TENANT/SET_CURRENT_PAGE";
export const ADD_TENANT_SET_ADVANCED_MODE = "ADD_TENANT/SET_ADVANCED_MODE";
@@ -64,6 +65,7 @@ export const TENANT_DETAILS_SET_CURRENT_TENANT =
"TENANT_DETAILS/SET_CURRENT_TENANT";
export const TENANT_DETAILS_SET_TENANT = "TENANT_DETAILS/SET_TENANT";
export const TENANT_DETAILS_SET_TAB = "TENANT_DETAILS/SET_TAB";
export interface ICertificateInfo {
name: string;
serialNumber: string;
@@ -86,7 +88,6 @@ export interface ITenantSecurityResponse {
export interface ICreateTenant {
page: number;
validPages: string[];
advancedModeOn: boolean;
storageClasses: Opts[];
limitSize: any;
fields: IFieldStore;
@@ -119,6 +120,7 @@ export interface INameTenantFields {
tenantName: string;
namespace: string;
selectedStorageClass: string;
selectedStorageType: string;
}
export interface ISecurityContext {
@@ -237,7 +239,6 @@ export interface ITenantSizeFields {
ecParity: string;
ecParityChoices: Opts[];
cleanECChoices: string[];
maxAllocableMemo: number;
resourcesSize: IResourcesSize;
distribution: any;
ecParityCalc: IErasureCodeCalc;
@@ -246,6 +247,19 @@ export interface ITenantSizeFields {
maxAllocatableResources: AllocableResourcesResponse;
maxCPUsUse: string;
maxMemorySize: string;
integrationSelection: IntegrationConfiguration;
resourcesSpecifyLimit: boolean;
resourcesCPURequestError: string;
resourcesCPURequest: string;
resourcesCPULimitError: string;
resourcesCPULimit: string;
resourcesMemoryRequestError: string;
resourcesMemoryRequest: string;
resourcesMemoryLimitError: string;
resourcesMemoryLimit: string;
}
export interface ITenantAffinity {
@@ -289,11 +303,6 @@ interface SetTenantWizardPage {
page: number;
}
interface SetAdvancedMode {
type: typeof ADD_TENANT_SET_ADVANCED_MODE;
state: boolean;
}
interface UpdateATField {
type: typeof ADD_TENANT_UPDATE_FIELD;
pageName: keyof IFieldStore;
@@ -333,6 +342,7 @@ interface DeleteMinioKeyPair {
type: typeof ADD_TENANT_DELETE_MINIO_KEYPAIR;
id: string;
}
interface AddCAKeyPair {
type: typeof ADD_TENANT_ADD_CA_KEYPAIR;
}
@@ -349,6 +359,7 @@ interface DeleteCAKeyPair {
type: typeof ADD_TENANT_DELETE_CA_KEYPAIR;
id: string;
}
interface AddConsoleCAKeyPair {
type: typeof ADD_TENANT_ADD_CONSOLE_CA_KEYPAIR;
}
@@ -436,7 +447,6 @@ export type FieldsToHandle = INameTenantFields;
export type TenantsManagementTypes =
| SetTenantWizardPage
| SetAdvancedMode
| UpdateATField
| SetPageValid
| SetStorageClassesList

View File

@@ -998,6 +998,10 @@ definitions:
operatorSessionResponse:
type: object
properties:
features:
type: array
items:
type: string
status:
type: string
enum: [ok]