Skip to content

Commit 112dd09

Browse files
committed
frontend/admin/on-prem: gpu support
1 parent ec2a051 commit 112dd09

File tree

2 files changed

+135
-25
lines changed

2 files changed

+135
-25
lines changed

src/packages/frontend/site-licenses/purchase/quota-editor.tsx

Lines changed: 129 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
Typography,
2323
} from "antd";
2424

25+
import { JsonEditor } from "@cocalc/frontend/admin/json-editor";
2526
import {
2627
CSS,
2728
React,
@@ -33,6 +34,7 @@ import {
3334
A,
3435
Gap,
3536
HelpIcon,
37+
NumberInput,
3638
Paragraph,
3739
TextInput,
3840
} from "@cocalc/frontend/components";
@@ -54,7 +56,6 @@ import { plural, round1, test_valid_jsonpatch } from "@cocalc/util/misc";
5456
import { SiteLicenseQuota } from "@cocalc/util/types/site-licenses";
5557
import { DEDICATED_VM_ONPREM_MACHINE } from "@cocalc/util/upgrades/consts";
5658
import { Upgrades } from "@cocalc/util/upgrades/quota";
57-
import { JsonEditor } from "../../admin/json-editor";
5859

5960
const { Text } = Typography;
6061

@@ -101,10 +102,10 @@ export const QuotaEditor: React.FC<Props> = (props: Props) => {
101102
const max_upgrades = useTypedRedux("customize", "max_upgrades");
102103

103104
const [show_advanced, set_show_advanced] = useState<boolean>(
104-
show_advanced_default ?? false
105+
show_advanced_default ?? false,
105106
);
106107
const [jsonPatchError, setJSONPatchError] = useState<string | undefined>(
107-
undefined
108+
undefined,
108109
);
109110

110111
const hosting_multiplier = useMemo(() => {
@@ -191,12 +192,12 @@ export const QuotaEditor: React.FC<Props> = (props: Props) => {
191192
{`${money(
192193
COSTS.user_discount[user()] *
193194
COSTS.custom_cost.cpu *
194-
hosting_multiplier
195+
hosting_multiplier,
195196
)}/CPU cores per month per project`}
196197
)
197198
</b>
198199
{render_explanation(
199-
"Google Cloud vCPUs shared with other projects (member hosting significantly reduces sharing)"
200+
"Google Cloud vCPUs shared with other projects (member hosting significantly reduces sharing)",
200201
)}
201202
</Col>
202203
)}
@@ -236,7 +237,7 @@ export const QuotaEditor: React.FC<Props> = (props: Props) => {
236237
{`${money(
237238
COSTS.user_discount[user()] *
238239
COSTS.custom_cost.ram *
239-
hosting_multiplier
240+
hosting_multiplier,
240241
)}/GB RAM per month per project`}
241242
)
242243
</b>
@@ -287,12 +288,12 @@ export const QuotaEditor: React.FC<Props> = (props: Props) => {
287288
{`${money(
288289
COSTS.user_discount[user()] *
289290
COSTS.custom_cost.dedicated_cpu *
290-
hosting_multiplier
291+
hosting_multiplier,
291292
)}/CPU cores per month per project`}
292293
)
293294
</b>
294295
{render_explanation(
295-
"Google Cloud vCPUs NOT shared with other projects. You can enter a fractional value, e.g., 0.5 for a half dedicated core."
296+
"Google Cloud vCPUs NOT shared with other projects. You can enter a fractional value, e.g., 0.5 for a half dedicated core.",
296297
)}
297298
</Col>
298299
)}
@@ -338,7 +339,7 @@ export const QuotaEditor: React.FC<Props> = (props: Props) => {
338339
{`${money(
339340
COSTS.user_discount[user()] *
340341
COSTS.custom_cost.dedicated_ram *
341-
hosting_multiplier
342+
hosting_multiplier,
342343
)}/GB RAM per month per project`}
343344
)
344345
</b>
@@ -380,12 +381,12 @@ export const QuotaEditor: React.FC<Props> = (props: Props) => {
380381
<b>
381382
G Disk Space (
382383
{`${money(
383-
COSTS.user_discount[user()] * COSTS.custom_cost.disk
384+
COSTS.user_discount[user()] * COSTS.custom_cost.disk,
384385
)}/G disk per month per project`}
385386
)
386387
</b>
387388
{render_explanation(
388-
"store a larger number of files. Snapshots and file edit history is included at no additional charge."
389+
"store a larger number of files. Snapshots and file edit history is included at no additional charge.",
389390
)}
390391
</Col>
391392
)}
@@ -415,7 +416,7 @@ export const QuotaEditor: React.FC<Props> = (props: Props) => {
415416
member hosting{" "}
416417
<b>(multiplies RAM/CPU price by {COSTS.custom_cost.member})</b>
417418
{render_explanation(
418-
"project runs on computers with far fewer other projects. If not selected your project runs on very, very heavily loaded trial servers, which might be OK depending on your application."
419+
"project runs on computers with far fewer other projects. If not selected your project runs on very, very heavily loaded trial servers, which might be OK depending on your application.",
419420
)}
420421
</Col>
421422
)}
@@ -519,6 +520,90 @@ export const QuotaEditor: React.FC<Props> = (props: Props) => {
519520
);
520521
}
521522

