Resolve "Release image needs libfuse"

Closes #383 (closed)

Merge request reports

Loading
+1 −0
Changes for docker/0.9.6/deps/Dockerfile: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ RUN apt-get update && \
  python3-venv \
  python3-setuptools \
  libnuma-dev libyaml-dev libcurl4-openssl-dev \
  libfuse3-dev fuse3 \
  procps && \
  rm -rf /var/lib/apt/lists/* && \
  apt-get clean && apt-get autoclean
+4 −2
Changes for docker/0.9.6/release/Dockerfile: 4 added lines, 2 removed lines.
Original line number Diff line number Diff line
# syntax=docker/dockerfile:1

# Builder stage
FROM debian:bookworm-slim AS builder
FROM debian:trixie-slim AS builder

# Install build dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
@@ -25,6 +25,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
    libcurl4-openssl-dev \
    libffi-dev \
    zlib1g-dev \
    libfuse3-dev \
    python3 \
    perl \
    patch \
@@ -82,7 +83,7 @@ RUN cmake \
    make install

# Runtime stage
FROM debian:bookworm-slim
FROM debian:trixie-slim

# Install runtime dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
@@ -94,6 +95,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
    libcap2 \
    libzmq5 \
    liburing2 \
    fuse3 \
    && rm -rf /var/lib/apt/lists/*

# Copy GekkoFS artifacts
+4 −2
Changes for src/daemon/handler/srv_metadata.cpp: 4 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@
 * @endinternal
 */

