mirror of
https://github.com/nicotsx/zerobyte.git
synced 2026-04-21 07:22:47 -04:00
refactor(volume-backend): assert mounted helper
This commit is contained in:
@@ -7,7 +7,7 @@ import { logger } from "@zerobyte/core/node";
|
||||
import { getMountForPath } from "../../../utils/mountinfo";
|
||||
import { withTimeout } from "../../../utils/timeout";
|
||||
import type { VolumeBackend } from "../backend";
|
||||
import { executeMount, executeUnmount } from "../utils/backend-utils";
|
||||
import { assertMounted, executeMount, executeUnmount } from "../utils/backend-utils";
|
||||
|
||||
const mount = async (config: BackendConfig, path: string) => {
|
||||
logger.debug(`Mounting volume ${path}...`);
|
||||
@@ -111,21 +111,7 @@ const unmount = async (path: string) => {
|
||||
|
||||
const checkHealth = async (path: string) => {
|
||||
const run = async () => {
|
||||
try {
|
||||
await fs.access(path);
|
||||
} catch {
|
||||
throw new Error("Volume is not mounted");
|
||||
}
|
||||
|
||||
const mount = await getMountForPath(path);
|
||||
|
||||
if (!mount || mount.mountPoint !== path) {
|
||||
throw new Error("Volume is not mounted");
|
||||
}
|
||||
|
||||
if (!mount.fstype.startsWith("nfs")) {
|
||||
throw new Error(`Path ${path} is not mounted as NFS (found ${mount.fstype}).`);
|
||||
}
|
||||
await assertMounted(path, (fstype) => fstype.startsWith("nfs"));
|
||||
|
||||
logger.debug(`NFS volume at ${path} is healthy and mounted.`);
|
||||
return { status: BACKEND_STATUS.mounted };
|
||||
|
||||
@@ -6,7 +6,7 @@ import { logger } from "@zerobyte/core/node";
|
||||
import { getMountForPath } from "../../../utils/mountinfo";
|
||||
import { withTimeout } from "../../../utils/timeout";
|
||||
import type { VolumeBackend } from "../backend";
|
||||
import { executeUnmount } from "../utils/backend-utils";
|
||||
import { assertMounted, executeUnmount } from "../utils/backend-utils";
|
||||
import { BACKEND_STATUS, type BackendConfig } from "~/schemas/volumes";
|
||||
import { safeExec } from "@zerobyte/core/node";
|
||||
import { config as zbConfig } from "~/server/core/config";
|
||||
@@ -102,21 +102,7 @@ const unmount = async (path: string) => {
|
||||
|
||||
const checkHealth = async (path: string) => {
|
||||
const run = async () => {
|
||||
try {
|
||||
await fs.access(path);
|
||||
} catch {
|
||||
throw new Error("Volume is not mounted");
|
||||
}
|
||||
|
||||
const mount = await getMountForPath(path);
|
||||
|
||||
if (!mount || mount.mountPoint !== path) {
|
||||
throw new Error("Volume is not mounted");
|
||||
}
|
||||
|
||||
if (!mount.fstype.includes("rclone")) {
|
||||
throw new Error(`Path ${path} is not mounted as rclone (found ${mount.fstype}).`);
|
||||
}
|
||||
await assertMounted(path, (fstype) => fstype.includes("rclone"));
|
||||
|
||||
logger.debug(`Rclone volume at ${path} is healthy and mounted.`);
|
||||
return { status: BACKEND_STATUS.mounted };
|
||||
|
||||
@@ -7,7 +7,7 @@ import { logger } from "@zerobyte/core/node";
|
||||
import { getMountForPath } from "../../../utils/mountinfo";
|
||||
import { withTimeout } from "../../../utils/timeout";
|
||||
import type { VolumeBackend } from "../backend";
|
||||
import { executeMount, executeUnmount } from "../utils/backend-utils";
|
||||
import { assertMounted, executeMount, executeUnmount } from "../utils/backend-utils";
|
||||
import { BACKEND_STATUS, type BackendConfig } from "~/schemas/volumes";
|
||||
|
||||
const mount = async (config: BackendConfig, path: string) => {
|
||||
@@ -117,21 +117,7 @@ const unmount = async (path: string) => {
|
||||
|
||||
const checkHealth = async (path: string) => {
|
||||
const run = async () => {
|
||||
try {
|
||||
await fs.access(path);
|
||||
} catch {
|
||||
throw new Error("Volume is not mounted");
|
||||
}
|
||||
|
||||
const mount = await getMountForPath(path);
|
||||
|
||||
if (!mount || mount.mountPoint !== path) {
|
||||
throw new Error("Volume is not mounted");
|
||||
}
|
||||
|
||||
if (mount.fstype !== "cifs") {
|
||||
throw new Error(`Path ${path} is not mounted as CIFS/SMB (found ${mount.fstype}).`);
|
||||
}
|
||||
await assertMounted(path, (fstype) => fstype === "cifs");
|
||||
|
||||
logger.debug(`SMB volume at ${path} is healthy and mounted.`);
|
||||
return { status: BACKEND_STATUS.mounted };
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import * as fs from "node:fs/promises";
|
||||
import { afterEach, describe, expect, mock, spyOn, test } from "bun:test";
|
||||
import * as mountinfo from "../../../../utils/mountinfo";
|
||||
import { assertMounted } from "../backend-utils";
|
||||
|
||||
afterEach(() => {
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
describe("assertMountedFilesystem", () => {
|
||||
test("throws when the path is not accessible", async () => {
|
||||
spyOn(fs, "access").mockRejectedValueOnce(new Error("missing"));
|
||||
|
||||
await expect(assertMounted("/tmp/volume", (fstype) => fstype.startsWith("nfs"))).rejects.toThrow(
|
||||
"Volume is not mounted",
|
||||
);
|
||||
});
|
||||
|
||||
test("throws when the mount filesystem does not match", async () => {
|
||||
spyOn(fs, "access").mockResolvedValueOnce(undefined);
|
||||
spyOn(mountinfo, "getMountForPath").mockResolvedValueOnce({
|
||||
mountPoint: "/tmp/volume",
|
||||
fstype: "cifs",
|
||||
});
|
||||
|
||||
await expect(assertMounted("/tmp/volume", (fstype) => fstype.startsWith("nfs"))).rejects.toThrow(
|
||||
"Path /tmp/volume is not mounted as correct fstype (found cifs).",
|
||||
);
|
||||
});
|
||||
|
||||
test("accepts a matching mounted filesystem", async () => {
|
||||
spyOn(fs, "access").mockResolvedValueOnce(undefined);
|
||||
spyOn(mountinfo, "getMountForPath").mockResolvedValueOnce({
|
||||
mountPoint: "/tmp/volume",
|
||||
fstype: "nfs4",
|
||||
});
|
||||
|
||||
await expect(assertMounted("/tmp/volume", (fstype) => fstype.startsWith("nfs"))).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -3,6 +3,7 @@ import * as npath from "node:path";
|
||||
import { toMessage } from "../../../utils/errors";
|
||||
import { logger } from "@zerobyte/core/node";
|
||||
import { safeExec } from "@zerobyte/core/node";
|
||||
import { getMountForPath } from "../../../utils/mountinfo";
|
||||
|
||||
export const executeMount = async (args: string[]): Promise<void> => {
|
||||
const shouldBeVerbose = process.env.LOG_LEVEL === "debug" || process.env.NODE_ENV !== "production";
|
||||
@@ -44,6 +45,24 @@ export const executeUnmount = async (path: string): Promise<void> => {
|
||||
}
|
||||
};
|
||||
|
||||
export const assertMounted = async (path: string, isExpectedFilesystem: (fstype: string) => boolean) => {
|
||||
try {
|
||||
await fs.access(path);
|
||||
} catch {
|
||||
throw new Error("Volume is not mounted");
|
||||
}
|
||||
|
||||
const mount = await getMountForPath(path);
|
||||
|
||||
if (!mount || mount.mountPoint !== path) {
|
||||
throw new Error("Volume is not mounted");
|
||||
}
|
||||
|
||||
if (!isExpectedFilesystem(mount.fstype)) {
|
||||
throw new Error(`Path ${path} is not mounted as correct fstype (found ${mount.fstype}).`);
|
||||
}
|
||||
};
|
||||
|
||||
export const createTestFile = async (path: string): Promise<void> => {
|
||||
const testFilePath = npath.join(path, `.healthcheck-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { logger } from "@zerobyte/core/node";
|
||||
import { getMountForPath } from "../../../utils/mountinfo";
|
||||
import { withTimeout } from "../../../utils/timeout";
|
||||
import type { VolumeBackend } from "../backend";
|
||||
import { executeMount, executeUnmount } from "../utils/backend-utils";
|
||||
import { assertMounted, executeMount, executeUnmount } from "../utils/backend-utils";
|
||||
import { BACKEND_STATUS, type BackendConfig } from "~/schemas/volumes";
|
||||
|
||||
const mount = async (config: BackendConfig, path: string) => {
|
||||
@@ -135,21 +135,7 @@ const unmount = async (path: string) => {
|
||||
|
||||
const checkHealth = async (path: string) => {
|
||||
const run = async () => {
|
||||
try {
|
||||
await fs.access(path);
|
||||
} catch {
|
||||
throw new Error("Volume is not mounted");
|
||||
}
|
||||
|
||||
const mount = await getMountForPath(path);
|
||||
|
||||
if (!mount || mount.mountPoint !== path) {
|
||||
throw new Error("Volume is not mounted");
|
||||
}
|
||||
|
||||
if (mount.fstype !== "fuse" && mount.fstype !== "davfs") {
|
||||
throw new Error(`Path ${path} is not mounted as WebDAV (found ${mount.fstype}).`);
|
||||
}
|
||||
await assertMounted(path, (fstype) => fstype === "fuse" || fstype === "davfs");
|
||||
|
||||
logger.debug(`WebDAV volume at ${path} is healthy and mounted.`);
|
||||
return { status: BACKEND_STATUS.mounted };
|
||||
|
||||
Reference in New Issue
Block a user