Path: blob/master/src/packages/file-server/zfs/create.ts
1447 views
import { create, get, getDb, deleteFromDb, filesystemExists } from "./db";1import { exec } from "./util";2import {3filesystemArchivePath,4bupFilesystemMountpoint,5filesystemDataset,6filesystemMountpoint,7} from "./names";8import { getPools, initializePool } from "./pools";9import { dearchiveFilesystem } from "./archive";10import { UID, GID } from "./config";11import { createSnapshot } from "./snapshots";12import { type Filesystem, primaryKey, type PrimaryKey } from "./types";1314export async function createFilesystem(15opts: PrimaryKey & {16affinity?: string;17clone?: PrimaryKey;18},19): Promise<Filesystem> {20if (filesystemExists(opts)) {21return get(opts);22}23const pk = primaryKey(opts);24const { namespace } = pk;25const { affinity, clone } = opts;26const source = clone ? get(clone) : undefined;2728const db = getDb();29// select a pool:30let pool: undefined | string = undefined;3132if (source != null) {33// use same pool as source filesystem. (we could use zfs send/recv but that's much slower and not a clone)34pool = source.pool;35} else {36if (affinity) {37// if affinity is set, have preference to use same pool as other filesystems with this affinity.38const x = db39.prepare(40"SELECT pool, COUNT(pool) AS cnt FROM filesystems WHERE namespace=? AND affinity=? ORDER by cnt DESC",41)42.get(namespace, affinity) as { pool: string; cnt: number } | undefined;43pool = x?.pool;44}45if (!pool) {46// assign one with *least* filesystems47const x = db48.prepare(49"SELECT pool, COUNT(pool) AS cnt FROM filesystems GROUP BY pool ORDER by cnt ASC",50)51.all() as any;52const pools = await getPools();53if (Object.keys(pools).length > x.length) {54// rare case: there exists a pool that isn't used yet, so not55// represented in above query at all; use it56const v = new Set<string>();57for (const { pool } of x) {58v.add(pool);59}60for (const name in pools) {61if (!v.has(name)) {62pool = name;63break;64}65}66} else {67if (x.length == 0) {68throw Error("cannot create filesystem -- no available pools");69}70// just use the least crowded71pool = x[0].pool;72}73}74}75if (!pool) {76throw Error("bug -- unable to select a pool");77}7879const { cnt } = db80.prepare(81"SELECT COUNT(pool) AS cnt FROM filesystems WHERE pool=? AND namespace=?",82)83.get(pool, namespace) as { cnt: number };8485if (cnt == 0) {86// initialize pool for use in this namespace:87await initializePool({ pool, namespace });88}8990if (source == null) {91// create filesystem on the selected pool92const mountpoint = filesystemMountpoint(pk);93const dataset = filesystemDataset({ ...pk, pool });94await exec({95verbose: true,96command: "sudo",97args: [98"zfs",99"create",100"-o",101`mountpoint=${mountpoint}`,102"-o",103"compression=lz4",104"-o",105"dedup=on",106dataset,107],108what: {109...pk,110desc: `create filesystem ${dataset} for filesystem on the selected pool mounted at ${mountpoint}`,111},112});113await exec({114verbose: true,115command: "sudo",116args: ["chown", "-R", `${UID}:${GID}`, mountpoint],117whate: {118...pk,119desc: `setting permissions of filesystem mounted at ${mountpoint}`,120},121});122} else {123// clone source124// First ensure filesystem isn't archived125// (we might alternatively de-archive to make the clone...?)126if (source.archived) {127await dearchiveFilesystem(source);128}129// Get newest snapshot, or make one if there are none130const snapshot = await createSnapshot({ ...source, ifChanged: true });131if (!snapshot) {132throw Error("bug -- source should have snapshot");133}134const source_snapshot = `${filesystemDataset(source)}@${snapshot}`;135await exec({136verbose: true,137command: "sudo",138args: [139"zfs",140"clone",141"-o",142`mountpoint=${filesystemMountpoint(pk)}`,143"-o",144"compression=lz4",145"-o",146"dedup=on",147source_snapshot,148filesystemDataset({ ...pk, pool }),149],150what: {151...pk,152desc: `clone filesystem from ${source_snapshot}`,153},154});155}156157// update database158create({ ...pk, pool, affinity });159return get(pk);160}161162// delete -- This is very dangerous -- it deletes the filesystem,163// the archive, and any backups and removes knowledge the filesystem from the db.164165export async function deleteFilesystem(fs: PrimaryKey) {166const filesystem = get(fs);167const dataset = filesystemDataset(filesystem);168if (!filesystem.archived) {169await exec({170verbose: true,171command: "sudo",172args: ["zfs", "destroy", "-r", dataset],173what: {174...filesystem,175desc: `destroy dataset ${dataset} containing the filesystem`,176},177});178}179await exec({180verbose: true,181command: "sudo",182args: ["rm", "-rf", filesystemMountpoint(filesystem)],183what: {184...filesystem,185desc: `delete directory '${filesystemMountpoint(filesystem)}' where filesystem was stored`,186},187});188await exec({189verbose: true,190command: "sudo",191args: ["rm", "-rf", bupFilesystemMountpoint(filesystem)],192what: {193...filesystem,194desc: `delete directory '${bupFilesystemMountpoint(filesystem)}' where backups were stored`,195},196});197await exec({198verbose: true,199command: "sudo",200args: ["rm", "-rf", filesystemArchivePath(filesystem)],201what: {202...filesystem,203desc: `delete directory '${filesystemArchivePath(filesystem)}' where archives were stored`,204},205});206deleteFromDb(filesystem);207}208209210