CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/util/licenses/purchase/sanity-checks.ts
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import { LicenseIdleTimeouts, Uptime } from "@cocalc/util/consts/site-license";
7
import {
8
DedicatedDiskSpeedNames,
9
DedicatedDiskSpeeds,
10
} from "@cocalc/util/types/dedicated";
11
import {
12
getDedicatedDiskKey,
13
MAX_DEDICATED_DISK_SIZE,
14
PRICES,
15
} from "@cocalc/util/upgrades/dedicated";
16
import { isEqual } from "lodash";
17
import { testDedicatedDiskNameBasic } from "../check-disk-name-basics";
18
import { checkDedicateDiskNameUniqueness } from "../check-disk-name-uniqueness";
19
import { compute_cost } from "./compute-cost";
20
import { MAX } from "./consts";
21
import { PurchaseInfo } from "./types";
22
23
// throws an exception if it spots something funny...
24
export async function sanity_checks(pool, info: PurchaseInfo) {
25
const { type } = info;
26
if (typeof info != "object") {
27
throw Error("must be an object");
28
}
29
30
if (!["quota", "vm", "disk"].includes(type)) {
31
throw new Error(`type must be one of quota, vm, disk – but got "${type}"`);
32
}
33
34
sanity_check_start_end(info);
35
sanity_check_quota(info);
36
await sanity_check_dedicated(pool, info);
37
sanity_check_cost(info);
38
}
39
40
// Check that cost in the info object matches computing the cost again
41
// from scratch. We *only* do this if a cost is set, since we also
42
// use this code for creating licenses associated to a voucher, where
43
// payment has already happened (or will happen later).
44
function sanity_check_cost(info: PurchaseInfo) {
45
if (info.cost != null && !isEqual(info.cost, compute_cost(info))) {
46
throw Error("cost does not match");
47
}
48
}
49
50
function sanity_check_start_end(info: PurchaseInfo) {
51
const { type } = info;
52
53
if ((type === "quota" && info.subscription === "no") || type === "vm") {
54
if (info.start == null) {
55
throw Error("must have start date set");
56
}
57
}
58
59
if (type === "vm" || type === "quota") {
60
const start = info.start ? new Date(info.start) : undefined;
61
const end = info.end ? new Date(info.end) : undefined;
62
63
if (info.subscription == "no") {
64
if (start == null || end == null) {
65
throw Error(
66
"start and end dates must both be given if not a subscription"
67
);
68
}
69
70
if (end <= start) {
71
throw Error("end date must be after start date");
72
}
73
}
74
}
75
}
76
77
function sanity_check_quota(info: PurchaseInfo) {
78
const { type } = info;
79
if (type !== "quota") return;
80
for (const x of ["ram", "cpu", "disk", "dedicated_ram", "dedicated_cpu"]) {
81
const field = "custom_" + x;
82
if (typeof info[field] !== "number") {
83
throw Error(`field "${field}" must be number`);
84
}
85
if (info[field] < 0 || info[field] > MAX[field]) {
86
throw Error(`field "${field}" too small or too big`);
87
}
88
}
89
if (info.custom_uptime == null || typeof info.custom_uptime !== "string") {
90
throw new Error(`field "custom_uptime" must be set`);
91
}
92
93
if (
94
LicenseIdleTimeouts[info.custom_uptime] == null &&
95
info.custom_uptime != ("always_running" as Uptime)
96
) {
97
const tos = Object.keys(LicenseIdleTimeouts).join(", ");
98
throw new Error(
99
`field "custom_uptime" must be one of ${tos} or "always_running"`
100
);
101
}
102
103
for (const x of ["member"]) {
104
const field = "custom_" + x;
105
if (typeof info[field] !== "boolean") {
106
throw Error(`field "${field}" must be boolean`);
107
}
108
}
109
}
110
111
async function sanity_check_dedicated(pool, info: PurchaseInfo) {
112
const { type } = info;
113
114
if (type === "vm") {
115
if (info.dedicated_vm == null) {
116
throw new Error(
117
`license type "vm", but there is no data about a dedicated VM`
118
);
119
}
120
const machine = info.dedicated_vm.machine;
121
if (typeof machine !== "string")
122
throw new Error(`field dedicated_vm must be string`);
123
if (PRICES.vms[machine] == null)
124
throw new Error(`field dedicated_vm ${machine} not found`);
125
}
126
127
if (type === "disk") {
128
if (info.dedicated_disk == null) {
129
throw new Error(
130
`license type "disk", but there is no data about a dedicated disk`
131
);
132
}
133
const dd = info.dedicated_disk;
134
if (typeof dd === "object") {
135
const { size_gb, speed } = dd;
136
if (typeof size_gb !== "number") {
137
throw new Error(`field dedicated_disk.size must be number`);
138
}
139
if (size_gb < 0 || size_gb > MAX_DEDICATED_DISK_SIZE) {
140
throw new Error(`field dedicated_disk.size_gb < 0 or too big`);
141
}
142
if (
143
typeof speed !== "string" ||
144
!DedicatedDiskSpeedNames.includes(speed as DedicatedDiskSpeeds)
145
)
146
throw new Error(
147
`field dedicated_disk.speed must be string and one of ${DedicatedDiskSpeedNames.join(
148
", "
149
)}`
150
);
151
152
const key = getDedicatedDiskKey({ speed, size_gb });
153
if (PRICES.disks[key] == null) {
154
throw new Error(`field dedicated_disk "${key}" not found`);
155
}
156
157
// check lenght/charaters of disk name
158
testDedicatedDiskNameBasic(dd.name);
159
// finally we check again to make sure the disk name is unique
160
await checkDedicateDiskNameUniqueness(pool, dd.name);
161
}
162
}
163
}
164
165