523+
function render_gpu_help(): JSX.Element {
524+
return (
525+
<HelpIcon title="GPU Support">
526+
<Paragraph>
527+
This configures a license, which will cause the project to run on a
528+
GPU. You need to configure your VMs in your cluster in such a way,
529+
that requesting a GPU is possible.
530+
</Paragraph>
531+
<Paragraph>
532+
In particular, the pod will get the following resource limit:{" "}
533+
<code>nvidia.com/gpu: 1</code>.
534+
</Paragraph>
535+
<Paragraph>
536+
On top of that, you can optionally specify a taint, which will be
537+
tolerated by that pod. This helps with keeping all other project pods
538+
away from your GPU enabled nodes:{" "}
539+
<code>
540+
tolerations:{" "}
541+
{JSON.stringify([
542+
{
543+
key: "gpu",
544+
operator: "Equal",
545+
value: "cocalc",
546+
effect: "NoSchedule",
547+
},
548+
])}
549+
</code>
550+
.
551+
</Paragraph>
552+
</HelpIcon>
553+
);
554+
}
555+
556+
function render_gpu(): JSX.Element {
557+
const { gpu } = quota;
558+
const { num = 0, toleration = "" } =
559+
typeof gpu === "object"
560+
? gpu
561+
: typeof gpu === "number"
562+
? { num: gpu }
563+
: {};
564+
return (
565+
<Row style={ROW_STYLE}>
566+
<Col md={col.control}>
567+
<Paragraph>
568+
<Text strong>Request a GPU</Text>
569+
</Paragraph>{" "}
570+
<Paragraph>
571+
<NumberInput
572+
number={num}
573+
min={0}
574+
max={8}
575+
on_change={(num: number) =>
576+
onChange({ gpu: { num, toleration } })
577+
}
578+
/>{" "}
579+
GPUs (<code>nvidia.com/gpu: {num}</code>)
580+
</Paragraph>
581+
<Paragraph>
582+
<Text>(optional) Tolerate a taint:</Text>{" "}
583+
<TextInput
584+
disabled={disabled}
585+
type={"text"}
586+
on_change={(tol) =>
587+
onChange({ gpu: { num, toleration: tol.trim() } })
588+
}
589+
style={{
590+
fontWeight: "normal",
591+
display: "inline-block",
592+
margin: "0 10px",
593+
}}
594+
text={toleration}
595+
/>{" "}
596+
(Enter <code>key=value</code> of a node taint. e.g.{" "}
597+
<code>gpu=cocalc</code> means to ignore nodes tatined with key{" "}
598+
<code>gpu</code> and value <code>cocalc</code>. Keep empty if you do
599+
not use taints!)
600+
</Paragraph>
601+
{render_gpu_help()}
602+
</Col>
603+
</Row>
604+
);
605+
}
606+
522607
function on_json_patch_change(patch: string): void {
523608
try {
524609
const patchObj = JSON.parse(patch);
@@ -527,7 +612,7 @@ export const QuotaEditor: React.FC<Props> = (props: Props) => {
527612
onChange({ patch }); // we save the string, not the object!
528613
} else {
529614
setJSONPatchError(
530-
'Must be a list of {`[{"op": "replace", "path": "…", "value": "…"}, …]`} objects.'
615+
'Must be a list of {`[{"op": "replace", "path": "…", "value": "…"}, …]`} objects.',
531616
);
532617
}
533618
} catch (err) {
@@ -564,13 +649,13 @@ export const QuotaEditor: React.FC<Props> = (props: Props) => {
564649
ret.push(
565650
<Select.Option key={key} value={key}>
566651
{it.label}
567-
</Select.Option>
652+
</Select.Option>,
568653
);
569654
}
570655
ret.push(
571656
<Select.Option key={"always_running"} value={"always_running"}>
572657
Always running
573-
</Select.Option>
658+
</Select.Option>,
574659
);
575660
return ret;
576661
}
@@ -639,7 +724,7 @@ export const QuotaEditor: React.FC<Props> = (props: Props) => {
639724
<Col md={col.desc}>
640725
priority support
641726
{render_explanation(
642-
"we prioritize your support requests much higher (included with all licensed projects)"
727+
"we prioritize your support requests much higher (included with all licensed projects)",
643728
)}
644729
</Col>
645730
)}
@@ -661,7 +746,7 @@ export const QuotaEditor: React.FC<Props> = (props: Props) => {
661746
<Col md={col.desc}>
662747
network access
663748
{render_explanation(
664-
"project can connect to the Internet to clone git repositories, download files, send emails, etc. (included with all licensed projects)"
749+
"project can connect to the Internet to clone git repositories, download files, send emails, etc. (included with all licensed projects)",
665750
)}
666751
</Col>
667752
)}
@@ -708,6 +793,32 @@ export const QuotaEditor: React.FC<Props> = (props: Props) => {
708793
);
709794
}
710795

