Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge feature branch f/storage-class #2503

Merged
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
4eb9817
StorageClassKind array of cluster storage classes. get logic in App /…
shalberd Sep 3, 2023
2016243
Merge pull request #1740 from shalberd/f/storage-class
openshift-ci[bot] Oct 6, 2023
6964618
Merge branch 'main' into f/storage-class
andrewballantyne Oct 6, 2023
a1100d6
fix bug when filtering cluster storageclasses and now doing correct s…
shalberd Oct 6, 2023
1df7bc1
Merge pull request #1926 from shalberd/f/storage-class
openshift-ci[bot] Oct 6, 2023
dfe4c20
Merge branch 'main' into merge-f/storage-class
andrewballantyne Nov 22, 2023
a155941
Merge pull request #2215 from andrewballantyne/merge-main
openshift-merge-bot[bot] Nov 22, 2023
edcba3d
Merge branch 'main' into f/storage-class
alexcreasy Feb 13, 2024
7f83c2c
Fixes errors from merge conflict resolution
alexcreasy Feb 14, 2024
30db3ea
Fixes errors from merge conflict resolution round 2
alexcreasy Feb 14, 2024
65400cb
Fixes log message
alexcreasy Feb 16, 2024
df16296
Merge remote-tracking branch 'upstream/main' into merge-f/storage-class
alexcreasy Feb 19, 2024
9ad5f2c
Fix tests
alexcreasy Feb 19, 2024
abbeebd
Default storage class is now added to standalone PVCs created without…
alexcreasy Feb 22, 2024
d017032
Default storage class now added when changing root storage class of w…
alexcreasy Feb 26, 2024
112ef22
Merge remote-tracking branch 'upstream/main' into merge-f/storage-class
alexcreasy Feb 26, 2024
ce34a6b
Codestyle tweaks
alexcreasy Feb 26, 2024
61b9ffb
Merge remote-tracking branch 'upstream/main' into merge-f/storage-class
alexcreasy Feb 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions frontend/.storybook/preview.js
alexcreasy marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ export const decorators = [
(Story) => (
<AppContext.Provider
value={{
buildStatuses: [],
dashboardConfig: mockDashboardConfig({}),
storageClasses: [],
}}
>
<Provider store={sdkStore}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ const Template: (props: TemplateProps) => StoryFn<typeof ModelServingGlobal> = (
useDetectUser();
const { dashboardConfig, loaded } = useApplicationSettings();
return loaded && dashboardConfig ? (
<AppContext.Provider value={{ buildStatuses: [], dashboardConfig }}>
<AppContext.Provider value={{ buildStatuses: [], dashboardConfig, storageClasses: [] }}>
<AreaContext.Provider
value={{
dscStatus: mockDscStatus({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ const Template: StoryFn<typeof ModelServingPlatform> = (args) => {
useDetectUser();
const { dashboardConfig, loaded } = useApplicationSettings();
return loaded && dashboardConfig ? (
<AppContext.Provider value={{ buildStatuses: [], dashboardConfig }}>
<AppContext.Provider value={{ buildStatuses: [], dashboardConfig, storageClasses: [] }}>
<AreaContext.Provider
value={{
dscStatus: mockDscStatus({
Expand Down
1 change: 1 addition & 0 deletions frontend/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export * from './k8s/routes';
export * from './k8s/secrets';
export * from './k8s/serviceAccounts';
export * from './k8s/servingRuntimes';
export * from './k8s/storageClasses';
export * from './k8s/users';
export * from './k8s/groups';
export * from './k8s/templates';
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/api/k8s/pvcs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const assemblePvc = (
data: CreatingStorageObject,
namespace: string,
editName?: string,
storageClassName?: string,
): PersistentVolumeClaimKind => {
const {
nameDesc: { name: pvcName, description },
Expand Down Expand Up @@ -48,6 +49,7 @@ export const assemblePvc = (
storage: size,
},
},
storageClassName,
volumeMode: 'Filesystem',
},
status: {
Expand Down Expand Up @@ -84,9 +86,10 @@ export const getAvailableMultiUsePvcs = (
export const createPvc = (
data: CreatingStorageObject,
namespace: string,
storageClassName?: string,
opts?: K8sAPIOptions,
): Promise<PersistentVolumeClaimKind> => {
const pvc = assemblePvc(data, namespace);
const pvc = assemblePvc(data, namespace, undefined, storageClassName);

return k8sCreateResource<PersistentVolumeClaimKind>(
applyK8sAPIOptions({ model: PVCModel, resource: pvc }, opts),
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/api/k8s/storageClasses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { k8sListResource } from '@openshift/dynamic-plugin-sdk-utils';
import { StorageClassKind } from '~/k8sTypes';
import { StorageClassModel } from '~/api/models';

export const getStorageClasses = (): Promise<StorageClassKind[]> =>
k8sListResource<StorageClassKind>({
model: StorageClassModel,
queryOptions: {},
}).then((listResource) => listResource.items);
7 changes: 7 additions & 0 deletions frontend/src/api/models/k8s.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ export const PVCModel: K8sModelCommon = {
plural: 'persistentvolumeclaims',
};

export const StorageClassModel: K8sModelCommon = {
apiVersion: 'v1',
apiGroup: 'storage.k8s.io',
kind: 'StorageClass',
plural: 'storageclasses',
};

export const NamespaceModel: K8sModelCommon = {
apiVersion: 'v1',
kind: 'Namespace',
Expand Down
132 changes: 66 additions & 66 deletions frontend/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { useUser } from '~/redux/selectors';
import { DASHBOARD_MAIN_CONTAINER_ID } from '~/utilities/const';
import useDetectUser from '~/utilities/useDetectUser';
import ProjectsContextProvider from '~/concepts/projects/ProjectsContext';
import useStorageClasses from '~/concepts/k8s/useStorageClasses';
import AreaContextProvider from '~/concepts/areas/AreaContext';
import Header from './Header';
import AppRoutes from './AppRoutes';
Expand All @@ -42,80 +43,79 @@ const App: React.FC = () => {
loadError: fetchConfigError,
} = useApplicationSettings();

const [storageClasses] = useStorageClasses();
alexcreasy marked this conversation as resolved.
Show resolved Hide resolved

useDetectUser();

// We lack the critical data to startup the app
if (userError || fetchConfigError) {
// There was an error fetching critical data
if (!username || !configLoaded || !dashboardConfig) {
// We lack the critical data to startup the app
if (userError || fetchConfigError) {
// There was an error fetching critical data
return (
<Page>
<PageSection>
<Stack hasGutter>
<StackItem>
<Alert variant="danger" isInline title="General loading error">
<p>
{(userError ? userError.message : fetchConfigError?.message) ||
'Unknown error occurred during startup.'}
</p>
<p>Logging out and logging back in may solve the issue.</p>
</Alert>
</StackItem>
<StackItem>
<Button
variant="secondary"
onClick={() => logout().then(() => window.location.reload())}
>
Logout
</Button>
</StackItem>
</Stack>
</PageSection>
</Page>
);
}

// Assume we are still waiting on the API to finish
return (
<Page>
<PageSection>
<Stack hasGutter>
<StackItem>
<Alert variant="danger" isInline title="General loading error">
<p>
{(userError ? userError.message : fetchConfigError?.message) ||
'Unknown error occurred during startup.'}
</p>
<p>Logging out and logging back in may solve the issue.</p>
</Alert>
</StackItem>
<StackItem>
<Button
variant="secondary"
onClick={() => logout().then(() => window.location.reload())}
>
Logout
</Button>
</StackItem>
</Stack>
</PageSection>
</Page>
<Bullseye>
<Spinner />
</Bullseye>
);
}

// Waiting on the API to finish
const loading = !username || !configLoaded || !dashboardConfig;

return (
<AreaContextProvider>
{loading ? (
<Bullseye>
<Spinner />
</Bullseye>
) : (
<AppContext.Provider
value={{
buildStatuses,
dashboardConfig,
}}
<AppContext.Provider
value={{
alexcreasy marked this conversation as resolved.
Show resolved Hide resolved
buildStatuses,
dashboardConfig,
storageClasses,
}}
>
<AreaContextProvider>
<Page
className="odh-dashboard"
isManagedSidebar
header={<Header onNotificationsClick={() => setNotificationsOpen(!notificationsOpen)} />}
sidebar={isAllowed ? <NavSidebar /> : undefined}
notificationDrawer={<AppNotificationDrawer onClose={() => setNotificationsOpen(false)} />}
isNotificationDrawerExpanded={notificationsOpen}
mainContainerId={DASHBOARD_MAIN_CONTAINER_ID}
>
<Page
className="odh-dashboard"
isManagedSidebar
header={
<Header onNotificationsClick={() => setNotificationsOpen(!notificationsOpen)} />
}
sidebar={isAllowed ? <NavSidebar /> : undefined}
notificationDrawer={
<AppNotificationDrawer onClose={() => setNotificationsOpen(false)} />
}
isNotificationDrawerExpanded={notificationsOpen}
mainContainerId={DASHBOARD_MAIN_CONTAINER_ID}
>
<ErrorBoundary>
<ProjectsContextProvider>
<QuickStarts>
<AppRoutes />
</QuickStarts>
</ProjectsContextProvider>
<ToastNotifications />
<TelemetrySetup />
</ErrorBoundary>
</Page>
</AppContext.Provider>
)}
</AreaContextProvider>
<ErrorBoundary>
<ProjectsContextProvider>
<QuickStarts>
<AppRoutes />
</QuickStarts>
</ProjectsContextProvider>
<ToastNotifications />
<TelemetrySetup />
</ErrorBoundary>
</Page>
</AreaContextProvider>
</AppContext.Provider>
);
};

Expand Down
4 changes: 3 additions & 1 deletion frontend/src/app/AppContext.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import * as React from 'react';
import { DashboardConfigKind } from '~/k8sTypes';
import { StorageClassKind, DashboardConfigKind } from '~/k8sTypes';
alexcreasy marked this conversation as resolved.
Show resolved Hide resolved
import { BuildStatus } from '~/types';

type AppContextProps = {
buildStatuses: BuildStatus[];
dashboardConfig: DashboardConfigKind;
storageClasses: StorageClassKind[];
};

const defaultAppContext: AppContextProps = {
buildStatuses: [],
// At runtime dashboardConfig is never null -- DO NOT DO THIS usually
dashboardConfig: null as unknown as DashboardConfigKind,
storageClasses: [] as StorageClassKind[],
};

export const AppContext = React.createContext(defaultAppContext);
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/concepts/k8s/useStorageClasses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import useFetchState, { FetchState } from '~/utilities/useFetchState';
import { getStorageClasses } from '~/api';
import { StorageClassKind } from '~/k8sTypes';

const useStorageClasses = (): FetchState<StorageClassKind[]> =>
useFetchState(getStorageClasses, []);

alexcreasy marked this conversation as resolved.
Show resolved Hide resolved
export default useStorageClasses;
21 changes: 21 additions & 0 deletions frontend/src/k8sTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ type DisplayNameAnnotations = Partial<{
'openshift.io/display-name': string; // the name provided by the user
}>;

type StorageClassAnnotations = Partial<{
// if true, enables any persistent volume claim (PVC) that does not specify a specific storage class to automatically be provisioned.
// Only one, if any, StorageClass per cluster can be set as default.
'storageclass.kubernetes.io/is-default-class': 'true' | 'false';
// the description provided by the cluster admin or Container Storage Interface (CSI) provider
'kubernetes.io/description': string;
}>;

export type K8sDSGResource = K8sResourceCommon & {
metadata: {
annotations?: DisplayNameAnnotations &
Expand Down Expand Up @@ -253,6 +261,18 @@ export type PersistentVolumeClaimKind = K8sResourceCommon & {
} & Record<string, unknown>;
};

export type StorageClassKind = K8sResourceCommon & {
metadata: {
annotations?: StorageClassAnnotations;
name: string;
};
provisioner: string;
parameters?: string;
reclaimPolicy: string;
volumeBindingMode: string;
allowVolumeExpansion?: boolean;
};

export type PodSpec = {
affinity?: PodAffinity;
enableServiceLinks?: boolean;
Expand Down Expand Up @@ -822,6 +842,7 @@ export type DashboardConfigKind = K8sResourceCommon & {
notebookController?: {
enabled: boolean;
pvcSize?: string;
storageClassName?: string;
notebookNamespace?: string;
notebookTolerationSettings?: TolerationSettings;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const ManageStorageModal: React.FC<AddStorageModalProps> = ({ existingData, isOp
}
return;
}
const createdPvc = await createPvc(createData, namespace, { dryRun });
const createdPvc = await createPvc(createData, namespace, undefined, { dryRun });
alexcreasy marked this conversation as resolved.
Show resolved Hide resolved
if (notebookName) {
await attachNotebookPVC(notebookName, namespace, createdPvc.metadata.name, mountPath.value, {
dryRun,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { useUser } from '~/redux/selectors';
import { ProjectDetailsContext } from '~/pages/projects/ProjectDetailsContext';
import { AppContext } from '~/app/AppContext';
import { fireTrackingEvent } from '~/utilities/segmentIOUtils';
import usePreferredStorageClass from '~/pages/projects/screens/spawner/storage/usePreferredStorageClass';
import {
createPvcDataForNotebook,
createConfigMapsAndSecretsForNotebook,
Expand All @@ -44,12 +45,14 @@ const SpawnerFooter: React.FC<SpawnerFooterProps> = ({
canEnablePipelines,
}) => {
const [errorMessage, setErrorMessage] = React.useState('');

const {
dashboardConfig: {
spec: { notebookController },
},
} = React.useContext(AppContext);
const tolerationSettings = notebookController?.notebookTolerationSettings;
const storageClass = usePreferredStorageClass();
const {
notebooks: { data },
dataConnections: { data: existingDataConnections },
Expand Down Expand Up @@ -208,7 +211,11 @@ const SpawnerFooter: React.FC<SpawnerFooterProps> = ({
? [dataConnection.existing]
: [];

const pvcDetails = await createPvcDataForNotebook(projectName, storageData).catch(handleError);
const pvcDetails = await createPvcDataForNotebook(
projectName,
storageData,
storageClass?.metadata.name,
).catch(handleError);
const envFrom = await createConfigMapsAndSecretsForNotebook(projectName, [
...envVariables,
...newDataConnection,
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/pages/projects/screens/spawner/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,14 @@ import { fetchNotebookEnvVariables } from './environmentVariables/useNotebookEnv
export const createPvcDataForNotebook = async (
projectName: string,
storageData: StorageData,
storageClassName?: string,
): Promise<{ volumes: Volume[]; volumeMounts: VolumeMount[] }> => {
const { storageType } = storageData;

const { volumes, volumeMounts } = getVolumesByStorageData(storageData);

if (storageType === StorageType.NEW_PVC) {
const pvc = await createPvc(storageData.creating, projectName);
const pvc = await createPvc(storageData.creating, projectName, storageClassName);
alexcreasy marked this conversation as resolved.
Show resolved Hide resolved
const newPvcName = pvc.metadata.name;
volumes.push({ name: newPvcName, persistentVolumeClaim: { claimName: newPvcName } });
volumeMounts.push({ mountPath: ROOT_MOUNT_PATH, name: newPvcName });
Expand Down Expand Up @@ -68,7 +69,7 @@ export const replaceRootVolumesForNotebook = async (
};
replacedVolumeMount = { name: existingName, mountPath: ROOT_MOUNT_PATH };
} else {
const pvc = await createPvc(storageData.creating, projectName, { dryRun });
const pvc = await createPvc(storageData.creating, projectName, undefined, { dryRun });
alexcreasy marked this conversation as resolved.
Show resolved Hide resolved
const newPvcName = pvc.metadata.name;
replacedVolume = { name: newPvcName, persistentVolumeClaim: { claimName: newPvcName } };
replacedVolumeMount = { mountPath: ROOT_MOUNT_PATH, name: newPvcName };
Expand Down
Loading
Loading