#include <cerrno>
#include <daemon/handler/rpc_defs.hpp>
#include <daemon/handler/rpc_util.hpp>
#include <daemon/backend/metadata/db.hpp>
@@ -1070,9 +1071,10 @@ rpc_srv_read_data_inline(const tl::request& req,

            if(md.size() > 0 && stored_data.empty()) {
                // Inline data key missing despite non-zero metadata size
                // Treat as empty file or error state
                // It might be that the file is in chunks (e.g., after a
                // truncate)
                out.err = EAGAIN;
                out.count = 0;
                out.err = 0;
            } else if(in.offset >= stored_data.size()) {
                // EOF
                out.count = 0;
+3 −3
Changes for tests/integration/fuse/test_basic_operations.py: 3 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -58,13 +58,13 @@ def test_read(gkfs_daemon, fuse_client):
    assert sh.wc("-c", str(file2)) == "20 " + str(file2) + "\n"
    sh.mkdir(str(dir))
    assert sh.ls(fuse_client.mountdir) == "dir  file  file2\n"
    sh.cd(str(dir))
    os.chdir(str(dir))
    assert sh.pwd() == str(dir) + "\n"
    sh.mkdir("-p", "foo/bar")
    assert sh.ls() == "foo\n"
    sh.cd("foo")
    os.chdir("foo")
    sh.rmdir("bar")
    sh.cd("..")
    os.chdir("..")
    sh.rmdir("foo")
    sh.rm(str(file2))
    assert sh.ls(fuse_client.mountdir) == "dir  file\n"
+172 −0
Changes for tests/integration/fuse/test_standard_compliance.py: 172 added lines, 0 removed lines.
Original line number Diff line number Diff line
import harness
from pathlib import Path
import errno
import stat
import os
import ctypes
import sh
import sys
import pytest
import time
import hashlib
import random
import string
from harness.logger import logger

def calculate_md5(file_path):
    hash_md5 = hashlib.md5()
    with open(file_path, "rb") as f:
        for chunk in iter(lambda: f.read(4096), b""):
            hash_md5.update(chunk)
    return hash_md5.hexdigest()

def generate_random_data(size):
    return ''.join(random.choices(string.ascii_letters + string.digits, k=size)).encode()

def test_large_io(gkfs_daemon, fuse_client):
    """Test reading and writing large files spanning multiple chunks."""
    
    # 2MB file (assuming default chunksize of 512KB, this covers multiple chunks)
    file_size = 2 * 1024 * 1024 
    file_path = gkfs_daemon.mountdir / "large_file"
    
    # Generate random data
    data = generate_random_data(file_size)
    
    # Write data
    with open(file_path, "wb") as f:
        f.write(data)
        
    # Verify size via stat
    st = os.stat(file_path)
    assert st.st_size == file_size

    # Verify content md5
    assert calculate_md5(file_path) == hashlib.md5(data).hexdigest()
    
    # Read back and verify
    with open(file_path, "rb") as f:
        read_data = f.read()
    assert read_data == data

def test_truncate(gkfs_daemon, fuse_client):
    """Test file truncation (extend and shrink)."""
    
    file_path = gkfs_daemon.mountdir / "truncate_file"
    
    # Start with small content
    initial_data = b"start"
    with open(file_path, "wb") as f:
        f.write(initial_data)
        
    # Extend
    new_size = 100 * 1024 # 100KB
    os.truncate(file_path, new_size)
    
    assert os.stat(file_path).st_size == new_size
    
    # Verify hole is zero-filled
    with open(file_path, "rb") as f:
        content = f.read()
    assert content[:len(initial_data)] == initial_data
    assert content[len(initial_data):] == b'\0' * (new_size - len(initial_data))
    
    # Shrink
    shrink_size = 3
    os.truncate(file_path, shrink_size)
    assert os.stat(file_path).st_size == shrink_size
    
    # Force flush to ensure consistency
    fd = os.open(file_path, os.O_RDONLY)
    os.fsync(fd)
    os.close(fd)
    
    with open(file_path, "rb") as f:
        content = f.read()
    assert content == initial_data[:shrink_size]

def test_metadata(gkfs_daemon, fuse_client):
    """Verify metadata correctness."""
    
    file_path = gkfs_daemon.mountdir / "meta_file"
    
    # Create file
    with open(file_path, "w") as f:
        f.write("test")
        
    # Check stat
    st = os.stat(file_path)
    assert stat.S_ISREG(st.st_mode)
    assert st.st_size == 4
    
    # Check permissions (basic check)
    # Note: FUSE permissions depend on mount options, but should be consistent
    original_mode = st.st_mode
    new_mode = 0o777
    try:
        os.chmod(file_path, new_mode)
        # Refresh stat
        st = os.stat(file_path)
        # Mask with 0o777 to check permission bits only
        # GekkoFS might not support chmod fully in all backends or FUSE might mask it
        if (st.st_mode & 0o777) != new_mode:
             logger.warning(f"chmod mismatch: expected {oct(new_mode)}, got {oct(st.st_mode & 0o777)}")
    except OSError:
        pass # Optional support

def test_directories_nested(gkfs_daemon, fuse_client):
    """Test nested directory operations."""
    
    top_dir = gkfs_daemon.mountdir / "top"
    nested_dir = top_dir / "nested" / "deep"
    
    os.makedirs(nested_dir)
    
    assert os.path.exists(nested_dir)
    assert os.path.isdir(nested_dir)
    
    # Create file in deep dir
    file_path = nested_dir / "deep_file"
    with open(file_path, "w") as f:
        f.write("deep")
        
    assert os.path.exists(file_path)
    
    # List directory
    entries = os.listdir(nested_dir)
    assert "deep_file" in entries
    
    # Remove
    os.remove(file_path)
    os.removedirs(nested_dir) # Should remove all empty parents up to 'top' if empty?
    # removedirs removes parents if they become empty. 
    # 'top' is in mountdir.
    
    assert not os.path.exists(nested_dir)

def test_overwrite_middle(gkfs_daemon, fuse_client):
    """Test overwriting data in the middle of a file."""
    
    file_path = gkfs_daemon.mountdir / "overwrite_file"
    
    # Write 10KB
    size = 10 * 1024
    data = b'A' * size
    with open(file_path, "wb") as f:
        f.write(data)
        
    # Overwrite middle 2KB with 'B'
    offset = 4 * 1024
    overwrite_len = 2 * 1024
    new_data = b'B' * overwrite_len
    
    with open(file_path, "r+b") as f:
        f.seek(offset)
        f.write(new_data)
        
    # Verify
    expected = b'A' * offset + b'B' * overwrite_len + b'A' * (size - offset - overwrite_len)
    with open(file_path, "rb") as f:
        content = f.read()
    
    assert content == expected
Loading
Loading