Skip to content

Commit

Permalink
feat: added zfs storage pool configs
Browse files Browse the repository at this point in the history
Signed-off-by: Mason Hu <mason.hu@canonical.com>
  • Loading branch information
mas-who committed Mar 5, 2024
1 parent 9e67b4c commit 827bee5
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 36 deletions.
50 changes: 27 additions & 23 deletions src/api/storage-pools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { LxdApiResponse } from "types/apiResponse";
import { LxdOperationResponse } from "types/operation";
import axios, { AxiosResponse } from "axios";
import { LxdClusterMember } from "types/cluster";
import { cephDriver } from "util/storageOptions";

export const fetchStoragePool = (
pool: string,
Expand Down Expand Up @@ -73,35 +72,40 @@ export const createPool = (
});
};

const getClusterPool = (
pool: Partial<LxdStoragePool>,
): Partial<LxdStoragePool> => {
const poolForCluster = { ...pool };
if (pool.driver !== cephDriver) {
delete poolForCluster.config;
const getClusterAndMemberPools = (pool: Partial<LxdStoragePool>) => {
const memberSpecificConfigKeys = new Set([
"source",
"size",
"zfs.pool_name",
"lvm.thinpool_name",
"lvm.vg_name",
]);
const configKeys = Object.keys(pool.config || {});
const memberConfigs: LxdStoragePool["config"] = {};
const clusterConfigs: LxdStoragePool["config"] = {};
for (const key of configKeys) {
if (memberSpecificConfigKeys.has(key)) {
memberConfigs[key] = pool.config?.[key];
} else {
clusterConfigs[key] = pool.config?.[key];
}
}

return poolForCluster;
};

const getMemberPool = (
pool: Partial<LxdStoragePool>,
): Partial<LxdStoragePool> => {
const poolForMember = { ...pool };
// config keys for ceph storage pool cannot be member specific
if (pool.driver === cephDriver) {
delete poolForMember.config;
}
const clusterPool = { ...pool, config: clusterConfigs };
const memberPool = { ...pool, config: memberConfigs };

return poolForMember;
return {
clusterPool,
memberPool,
};
};

export const createClusteredPool = (
pool: LxdStoragePool,
project: string,
clusterMembers: LxdClusterMember[],
): Promise<void> => {
const memberPool = getMemberPool(pool);
const { memberPool, clusterPool } = getClusterAndMemberPools(pool);
return new Promise((resolve, reject) => {
void Promise.allSettled(
clusterMembers.map((item) =>
Expand All @@ -110,7 +114,7 @@ export const createClusteredPool = (
)
.then(handleSettledResult)
.then(() => {
return createPool(getClusterPool(pool), project);
return createPool(clusterPool, project);
})
.then(resolve)
.catch(reject);
Expand Down Expand Up @@ -139,15 +143,15 @@ export const updateClusteredPool = (
project: string,
clusterMembers: LxdClusterMember[],
): Promise<void> => {
const memberPool = getMemberPool(pool);
const { memberPool, clusterPool } = getClusterAndMemberPools(pool);
return new Promise((resolve, reject) => {
void Promise.allSettled(
clusterMembers.map(async (item) =>
updatePool(memberPool, project, item.server_name),
),
)
.then(handleSettledResult)
.then(() => updatePool(getClusterPool(pool), project))
.then(() => updatePool(clusterPool, project))
.then(resolve)
.catch(reject);
});
Expand Down
24 changes: 13 additions & 11 deletions src/components/ConfigurationRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,19 @@ export const getConfigurationRow = ({
})}
</div>,
)}
<div>
<Button
onClick={toggleDefault}
type="button"
appearance="base"
title="Clear override"
hasIcon
>
<Icon name="close" className="clear-configuration-icon" />
</Button>
</div>
{!disabled && (
<div>
<Button
onClick={toggleDefault}
type="button"
appearance="base"
title="Clear override"
hasIcon
>
<Icon name="close" className="clear-configuration-icon" />
</Button>
</div>
)}
</div>
);
};
Expand Down
18 changes: 17 additions & 1 deletion src/pages/storage/forms/StoragePoolForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ import StoragePoolFormMain from "./StoragePoolFormMain";
import StoragePoolFormMenu, {
CEPH_CONFIGURATION,
MAIN_CONFIGURATION,
ZFS_CONFIGURATION,
} from "./StoragePoolFormMenu";
import useEventListener from "@use-it/event-listener";
import { updateMaxHeight } from "util/updateMaxHeight";
import { LxdStoragePool } from "types/storage";
import { btrfsDriver, cephDriver } from "util/storageOptions";
import { btrfsDriver, cephDriver, zfsDriver } from "util/storageOptions";
import { getPoolKey } from "util/storagePool";
import StoragePoolFormCeph from "./StoragePoolFormCeph";
import { slugify } from "util/slugify";
import StoragePoolFormZFS from "./StoragePoolFormZFS";

export interface StoragePoolFormValues {
isCreating: boolean;
Expand All @@ -28,6 +30,9 @@ export interface StoragePoolFormValues {
ceph_rbd_clone_copy?: string;
ceph_user_name?: string;
ceph_rbd_features?: string;
zfs_clone_copy?: string;
zfs_export?: string;
zfs_pool_name?: string;
}

interface Props {
Expand All @@ -40,6 +45,7 @@ export const storagePoolFormToPayload = (
values: StoragePoolFormValues,
): LxdStoragePool => {
const isCephDriver = values.driver === cephDriver;
const isZFSDriver = values.driver === zfsDriver;
const hasValidSize = values.size?.match(/^\d/);

const getConfig = () => {
Expand All @@ -52,6 +58,13 @@ export const storagePoolFormToPayload = (
[getPoolKey("ceph_rbd_features")]: values.ceph_rbd_features,
source: values.source,
};
} else if (isZFSDriver) {
return {
[getPoolKey("zfs_clone_copy")]: values.zfs_clone_copy,
[getPoolKey("zfs_export")]: values.zfs_export,
[getPoolKey("zfs_pool_name")]: values.zfs_pool_name,
size: hasValidSize ? values.size : undefined,
};
} else {
return {
size: hasValidSize ? values.size : undefined,
Expand Down Expand Up @@ -94,6 +107,9 @@ const StoragePoolForm: FC<Props> = ({ formik, section, setSection }) => {
{section === slugify(CEPH_CONFIGURATION) && (
<StoragePoolFormCeph formik={formik} />
)}
{section === slugify(ZFS_CONFIGURATION) && (
<StoragePoolFormZFS formik={formik} />
)}
</Col>
</Row>
</Form>
Expand Down
11 changes: 10 additions & 1 deletion src/pages/storage/forms/StoragePoolFormMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import { updateMaxHeight } from "util/updateMaxHeight";
import useEventListener from "@use-it/event-listener";
import { FormikProps } from "formik";
import { StoragePoolFormValues } from "./StoragePoolForm";
import { cephDriver } from "util/storageOptions";
import { cephDriver, zfsDriver } from "util/storageOptions";

export const MAIN_CONFIGURATION = "Main configuration";
export const CEPH_CONFIGURATION = "Ceph";
export const ZFS_CONFIGURATION = "ZFS";

interface Props {
active: string;
Expand All @@ -24,6 +25,7 @@ const StoragePoolFormMenu: FC<Props> = ({ formik, active, setActive }) => {
};

const isCephDriver = formik.values.driver === cephDriver;
const isZfsDriver = formik.values.driver === zfsDriver;
const hasName = formik.values.name.length > 0;
const disableReason = hasName
? undefined
Expand All @@ -46,6 +48,13 @@ const StoragePoolFormMenu: FC<Props> = ({ formik, active, setActive }) => {
disableReason={disableReason}
/>
)}
{isZfsDriver && (
<MenuItem
label={ZFS_CONFIGURATION}
{...menuItemProps}
disableReason={disableReason}
/>
)}
</ul>
</nav>
</div>
Expand Down
45 changes: 45 additions & 0 deletions src/pages/storage/forms/StoragePoolFormZFS.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { FormikProps } from "formik";
import { FC } from "react";
import { StoragePoolFormValues } from "./StoragePoolForm";
import { getConfigurationRow } from "components/ConfigurationRow";
import { Input, Select } from "@canonical/react-components";
import { optionTrueFalse } from "util/instanceOptions";
import ScrollableConfigurationTable from "components/forms/ScrollableConfigurationTable";

interface Props {
formik: FormikProps<StoragePoolFormValues>;
}

const StoragePoolFormZFS: FC<Props> = ({ formik }) => {
return (
<ScrollableConfigurationTable
rows={[
getConfigurationRow({
formik,
label: "ZFS pool name",
name: "zfs_pool_name",
defaultValue: "",
children: <Input type="text" placeholder="Enter ZFS pool name" />,
disabled: !formik.values.isCreating || formik.values.readOnly,
disabledReason: "ZFS pool name cannot be modified",
}),
getConfigurationRow({
formik,
label: "Clone copy",
name: "zfs_clone_copy",
defaultValue: "",
children: <Select options={optionTrueFalse} />,
}),
getConfigurationRow({
formik,
label: "Export",
name: "zfs_export",
defaultValue: "",
children: <Select options={optionTrueFalse} />,
}),
]}
/>
);
};

export default StoragePoolFormZFS;
3 changes: 3 additions & 0 deletions src/util/storagePool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ export const storagePoolFormFieldToPayloadName: Record<string, string> = {
ceph_rbd_clone_copy: "ceph.rbd.clone_copy",
ceph_user_name: "ceph.user.name",
ceph_rbd_features: "ceph.rbd.features",
zfs_clone_copy: "zfs.clone_copy",
zfs_export: "zfs.export",
zfs_pool_name: "zfs.pool_name",
};

export const getPoolKey = (formField: string): string => {
Expand Down
3 changes: 3 additions & 0 deletions src/util/storagePoolEdit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,8 @@ export const getStoragePoolEditValues = (
ceph_rbd_clone_copy: pool.config["ceph.rbd.clone_copy"],
ceph_user_name: pool.config["ceph.user.name"],
ceph_rbd_features: pool.config["ceph.rbd.features"],
zfs_clone_copy: pool.config["zfs.clone_copy"],
zfs_export: pool.config["zfs.export"],
zfs_pool_name: pool.config["zfs.pool_name"],
};
};

0 comments on commit 827bee5

Please sign in to comment.