Path: blob/master/src/packages/frontend/compute/api.ts
1503 views
import api from "@cocalc/frontend/client/api";1import type {2Action,3Cloud,4ComputeServerTemplate,5ComputeServerUserInfo,6Configuration,7Images,8GoogleCloudImages,9} from "@cocalc/util/db-schema/compute-servers";10import type { GoogleCloudData } from "@cocalc/util/compute/cloud/google-cloud/compute-cost";11import type { HyperstackPriceData } from "@cocalc/util/compute/cloud/hyperstack/pricing";12import type {13ConfigurationTemplate,14ConfigurationTemplates,15} from "@cocalc/util/compute/templates";16import { reuseInFlight } from "@cocalc/util/reuse-in-flight";17import TTL from "@isaacs/ttlcache";1819export async function createServer(opts: {20project_id: string;21title?: string;22color?: string;23autorestart?: boolean;24cloud?: Cloud;25configuration?: Configuration;26notes?: string;27course_project_id?: string;28course_server_id?: number;29}): Promise<number> {30return await api("compute/create-server", opts);31}3233export async function computeServerAction(opts: {34id: number;35action: Action;36}) {37await api("compute/compute-server-action", opts);38}3940// Get servers across potentially different projects by their global unique id.41// Use the fields parameter to restrict to a much smaller subset of information42// about each server (e.g., just the state field). Caller must be a collaborator43// on each project containing the servers.44// If you give an id of a server that doesn't exist, it'll just be excluded in the result.45// Similarly, if you give a field that doesn't exist, it is excluded.46// The order of the returned servers and count probably will NOT match that in47// ids, so you should include 'id' in fields.48export async function getServersById(opts: {49ids: number[];50fields?: string[];51}): Promise<Partial<ComputeServerUserInfo>[]> {52return await api("compute/get-servers-by-id", opts);53}5455export async function getServers(opts: {56id?: number;57project_id: string;58}): Promise<ComputeServerUserInfo[]> {59return await api("compute/get-servers", opts);60}6162export async function getServerState(id: number) {63return await api("compute/get-server-state", { id });64}6566export async function getSerialPortOutput(id: number) {67return await api("compute/get-serial-port-output", { id });68}6970export async function deleteServer(id: number) {71return await api("compute/delete-server", { id });72}7374export async function isDnsAvailable(dns: string) {75const { isAvailable } = await api("compute/is-dns-available", { dns });76return isAvailable;77}7879export async function undeleteServer(id: number) {80await api("compute/undelete-server", { id });81}8283// only owner can change properties of a compute server.8485export async function setServerColor(opts: { id: number; color: string }) {86return await api("compute/set-server-color", opts);87}8889export async function setServerTitle(opts: { id: number; title: string }) {90return await api("compute/set-server-title", opts);91}9293export async function setServerConfiguration(opts: {94id: number;95configuration: Partial<Configuration>;96}) {97return await api("compute/set-server-configuration", opts);98}99100// only for admins!101export async function setTemplate(opts: {102id: number;103template: ComputeServerTemplate;104}) {105return await api("compute/set-template", opts);106}107108// 5-minute client side ttl cache of all and specific template, since109// templates change rarely.110111const templatesCache = new TTL({ ttl: 60 * 1000 * 5 });112113export async function getTemplate(id: number): Promise<ConfigurationTemplate> {114if (templatesCache.has(id)) {115return templatesCache.get(id)!;116}117const x = await api("compute/get-template", { id });118templatesCache.set(id, x);119return x;120}121122export async function getTemplates(): Promise<ConfigurationTemplates> {123if (templatesCache.has("templates")) {124return templatesCache.get("templates")!;125}126const x = await api("compute/get-templates");127templatesCache.set("templates", x);128return x;129}130131export async function setServerCloud(opts: { id: number; cloud: string }) {132return await api("compute/set-server-cloud", opts);133}134135export async function setServerOwner(opts: {136id: number;137new_account_id: string;138}) {139return await api("compute/set-server-owner", opts);140}141142// Cache for 12 hours143let googleCloudPriceData: GoogleCloudData | null = null;144let googleCloudPriceDataExpire: number = 0;145export const getGoogleCloudPriceData = reuseInFlight(146async (): Promise<GoogleCloudData> => {147if (148googleCloudPriceData == null ||149Date.now() >= googleCloudPriceDataExpire150) {151googleCloudPriceData = await api("compute/google-cloud/get-pricing-data");152googleCloudPriceDataExpire = Date.now() + 1000 * 60 * 60 * 12; // 12 hour cache153}154if (googleCloudPriceData == null) {155throw Error("bug");156}157return googleCloudPriceData;158},159);160161import { useState, useEffect } from "react";162export function useGoogleCloudPriceData() {163const [priceData, setPriceData] = useState<null | GoogleCloudData>(null);164const [error, setError] = useState<string>("");165useEffect(() => {166(async () => {167try {168setError("");169setPriceData(await getGoogleCloudPriceData());170} catch (err) {171setError(`${err}`);172}173})();174}, []);175return [priceData, error];176}177178// Cache for 5 minutes -- cache less since this includes realtime179// information about GPU availability.180let hyperstackPriceData: HyperstackPriceData | null = null;181let hyperstackPriceDataExpire: number = 0;182export const getHyperstackPriceData = reuseInFlight(183async (): Promise<HyperstackPriceData> => {184if (185hyperstackPriceData == null ||186Date.now() >= hyperstackPriceDataExpire187) {188hyperstackPriceData = await api("compute/get-hyperstack-pricing-data");189hyperstackPriceDataExpire = Date.now() + 1000 * 60 * 5; // 5 minute cache190}191if (hyperstackPriceData == null) {192throw Error("bug");193}194return hyperstackPriceData;195},196);197198// Returns network usage during the given interval. Returns199// amount in GiB and cost at our current rate.200export async function getNetworkUsage(opts: {201id: number;202start: Date;203end: Date;204}): Promise<{ amount: number; cost: number }> {205return await api("compute/get-network-usage", opts);206}207208// Get the current api key for a specific (on prem) server.209// We only need this for on prem, so we are restricting to that right now.210// If no key is allocated, one will be created.211export async function getApiKey(opts: { id }): Promise<string> {212return await api("compute/get-api-key", opts);213}214export async function deleteApiKey(opts: { id }): Promise<string> {215return await api("compute/delete-api-key", opts);216}217218// Get the project log entries directly for just one compute server219export async function getLog(opts: { id; type: "activity" | "files" }) {220return await api("compute/get-log", opts);221}222223export const getTitle = reuseInFlight(224async (opts: {225id: number;226}): Promise<{227title: string;228color: string;229project_specific_id: number;230}> => {231return await api("compute/get-server-title", opts);232},233);234235// Setting a detailed state component for a compute server236export async function setDetailedState(opts: {237project_id: string;238id: number;239name: string;240state?: string;241extra?: string;242timeout?: number;243progress?: number;244}) {245return await api("compute/set-detailed-state", opts);246}247248// We cache images for 5 minutes.249const IMAGES_TTL = 5 * 60 * 1000;250251const imagesCache: {252[cloud: string]: { timestamp: number; images: Images | null };253} = {};254255function cacheHas(cloud: string) {256const x = imagesCache[cloud];257if (x == null) {258return false;259}260if (Math.abs(x.timestamp - Date.now()) <= IMAGES_TTL) {261return true;262}263return false;264}265266function cacheGet(cloud) {267return imagesCache[cloud]?.images;268}269270function cacheSet(cloud, images) {271imagesCache[cloud] = { images, timestamp: Date.now() };272}273274async function getImagesFor({275cloud,276endpoint,277reload,278}: {279cloud: string;280endpoint: string;281reload?: boolean;282}): Promise<any> {283if (!reload && cacheHas(cloud)) {284return cacheGet(cloud);285}286287try {288const images = await api(289endpoint,290// admin reload forces fetch data from github and/or google cloud - normal users just have their cache ignored above291reload ? { noCache: true } : undefined,292);293cacheSet(cloud, images);294return images;295} catch (err) {296const images = cacheGet(cloud);297if (images != null) {298console.warn(299"ERROR getting updated compute server images -- using cached data",300err,301);302return images;303}304throw err;305}306}307308export async function getImages(reload?: boolean): Promise<Images> {309return await getImagesFor({310cloud: "",311endpoint: "compute/get-images",312reload,313});314}315316export async function getGoogleCloudImages(317reload?: boolean,318): Promise<GoogleCloudImages> {319return await getImagesFor({320cloud: "google",321endpoint: "compute/get-images-google",322reload,323});324}325326export async function setImageTested(opts: {327id: number; // server id328tested: boolean;329}) {330return await api("compute/set-image-tested", opts);331}332333334