diff --git a/src/packages/frontend/cspell.json b/src/packages/frontend/cspell.json index 1ce3d01b55..207f317358 100644 --- a/src/packages/frontend/cspell.json +++ b/src/packages/frontend/cspell.json @@ -30,7 +30,8 @@ "tidyverse", "timetravel", "undelete", - "undeleting" + "undeleting", + "tolerations" ], "ignoreWords": [ "antd", diff --git a/src/packages/frontend/site-licenses/purchase/quota-editor.tsx b/src/packages/frontend/site-licenses/purchase/quota-editor.tsx index f577895994..8fa0da53c5 100644 --- a/src/packages/frontend/site-licenses/purchase/quota-editor.tsx +++ b/src/packages/frontend/site-licenses/purchase/quota-editor.tsx @@ -4,14 +4,15 @@ */ /* - Editing a quota - shows user rows for cpu, ram, disk, member, and always_running: optional - they can edit all the rows. - optional: also shows rows for support and network that can't be edited - */ + +// cSpell: ignore jsonpatch requiresMemberhosting + import { Button, Checkbox, @@ -44,14 +45,15 @@ import { untangleUptime, } from "@cocalc/util/consts/site-license"; import { KUCALC_ON_PREMISES } from "@cocalc/util/db-schema/site-defaults"; -import { - COSTS, - CostMap, -} from "@cocalc/util/licenses/purchase/consts"; +import { COSTS, CostMap } from "@cocalc/util/licenses/purchase/consts"; import { User } from "@cocalc/util/licenses/purchase/types"; import { money } from "@cocalc/util/licenses/purchase/utils"; import { plural, round1, test_valid_jsonpatch } from "@cocalc/util/misc"; -import { extract_gpu, process_gpu_quota } from "@cocalc/util/types/gpu"; +import { + extract_gpu, + GPU_DEFAULT_RESOURCE, + process_gpu_quota, +} from "@cocalc/util/types/gpu"; import { SiteLicenseQuota } from "@cocalc/util/types/site-licenses"; import { DEDICATED_VM_ONPREM_MACHINE } from "@cocalc/util/upgrades/consts"; import { Upgrades } from "@cocalc/util/upgrades/quota"; @@ -529,7 +531,8 @@ export const QuotaEditor: React.FC = (props: Props) => { In particular, the pod will get the following resource limit:{" "} - nvidia.com/gpu: 1. + nvidia.com/gpu: 1, where the specific GPU resource name + can be configured. On top of that, you can optionally specify a{" "} @@ -565,10 +568,18 @@ export const QuotaEditor: React.FC = (props: Props) => { } function render_gpu(): JSX.Element { - const { num = 0, toleration = "", nodeLabel = "" } = extract_gpu(quota); + const { + num = 0, + toleration = "", + nodeLabel = "", + resource = GPU_DEFAULT_RESOURCE, + } = extract_gpu(quota); const debug = process_gpu_quota(quota); + // edit text and save button on the same row + const style: CSS = { display: "inline-flex", gap: "5px" } as const; + return ( @@ -583,6 +594,7 @@ export const QuotaEditor: React.FC = (props: Props) => { num: e.target.checked ? Math.max(1, num) : 0, toleration, nodeLabel, + resource, }, }) } @@ -600,11 +612,33 @@ export const QuotaEditor: React.FC = (props: Props) => { size={"small"} value={num} onChange={(num: number) => - onChange({ gpu: { num, toleration, nodeLabel } }) + onChange({ gpu: { num, toleration, nodeLabel, resource } }) } />{" "} (usually "1") + + GPU Resource{" "} + + onChange({ + gpu: { + resource: resource.trim(), + toleration, + num, + nodeLabel, + }, + }) + } + style={style} + text={resource} + />{" "} + (optional, default "{GPU_DEFAULT_RESOURCE}", alternatively e.g. + "nvidia.com/mig-1g.5gb") + Node selector:{" "} = (props: Props) => { type={"text"} on_change={(label) => onChange({ - gpu: { nodeLabel: label.trim(), toleration, num }, + gpu: { nodeLabel: label.trim(), toleration, num, resource }, }) } - style={{ display: "inline-block" }} + style={style} text={nodeLabel} />{" "} (optional, [1]) @@ -628,9 +662,11 @@ export const QuotaEditor: React.FC = (props: Props) => { disabled={disabled} type={"text"} on_change={(tol) => - onChange({ gpu: { toleration: tol.trim(), nodeLabel, num } }) + onChange({ + gpu: { toleration: tol.trim(), nodeLabel, num, resource }, + }) } - style={{ display: "inline-block" }} + style={style} text={toleration} />{" "} (optional, [1]) @@ -639,7 +675,7 @@ export const QuotaEditor: React.FC = (props: Props) => { [1] format: key=value or for taints, also{" "} key to tolerate the key regardless of value. Keep the field empty if you do not use label selectors or taints. Specify - mulitple ones via a "," comma. Below is a "debug" view. + multiple ones via a "," comma. Below is a "debug" view.
               {JSON.stringify(debug, null, 2)}
diff --git a/src/packages/util/types/gpu.ts b/src/packages/util/types/gpu.ts
index 8dfa1294ff..962a0a0358 100644
--- a/src/packages/util/types/gpu.ts
+++ b/src/packages/util/types/gpu.ts
@@ -2,6 +2,8 @@
 
 import { SiteLicenseQuota } from "./site-licenses";
 
+export const GPU_DEFAULT_RESOURCE = "nvidia.com/gpu"
+
 export function extract_gpu(quota: SiteLicenseQuota = {}) {
   const { gpu } = quota;
   if (gpu == null) return { num: 0 };
@@ -10,7 +12,11 @@ export function extract_gpu(quota: SiteLicenseQuota = {}) {
 }
 
 type GPUQuotaInfo = {
-  resources?: { limits: { "nvidia.com/gpu": number } };
+  resources?: {
+    limits: {
+      [resource: string]: number; // resource default: $GPU_DEFAULT_RESOURCE
+    };
+  };
   nodeSelector?: { [key: string]: string };
   tolerations?: (
     | {
@@ -28,31 +34,38 @@ type GPUQuotaInfo = {
 };
 
 export function process_gpu_quota(quota: SiteLicenseQuota = {}): GPUQuotaInfo {
-  const { num = 0, toleration = "", nodeLabel = "" } = extract_gpu(quota);
+  const {
+    num = 0,
+    toleration = "",
+    nodeLabel = "",
+    resource = GPU_DEFAULT_RESOURCE,
+  } = extract_gpu(quota);
 
-  const debug: GPUQuotaInfo = {};
+  const info: GPUQuotaInfo = {};
   if (num > 0) {
-    debug.resources = { limits: { "nvidia.com/gpu": num } };
+    info.resources = { limits: { [resource]: num } };
+
     if (nodeLabel) {
-      debug.nodeSelector = {};
+      info.nodeSelector = {};
       for (const label of nodeLabel.split(",")) {
         const [key, val] = label.trim().split("=");
-        debug.nodeSelector[key] = val;
+        info.nodeSelector[key] = val;
       }
     }
+
     if (toleration) {
-      debug.tolerations = [];
+      info.tolerations = [];
       for (const tol of toleration.split(",")) {
         const [key, val] = tol.trim().split("=");
         if (val) {
-          debug.tolerations.push({
+          info.tolerations.push({
             key,
             operator: "Equal",
             value: val,
             effect: "NoSchedule",
           });
         } else {
-          debug.tolerations.push({
+          info.tolerations.push({
             key,
             operator: "Exists",
             effect: "NoSchedule",
@@ -61,5 +74,5 @@ export function process_gpu_quota(quota: SiteLicenseQuota = {}): GPUQuotaInfo {
       }
     }
   }
-  return debug;
+  return info;
 }
diff --git a/src/packages/util/types/site-licenses.ts b/src/packages/util/types/site-licenses.ts
index a76d0819d6..9b76637625 100644
--- a/src/packages/util/types/site-licenses.ts
+++ b/src/packages/util/types/site-licenses.ts
@@ -11,6 +11,7 @@ export type GPU = {
   num?: number; // usualy 1, to set nvidia.com/gpu=1, 0 means "disabled"
   toleration?: string; // e.g. gpu=cocalc for key=value
   nodeLabel?: string; // e.g. gpu=cocalc for key=value
+  resource?: string; // default: $GPU_DEFAULT_RESOURCE
 };
 
 export interface SiteLicenseQuota {