test: automate NFS integration
This commit is contained in:
parent
dfd787c8ae
commit
1526e3d441
8 changed files with 89 additions and 56 deletions
|
|
@ -4,8 +4,6 @@ set -euo pipefail
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
TARGET_HOST="192.168.2.41"
|
TARGET_HOST="192.168.2.41"
|
||||||
TARGET="root@$TARGET_HOST"
|
TARGET="root@$TARGET_HOST"
|
||||||
FIXTURE_UID="1000"
|
|
||||||
FIXTURE_GID="1000"
|
|
||||||
|
|
||||||
ARTIFACTS_DIR="$SCRIPT_DIR/artifacts/$TARGET_HOST"
|
ARTIFACTS_DIR="$SCRIPT_DIR/artifacts/$TARGET_HOST"
|
||||||
KNOWN_HOSTS_PATH="$ARTIFACTS_DIR/known_hosts"
|
KNOWN_HOSTS_PATH="$ARTIFACTS_DIR/known_hosts"
|
||||||
|
|
@ -30,12 +28,10 @@ chmod 700 "$ARTIFACTS_DIR"
|
||||||
|
|
||||||
SFTP_PASSWORD="$(read_or_create_secret "$SFTP_PASSWORD_FILE")"
|
SFTP_PASSWORD="$(read_or_create_secret "$SFTP_PASSWORD_FILE")"
|
||||||
|
|
||||||
ssh "$TARGET" bash -s -- "$FIXTURE_UID" "$FIXTURE_GID" "$SFTP_PASSWORD" <<'REMOTE'
|
ssh "$TARGET" bash -s -- "$SFTP_PASSWORD" <<'REMOTE'
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
fixture_uid="$1"
|
sftp_password="$1"
|
||||||
fixture_gid="$2"
|
|
||||||
sftp_password="$3"
|
|
||||||
legacy_sshd_dir="/etc/ssh/zerobyte-backend-integration-legacy"
|
legacy_sshd_dir="/etc/ssh/zerobyte-backend-integration-legacy"
|
||||||
|
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
@ -46,30 +42,19 @@ write_file() {
|
||||||
}
|
}
|
||||||
|
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y nfs-kernel-server openssh-server rpcbind
|
apt-get install -y openssh-server
|
||||||
|
|
||||||
id -u zerobyte-sftp >/dev/null 2>&1 || useradd --create-home --home-dir /home/zerobyte-sftp --shell /bin/bash zerobyte-sftp
|
id -u zerobyte-sftp >/dev/null 2>&1 || useradd --create-home --home-dir /home/zerobyte-sftp --shell /bin/bash zerobyte-sftp
|
||||||
|
|
||||||
install -d -m 0755 /srv/zerobyte-backend-integration/fixtures/case-a/docs
|
install -d -m 0755 /srv/zerobyte-backend-integration/fixtures/case-a/docs
|
||||||
printf 'hello from zerobyte integration\n' >/srv/zerobyte-backend-integration/fixtures/case-a/hello.txt
|
printf 'hello from zerobyte integration\n' >/srv/zerobyte-backend-integration/fixtures/case-a/hello.txt
|
||||||
printf 'fixture documentation\n' >/srv/zerobyte-backend-integration/fixtures/case-a/docs/readme.md
|
printf 'fixture documentation\n' >/srv/zerobyte-backend-integration/fixtures/case-a/docs/readme.md
|
||||||
chown -R "$fixture_uid:$fixture_gid" /srv/zerobyte-backend-integration/fixtures
|
|
||||||
find /srv/zerobyte-backend-integration/fixtures -type d -exec chmod 0755 {} +
|
find /srv/zerobyte-backend-integration/fixtures -type d -exec chmod 0755 {} +
|
||||||
find /srv/zerobyte-backend-integration/fixtures -type f -exec chmod 0644 {} +
|
find /srv/zerobyte-backend-integration/fixtures -type f -exec chmod 0644 {} +
|
||||||
|
|
||||||
printf 'zerobyte-sftp:%s\n' "$sftp_password" | chpasswd
|
printf 'zerobyte-sftp:%s\n' "$sftp_password" | chpasswd
|
||||||
passwd -u zerobyte-sftp >/dev/null 2>&1 || true
|
passwd -u zerobyte-sftp >/dev/null 2>&1 || true
|
||||||
|
|
||||||
write_file /etc/exports <<'EOF'
|
|
||||||
/srv/zerobyte-backend-integration/fixtures *(ro,sync,no_subtree_check,insecure)
|
|
||||||
EOF
|
|
||||||
exportfs -ra
|
|
||||||
systemctl unmask rpcbind rpcbind.socket >/dev/null 2>&1
|
|
||||||
systemctl start rpcbind.socket
|
|
||||||
systemctl start rpcbind
|
|
||||||
systemctl start proc-fs-nfsd.mount
|
|
||||||
systemctl restart nfs-kernel-server
|
|
||||||
|
|
||||||
install -d -m 0700 "$legacy_sshd_dir"
|
install -d -m 0700 "$legacy_sshd_dir"
|
||||||
if [[ ! -f "$legacy_sshd_dir/ssh_host_rsa_key" ]]; then
|
if [[ ! -f "$legacy_sshd_dir/ssh_host_rsa_key" ]]; then
|
||||||
ssh-keygen -q -t rsa -b 2048 -N "" -f "$legacy_sshd_dir/ssh_host_rsa_key"
|
ssh-keygen -q -t rsa -b 2048 -N "" -f "$legacy_sshd_dir/ssh_host_rsa_key"
|
||||||
|
|
@ -143,8 +128,6 @@ if ! ssh-keyscan -T 5 -p 2222 "$TARGET_HOST" >>"$KNOWN_HOSTS_PATH" 2>/dev/null;
|
||||||
fi
|
fi
|
||||||
|
|
||||||
INTEGRATION_HOST="$TARGET_HOST" \
|
INTEGRATION_HOST="$TARGET_HOST" \
|
||||||
FIXTURE_UID="$FIXTURE_UID" \
|
|
||||||
FIXTURE_GID="$FIXTURE_GID" \
|
|
||||||
SFTP_PASSWORD="$SFTP_PASSWORD" \
|
SFTP_PASSWORD="$SFTP_PASSWORD" \
|
||||||
KNOWN_HOSTS_PATH="$KNOWN_HOSTS_PATH" \
|
KNOWN_HOSTS_PATH="$KNOWN_HOSTS_PATH" \
|
||||||
CONFIG_PATH="$CONFIG_PATH" \
|
CONFIG_PATH="$CONFIG_PATH" \
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,7 @@ function getRequiredEnv(name: string) {
|
||||||
return process.env[name]!;
|
return process.env[name]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRequiredNumberEnv(name: string) {
|
|
||||||
return Number.parseInt(getRequiredEnv(name), 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
const host = getRequiredEnv("INTEGRATION_HOST");
|
const host = getRequiredEnv("INTEGRATION_HOST");
|
||||||
const fixtureUid = getRequiredNumberEnv("FIXTURE_UID");
|
|
||||||
const fixtureGid = getRequiredNumberEnv("FIXTURE_GID");
|
|
||||||
const sftpPassword = getRequiredEnv("SFTP_PASSWORD");
|
const sftpPassword = getRequiredEnv("SFTP_PASSWORD");
|
||||||
const knownHosts = fs.readFileSync(getRequiredEnv("KNOWN_HOSTS_PATH"), "utf8");
|
const knownHosts = fs.readFileSync(getRequiredEnv("KNOWN_HOSTS_PATH"), "utf8");
|
||||||
const configPath = getRequiredEnv("CONFIG_PATH");
|
const configPath = getRequiredEnv("CONFIG_PATH");
|
||||||
|
|
@ -18,12 +12,6 @@ const configPath = getRequiredEnv("CONFIG_PATH");
|
||||||
const fileText = "hello from zerobyte integration\n";
|
const fileText = "hello from zerobyte integration\n";
|
||||||
const readmeText = "fixture documentation\n";
|
const readmeText = "fixture documentation\n";
|
||||||
|
|
||||||
const nfsEntries = [
|
|
||||||
{ path: "hello.txt", type: "file", uid: fixtureUid, gid: fixtureGid, mode: "0644", text: fileText },
|
|
||||||
{ path: "docs", type: "directory", uid: fixtureUid, gid: fixtureGid, mode: "0755" },
|
|
||||||
{ path: "docs/readme.md", type: "file", uid: fixtureUid, gid: fixtureGid, mode: "0644", text: readmeText },
|
|
||||||
];
|
|
||||||
|
|
||||||
const contentOnlyEntries = [
|
const contentOnlyEntries = [
|
||||||
{ path: "hello.txt", type: "file", text: fileText },
|
{ path: "hello.txt", type: "file", text: fileText },
|
||||||
{ path: "docs", type: "directory" },
|
{ path: "docs", type: "directory" },
|
||||||
|
|
@ -33,20 +21,6 @@ const contentOnlyEntries = [
|
||||||
const config = {
|
const config = {
|
||||||
version: 1,
|
version: 1,
|
||||||
scenarios: [
|
scenarios: [
|
||||||
{
|
|
||||||
id: "nfs-local-repo",
|
|
||||||
volume: {
|
|
||||||
backend: "nfs",
|
|
||||||
server: host,
|
|
||||||
exportPath: "/srv/zerobyte-backend-integration/fixtures",
|
|
||||||
port: 2049,
|
|
||||||
version: "4.1",
|
|
||||||
readOnly: true,
|
|
||||||
},
|
|
||||||
repository: { backend: "local", path: "repo-nfs" },
|
|
||||||
fixtureRoot: "case-a",
|
|
||||||
expectedEntries: nfsEntries,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "sftp-legacy-rsa-hostkey-local-repo",
|
id: "sftp-legacy-rsa-hostkey-local-repo",
|
||||||
volume: {
|
volume: {
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,18 @@ services:
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 30
|
retries: 30
|
||||||
|
|
||||||
|
nfs:
|
||||||
|
build:
|
||||||
|
context: ./nfs
|
||||||
|
privileged: true
|
||||||
|
volumes:
|
||||||
|
- nfs-data:/srv/zerobyte-integration
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "sh", "-c", "exportfs -v | grep -q /srv/zerobyte-integration/fixtures"]
|
||||||
|
interval: 1s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 30
|
||||||
|
|
||||||
integration:
|
integration:
|
||||||
build:
|
build:
|
||||||
context: ../../../..
|
context: ../../../..
|
||||||
|
|
@ -96,6 +108,8 @@ services:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
smb:
|
smb:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
nfs:
|
||||||
|
condition: service_healthy
|
||||||
cap_add:
|
cap_add:
|
||||||
- SYS_ADMIN
|
- SYS_ADMIN
|
||||||
devices:
|
devices:
|
||||||
|
|
@ -111,3 +125,4 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
rustfs-data:
|
rustfs-data:
|
||||||
sftp-data:
|
sftp-data:
|
||||||
|
nfs-data:
|
||||||
|
|
|
||||||
10
app/test/integration/infra/nfs/Dockerfile
Normal file
10
app/test/integration/infra/nfs/Dockerfile
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
FROM alpine:3.21
|
||||||
|
|
||||||
|
RUN apk add --no-cache nfs-utils
|
||||||
|
|
||||||
|
COPY entrypoint.sh /usr/local/bin/zerobyte-nfs-entrypoint
|
||||||
|
RUN chmod +x /usr/local/bin/zerobyte-nfs-entrypoint
|
||||||
|
|
||||||
|
EXPOSE 2049
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/local/bin/zerobyte-nfs-entrypoint"]
|
||||||
30
app/test/integration/infra/nfs/entrypoint.sh
Normal file
30
app/test/integration/infra/nfs/entrypoint.sh
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
FIXTURE_ROOT="/srv/zerobyte-integration/fixtures"
|
||||||
|
|
||||||
|
install -d -m 0755 "$FIXTURE_ROOT/case-a/docs"
|
||||||
|
printf 'hello from zerobyte integration\n' >"$FIXTURE_ROOT/case-a/hello.txt"
|
||||||
|
printf 'fixture documentation\n' >"$FIXTURE_ROOT/case-a/docs/readme.md"
|
||||||
|
find "$FIXTURE_ROOT" -type d -exec chmod 0755 {} +
|
||||||
|
find "$FIXTURE_ROOT" -type f -exec chmod 0644 {} +
|
||||||
|
|
||||||
|
mkdir -p /run/rpc_pipefs /proc/fs/nfsd
|
||||||
|
mountpoint -q /proc/fs/nfsd || mount -t nfsd nfsd /proc/fs/nfsd
|
||||||
|
mountpoint -q /run/rpc_pipefs || mount -t rpc_pipefs rpc_pipefs /run/rpc_pipefs
|
||||||
|
printf '1\n' >/proc/fs/nfsd/nfsv4gracetime
|
||||||
|
printf '1\n' >/proc/fs/nfsd/nfsv4leasetime
|
||||||
|
|
||||||
|
cat >/etc/exports <<EOF
|
||||||
|
$FIXTURE_ROOT *(ro,sync,no_subtree_check,insecure,fsid=0)
|
||||||
|
EOF
|
||||||
|
|
||||||
|
rpcbind -w
|
||||||
|
rpc.mountd --no-udp --foreground &
|
||||||
|
exportfs -ra
|
||||||
|
rpc.nfsd -V 4 -V 4.1 8
|
||||||
|
|
||||||
|
trap 'exportfs -ua; rpc.nfsd 0' INT TERM
|
||||||
|
while kill -0 "$(pidof rpc.mountd)" 2>/dev/null; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
@ -36,7 +36,7 @@ if [[ "$exit_code" -eq 0 ]]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$exit_code" -eq 0 ]]; then
|
if [[ "$exit_code" -eq 0 ]]; then
|
||||||
"${compose[@]}" up --build --no-color --detach --wait sftp webdav smb >>"$docker_output_log" 2>&1 || exit_code=$?
|
"${compose[@]}" up --build --no-color --detach --wait sftp webdav smb nfs >>"$docker_output_log" 2>&1 || exit_code=$?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$exit_code" -eq 0 ]]; then
|
if [[ "$exit_code" -eq 0 ]]; then
|
||||||
|
|
|
||||||
14
app/test/integration/src/helpers/nfs.ts
Normal file
14
app/test/integration/src/helpers/nfs.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import type { BackendConfig } from "@zerobyte/contracts/volumes";
|
||||||
|
|
||||||
|
export const NFS_HOST = "nfs";
|
||||||
|
export const NFS_PORT = 2049;
|
||||||
|
export const NFS_EXPORT_PATH = "/";
|
||||||
|
|
||||||
|
export const buildNfsVolumeConfig = (): BackendConfig => ({
|
||||||
|
backend: "nfs",
|
||||||
|
server: NFS_HOST,
|
||||||
|
exportPath: NFS_EXPORT_PATH,
|
||||||
|
port: NFS_PORT,
|
||||||
|
version: "4.1",
|
||||||
|
readOnly: true,
|
||||||
|
});
|
||||||
|
|
@ -4,6 +4,7 @@ import path from "node:path";
|
||||||
import type { RepositoryConfig } from "@zerobyte/core/restic";
|
import type { RepositoryConfig } from "@zerobyte/core/restic";
|
||||||
import { Effect } from "effect";
|
import { Effect } from "effect";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
import { makeNfsBackend } from "../../../../apps/agent/src/volume-host/backends/nfs";
|
||||||
import { makeSmbBackend } from "../../../../apps/agent/src/volume-host/backends/smb";
|
import { makeSmbBackend } from "../../../../apps/agent/src/volume-host/backends/smb";
|
||||||
import { makeSftpBackend } from "../../../../apps/agent/src/volume-host/backends/sftp";
|
import { makeSftpBackend } from "../../../../apps/agent/src/volume-host/backends/sftp";
|
||||||
import { makeWebdavBackend } from "../../../../apps/agent/src/volume-host/backends/webdav";
|
import { makeWebdavBackend } from "../../../../apps/agent/src/volume-host/backends/webdav";
|
||||||
|
|
@ -11,6 +12,7 @@ import type { VolumeBackend } from "../../../../apps/agent/src/volume-host/types
|
||||||
import { INTEGRATION_ORGANIZATION_ID, INTEGRATION_RUNS_DIR } from "./constants";
|
import { INTEGRATION_ORGANIZATION_ID, INTEGRATION_RUNS_DIR } from "./constants";
|
||||||
import { assertFixtureSourceExists, assertRestoredFixture, assertSnapshotContainsFixture } from "./helpers/assertions";
|
import { assertFixtureSourceExists, assertRestoredFixture, assertSnapshotContainsFixture } from "./helpers/assertions";
|
||||||
import { createStaticVolumeFixture } from "./helpers/fixture";
|
import { createStaticVolumeFixture } from "./helpers/fixture";
|
||||||
|
import { buildNfsVolumeConfig } from "./helpers/nfs";
|
||||||
import { createIntegrationRestic } from "./helpers/restic";
|
import { createIntegrationRestic } from "./helpers/restic";
|
||||||
import {
|
import {
|
||||||
buildSftpPasswordVolumeConfig,
|
buildSftpPasswordVolumeConfig,
|
||||||
|
|
@ -57,6 +59,11 @@ const scenarios: VolumeScenario[] = [
|
||||||
name: "SMB volume with local repository",
|
name: "SMB volume with local repository",
|
||||||
createBackend: async (mountPath) => makeSmbBackend(buildSmbVolumeConfig(), mountPath),
|
createBackend: async (mountPath) => makeSmbBackend(buildSmbVolumeConfig(), mountPath),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "nfs-local-repo",
|
||||||
|
name: "NFS volume with local repository",
|
||||||
|
createBackend: async (mountPath) => makeNfsBackend(buildNfsVolumeConfig(), mountPath),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const volumeMountTest = process.env.SKIP_VOLUME_MOUNT_INTEGRATION_TESTS === "true" ? test.skip : test;
|
const volumeMountTest = process.env.SKIP_VOLUME_MOUNT_INTEGRATION_TESTS === "true" ? test.skip : test;
|
||||||
|
|
@ -97,6 +104,15 @@ volumeMountTest.concurrent.each(scenarios)("$name can backup and restore static
|
||||||
try {
|
try {
|
||||||
await fs.mkdir(workspace, { recursive: true });
|
await fs.mkdir(workspace, { recursive: true });
|
||||||
|
|
||||||
|
const initResult = await Effect.runPromise(
|
||||||
|
restic.init(repositoryConfig, {
|
||||||
|
organizationId: INTEGRATION_ORGANIZATION_ID,
|
||||||
|
timeoutMs: 120_000,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(initResult.success).toBe(true);
|
||||||
|
expect(initResult.error).toBeNull();
|
||||||
|
|
||||||
backend = await scenario.createBackend(mountPath);
|
backend = await scenario.createBackend(mountPath);
|
||||||
|
|
||||||
const mountResult = await backend.mount();
|
const mountResult = await backend.mount();
|
||||||
|
|
@ -108,15 +124,6 @@ volumeMountTest.concurrent.each(scenarios)("$name can backup and restore static
|
||||||
const fixture = createStaticVolumeFixture(path.join(mountPath, "case-a"));
|
const fixture = createStaticVolumeFixture(path.join(mountPath, "case-a"));
|
||||||
await assertFixtureSourceExists(fixture);
|
await assertFixtureSourceExists(fixture);
|
||||||
|
|
||||||
const initResult = await Effect.runPromise(
|
|
||||||
restic.init(repositoryConfig, {
|
|
||||||
organizationId: INTEGRATION_ORGANIZATION_ID,
|
|
||||||
timeoutMs: 120_000,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
expect(initResult.success).toBe(true);
|
|
||||||
expect(initResult.error).toBeNull();
|
|
||||||
|
|
||||||
const backupResult = await Effect.runPromise(
|
const backupResult = await Effect.runPromise(
|
||||||
restic.backup(repositoryConfig, fixture.sourceRoot, {
|
restic.backup(repositoryConfig, fixture.sourceRoot, {
|
||||||
organizationId: INTEGRATION_ORGANIZATION_ID,
|
organizationId: INTEGRATION_ORGANIZATION_ID,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue