Skip to content

Commit

Permalink
feat: [WD-17724] CMS Storage Pool Size field
Browse files Browse the repository at this point in the history
Signed-off-by: Nkeiruka <[email protected]>
  • Loading branch information
Kxiru committed Jan 30, 2025
1 parent 029ccc8 commit 6a8d8c1
Show file tree
Hide file tree
Showing 9 changed files with 222 additions and 34 deletions.
16 changes: 13 additions & 3 deletions src/api/storage-pools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export const createClusteredPool = (
pool: LxdStoragePool,
clusterMembers: LxdClusterMember[],
sourcePerClusterMember?: ClusterSpecificValues,
sizePerClusterMember?: ClusterSpecificValues,
): Promise<void> => {
const { memberPoolPayload, clusterPoolPayload } =
getClusterAndMemberPoolPayload(pool);
Expand All @@ -112,6 +113,7 @@ export const createClusteredPool = (
config: {
...memberPoolPayload.config,
source: sourcePerClusterMember?.[item.server_name],
size: sizePerClusterMember?.[item.server_name],
},
};
return createPool(clusteredMemberPool, item.server_name);
Expand Down Expand Up @@ -145,14 +147,22 @@ export const updatePool = (
export const updateClusteredPool = (
pool: Partial<LxdStoragePool>,
clusterMembers: LxdClusterMember[],
sizePerClusterMember?: ClusterSpecificValues,
): Promise<void> => {
const { memberPoolPayload, clusterPoolPayload } =
getClusterAndMemberPoolPayload(pool);
return new Promise((resolve, reject) => {
Promise.allSettled(
clusterMembers.map(async (item) =>
updatePool(memberPoolPayload, item.server_name),
),
clusterMembers.map(async (item) => {
const clusteredMemberPool = {
...memberPoolPayload,
config: {
...memberPoolPayload.config,
size: sizePerClusterMember?.[item.server_name],
},
};
return updatePool(clusteredMemberPool, item.server_name);
}),
)
.then(handleSettledResult)
.then(() => updatePool(clusterPoolPayload))
Expand Down
148 changes: 148 additions & 0 deletions src/components/forms/ClusteredDiskSizeSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { FC, Fragment, useEffect, useState } from "react";
import { CheckboxInput, Label } from "@canonical/react-components";
import ResourceLink from "components/ResourceLink";
import FormEditButton from "components/FormEditButton";
import { ClusterSpecificValues } from "components/ClusterSpecificSelect";
import { useClusterMembers } from "context/useClusterMembers";
import DiskSizeSelector from "./DiskSizeSelector";

interface Props {
canToggleSpecific?: boolean;
id: string;
toggleReadOnly: () => void;
setMemoryLimit: (value: ClusterSpecificValues) => void;
sizeValues?: ClusterSpecificValues;
isReadOnly?: boolean;
isDefaultSpecific?: boolean;
clusterMemberLinkTarget?: (member: string) => string;
disabled?: boolean;
helpText?: string;
}

const ClusteredDiskSizeSelector: FC<Props> = ({
id,
canToggleSpecific = true,
toggleReadOnly,
setMemoryLimit,
sizeValues,
isReadOnly = false,
isDefaultSpecific = null,
clusterMemberLinkTarget = () => "/ui/cluster",
disabled = false,
helpText,
}) => {
const { data: clusterMembers = [] } = useClusterMembers();
const memberNames = clusterMembers.map((member) => member.server_name);
const [isSpecific, setIsSpecific] = useState<boolean | null>(
isDefaultSpecific,
);
const firstValue = Object.values(sizeValues ?? {})[0];

useEffect(() => {
const rawValues = Object.values(sizeValues ?? {});
if (isSpecific === null && rawValues.length > 0) {
const newDefaultSpecific = rawValues.some(
(item) => item !== rawValues[0],
);
setIsSpecific(newDefaultSpecific);
}
}, [isSpecific, sizeValues]);

const setValueForAllMembers = (value: string) => {
const update: ClusterSpecificValues = {};
memberNames.forEach((member) => (update[member] = value));
setMemoryLimit(update);
};

const setValueForMember = (value: string, member: string) => {
const update = {
...sizeValues,
[member]: value,
};
setMemoryLimit(update);
};

return (
<div className="u-sv3">
<Label forId="sizePerClusterMember">Size</Label>
{canToggleSpecific && !isReadOnly && (
<CheckboxInput
id={`${id}-same-for-all-toggle`}
label="Same for all cluster members"
checked={!isSpecific}
onChange={() => {
setIsSpecific((val) => !val);
}}
/>
)}
{isSpecific && (
<div className="cluster-specific-input">
{memberNames.map((item) => {
const activeValue = sizeValues?.[item] ?? "";

return (
<Fragment key={item}>
<div className="cluster-specific-member">
<ResourceLink
type="cluster-member"
value={item}
to={clusterMemberLinkTarget(item)}
/>
</div>

<div className="cluster-specific-value">
{isReadOnly ? (
<>
{activeValue}
<FormEditButton toggleReadOnly={toggleReadOnly} />
</>
) : (
<DiskSizeSelector
id={
memberNames.indexOf(item) === 0 ? id : `${id}-${item}`
}
value={activeValue}
setMemoryLimit={(value) =>
setValueForMember(value ?? "", item)
}
disabled={disabled}
classname="u-no-margin--bottom"
/>
)}
</div>
</Fragment>
);
})}
{helpText && (
<div className="p-form-help-text cluster-specific-helptext">
{helpText}
</div>
)}
</div>
)}

{!isSpecific && (
<div>
{isReadOnly ? (
<>
{firstValue}
<FormEditButton toggleReadOnly={toggleReadOnly} />
</>
) : (
<>
<DiskSizeSelector
id={id}
value={firstValue}
setMemoryLimit={(value) => setValueForAllMembers(value ?? "")}
disabled={disabled}
/>
{helpText && <div className="p-form-help-text">{helpText}</div>}
</>
)}
</div>
)}
</div>
);
};

export default ClusteredDiskSizeSelector;
10 changes: 8 additions & 2 deletions src/components/forms/DiskSizeSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,23 @@ import { BYTES_UNITS } from "types/limits";
import { parseMemoryLimit } from "util/limits";

interface Props {
id?: string;
label?: string;
value?: string;
help?: string;
setMemoryLimit: (val?: string) => void;
disabled?: boolean;
classname?: string;
}

const DiskSizeSelector: FC<Props> = ({
id = "limits_disk",
label,
value,
help,
setMemoryLimit,
disabled,
classname,
}) => {
const limit = parseMemoryLimit(value) ?? {
value: 1,
Expand All @@ -39,7 +43,7 @@ const DiskSizeSelector: FC<Props> = ({
)}
<div className="memory-limit-with-unit">
<Input
id="limits_disk"
id={id}
name="limits_disk"
type="number"
min="0"
Expand All @@ -48,9 +52,10 @@ const DiskSizeSelector: FC<Props> = ({
onChange={(e) => setMemoryLimit(e.target.value + limit.unit)}
value={value?.match(/^\d/) ? limit.value : ""}
disabled={disabled}
className={classname}
/>
<Select
id="memUnitSelect"
id={`memUnitSelect-${id}`}
name="memUnitSelect"
label="Select disk size unit"
labelClassName="u-off-screen"
Expand All @@ -60,6 +65,7 @@ const DiskSizeSelector: FC<Props> = ({
}
value={limit.unit}
disabled={disabled}
className={classname}
/>
</div>
{help && <p className="p-form-help-text">{help}</p>}
Expand Down
1 change: 1 addition & 0 deletions src/pages/storage/CreateStoragePool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ const CreateStoragePool: FC = () => {
storagePool,
clusterMembers,
values.sourcePerClusterMember,
values.sizePerClusterMember,
)
: () => createPool(storagePool);

Expand Down
7 changes: 6 additions & 1 deletion src/pages/storage/EditStoragePool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,12 @@ const EditStoragePool: FC<Props> = ({ pool }) => {

const mutation =
clusterMembers.length > 0
? () => updateClusteredPool(savedPool, clusterMembers)
? () =>
updateClusteredPool(
savedPool,
clusterMembers,
values.sizePerClusterMember,
)
: () => updatePool(savedPool);

mutation()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ interface Props {
helpText?: string;
}

const StoragePoolClusteredSourceSelector: FC<Props> = ({
formik,
helpText,
}) => {
const ClusteredSourceSelector: FC<Props> = ({ formik, helpText }) => {
const { data: clusterMembers = [] } = useClusterMembers();
const memberNames = clusterMembers.map((member) => member.server_name);

Expand All @@ -37,4 +34,4 @@ const StoragePoolClusteredSourceSelector: FC<Props> = ({
);
};

export default StoragePoolClusteredSourceSelector;
export default ClusteredSourceSelector;
1 change: 1 addition & 0 deletions src/pages/storage/forms/StoragePoolForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export interface StoragePoolFormValues {
powerflex_sdt?: string;
powerflex_user_name?: string;
powerflex_user_password?: string;
sizePerClusterMember?: ClusterSpecificValues;
pure_api_token?: string;
pure_gateway?: string;
pure_gateway_verify?: string;
Expand Down
35 changes: 26 additions & 9 deletions src/pages/storage/forms/StoragePoolFormMain.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
powerFlex,
pureStorage,
cephFSDriver,
lvmDriver,
} from "util/storageOptions";
import { StoragePoolFormValues } from "./StoragePoolForm";
import DiskSizeSelector from "components/forms/DiskSizeSelector";
Expand All @@ -20,8 +21,9 @@ import { useSettings } from "context/useSettings";
import ScrollableForm from "components/ScrollableForm";
import { ensureEditMode } from "util/instanceEdit";
import { optionTrueFalse } from "util/instanceOptions";
import StoragePoolClusteredSourceSelector from "./StoragePoolClusteredSourceSelector";
import ClusteredSourceSelector from "./ClusteredSourceSelector";
import { isClusteredServer } from "util/settings";
import ClusteredDiskSizeSelector from "components/forms/ClusteredDiskSizeSelector";

interface Props {
formik: FormikProps<StoragePoolFormValues>;
Expand All @@ -42,11 +44,13 @@ const StoragePoolFormMain: FC<Props> = ({ formik }) => {
};
};

const isBtrfsDriver = formik.values.driver == btrfsDriver;
const isCephDriver = formik.values.driver === cephDriver;
const isCephFSDriver = formik.values.driver === cephFSDriver;
const isDirDriver = formik.values.driver === dirDriver;
const isLvmDriver = formik.values.driver === lvmDriver;
const isPowerFlexDriver = formik.values.driver === powerFlex;
const isPureDriver = formik.values.driver === pureStorage;
const isZfsDriver = formik.values.driver === zfsDriver;
const storageDriverOptions = getStorageDriverOptions(settings);
const hasClusterWideSource = isCephDriver || isCephFSDriver;
const hasSource = !isPureDriver && !isPowerFlexDriver;
Expand All @@ -55,6 +59,8 @@ const StoragePoolFormMain: FC<Props> = ({ formik }) => {
? getSourceHelpForDriver(formik.values.driver)
: "Source can't be changed";

const hasSize = isZfsDriver || isLvmDriver || isBtrfsDriver;

return (
<ScrollableForm>
<Row>
Expand Down Expand Up @@ -112,11 +118,22 @@ const StoragePoolFormMain: FC<Props> = ({ formik }) => {
required
disabled={!formik.values.isCreating}
/>
{!isCephDriver &&
!isCephFSDriver &&
!isDirDriver &&
!isPureDriver &&
!isPowerFlexDriver && (
{hasSize &&
(isClusteredServer(settings) ? (
<ClusteredDiskSizeSelector
id="sizePerClusterMember"
sizeValues={formik.values.sizePerClusterMember}
canToggleSpecific={formik.values.isCreating}
toggleReadOnly={() => {}}
setMemoryLimit={(value) => {
ensureEditMode(formik);
void formik.setFieldValue("sizePerClusterMember", value);
}}
helpText={
"When left blank, defaults to 20% of free disk space. Default will be between 5GiB and 30GiB"
}
/>
) : (
<DiskSizeSelector
label="Size"
value={formik.values.size}
Expand All @@ -131,7 +148,7 @@ const StoragePoolFormMain: FC<Props> = ({ formik }) => {
}}
disabled={formik.values.driver === dirDriver}
/>
)}
))}
{hasSource &&
(hasClusterWideSource || !isClusteredServer(settings) ? (
<Input
Expand All @@ -142,7 +159,7 @@ const StoragePoolFormMain: FC<Props> = ({ formik }) => {
label="Source"
/>
) : (
<StoragePoolClusteredSourceSelector
<ClusteredSourceSelector
formik={formik}
helpText={sourceHelpText}
/>
Expand Down
Loading

0 comments on commit 6a8d8c1

Please sign in to comment.