Skip to content

Commit 82551dd

Browse files
authored
feat: handle platform data for sessions (#3910)
Update the session launcher UI to indicate when the container image is not compatible with the resource pool. This feature will help users understand how to configure sessions running on `linux/arm64` (e.g. `daint` at CSCS).
1 parent 6f2efc1 commit 82551dd

File tree

9 files changed

+216
-58
lines changed

9 files changed

+216
-58
lines changed

client/scripts/update_api_spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { parseDocument } from "yaml";
2424

2525
const GH_BASE_URL = "https://raw.githubusercontent.com";
2626
const DATA_SERVICES_REPO = "SwissDataScienceCenter/renku-data-services";
27-
const DATA_SERVICES_RELEASE = "leafty/feat-resource-class-platforms";
27+
const DATA_SERVICES_RELEASE = "build/support-build-arm";
2828

2929
async function main() {
3030
argv.forEach((arg) => {

client/src/features/sessionsV2/SessionImageModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export default function SessionImageModal({
7676
) : (
7777
<>
7878
<div className="mb-2">
79-
<SessionImageBadge data={data} loading={isLoading} />
79+
<SessionImageBadge data={data} isLoading={isLoading} />
8080
</div>
8181
{!data.connection && !data.provider ? (
8282
<>

client/src/features/sessionsV2/SessionList/SessionLauncherCard.tsx

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import { skipToken } from "@reduxjs/toolkit/query";
2020
import cx from "classnames";
21-
import { useContext } from "react";
21+
import { useContext, useMemo } from "react";
2222
import { CircleFill, Link45deg, Pencil, Trash } from "react-bootstrap-icons";
2323
import { Card, CardBody, Col, DropdownItem, Row } from "reactstrap";
2424

@@ -29,6 +29,7 @@ import { DEFAULT_APP_PARAMS } from "../../../utils/context/appParams.constants";
2929
import PermissionsGuard from "../../permissionsV2/PermissionsGuard";
3030
import useProjectPermissions from "../../ProjectPageV2/utils/useProjectPermissions.hook";
3131
import { Project } from "../../projectsV2/api/projectV2.api";
32+
import { computeResourcesApi } from "../api/computeResources.api";
3233
import type { SessionLauncher } from "../api/sessionLaunchersV2.api";
3334
import {
3435
sessionLaunchersV2Api,
@@ -127,15 +128,24 @@ export default function SessionLauncherCard({
127128
"text-muted",
128129
];
129130

130-
const { data: containerImage, isLoading: loadingContainerImage } =
131+
const { data: containerImage, isLoading: isLoadingContainerImage } =
131132
useGetSessionsImagesQuery(
132-
environment &&
133-
environment.environment_kind === "CUSTOM" &&
134-
environment.container_image
133+
environment?.container_image != null
135134
? { imageUrl: environment.container_image }
136135
: skipToken
137136
);
138137

138+
const { data: resourcePools, isLoading: isLoadingResourcePools } =
139+
computeResourcesApi.endpoints.getResourcePools.useQueryState({});
140+
const resourcePool = useMemo(() => {
141+
if (launcher?.resource_class_id == null || resourcePools == null) {
142+
return undefined;
143+
}
144+
return resourcePools.find(({ classes }) =>
145+
classes.some(({ id }) => id === launcher.resource_class_id)
146+
);
147+
}, [launcher?.resource_class_id, resourcePools]);
148+
139149
return (
140150
<Card
141151
className={cx(
@@ -203,10 +213,13 @@ export default function SessionLauncherCard({
203213
<SessionEnvironmentGitLabWarningBadge launcher={launcher} />
204214
</Col>
205215
</Row>
206-
{isCodeEnvironment && (
216+
{isCodeEnvironment ? (
207217
<Row className="g-2">
208218
<Col xs={12} xl={4}>
209-
{isCodeEnvironment && isLoading ? (
219+
{isCodeEnvironment &&
220+
(isLoading ||
221+
isLoadingContainerImage ||
222+
isLoadingResourcePools) ? (
210223
<SessionBadge
211224
className={cx("border-warning", "bg-warning-subtle")}
212225
>
@@ -220,7 +233,11 @@ export default function SessionLauncherCard({
220233
</span>
221234
</SessionBadge>
222235
) : isCodeEnvironment && lastBuild ? (
223-
<BuildStatusBadge status={lastBuild?.status} />
236+
<BuildStatusBadge
237+
buildStatus={lastBuild?.status}
238+
imageCheck={containerImage}
239+
resourcePool={resourcePool}
240+
/>
224241
) : !hasSession ? (
225242
<SessionBadge
226243
className={cx("border-dark-subtle", "bg-light")}
@@ -253,13 +270,14 @@ export default function SessionLauncherCard({
253270
/>
254271
</Col>
255272
</Row>
256-
)}
257-
{isExternalImageEnvironment && (
273+
) : (
258274
<Row>
259275
<Col>
260276
<SessionImageBadge
261277
data={containerImage}
262-
loading={loadingContainerImage}
278+
isLoading={isLoadingContainerImage}
279+
resourcePool={resourcePool}
280+
isLoadingResourcePools={isLoadingResourcePools}
263281
/>
264282
</Col>
265283
</Row>

client/src/features/sessionsV2/SessionView/EnvironmentCard.tsx

Lines changed: 81 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import AppContext from "../../../utils/context/appContext";
3232
import { DEFAULT_APP_PARAMS } from "../../../utils/context/appParams.constants";
3333
import useAppDispatch from "../../../utils/customHooks/useAppDispatch.hook";
3434
import { toHumanDateTime } from "../../../utils/helpers/DateTimeUtils";
35+
import { computeResourcesApi } from "../api/computeResources.api";
3536
import type { SessionLauncher } from "../api/sessionLaunchersV2.api";
3637
import {
3738
sessionLaunchersV2Api,
@@ -115,6 +116,7 @@ export default function EnvironmentCard({
115116
</EnvironmentRow>
116117
{environment_kind === "GLOBAL" && (
117118
<>
119+
<GlobalEnvironmentSessionImageBadge launcher={launcher} />
118120
<EnvironmentRow>
119121
{environment?.description ? (
120122
<p className={cx("text-truncate", "text-wrap")}>
@@ -150,20 +152,56 @@ export default function EnvironmentCard({
150152
);
151153
}
152154

155+
function GlobalEnvironmentSessionImageBadge({
156+
launcher,
157+
}: {
158+
launcher: SessionLauncher;
159+
}) {
160+
const environment = launcher.environment;
161+
const { data, isLoading } = useGetSessionsImagesQuery(
162+
environment && environment.container_image
163+
? { imageUrl: environment.container_image }
164+
: skipToken
165+
);
166+
const { data: resourcePools, isLoading: isLoadingResourcePools } =
167+
computeResourcesApi.endpoints.getResourcePools.useQueryState({});
168+
const resourcePool = useMemo(() => {
169+
if (launcher?.resource_class_id == null || resourcePools == null) {
170+
return undefined;
171+
}
172+
return resourcePools.find(({ classes }) =>
173+
classes.some(({ id }) => id === launcher.resource_class_id)
174+
);
175+
}, [launcher?.resource_class_id, resourcePools]);
176+
177+
return (
178+
<div className="mb-2">
179+
<SessionImageBadge
180+
data={data}
181+
isLoading={isLoading}
182+
resourcePool={resourcePool}
183+
isLoadingResourcePools={isLoadingResourcePools}
184+
/>
185+
</div>
186+
);
187+
}
188+
153189
function CustomEnvironmentValues({ launcher }: { launcher: SessionLauncher }) {
154190
const { environment } = launcher;
155191

156192
if (environment.environment_image_source === "image") {
157-
return <CustomImageEnvironmentValues launcher={launcher} />;
193+
return <CustomImageEnvironmentValues launcher={launcher} showImageBadge />;
158194
}
159195

160196
return <CustomBuildEnvironmentValues launcher={launcher} />;
161197
}
162198

163199
function CustomImageEnvironmentValues({
164200
launcher,
201+
showImageBadge,
165202
}: {
166203
launcher: SessionLauncher;
204+
showImageBadge?: boolean;
167205
}) {
168206
const { pathname, hash } = useLocation();
169207
const environment = launcher.environment;
@@ -175,6 +213,16 @@ function CustomImageEnvironmentValues({
175213
? { imageUrl: environment.container_image }
176214
: skipToken
177215
);
216+
const { data: resourcePools, isLoading: isLoadingResourcePools } =
217+
computeResourcesApi.endpoints.getResourcePools.useQueryState({});
218+
const resourcePool = useMemo(() => {
219+
if (launcher?.resource_class_id == null || resourcePools == null) {
220+
return undefined;
221+
}
222+
return resourcePools.find(({ classes }) =>
223+
classes.some(({ id }) => id === launcher.resource_class_id)
224+
);
225+
}, [launcher?.resource_class_id, resourcePools]);
178226
const search = useMemo(() => {
179227
return `?${new URLSearchParams({
180228
targetProvider: data?.provider?.id ?? "",
@@ -188,7 +236,14 @@ function CustomImageEnvironmentValues({
188236
return (
189237
<>
190238
<div className="mb-2">
191-
<SessionImageBadge data={data} loading={isLoading} />
239+
{showImageBadge && (
240+
<SessionImageBadge
241+
data={data}
242+
isLoading={isLoading}
243+
resourcePool={resourcePool}
244+
isLoadingResourcePools={isLoadingResourcePools}
245+
/>
246+
)}
192247
{!isLoading && data?.accessible === false && (
193248
<div className="mt-2">
194249
{!data.connection && !data.provider ? (
@@ -336,6 +391,23 @@ function CustomBuildEnvironmentValues({
336391
}
337392
);
338393

394+
const { data: imageCheck, isLoading: isLoadingContainerImage } =
395+
useGetSessionsImagesQuery(
396+
environment.container_image != null
397+
? { imageUrl: environment.container_image }
398+
: skipToken
399+
);
400+
const { data: resourcePools, isLoading: isLoadingResourcePools } =
401+
computeResourcesApi.endpoints.getResourcePools.useQueryState({});
402+
const resourcePool = useMemo(() => {
403+
if (launcher?.resource_class_id == null || resourcePools == null) {
404+
return undefined;
405+
}
406+
return resourcePools.find(({ classes }) =>
407+
classes.some(({ id }) => id === launcher.resource_class_id)
408+
);
409+
}, [launcher?.resource_class_id, resourcePools]);
410+
339411
// Invalidate launchers if the container image is not the same as the
340412
// image from the last successful build
341413
const dispatch = useAppDispatch();
@@ -368,7 +440,12 @@ function CustomBuildEnvironmentValues({
368440
<NotReadyStatusBadge />
369441
) : (
370442
<>
371-
<ReadyStatusBadge />
443+
<SessionImageBadge
444+
data={imageCheck}
445+
isLoading={isLoadingContainerImage}
446+
resourcePool={resourcePool}
447+
isLoadingResourcePools={isLoadingResourcePools}
448+
/>
372449
{lastSuccessfulBuild && (
373450
<BuildStatusDescription
374451
isOldImage={
@@ -415,7 +492,7 @@ function CustomBuildEnvironmentValues({
415492
Last build status:
416493
</label>
417494
<span>
418-
<BuildStatusBadge status={lastBuild.status} />
495+
<BuildStatusBadge buildStatus={lastBuild.status} />
419496
</span>
420497
</div>
421498
)}
@@ -505,25 +582,6 @@ function EnvironmentJSONArrayRowWithLabel({
505582
);
506583
}
507584

508-
function ReadyStatusBadge() {
509-
return (
510-
<Badge
511-
className={cx(
512-
"border",
513-
"bg-success-subtle",
514-
"border-success",
515-
"text-success-emphasis",
516-
"fs-small",
517-
"fw-normal"
518-
)}
519-
pill
520-
>
521-
<CircleFill className={cx("bi", "me-1")} />
522-
Ready
523-
</Badge>
524-
);
525-
}
526-
527585
function NotReadyStatusBadge() {
528586
return (
529587
<Badge

client/src/features/sessionsV2/api/computeResources.generated-api.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,7 @@ export type RemoteConfigurationFirecrest = {
653653
export type RemoteConfiguration = RemoteConfigurationFirecrest;
654654
export type IdleThreshold = number;
655655
export type HibernationThreshold = number;
656+
export type RuntimePlatform = "linux/amd64" | "linux/arm64";
656657
export type ResourcePoolWithIdFiltered = {
657658
quota?: QuotaWithId;
658659
classes: ResourceClassWithIdFiltered[];
@@ -664,6 +665,7 @@ export type ResourcePoolWithIdFiltered = {
664665
idle_threshold?: IdleThreshold;
665666
hibernation_threshold?: HibernationThreshold;
666667
cluster_id?: Ulid;
668+
platform: RuntimePlatform;
667669
};
668670
export type ResourcePoolsWithIdFiltered = ResourcePoolWithIdFiltered[];
669671
export type CpuFilter = number;
@@ -682,6 +684,7 @@ export type ResourcePoolWithId = {
682684
cluster?: {
683685
id: Ulid;
684686
};
687+
platform: RuntimePlatform;
685688
};
686689
export type QuotaWithOptionalId = {
687690
cpu: Cpu;
@@ -711,6 +714,7 @@ export type ResourcePool = {
711714
idle_threshold?: IdleThreshold;
712715
hibernation_threshold?: HibernationThreshold;
713716
cluster_id?: Ulid;
717+
platform?: RuntimePlatform;
714718
};
715719
export type ResourceClassesWithId = ResourceClassWithId[];
716720
export type ResourcePoolPut = {
@@ -723,6 +727,7 @@ export type ResourcePoolPut = {
723727
idle_threshold?: IdleThreshold;
724728
hibernation_threshold?: HibernationThreshold;
725729
cluster_id?: Ulid;
730+
platform: RuntimePlatform;
726731
};
727732
export type QuotaPatch = {
728733
cpu?: Cpu;
@@ -766,6 +771,7 @@ export type ResourcePoolPatch = {
766771
idle_threshold?: IdleThreshold;
767772
hibernation_threshold?: HibernationThreshold;
768773
cluster_id?: Ulid;
774+
platform?: RuntimePlatform;
769775
};
770776
export type ResourceClassesWithIdResponse = ResourceClassWithId[];
771777
export type ResourceClassPatch = {

0 commit comments

Comments
 (0)