Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/util/db-schema/compute-servers.ts
Views: 687
/*1* This file is part of CoCalc: Copyright © 2023 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import type {6Region as HyperstackRegion,7VirtualMachine as HyperstackVirtualMachine,8} from "@cocalc/util/compute/cloud/hyperstack/api-types";9import { COLORS } from "@cocalc/util/theme";10import { ID, NOTES } from "./crm";11import { SCHEMA as schema } from "./index";12import { Table } from "./types";13export {14CLOUDS_BY_NAME,15GOOGLE_CLOUD_DEFAULTS,16ON_PREM_DEFAULTS,17} from "@cocalc/util/compute/cloud/clouds";1819// These are just fallbacks in case something is wrong with the image configuration.20export const STANDARD_DISK_SIZE = 20;21export const CUDA_DISK_SIZE = 60;2223export const CHECK_IN_PERIOD_S = 20;24export const CHECK_IN_PATH = "/cocalc/conf/check-in";2526// Clients are recommended to wait this long after a purchase ends before27// requesting the cost. This should give us about a day of wiggle room.28// There is no SLA on billing data.29const GOOGLE_COST_LAG_DAYS = 2;30export const GOOGLE_COST_LAG_MS = GOOGLE_COST_LAG_DAYS * 24 * 60 * 60 * 1000;3132// Compute Server Images -- typings. See packages/server/compute/images.ts for33// how the actual data is populated.3435export interface ImageVersion {36// tag - must be given and distinct for each version -- this typically identifies the image to docker37tag: string;38// version -- defaults to tag if not given; usually the upstream version39version?: string;40// label -- defaults to the tag; this is to display to the user41label?: string;42// tested -- if this is not set to true, then this version should not be shown by default.43// If not tested, only show to users who explicitly really want this (e.g., admins).44tested?: boolean;45}4647export const AUTOMATIC_SHUTDOWN_DEFAULTS = {48INTERVAL_MINUTES: 1,49ATTEMPTS: 3,50};5152export interface AutomaticShutdown {53// run the command with given args on the compute server.54// if the output contains the the trigger string, then the55// compute server turns off. If it contains the deprovision56// string, then it deprovisions.57command: string;58// timeout in seconds for running the command59timeout?: number;60// how often to run the command61interval_minutes?: number;62// try this many times before giving up on running the command and turning machine off.63attempts?: number;64// turn server off when the script exits with this code.65exit_code?: number;66// action: 'shtudown', 'deprovision', 'restart'67action?: "shutdown" | "deprovision" | "restart" | "suspend";68}6970interface ProxyRoute {71path: string;72target: string;73ws?: boolean;74}7576export interface Image {77// What we show the user to describe this image, e.g., in the image select menu.78label: string;79// The name of the package on npmjs or dockerhub:80package: string;81// In case there is a different package name for ARM64, the name of it.82package_arm64?: string;83// Root filesystem image must be at least this big in GB.84minDiskSizeGb?: number;85// Description in MARKDOWN to show user of this image. Can include links.86// Rough estimate of compressed size of Docker image; useful87// to get a sense of how long it will take to download image88// on clouds without pregenerated images.89dockerSizeGb?: number;90description?: string;91// Upstream URL for this image, e.g., https://julialang.org/ for the Julia image.92url: string;93// Icon to show next to the label for this image.94icon: string;95// Link to a URL with the source for building this image.96source: string;97// optional list of links to videos about this image, ordered from lowest to highest priority.98videos?: string[];99// optional list of links to tutorials100tutorials?: string[];101// The versions of this image that we claim to have built.102// The ones with role='prod' (or not specified) are shown103// to users as options.104versions: ImageVersion[];105// If true, then a GPU is required to use this image.106gpu?: boolean;107// If true, then the microk8s snap is required to use this image.108microk8s?: boolean;109// authToken: if true, image has web interface that supports configurable auth token110authToken?: boolean;111// jupyterKernels: if false, no jupyter kernels included. If true or a list of112// names, there are kernels available – used in frontend/jupyter/select-kernel.tsx113jupyterKernels?: false | true | string[];114// If set to true, do not allow creating this compute server with a DNS subdomain.115// Some images only make sense to use over the web, and the web server just won't116// work without DNS setup properly (e.g., VS Code with LEAN). Ignored for on prem.117requireDns?: boolean;118// system: if true, this is a system container that is not for user compute119system?: boolean;120// disabled: if true, this image is completely disabled, so will not be used in any way.121disabled?: boolean;122// priority -- optional integer used for sorting options to display to user. The bigger the higher.123priority?: number;124// proxy: if false, do NOT run https proxy server on host VM125// if nothing given, runs proxy server with no default config (so does nothing)126// if given, is array of proxy config.127proxy?: false | ProxyRoute[];128apps?: {129[name: string]: {130icon: string;131label: string;132url: string;133path: string;134launch: string;135requiresDns?: boolean;136};137};138}139140export type Images = { [name: string]: Image };141142export interface GoogleCloudImage {143labels: { [name: string]: string };144diskSizeGb: number;145creationTimestamp: string;146}147export type GoogleCloudImages = { [name: string]: GoogleCloudImage };148149// valid for google cloud -- probably not sufficient150export function makeValidGoogleName(s: string): string {151return s.replace(/[._]/g, "-").toLowerCase().slice(0, 63);152}153154export type State =155| "off"156| "starting"157| "running"158| "stopping"159| "deprovisioned"160| "suspending"161| "suspended"162| "unknown";163164// used for sorting by state -- ordered from my alive to least alive.165export const ORDERED_STATES: State[] = [166"running",167"starting",168"stopping",169"suspending",170"suspended",171"off",172"deprovisioned",173"unknown",174];175export const STATE_TO_NUMBER: { [state: string]: number } = {};176let n = 0;177for (const state of ORDERED_STATES) {178STATE_TO_NUMBER[state] = n;179n += 1;180}181182export type Action =183| "start"184| "resume"185| "stop"186| "suspend"187| "deprovision"188| "reboot";189190export const ACTION_INFO: {191[action: string]: {192label: string;193icon: string;194tip: string;195description: string;196confirm?: boolean;197confirmMessage?: string;198danger?: boolean;199target: State; // target stable state after doing this action.200clouds?: Cloud[];201};202} = {203start: {204label: "Start",205icon: "play",206tip: "Start",207description: "Start the compute server running.",208target: "running",209},210resume: {211label: "Resume",212icon: "play",213clouds: ["google-cloud"],214tip: "Resume",215description: "Resume the compute server from suspend.",216target: "running",217},218stop: {219label: "Stop",220icon: "stop",221tip: "Turn off",222description:223"Turn the compute server off. No data on disk is lost, but any data and state in memory will be lost. This is like turning your laptop off.",224confirm: true,225target: "off",226},227deprovision: {228label: "Deprovision",229icon: "trash",230tip: "Deprovision the virtual machine",231description:232"Deprovisioning DELETES THE VIRTUAL MACHINE BOOT DISK, but keeps the compute server parameters. There are no costs associated with a deprovisioned compute server, and you can move it to a different region or zone. Any files in the home directory of your project are not affected.",233confirm: true,234confirmMessage:235"I understand that my compute server disks will be deleted.",236danger: true,237target: "deprovisioned",238},239reboot: {240label: "Hard Reboot",241icon: "refresh",242tip: "Hard reboot the virtual machine.",243description:244"Perform a HARD reset on the virtual machine, which wipes the memory contents and resets the virtual machine to its initial state. This should not delete data from the disk, but can lead to filesystem corruption.",245confirm: true,246confirmMessage:247"I understand that this can lead to filesystem corruption and is slightly dangerous.",248danger: true,249target: "running",250clouds: ["google-cloud", "hyperstack"],251},252suspend: {253label: "Suspend",254icon: "pause",255clouds: ["google-cloud"],256tip: "Suspend disk and memory state",257confirm: true,258description:259"Suspend the compute server. No data on disk or memory is lost, and you are only charged for storing disk and memory. This is like closing your laptop screen. You can leave a compute server suspended for up to 60 days before it automatically shuts off.",260target: "suspended",261},262};263264export const STATE_INFO: {265[state: string]: {266label: string;267actions: Action[];268icon: string;269color?: string;270stable?: boolean;271target?: State; // if not stable, this is the target state it is heading to272};273} = {274off: {275label: "Off",276color: "#ff4b00",277actions: ["start", "deprovision"],278icon: "stop",279stable: true,280},281suspended: {282label: "Suspended",283actions: ["resume", "deprovision", "stop"],284icon: "pause",285color: "#0097a7",286stable: true,287},288suspending: {289label: "Suspending",290actions: ["suspend"],291icon: "pause",292color: "#00bcd4",293stable: false,294target: "suspended",295},296starting: {297label: "Starting",298color: "#388e3c",299actions: ["start"],300icon: "bolt",301stable: false,302target: "running",303},304running: {305label: "Running",306color: COLORS.RUN,307actions: ["stop", "deprovision", "reboot", "suspend"],308icon: "run",309stable: true,310},311stopping: {312label: "Stopping",313color: "#ff9800",314actions: ["stop"],315icon: "hand",316stable: false,317target: "off",318},319unknown: {320label: "Unknown (click to refresh)",321actions: [],322icon: "question-circle",323stable: true,324},325deprovisioned: {326label: "Deprovisioned",327actions: ["start"],328color: "#888",329icon: "minus-square",330stable: true,331},332};333334export function getTargetState(x: State | Action): State {335if (ACTION_INFO[x] != null) {336return ACTION_INFO[x].target;337}338if (STATE_INFO[x] != null) {339if (!STATE_INFO[x]?.stable) {340return (STATE_INFO[x].target ?? x) as State;341}342return x as State;343}344throw Error(`x =${x} must be a state or action`);345}346347export type Architecture = "x86_64" | "arm64";348349// Convention is used in cocalc-compute-docker for making350// the npm packages @cocalc/compute-server. Don't mess with it!351export function getImageField(arch: Architecture) {352return arch == "x86_64" ? "package" : "package_arm64";353}354355export type Cloud =356| "any"357| "onprem"358| "core-weave"359| "hyperstack"360| "lambda-cloud"361| "google-cloud"362| "aws"363| "fluid-stack"364| "test";365366export function getMinDiskSizeGb({367configuration,368IMAGES,369}: {370configuration;371IMAGES: Images;372}) {373if (configuration?.image) {374const { minDiskSizeGb } = IMAGES[configuration.image] ?? {};375if (minDiskSizeGb) {376return minDiskSizeGb;377}378}379// TODO: will have to do something based on actual image size,380// maybe, unless I come up with a clever trick involving381// one PD mounted on many machines (?).382if (configuration?.acceleratorType) {383return CUDA_DISK_SIZE;384} else {385return STANDARD_DISK_SIZE;386}387}388389interface BaseConfiguration {390// image: name of the image to use, e.g. 'python' or 'pytorch'.391// images are managed in src/packages/server/compute/images.ts392image: string;393// tag: tag for the image to use when starting the compute server.394// this references the versions field of the image.395// If the tag is not given or not available, we use the latest396// available tag.397tag?: string;398// tag_filesystem: tag for the file system container399tag_filesystem?: string;400// tag_cocalc: tag for the @cocalc/compute-server package.401tag_cocalc?: string;402// dns - If the string is set and the VM has an external ip address403// and dns is configured, then point https://{dns}....404// with ssl proxying to this compute server when it is running.405dns?: string;406// Array of top level directories to exclude from sync.407// These can't have "|" in them, since we use that as a separator.408// Use "~" to completely disable sync.409excludeFromSync?: readonly string[];410// If true, view data on the compute server as ephemeral.411// Currently this is only meant to impact the user interface.412ephemeral?: boolean;413// Token used for authentication at https://compute-server...414authToken?: string;415// Configuration of the https proxy server.416proxy?: ProxyRoute[];417// If this compute server stops pinging us, e.g., due to being preempted or418// just crashing due to out of memory (etc) should we automatically do a419// forced restart. Note that currently for on prem this isn't possible.420autoRestart?: boolean;421autoRestartDisabled?: boolean; // used to temporarily disable it to avoid accidentally triggering it.422// Allow collaborators to control the state of the compute server.423// They cannot change any other configuration. User still pays for everything and owns compute server.424allowCollaboratorControl?: boolean;425}426427interface LambdaConfiguration extends BaseConfiguration {428cloud: "lambda-cloud";429instance_type_name: string;430region_name: string;431}432433export interface HyperstackConfiguration extends BaseConfiguration {434cloud: "hyperstack";435flavor_name: string;436region_name: HyperstackRegion;437// diskSizeGb is an integer >= 1. It defaults to 10.438// It's the size of the /data partition. It's implemented439// using 1 or more hyperstack (=ceph) volumes, which are combined440// together as a ZFS pool. If the compute server is441// named "foo", the volumes are named "foo-1", "foo-2",442// "foo-3", etc.443// There is also always a separate 50GB root volume, which444// is named "foo-0", and whose size is not configurable.445// NOTE: users install packages "systemwide" inside of446// a docker container and we configure docker to store447// its data in the zpool, so that's in here too.448diskSizeGb: number;449}450451export const COREWEAVE_CPU_TYPES = [452"amd-epyc-rome",453"amd-epyc-milan",454"intel-xeon-v1",455"intel-xeon-v2",456"intel-xeon-v3",457"intel-xeon-v4",458"intel-xeon-scalable",459] as const;460461export const COREWEAVE_GPU_TYPES = [462"Quadro_RTX_4000",463"Quadro_RTX_5000",464"RTX_A4000",465"RTX_A5000",466"RTX_A6000",467"A40",468"Tesla_V100_PCIE",469"Tesla_V100_NVLINK",470"A100_PCIE_40GB",471"A100_PCIE_80GB",472"A100_NVLINK_40GB",473"A100_NVLINK_80GB",474] as const;475476interface CoreWeaveConfiguration extends BaseConfiguration {477cloud: "core-weave";478gpu: {479type:480| "Quadro_RTX_4000"481| "Quadro_RTX_5000"482| "RTX_A4000"483| "RTX_A5000"484| "RTX_A6000"485| "A40"486| "Tesla_V100_PCIE"487| "Tesla_V100_NVLINK"488| "A100_PCIE_40GB"489| "A100_PCIE_80GB"490| "A100_NVLINK_40GB"491| "A100_NVLINK_80GB"; //(typeof COREWEAVE_GPU_TYPES)[number];492count: number;493};494cpu: {495count: number;496type?:497| "amd-epyc-rome"498| "amd-epyc-milan"499| "intel-xeon-v1"500| "intel-xeon-v2"501| "intel-xeon-v3"502| "intel-xeon-v4"503| "intel-xeon-scalable"; //(typeof COREWEAVE_CPU_TYPES)[number];504};505memory: string; // e.g., "12Gi"506storage?: {507root: {508size: string; // e.g., '40Gi'509};510};511}512513interface FluidStackConfiguration extends BaseConfiguration {514cloud: "fluid-stack";515plan: string;516region: string;517os: string;518}519export type GoogleCloudAcceleratorType =520| "nvidia-a100-80gb"521| "nvidia-tesla-a100"522| "nvidia-l4"523| "nvidia-tesla-t4"524| "nvidia-tesla-v100"525| "nvidia-tesla-p4"526| "nvidia-tesla-p100";527528export const GOOGLE_CLOUD_ACCELERATOR_TYPES: GoogleCloudAcceleratorType[] = [529"nvidia-a100-80gb",530"nvidia-tesla-a100",531"nvidia-l4",532"nvidia-tesla-t4",533"nvidia-tesla-v100",534"nvidia-tesla-p4",535"nvidia-tesla-p100",536];537538export type GoogleCloudDiskType =539| "pd-standard"540| "pd-balanced"541| "pd-ssd"542| "hyperdisk-balanced";543544export const GOOGLE_CLOUD_DISK_TYPES: GoogleCloudDiskType[] = [545"pd-standard",546"pd-balanced",547"pd-ssd",548// NOTE: hyperdisks are complicated and multidimensional, but for cocalc549// we just hardcode options for the iops and bandwidth, and allow the550// user to adjust the size. Also, "hyperdisk-balanced" means hyperdisk551// with the defaults for iops and bandwidth defined in552// src/packages/util/compute/cloud/google-cloud/compute-cost.ts553"hyperdisk-balanced",554];555556export interface GoogleCloudConfiguration extends BaseConfiguration {557cloud: "google-cloud";558region: string;559zone: string;560machineType: string;561// Ues a spot instance if spot is true.562spot?: boolean;563// The boot disk:564// diskSizeGb is an integer >= 10. It defaults to 10. It's the size of the boot disk.565diskSizeGb?: number;566hyperdiskBalancedIops?: number;567hyperdiskBalancedThroughput?: number;568diskType?: GoogleCloudDiskType;569acceleratorType?: GoogleCloudAcceleratorType;570// the allowed number depends on the accelerator; it defaults to 1.571acceleratorCount?: number;572// minCpuPlatform573terminationTime?: Date;574maxRunDurationSeconds?: number;575// if true, use newest image, whether or not it is labeled with prod=true.576test?: boolean;577// an image name of the form "2023-09-13-063355-test", i.e., a timestamp in that format578// followed by an optional string. Whether or not to use cuda and and the arch are579// determined by parameters above. This is meant to be used for two purposes (1) testing580// before deploying to production, and (2) stability, so a given compute server has the581// exact same base image every time it is started, instead of being updated. Regarding (2),582// this might not be needed, but we'll see. If image is not set, we use the newest583// image that is tagged prod:true, or its an error if no such image exists. This is584// all about Google Cloud images, not the IMAGES object defined elsewhere in this file.585sourceImage?: string;586// If true, then we have an external ip address587externalIp?: boolean;588// If true, can run full VM's inside of the machine, but there is 10% performance penalty.589// This will only work for Intel non-e2 non-a3 instance types. No AMD and no ARM64.590enableNestedVirtualization?: boolean;591}592593export interface OnPremCloudConfiguration extends BaseConfiguration {594cloud: "onprem";595arch?: Architecture;596gpu?: boolean;597}598599export type Configuration =600| LambdaConfiguration601| HyperstackConfiguration602| CoreWeaveConfiguration603| FluidStackConfiguration604| GoogleCloudConfiguration605| OnPremCloudConfiguration;606607interface BaseData {608cloudflareId?: string;609externalIp?: string;610internalIp?: string;611}612613export interface LambdaCloudData extends BaseData {614cloud: "lambda-cloud";615instance_id: string;616}617618export interface HyperstackData extends BaseData {619cloud: "hyperstack";620// name we are using for the vm621name?: string;622// hyperstack description of this vm.623vm?: HyperstackVirtualMachine;624// id's of persistent storage, with first id the boot disk.625// disks are named {name}-0, {name}-1, {name}-2, etc.,626// with {name}-0 being the boot disk.627disks?: number[];628creationTimestamp?: Date;629}630631export interface GoogleCloudData extends BaseData {632cloud: "google-cloud";633name?: string;634state?: State;635cpuPlatform?: string;636creationTimestamp?: Date;637lastStartTimestamp?: Date;638}639640export type Data = GoogleCloudData | LambdaCloudData | HyperstackData;641642export interface ComponentState {643state: string;644time: number;645expire?: number;646}647648export interface ComputeServerTemplate {649enabled?: boolean;650priority?: number;651}652653export interface ComputeServerUserInfo {654id: number;655project_specific_id?: number; // the project_specific_id of this compute server -- unique within project, minimal656account_id: string;657project_id: string;658title?: string;659color?: string;660cost_per_hour?: number;661deleted?: boolean;662state_changed?: Date;663started_by?: string;664error?: string;665state?: State;666idle_timeout?: number;667automatic_shutdown?: AutomaticShutdown;668// google-cloud has a new "Time limit" either by hour or by date, which seems like a great idea!669// time_limit670autorestart?: boolean;671cloud: Cloud;672configuration: Configuration;673provisioned_configuration?: Configuration;674data?: Data;675purchase_id?: number;676last_edited?: Date;677position?: number; // used for UI sorting.678detailed_state?: { [name: string]: ComponentState };679update_purchase?: boolean;680last_purchase_update?: Date;681template?: ComputeServerTemplate;682}683684export interface ComputeServer extends ComputeServerUserInfo {685api_key?: string; // project level api key for the project686api_key_id?: number; // id of the api key (needed so we can delete it from database).687}688689Table({690name: "compute_servers",691rules: {692primary_key: "id",693// unique vpn ip address *within* a given project only:694pg_unique_indexes: [695"(project_id, vpn_ip)",696"(project_id, project_specific_id)",697],698user_query: {699get: {700pg_where: [{ "project_id = $::UUID": "project_id" }],701throttle_changes: 0, // do not make this bigger; UI really feels off if throttled702fields: {703id: null,704account_id: null,705created: null,706title: null,707color: null,708cost_per_hour: null,709deleted: null,710project_id: null,711state_changed: null,712error: null,713state: null,714idle_timeout: null,715automatic_shutdown: null,716autorestart: null,717cloud: null,718configuration: null,719data: null,720provisioned_configuration: null,721avatar_image_tiny: null,722last_edited: null,723purchase_id: null,724position: null,725detailed_state: null,726template: null,727notes: null,728vpn_ip: null,729project_specific_id: null,730},731},732set: {733// ATTN: It's assumed that users can't set the data field. Doing so would be very bad and could allow734// them to maybe abuse the system and not pay for something.735// Most fields, e.g., configuration, get set via api calls, which ensures consistency in terms of valid736// data and what is actively deployed.737fields: {738project_id: "project_write",739id: true,740position: true,741error: true, // easily clear the error742notes: true,743automatic_shutdown: true,744},745},746},747},748fields: {749id: ID,750account_id: {751type: "uuid",752desc: "User that owns this compute server.",753render: { type: "account" },754},755created: {756type: "timestamp",757desc: "When the compute server was created.",758},759title: {760type: "string",761pg_type: "VARCHAR(254)",762desc: "Title of this computer server. Used purely to make it easier for the user to keep track of it.",763render: { type: "text", maxLength: 254, editable: true },764},765color: {766type: "string",767desc: "A user configurable color, which is used for tags and UI to indicate where a tab is running.",768pg_type: "VARCHAR(30)",769render: { type: "color", editable: true },770},771cost_per_hour: {772title: "Cost per Hour",773desc: "The cost in US dollars per hour that this compute server cost us when it is provisioned. Any time the state is changed, this is set by the server to the proper cost.",774type: "number",775pg_type: "real",776},777deleted: {778type: "boolean",779desc: "True if the compute server has been deleted.",780},781project_id: {782type: "uuid",783desc: "The project id that this compute server provides compute for.",784render: { type: "project_link" },785},786api_key: {787type: "string",788pg_type: "VARCHAR(128)",789desc: "api key to connect to the project. This is created by the system right when we are going to create the VM, and gets deleted when we stop it. It's not set by the user and should not be revealed to the user.",790},791api_key_id: {792type: "number",793desc: "id of the api key; needed so we can delete it from database",794},795state_changed: {796type: "timestamp",797desc: "When the state last changed.",798},799error: {800type: "string",801desc: "In case something went wrong, e.g., in starting this compute server, this field will get set with a string error message to show the user. It's also cleared right when we try to start server.",802},803state: {804type: "string",805desc: "One of - 'off', 'starting', 'running', 'stopping'. This is the underlying VM's state.",806pg_type: "VARCHAR(16)",807},808idle_timeout: {809type: "number",810desc: "The idle timeout in seconds of this compute server. If set to 0, never turn it off automatically. The compute server idle timeouts if none of the tabs it is providing are actively touched through the web UI.",811},812automatic_shutdown: {813type: "map",814pg_type: "jsonb",815desc: "Configuration to control various aspects of the state of the compute server via a background maintenance task.",816},817autorestart: {818type: "boolean",819desc: "If true and the compute server stops for any reason, then it will be automatically started again. This is primarily useful for stop instances.",820},821cloud: {822type: "string",823pg_type: "varchar(30)",824desc: "The cloud where this compute server runs: 'user', 'coreweave', 'lambda', 'google-cloud', 'aws', 'fluidstack'.",825},826configuration: {827type: "map",828pg_type: "jsonb",829desc: "Cloud specific configuration of the computer at the cloud host. The format depends on the cloud",830},831provisioned_configuration: {832type: "map",833pg_type: "jsonb",834desc: "Same as configuration, but this is the one we actually used last time we provisioned a VM in a cloud.",835},836data: {837type: "map",838pg_type: "jsonb",839desc: "Arbitrary data about this server that is cloud provider specific. Store data here to facilitate working with the virtual machine, e.g., the id of the server when it is running, etc. This *IS* returned to the user.",840},841avatar_image_tiny: {842title: "Image",843type: "string",844desc: "tiny (32x32) visual image associated with the compute server. Suitable to include as part of changefeed, since about 3kb. Derived from avatar_image_full.",845render: { type: "image" },846},847avatar_image_full: {848title: "Image",849type: "string",850desc: "User configurable visual image associated with the compute server. Could be 150kb. NOT include as part of changefeed of projects, since potentially big (e.g., 200kb x 1000 projects = 200MB!).",851render: { type: "image" },852},853purchase_id: {854type: "number",855desc: "if there is a current active purchase related to this compute server, this is the id of that purchase in the purchases table",856},857update_purchase: {858type: "boolean",859desc: "This is set to true if activity with this server is happening that warrants creating/ending a purchase.",860},861last_purchase_update: {862type: "timestamp",863desc: "Last time we requested an update to the purchase info about this compute server.",864},865position: {866type: "number",867desc: "Used for sorting a list of compute servers in the UI.",868},869last_edited: {870type: "timestamp",871desc: "Last time the configuration, state, etc., changed.",872},873detailed_state: {874type: "map",875pg_type: "jsonb",876desc: "Map from component name to something like {state:'running',time:Date.now()}, e.g., {vm: {state:'running', time:393939938484}}, filesystem: {state:'updating', time:939398484892}, uptime:{state:'22:56:33 up 3 days, 9:28, 0 users, load average: 0.93, 0.73, 0.56', time:?}}. This is used to provide users with insight into what's currently happening on their compute server.",877},878notes: NOTES,879template: {880type: "map",881pg_type: "jsonb",882desc: "Use this compute server configuration as a public template. Only admins can set this field for now. The exact structure of this jsonb is yet to be determined.",883},884vpn_ip: {885type: "string",886desc: "IP address of the compute server on the private encrypted project-wide VPN.",887},888vpn_public_key: {889type: "string",890desc: "Wireguard public key for this compute server.",891},892vpn_private_key: {893type: "string",894desc: "Wireguard private key for this compute server.",895},896project_specific_id: {897type: "integer",898desc: "A unique project-specific id assigned to this compute server. This is a positive integer that is guaranteed to be unique for compute servers *in a given project* and minimal when assigned (so it is as small as possible). This number is useful for distributed algorithms, since it can be used to ensure distinct sequence without any additional coordination. This is also useful to display to users so that the id number they see everywhere is not huge.",899},900},901});902903Table({904name: "crm_compute_servers",905fields: schema.compute_servers.fields,906rules: {907primary_key: schema.compute_servers.primary_key,908virtual: "compute_servers",909user_query: {910get: {911admin: true, // only admins can do get queries on this table912// (without this, users who have read access could read)913pg_where: [],914fields: {915...schema.compute_servers.user_query?.get?.fields,916template: null,917},918},919set: {920admin: true,921fields: {922id: true,923title: true,924color: true,925deleted: true,926notes: true,927template: true,928state_control: null,929},930},931},932},933});934935Table({936name: "compute_servers_cache",937fields: {938cloud: {939type: "string",940desc: "The cloud that we're caching information about",941},942key: {943type: "string",944desc: "The key for whatever we're caching.",945},946value: {947type: "string",948desc: "The cached data.",949},950expire: {951type: "timestamp",952desc: "When this action should be expired.",953},954},955rules: {956durability: "soft", // it's just a cache957desc: "Cache data about what's going on in various clouds that are used to implement compute servers.",958primary_key: ["cloud", "key"],959},960});961962963