Fix Tenant Details Bugs (#589)

This fixes #584 by making the expand set a name for the pool
This fixes #585 by making the expand set an affinity for the pool
This fixes #586 by generating a pool name if it's not indicated only
This commit is contained in:
Daniel Valdivia
2021-02-05 14:27:29 -08:00
committed by GitHub
parent 7174892231
commit 1dcdc61ce8
11 changed files with 250 additions and 146 deletions

File diff suppressed because one or more lines are too long

View File

@@ -15,12 +15,8 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import storage from "local-storage-fallback";
import {
ICapacity,
IErasureCodeCalc,
IPoolModel,
IStorageFactors,
} from "./types";
import { ICapacity, IErasureCodeCalc, IStorageFactors } from "./types";
import { IPool } from "../screens/Console/Tenants/ListTenants/types";
const minStReq = 1073741824; // Minimal Space required for MinIO
const minMemReq = 2147483648; // Minimal Memory required for MinIO in bytes
@@ -419,7 +415,7 @@ export const erasureCodeCalc = (
};
// Pool Name Generator
export const generatePoolName = (pools: IPoolModel[]) => {
export const generatePoolName = (pools: IPool[]) => {
const poolCounter = pools.length;
return `pool-${poolCounter}`;

View File

@@ -187,7 +187,7 @@ const Menu = ({ userLoggedIn, classes, pages, operatorMode }: IMenuProps) => {
});
};
const menuItems = [
let menuItems = [
{
group: "common",
type: "item",
@@ -316,14 +316,6 @@ const Menu = ({ userLoggedIn, classes, pages, operatorMode }: IMenuProps) => {
name: "Warp",
icon: <WarpIcon />,
},
{
group: "License",
type: "item",
component: NavLink,
to: "/license",
name: "License",
icon: <LicenseIcon />,
},
];
const allowedPages = pages.reduce((result: any, item: any, index: any) => {
@@ -331,6 +323,27 @@ const Menu = ({ userLoggedIn, classes, pages, operatorMode }: IMenuProps) => {
return result;
}, {});
// Append the license page according to the allowedPages
if (allowedPages.hasOwnProperty("/tenants")) {
menuItems.push({
group: "Operator",
type: "item",
component: NavLink,
to: "/license",
name: "License",
icon: <LicenseIcon />,
});
} else {
menuItems.push({
group: "License",
type: "item",
component: NavLink,
to: "/license",
name: "License",
icon: <LicenseIcon />,
});
}
const allowedItems = menuItems.filter(
(item: any) =>
allowedPages[item.to] || item.forceDisplay || item.type !== "item"

View File

@@ -19,6 +19,6 @@ export const menuGroups = [
{ label: "User", group: "User", collapsible: true },
{ label: "Admin", group: "Admin", collapsible: true },
{ label: "Tools", group: "Tools", collapsible: true },
{ label: "Operator", group: "Operator", collapsible: true },
{ label: "Operator", group: "Operator", collapsible: false },
{ label: "", group: "License", collapsible: false },
];

View File

@@ -66,6 +66,7 @@ import { IMemorySize } from "./types";
import Divider from "@material-ui/core/Divider";
import { connect } from "react-redux";
import { setModalErrorSnackMessage } from "../../../../actions";
import { getHardcodedAffinity } from "../TenantDetails/utils";
interface IAddTenantProps {
open: boolean;
@@ -917,29 +918,10 @@ const AddTenant = ({
if (addSending) {
const poolName = generatePoolName([]);
const hardCodedAffinity: IAffinityModel = {
podAntiAffinity: {
requiredDuringSchedulingIgnoredDuringExecution: [
{
labelSelector: {
matchExpressions: [
{
key: "v1.min.io/tenant",
operator: "In",
values: [tenantName],
},
{
key: "v1.min.io/pool",
operator: "In",
values: [poolName],
},
],
},
topologyKey: "kubernetes.io/hostname",
},
],
},
};
const hardCodedAffinity: IAffinityModel = getHardcodedAffinity(
tenantName,
poolName
);
const erasureCode = ecParity.split(":")[1];

View File

@@ -257,6 +257,7 @@ const ListTenants = ({ classes, setErrorSnackMessage }: ITenantsList) => {
itemActions={tableActions}
columns={[
{ label: "Name", elementKey: "name" },
{ label: "Namespace", elementKey: "namespace" },
{ label: "Capacity", elementKey: "capacity" },
{ label: "# of Pools", elementKey: "pool_count" },
{ label: "State", elementKey: "currentState" },

View File

@@ -15,6 +15,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import { LicenseInfo } from "../../License/types";
import { IAffinityModel } from "../../../../common/types";
export interface IPool {
name: string;
@@ -24,6 +25,7 @@ export interface IPool {
// computed
capacity: string;
volumes: number;
label?: string;
}
export interface IAddPoolRequest {
@@ -31,6 +33,7 @@ export interface IAddPoolRequest {
servers: number;
volumes_per_server: number;
volume_configuration: IVolumeConfiguration;
affinity?: IAffinityModel;
}
export interface IVolumeConfiguration {

View File

@@ -4,10 +4,12 @@ import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import Grid from "@material-ui/core/Grid";
import { niceBytes } from "../../../../common/utils";
import { generatePoolName, niceBytes } from "../../../../common/utils";
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";
interface IAddPoolProps {
tenant: ITenant;
@@ -81,8 +83,16 @@ const AddPoolModal = ({
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setAddSending(true);
const poolName = generatePoolName(tenant.pools);
const hardCodedAffinity: IAffinityModel = getHardcodedAffinity(
tenant.name,
poolName
);
const data: IAddPoolRequest = {
name: "",
name: poolName,
servers: numberOfNodes,
volumes_per_server: volumesPerServer,
volume_configuration: {
@@ -90,7 +100,9 @@ const AddPoolModal = ({
storage_class: "",
labels: null,
},
affinity: hardCodedAffinity,
};
api
.invoke(
"POST",

View File

@@ -115,6 +115,9 @@ const styles = (theme: Theme) =>
noUnderLine: {
textDecoration: "none",
},
poolLabel: {
color: "#666666",
},
...modalBasic,
...containerForHeader(theme.spacing(4)),
});
@@ -127,7 +130,7 @@ const TenantDetails = ({
const [selectedTab, setSelectedTab] = useState<number>(0);
const [capacity, setCapacity] = useState<number>(0);
const [poolCount, setPoolCount] = useState<number>(0);
const [serverSets, setServerSets] = useState<IPool[]>([]);
const [pools, setPools] = useState<IPool[]>([]);
const [instances, setInstances] = useState<number>(0);
const [volumes, setVolumes] = useState<number>(0);
const [addPoolOpen, setAddPool] = useState<boolean>(false);
@@ -199,25 +202,28 @@ const TenantDetails = ({
let totalInstances = 0;
let totalVolumes = 0;
let count = 1;
let poolNamedIndex = 0;
for (let pool of resPools) {
const cap =
pool.volumes_per_server *
pool.servers *
pool.volume_configuration.size;
pool.name = `pool-${count}`;
pool.label = `pool-${poolNamedIndex}`;
if (pool.name === undefined || pool.name === "") {
pool.name = pool.label;
}
pool.capacity = niceBytes(cap + "");
pool.volumes = pool.servers * pool.volumes_per_server;
totalInstances += pool.servers;
totalVolumes += pool.volumes;
count += 1;
poolNamedIndex += 1;
}
setCapacity(res.total_size);
setPoolCount(resPools.length);
setVolumes(totalVolumes);
setInstances(totalInstances);
setServerSets(resPools);
setPools(resPools);
setTenant(res);
})
@@ -415,7 +421,7 @@ const TenantDetails = ({
{ label: "# of Drives", elementKey: "volumes" },
]}
isLoading={false}
records={serverSets}
records={pools}
entityName="Servers"
idField="name"
/>

View File

@@ -0,0 +1,43 @@
// 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 { IAffinityModel } from "../../../../common/types";
export const getHardcodedAffinity = (tenantName: string, poolName: string) => {
const hardCodedAffinity: IAffinityModel = {
podAntiAffinity: {
requiredDuringSchedulingIgnoredDuringExecution: [
{
labelSelector: {
matchExpressions: [
{
key: "v1.min.io/tenant",
operator: "In",
values: [tenantName],
},
{
key: "v1.min.io/pool",
operator: "In",
values: [poolName],
},
],
},
topologyKey: "kubernetes.io/hostname",
},
],
},
};
return hardCodedAffinity;
};

View File

@@ -381,12 +381,14 @@ func getTenantInfoResponse(session *models.Principal, params admin_api.TenantInf
//minio service
minSvc, err := k8sClient.getService(ctx, minTenant.Namespace, minTenant.MinIOCIServiceName(), metav1.GetOptions{})
if err != nil {
return nil, prepareError(err)
// we can tolerate this error
log.Println(err)
}
//console service
conSvc, err := k8sClient.getService(ctx, minTenant.Namespace, minTenant.ConsoleCIServiceName(), metav1.GetOptions{})
if err != nil {
return nil, prepareError(err)
// we can tolerate this error
log.Println(err)
}
schema := "http"
@@ -401,10 +403,10 @@ func getTenantInfoResponse(session *models.Principal, params admin_api.TenantInf
}
var minioEndpoint string
var consoleEndpoint string
if len(minSvc.Status.LoadBalancer.Ingress) > 0 {
if minSvc != nil && len(minSvc.Status.LoadBalancer.Ingress) > 0 {
minioEndpoint = fmt.Sprintf("%s://%s", schema, minSvc.Status.LoadBalancer.Ingress[0].IP)
}
if len(conSvc.Status.LoadBalancer.Ingress) > 0 {
if conSvc != nil && len(conSvc.Status.LoadBalancer.Ingress) > 0 {
consoleEndpoint = fmt.Sprintf("%s://%s%s", consoleSchema, conSvc.Status.LoadBalancer.Ingress[0].IP, consolePort)
}
if minioEndpoint != "" || consoleEndpoint != "" {