796+
function render_advanced_onprem(): JSX.Element | undefined {
797+
if (!show_advanced || !isOnPrem) return;
798+
return (
799+
<>
800+
{render_ext_rw()}
801+
{render_dedicated_vm()}
802+
{render_gpu()}
803+
{render_patch_project_pod()}
804+
</>
805+
);
806+
}
807+
808+
function render_advanced(): JSX.Element | undefined {
809+
if (!show_advanced) return;
810+
return (
811+
<>
812+
{render_member()}
813+
{render_idle_timeout()}
814+
{render_dedicated_cpu()}
815+
{render_dedicated_ram()}
816+
{!hideExtra && render_dedicated()}
817+
{render_advanced_onprem()}
818+
</>
819+
);
820+
}
821+
711822
return (
712823
<>
713824
{render_cpu()}
@@ -716,14 +827,7 @@ export const QuotaEditor: React.FC<Props> = (props: Props) => {
716827
{!hideExtra && render_support()}
717828
{!hideExtra && render_network()}
718829
{render_show_advanced_link()}
719-
{show_advanced && render_member()}
720-
{show_advanced && render_idle_timeout()}
721-
{show_advanced && render_dedicated_cpu()}
722-
{show_advanced && render_dedicated_ram()}
723-
{show_advanced && !hideExtra && render_dedicated()}
724-
{show_advanced && isOnPrem && render_ext_rw()}
725-
{show_advanced && isOnPrem && render_dedicated_vm()}
726-
{show_advanced && isOnPrem && render_patch_project_pod()}
830+
{render_advanced()}
727831
</>
728832
);
729833
};

src/packages/util/types/site-licenses.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ export interface SiteLicenseQuota {
2727
// JSON array of Array of JSON Patch Operations, e.g. "[{op: \"add\", path: \"/foo\", value: \"bar\"}]"
2828
// It's not an array of objects, because somewhere the array is converted to weird map of "0, 1, 2,..." indexed objects.
2929
patch?: string;
30+
gpu?:
31+
| number
32+
| {
33+
num?: number; // usualy 1, to set nvidia.com/gpu=1
34+
toleration?: string; // e.g. gpu=cocalc for key=value
35+
};
3036
}
3137

3238
// For typescript use of these from user side, we make this available:

0 commit comments

Comments
 (0)