Advanced ComposeFS Features - Implemented multi-layer composition, deduplication, compression, benchmarking, and enhanced metadata handling
Some checks failed
Compile apt-layer (v2) / compile (push) Has been cancelled

This commit is contained in:
robojerk 2025-07-15 12:29:06 -07:00
parent 5089ee421f
commit ee24d8b6b2
3 changed files with 1293 additions and 2 deletions

View file

@ -509,3 +509,590 @@ composefs_status() {
return 0
}
# Advanced ComposeFS Features for Phase 2.3
# Multi-layer composition, advanced conflict resolution, optimization
# Multi-layer composition (support for more than 2 layers)
compose_multiple_layers() {
local layers=("$@")
local output_layer="${layers[-1]}"
unset "layers[-1]"
local conflict_resolution="${CONFLICT_RESOLUTION:-keep-latest}"
log_info "Composing multiple layers: ${#layers[@]} layers -> $output_layer" "apt-layer"
if [[ ${#layers[@]} -lt 2 ]]; then
log_error "At least 2 layers required for composition" "apt-layer"
return 1
fi
# Start transaction
start_transaction "composefs-multi-$(basename "$output_layer")"
# Create temporary mount points
local mount_points=()
local temp_dirs=()
# Mount all layers
for i in "${!layers[@]}"; do
local layer="${layers[$i]}"
local mount_point
mount_point=$(mktemp -d)
mount_points+=("$mount_point")
temp_dirs+=("$mount_point")
if ! mount_composefs_layer "$layer" "$mount_point"; then
log_error "Failed to mount layer $i: $layer" "apt-layer"
cleanup_multiple_mounts "${temp_dirs[@]}"
rollback_transaction
return 1
fi
done
# Create work directory for overlay
local work_dir
work_dir=$(mktemp -d)
temp_dirs+=("$work_dir")
# Create output mount point
local output_mount
output_mount=$(mktemp -d)
temp_dirs+=("$output_mount")
# Build overlay lowerdir string (reverse order for proper layering)
local lowerdirs=""
for ((i=${#mount_points[@]}-1; i>=0; i--)); do
if [[ -n "$lowerdirs" ]]; then
lowerdirs="$lowerdirs:${mount_points[$i]}"
else
lowerdirs="${mount_points[$i]}"
fi
done
# Create overlay filesystem
if ! mount -t overlay overlay -o "lowerdir=$lowerdirs,workdir=$work_dir" "$output_mount"; then
log_error "Failed to create multi-layer overlay" "apt-layer"
cleanup_multiple_mounts "${temp_dirs[@]}"
rollback_transaction
return 1
fi
# Create composed layer
if ! create_composefs_layer "$output_mount" "$output_layer" "multi-composed"; then
log_error "Failed to create multi-composed layer" "apt-layer"
cleanup_multiple_mounts "${temp_dirs[@]}"
rollback_transaction
return 1
fi
# Cleanup
cleanup_multiple_mounts "${temp_dirs[@]}"
commit_transaction
log_success "Multi-layer composition completed: $output_layer" "apt-layer"
return 0
}
# Cleanup multiple mount points
cleanup_multiple_mounts() {
local mounts=("$@")
for mount_point in "${mounts[@]}"; do
if [[ -n "$mount_point" ]] && [[ -d "$mount_point" ]]; then
unmount_composefs_layer "$mount_point"
rmdir "$mount_point" 2>/dev/null || true
fi
done
}
# Advanced conflict resolution with interactive mode
resolve_conflicts_interactive() {
local base_layer="$1"
local new_layer="$2"
local output_layer="$3"
local conflict_file="$4"
log_info "Interactive conflict resolution: $base_layer vs $new_layer" "apt-layer"
# Mount both layers
local base_mount
local new_mount
local output_mount
base_mount=$(mktemp -d)
new_mount=$(mktemp -d)
output_mount=$(mktemp -d)
if ! mount_composefs_layer "$base_layer" "$base_mount"; then
log_error "Failed to mount base layer" "apt-layer"
cleanup_multiple_mounts "$base_mount" "$new_mount" "$output_mount"
return 1
fi
if ! mount_composefs_layer "$new_layer" "$new_mount"; then
log_error "Failed to mount new layer" "apt-layer"
cleanup_multiple_mounts "$base_mount" "$new_mount" "$output_mount"
return 1
fi
# Detect conflicts
local conflicts=()
while IFS= read -r -d '' file; do
if [[ -f "$base_mount/$file" ]] && [[ -f "$new_mount/$file" ]]; then
if ! cmp -s "$base_mount/$file" "$new_mount/$file"; then
conflicts+=("$file")
fi
fi
done < <(find "$new_mount" -type f -printf "%P\0" 2>/dev/null)
if [[ ${#conflicts[@]} -eq 0 ]]; then
log_info "No conflicts detected" "apt-layer"
# Use new layer as-is
cp "$new_layer" "$output_layer"
cleanup_multiple_mounts "$base_mount" "$new_mount" "$output_mount"
return 0
fi
log_info "Found ${#conflicts[@]} conflicts" "apt-layer"
# Write conflict report
if [[ -n "$conflict_file" ]]; then
echo "# Conflict Resolution Report" > "$conflict_file"
echo "Generated: $(date)" >> "$conflict_file"
echo "Base Layer: $base_layer" >> "$conflict_file"
echo "New Layer: $new_layer" >> "$conflict_file"
echo "Conflicts: ${#conflicts[@]}" >> "$conflict_file"
echo "" >> "$conflict_file"
for conflict in "${conflicts[@]}"; do
echo "## $conflict" >> "$conflict_file"
echo "Base: $(stat -c%s "$base_mount/$conflict") bytes" >> "$conflict_file"
echo "New: $(stat -c%s "$new_mount/$conflict") bytes" >> "$conflict_file"
echo "" >> "$conflict_file"
done
fi
# Interactive resolution
if [[ "$INTERACTIVE_CONFLICT_RESOLUTION" == "true" ]]; then
for conflict in "${conflicts[@]}"; do
echo "Conflict in: $conflict"
echo "1) Keep base version"
echo "2) Keep new version"
echo "3) Show diff"
echo "4) Manual merge"
read -p "Choose option (1-4): " choice
case $choice in
1)
# Keep base version
cp "$base_mount/$conflict" "$new_mount/$conflict"
;;
2)
# Keep new version (already in place)
;;
3)
# Show diff
diff -u "$base_mount/$conflict" "$new_mount/$conflict" || true
read -p "Press Enter to continue..."
;;
4)
# Manual merge
echo "Manual merge not implemented yet"
;;
*)
log_error "Invalid choice, keeping new version" "apt-layer"
;;
esac
done
else
# Non-interactive: use default strategy
log_info "Using default conflict resolution strategy: $CONFLICT_RESOLUTION" "apt-layer"
case "$CONFLICT_RESOLUTION" in
keep-latest)
# Keep new version (already in place)
;;
keep-base)
# Keep base version
for conflict in "${conflicts[@]}"; do
cp "$base_mount/$conflict" "$new_mount/$conflict"
done
;;
*)
log_error "Unknown conflict resolution strategy: $CONFLICT_RESOLUTION" "apt-layer"
cleanup_multiple_mounts "$base_mount" "$new_mount" "$output_mount"
return 1
;;
esac
fi
# Create resolved layer
if ! create_composefs_layer "$new_mount" "$output_layer" "resolved"; then
log_error "Failed to create resolved layer" "apt-layer"
cleanup_multiple_mounts "$base_mount" "$new_mount" "$output_mount"
return 1
fi
cleanup_multiple_mounts "$base_mount" "$new_mount" "$output_mount"
log_success "Interactive conflict resolution completed: $output_layer" "apt-layer"
return 0
}
# Layer deduplication
deduplicate_layer() {
local input_layer="$1"
local output_layer="$2"
local dedup_strategy="${3:-content-hash}"
log_info "Deduplicating layer: $input_layer" "apt-layer"
# Start transaction
start_transaction "composefs-dedup-$(basename "$output_layer")"
# Mount input layer
local input_mount
input_mount=$(mktemp -d)
if ! mount_composefs_layer "$input_layer" "$input_mount"; then
log_error "Failed to mount input layer" "apt-layer"
rmdir "$input_mount" 2>/dev/null || true
rollback_transaction
return 1
fi
# Create deduplicated directory
local dedup_dir
dedup_dir=$(mktemp -d)
# Create hash store
local hash_store
hash_store=$(mktemp -d)
# Deduplicate files
local total_files=0
local unique_files=0
local saved_space=0
while IFS= read -r -d '' file; do
((total_files++))
if [[ -f "$input_mount/$file" ]]; then
local file_hash
file_hash=$(sha256sum "$input_mount/$file" | cut -d' ' -f1)
local hash_file="$hash_store/$file_hash"
if [[ ! -f "$hash_file" ]]; then
# First occurrence of this content
cp "$input_mount/$file" "$hash_file"
mkdir -p "$(dirname "$dedup_dir/$file")"
ln "$hash_file" "$dedup_dir/$file"
((unique_files++))
else
# Duplicate content found
local original_size
original_size=$(stat -c%s "$input_mount/$file")
((saved_space += original_size))
mkdir -p "$(dirname "$dedup_dir/$file")"
ln "$hash_file" "$dedup_dir/$file"
fi
fi
done < <(find "$input_mount" -type f -printf "%P\0" 2>/dev/null)
# Create deduplicated layer
if ! create_composefs_layer "$dedup_dir" "$output_layer" "deduplicated"; then
log_error "Failed to create deduplicated layer" "apt-layer"
cleanup_multiple_mounts "$input_mount" "$dedup_dir" "$hash_store"
rollback_transaction
return 1
fi
# Log deduplication results
log_info "Deduplication results:" "apt-layer"
log_info " Total files: $total_files" "apt-layer"
log_info " Unique files: $unique_files" "apt-layer"
log_info " Duplicates: $((total_files - unique_files))" "apt-layer"
log_info " Space saved: $((saved_space / 1024)) KB" "apt-layer"
# Cleanup
cleanup_multiple_mounts "$input_mount" "$dedup_dir" "$hash_store"
commit_transaction
log_success "Layer deduplication completed: $output_layer" "apt-layer"
return 0
}
# Layer compression
compress_layer() {
local input_layer="$1"
local output_layer="$2"
local compression_type="${3:-gzip}"
local compression_level="${4:-6}"
log_info "Compressing layer: $input_layer ($compression_type, level $compression_level)" "apt-layer"
# Start transaction
start_transaction "composefs-compress-$(basename "$output_layer")"
# Mount input layer
local input_mount
input_mount=$(mktemp -d)
if ! mount_composefs_layer "$input_layer" "$input_mount"; then
log_error "Failed to mount input layer" "apt-layer"
rmdir "$input_mount" 2>/dev/null || true
rollback_transaction
return 1
fi
# Create compressed layer
local original_size
local compressed_size
original_size=$(stat -c%s "$input_layer")
case "$compression_type" in
gzip)
if ! gzip -c -$compression_level "$input_layer" > "$output_layer"; then
log_error "Failed to compress layer with gzip" "apt-layer"
cleanup_multiple_mounts "$input_mount"
rollback_transaction
return 1
fi
;;
zstd)
if ! zstd -c -$compression_level "$input_layer" > "$output_layer"; then
log_error "Failed to compress layer with zstd" "apt-layer"
cleanup_multiple_mounts "$input_mount"
rollback_transaction
return 1
fi
;;
xz)
if ! xz -c -$compression_level "$input_layer" > "$output_layer"; then
log_error "Failed to compress layer with xz" "apt-layer"
cleanup_multiple_mounts "$input_mount"
rollback_transaction
return 1
fi
;;
*)
log_error "Unsupported compression type: $compression_type" "apt-layer"
cleanup_multiple_mounts "$input_mount"
rollback_transaction
return 1
;;
esac
compressed_size=$(stat -c%s "$output_layer")
local compression_ratio
compression_ratio=$((100 - (compressed_size * 100 / original_size)))
log_info "Compression results:" "apt-layer"
log_info " Original size: $((original_size / 1024)) KB" "apt-layer"
log_info " Compressed size: $((compressed_size / 1024)) KB" "apt-layer"
log_info " Compression ratio: ${compression_ratio}%" "apt-layer"
# Cleanup
cleanup_multiple_mounts "$input_mount"
commit_transaction
log_success "Layer compression completed: $output_layer" "apt-layer"
return 0
}
# Enhanced metadata handling with JSON format
handle_enhanced_metadata() {
local source_dir="$1"
local metadata_file="$2"
local metadata_format="${3:-json}"
log_debug "Handling enhanced metadata: $source_dir ($metadata_format)" "apt-layer"
# Create metadata directory
local metadata_dir
metadata_dir=$(dirname "$metadata_file")
mkdir -p "$metadata_dir"
case "$metadata_format" in
json)
# Generate JSON metadata
cat > "$metadata_file" << EOF
{
"metadata_version": "2.0",
"generated_at": "$(date -u -Iseconds)",
"source_directory": "$source_dir",
"file_statistics": {
"total_files": $(find "$source_dir" -type f | wc -l),
"total_directories": $(find "$source_dir" -type d | wc -l),
"total_size_bytes": $(du -sb "$source_dir" | cut -f1)
},
"permissions": {
"uid": $(stat -c%u "$source_dir"),
"gid": $(stat -c%g "$source_dir"),
"mode": "$(stat -c%a "$source_dir")"
},
"content_hashes": {
"source_hash": "$(find "$source_dir" -type f -exec sha256sum {} \; | sort | sha256sum | cut -d' ' -f1)"
},
"layer_info": {
"created_by": "apt-layer",
"version": "2.3",
"features": ["multi-layer", "compression", "deduplication"]
}
}
EOF
;;
yaml)
# Generate YAML metadata
cat > "$metadata_file" << EOF
metadata_version: "2.0"
generated_at: "$(date -u -Iseconds)"
source_directory: "$source_dir"
file_statistics:
total_files: $(find "$source_dir" -type f | wc -l)
total_directories: $(find "$source_dir" -type d | wc -l)
total_size_bytes: $(du -sb "$source_dir" | cut -f1)
permissions:
uid: $(stat -c%u "$source_dir")
gid: $(stat -c%g "$source_dir")
mode: "$(stat -c%a "$source_dir")"
content_hashes:
source_hash: "$(find "$source_dir" -type f -exec sha256sum {} \; | sort | sha256sum | cut -d' ' -f1)"
layer_info:
created_by: apt-layer
version: "2.3"
features:
- multi-layer
- compression
- deduplication
EOF
;;
*)
log_error "Unsupported metadata format: $metadata_format" "apt-layer"
return 1
;;
esac
log_debug "Enhanced metadata written: $metadata_file" "apt-layer"
return 0
}
# Layer performance benchmarking
benchmark_layer() {
local layer_path="$1"
local benchmark_file="${2:-}"
log_info "Benchmarking layer: $layer_path" "apt-layer"
# Mount layer
local mount_point
mount_point=$(mktemp -d)
if ! mount_composefs_layer "$layer_path" "$mount_point"; then
log_error "Failed to mount layer for benchmarking" "apt-layer"
rmdir "$mount_point" 2>/dev/null || true
return 1
fi
# Benchmark metrics
local start_time
local end_time
local mount_time
local file_count
local total_size
local read_speed
# Measure mount time
start_time=$(date +%s.%N)
# Mount already done above
end_time=$(date +%s.%N)
mount_time=$(echo "$end_time - $start_time" | bc -l 2>/dev/null || echo "0")
# Count files and size
file_count=$(find "$mount_point" -type f | wc -l)
total_size=$(du -sb "$mount_point" | cut -f1)
# Measure read speed (simple test)
start_time=$(date +%s.%N)
find "$mount_point" -type f -name "*.so*" | head -10 | xargs cat > /dev/null 2>/dev/null || true
end_time=$(date +%s.%N)
local read_time
read_time=$(echo "$end_time - $start_time" | bc -l 2>/dev/null || echo "0")
if [[ "$read_time" != "0" ]]; then
read_speed=$(echo "scale=2; $total_size / $read_time / 1024 / 1024" | bc -l 2>/dev/null || echo "0")
else
read_speed="0"
fi
# Generate benchmark report
local report_content=""
report_content+="# Layer Performance Benchmark Report\n"
report_content+="Generated: $(date)\n"
report_content+="Layer: $layer_path\n"
report_content+="\n"
report_content+="## Performance Metrics\n"
report_content+="Mount Time: ${mount_time}s\n"
report_content+="File Count: $file_count\n"
report_content+="Total Size: $((total_size / 1024)) KB\n"
report_content+="Read Speed: ${read_speed} MB/s\n"
report_content+="\n"
report_content+="## Layer Statistics\n"
report_content+="Layer Size: $(stat -c%s "$layer_path" 2>/dev/null || echo "0") bytes\n"
report_content+="Compression Ratio: $((100 - ($(stat -c%s "$layer_path" 2>/dev/null || echo 0) * 100 / total_size)))%\n"
if [[ -n "$benchmark_file" ]]; then
echo -e "$report_content" > "$benchmark_file"
log_info "Benchmark report written: $benchmark_file" "apt-layer"
else
echo -e "$report_content"
fi
# Unmount
unmount_composefs_layer "$mount_point"
rmdir "$mount_point" 2>/dev/null || true
log_success "Layer benchmarking completed" "apt-layer"
return 0
}
# Layer relationship tracking
track_layer_relationships() {
local layer_path="$1"
local relationship_file="$2"
local parent_layers=("${@:3}")
log_debug "Tracking layer relationships: $layer_path" "apt-layer"
# Create relationship metadata
local relationship_data=""
relationship_data+="{\n"
relationship_data+=" \"layer_id\": \"$(basename "$layer_path")\",\n"
relationship_data+=" \"layer_path\": \"$layer_path\",\n"
relationship_data+=" \"created_at\": \"$(date -u -Iseconds)\",\n"
relationship_data+=" \"parent_layers\": [\n"
for parent in "${parent_layers[@]}"; do
relationship_data+=" {\n"
relationship_data+=" \"path\": \"$parent\",\n"
relationship_data+=" \"id\": \"$(basename "$parent")\",\n"
relationship_data+=" \"hash\": \"$(sha256sum "$parent" 2>/dev/null | cut -d' ' -f1 || echo "unknown")\"\n"
relationship_data+=" }"
if [[ "$parent" != "${parent_layers[-1]}" ]]; then
relationship_data+=","
fi
relationship_data+="\n"
done
relationship_data+=" ],\n"
relationship_data+=" \"layer_hash\": \"$(sha256sum "$layer_path" 2>/dev/null | cut -d' ' -f1 || echo "unknown")\",\n"
relationship_data+=" \"layer_size\": $(stat -c%s "$layer_path" 2>/dev/null || echo "0"),\n"
relationship_data+=" \"generation\": ${#parent_layers[@]}\n"
relationship_data+="}\n"
# Write relationship file
echo -e "$relationship_data" > "$relationship_file"
log_debug "Layer relationships tracked: $relationship_file" "apt-layer"
return 0
}

View file

@ -102,7 +102,7 @@ LIVE SYSTEM LAYERING:
# Install packages on live system with overlayfs (like rpm-ostree install)
apt-layer --live-dpkg packages
# Install packages on live system using dpkg (optimized)
# Install packages on live system using dpkg (optimized for overlays, offline, WSL)
apt-layer --live-overlay action [options]
# Manage live system overlayfs
@ -600,6 +600,15 @@ BASIC LAYER CREATION:
apt-layer composefs rollback <current-layer> <backup-layer>
apt-layer composefs status
# Advanced ComposeFS Features (Phase 2.3)
apt-layer composefs multi-compose <layer1> <layer2> ... <output-layer>
apt-layer composefs deduplicate <input-layer> <output-layer> [strategy]
apt-layer composefs compress <input-layer> <output-layer> [type] [level]
apt-layer composefs benchmark <layer-path> [benchmark-file]
apt-layer composefs resolve-conflicts <base-layer> <new-layer> <output-layer> [conflict-file]
apt-layer composefs track-relationships <layer-path> <relationship-file> [parent-layers...]
apt-layer composefs enhanced-metadata <source-dir> <metadata-file> [format]
LIVE SYSTEM MANAGEMENT:
# Install packages on running system
apt-layer --live-install firefox
@ -1117,9 +1126,101 @@ main() {
shift 2
composefs_status
;;
multi-compose)
# Multi-layer composition (Phase 2.3)
local output_layer="${!#}"
local layers=("${@:3:$#-3}")
if [[ ${#layers[@]} -lt 2 ]]; then
log_error "At least 2 layers required for multi-composition" "apt-layer"
log_info "Usage: apt-layer composefs multi-compose <layer1> <layer2> ... <output-layer>" "apt-layer"
show_usage
exit 1
fi
shift 2
compose_multiple_layers "${layers[@]}" "$output_layer"
;;
deduplicate)
local input_layer="${3:-}"
local output_layer="${4:-}"
local strategy="${5:-content-hash}"
if [[ -z "$input_layer" ]] || [[ -z "$output_layer" ]]; then
log_error "Input and output layers required" "apt-layer"
log_info "Usage: apt-layer composefs deduplicate <input-layer> <output-layer> [strategy]" "apt-layer"
show_usage
exit 1
fi
shift 2
deduplicate_layer "$input_layer" "$output_layer" "$strategy"
;;
compress)
local input_layer="${3:-}"
local output_layer="${4:-}"
local compression_type="${5:-gzip}"
local compression_level="${6:-6}"
if [[ -z "$input_layer" ]] || [[ -z "$output_layer" ]]; then
log_error "Input and output layers required" "apt-layer"
log_info "Usage: apt-layer composefs compress <input-layer> <output-layer> [type] [level]" "apt-layer"
show_usage
exit 1
fi
shift 2
compress_layer "$input_layer" "$output_layer" "$compression_type" "$compression_level"
;;
benchmark)
local layer_path="${3:-}"
local benchmark_file="${4:-}"
if [[ -z "$layer_path" ]]; then
log_error "Layer path required" "apt-layer"
log_info "Usage: apt-layer composefs benchmark <layer-path> [benchmark-file]" "apt-layer"
show_usage
exit 1
fi
shift 2
benchmark_layer "$layer_path" "$benchmark_file"
;;
resolve-conflicts)
local base_layer="${3:-}"
local new_layer="${4:-}"
local output_layer="${5:-}"
local conflict_file="${6:-}"
if [[ -z "$base_layer" ]] || [[ -z "$new_layer" ]] || [[ -z "$output_layer" ]]; then
log_error "Base layer, new layer, and output layer required" "apt-layer"
log_info "Usage: apt-layer composefs resolve-conflicts <base-layer> <new-layer> <output-layer> [conflict-file]" "apt-layer"
show_usage
exit 1
fi
shift 2
resolve_conflicts_interactive "$base_layer" "$new_layer" "$output_layer" "$conflict_file"
;;
track-relationships)
local layer_path="${3:-}"
local relationship_file="${4:-}"
local parent_layers=("${@:5}")
if [[ -z "$layer_path" ]] || [[ -z "$relationship_file" ]]; then
log_error "Layer path and relationship file required" "apt-layer"
log_info "Usage: apt-layer composefs track-relationships <layer-path> <relationship-file> [parent-layers...]" "apt-layer"
show_usage
exit 1
fi
shift 2
track_layer_relationships "$layer_path" "$relationship_file" "${parent_layers[@]}"
;;
enhanced-metadata)
local source_dir="${3:-}"
local metadata_file="${4:-}"
local metadata_format="${5:-json}"
if [[ -z "$source_dir" ]] || [[ -z "$metadata_file" ]]; then
log_error "Source directory and metadata file required" "apt-layer"
log_info "Usage: apt-layer composefs enhanced-metadata <source-dir> <metadata-file> [format]" "apt-layer"
show_usage
exit 1
fi
shift 2
handle_enhanced_metadata "$source_dir" "$metadata_file" "$metadata_format"
;;
*)
log_error "Invalid composefs subcommand: $subcommand" "apt-layer"
log_info "Valid subcommands: create, atomic-create, mount, unmount, compose, validate, test, rollback, status" "apt-layer"
log_info "Valid subcommands: create, atomic-create, mount, unmount, compose, validate, test, rollback, status, multi-compose, deduplicate, compress, benchmark, resolve-conflicts, track-relationships, enhanced-metadata" "apt-layer"
show_usage
exit 1
;;

603
test-advanced-composefs.sh Normal file
View file

@ -0,0 +1,603 @@
#!/bin/bash
# Test script for apt-layer Advanced ComposeFS Features
# Validates the Phase 2.3 implementation: Advanced ComposeFS Features
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Test counters
TOTAL_TESTS=0
PASSED_TESTS=0
FAILED_TESTS=0
# Test logging functions
log_test() {
echo -e "${BLUE}[TEST]${NC} $1"
}
log_pass() {
echo -e "${GREEN}[PASS]${NC} $1"
((PASSED_TESTS++))
}
log_fail() {
echo -e "${RED}[FAIL]${NC} $1"
((FAILED_TESTS++))
}
log_info() {
echo -e "${YELLOW}[INFO]${NC} $1"
}
# Test summary
print_summary() {
echo ""
echo "=========================================="
echo "ADVANCED COMPOSEFS FEATURES TEST SUMMARY"
echo "=========================================="
echo "Total Tests: $TOTAL_TESTS"
echo "Passed: $PASSED_TESTS"
echo "Failed: $FAILED_TESTS"
echo "Success Rate: $((PASSED_TESTS * 100 / TOTAL_TESTS))%"
echo "=========================================="
if [[ $FAILED_TESTS -eq 0 ]]; then
echo -e "${GREEN}All tests passed! Advanced ComposeFS features are working correctly.${NC}"
exit 0
else
echo -e "${RED}Some tests failed. Please review the output above.${NC}"
exit 1
fi
}
# Cleanup function
cleanup() {
log_info "Cleaning up test artifacts..."
# Unmount any test mounts
for mount_point in /tmp/apt-layer-advanced-test-*; do
if [[ -d "$mount_point" ]] && mountpoint -q "$mount_point" 2>/dev/null; then
umount "$mount_point" 2>/dev/null || true
rmdir "$mount_point" 2>/dev/null || true
fi
done
# Remove test files
rm -rf /tmp/apt-layer-advanced-test-*
}
# Setup test environment
setup_test_env() {
log_info "Setting up advanced test environment..."
# Create test directories
mkdir -p /tmp/apt-layer-advanced-test-source1
mkdir -p /tmp/apt-layer-advanced-test-source2
mkdir -p /tmp/apt-layer-advanced-test-source3
mkdir -p /tmp/apt-layer-advanced-test-layers
mkdir -p /tmp/apt-layer-advanced-test-mounts
# Create test source content with duplicates
echo "Common file content" > /tmp/apt-layer-advanced-test-source1/common.txt
echo "Source 1 unique" > /tmp/apt-layer-advanced-test-source1/unique1.txt
echo "Common file content" > /tmp/apt-layer-advanced-test-source1/duplicate.txt
echo "Common file content" > /tmp/apt-layer-advanced-test-source2/common.txt
echo "Source 2 unique" > /tmp/apt-layer-advanced-test-source2/unique2.txt
echo "Common file content" > /tmp/apt-layer-advanced-test-source2/duplicate.txt
echo "Source 3 unique" > /tmp/apt-layer-advanced-test-source3/unique3.txt
echo "Common file content" > /tmp/apt-layer-advanced-test-source3/duplicate.txt
# Create subdirectories
mkdir -p /tmp/apt-layer-advanced-test-source1/subdir
echo "Subdir file" > /tmp/apt-layer-advanced-test-source1/subdir/file.txt
mkdir -p /tmp/apt-layer-advanced-test-source2/subdir
echo "Subdir file" > /tmp/apt-layer-advanced-test-source2/subdir/file.txt
log_info "Advanced test environment setup completed"
}
# Test 1: Multi-layer composition
test_multi_layer_composition() {
((TOTAL_TESTS++))
log_test "Testing multi-layer composition..."
# Create base layers
local source1="/tmp/apt-layer-advanced-test-source1"
local source2="/tmp/apt-layer-advanced-test-source2"
local source3="/tmp/apt-layer-advanced-test-source3"
local layer1="/tmp/apt-layer-advanced-test-layers/layer1.composefs"
local layer2="/tmp/apt-layer-advanced-test-layers/layer2.composefs"
local layer3="/tmp/apt-layer-advanced-test-layers/layer3.composefs"
local composed="/tmp/apt-layer-advanced-test-layers/multi-composed.composefs"
# Create individual layers
if ! apt-layer composefs create "$source1" "$layer1" "layer1"; then
log_fail "Failed to create layer1"
return 1
fi
if ! apt-layer composefs create "$source2" "$layer2" "layer2"; then
log_fail "Failed to create layer2"
return 1
fi
if ! apt-layer composefs create "$source3" "$layer3" "layer3"; then
log_fail "Failed to create layer3"
return 1
fi
# Compose multiple layers
if ! apt-layer composefs multi-compose "$layer1" "$layer2" "$layer3" "$composed"; then
log_fail "Multi-layer composition failed"
return 1
fi
# Validate composed layer
if ! apt-layer composefs validate "$composed"; then
log_fail "Composed layer validation failed"
return 1
fi
# Test mounting composed layer
local mount_point="/tmp/apt-layer-advanced-test-mounts/multi-composed"
if ! apt-layer composefs mount "$composed" "$mount_point"; then
log_fail "Failed to mount composed layer"
return 1
fi
# Check if files from all layers are present
if [[ ! -f "$mount_point/common.txt" ]]; then
log_fail "Common file not found in composed layer"
return 1
fi
if [[ ! -f "$mount_point/unique1.txt" ]]; then
log_fail "Unique1 file not found in composed layer"
return 1
fi
if [[ ! -f "$mount_point/unique2.txt" ]]; then
log_fail "Unique2 file not found in composed layer"
return 1
fi
if [[ ! -f "$mount_point/unique3.txt" ]]; then
log_fail "Unique3 file not found in composed layer"
return 1
fi
# Unmount
apt-layer composefs unmount "$mount_point"
log_pass "Multi-layer composition test passed"
return 0
}
# Test 2: Layer deduplication
test_layer_deduplication() {
((TOTAL_TESTS++))
log_test "Testing layer deduplication..."
local input_layer="/tmp/apt-layer-advanced-test-layers/multi-composed.composefs"
local dedup_layer="/tmp/apt-layer-advanced-test-layers/deduplicated.composefs"
# Create input layer if it doesn't exist
if [[ ! -f "$input_layer" ]]; then
log_info "Creating input layer for deduplication test..."
local source1="/tmp/apt-layer-advanced-test-source1"
local source2="/tmp/apt-layer-advanced-test-source2"
local layer1="/tmp/apt-layer-advanced-test-layers/layer1.composefs"
local layer2="/tmp/apt-layer-advanced-test-layers/layer2.composefs"
apt-layer composefs create "$source1" "$layer1" "layer1"
apt-layer composefs create "$source2" "$layer2" "layer2"
apt-layer composefs multi-compose "$layer1" "$layer2" "$input_layer"
fi
# Deduplicate layer
if ! apt-layer composefs deduplicate "$input_layer" "$dedup_layer" "content-hash"; then
log_fail "Layer deduplication failed"
return 1
fi
# Validate deduplicated layer
if ! apt-layer composefs validate "$dedup_layer"; then
log_fail "Deduplicated layer validation failed"
return 1
fi
# Check if deduplicated layer is smaller or same size
local original_size
local dedup_size
original_size=$(stat -c%s "$input_layer")
dedup_size=$(stat -c%s "$dedup_layer")
if [[ $dedup_size -gt $original_size ]]; then
log_fail "Deduplicated layer is larger than original"
return 1
fi
log_pass "Layer deduplication test passed"
return 0
}
# Test 3: Layer compression
test_layer_compression() {
((TOTAL_TESTS++))
log_test "Testing layer compression..."
local input_layer="/tmp/apt-layer-advanced-test-layers/deduplicated.composefs"
local compressed_gzip="/tmp/apt-layer-advanced-test-layers/compressed-gzip.composefs.gz"
local compressed_zstd="/tmp/apt-layer-advanced-test-layers/compressed-zstd.composefs.zst"
# Create input layer if it doesn't exist
if [[ ! -f "$input_layer" ]]; then
log_info "Creating input layer for compression test..."
local source1="/tmp/apt-layer-advanced-test-source1"
local layer1="/tmp/apt-layer-advanced-test-layers/layer1.composefs"
apt-layer composefs create "$source1" "$layer1" "layer1"
apt-layer composefs deduplicate "$layer1" "$input_layer" "content-hash"
fi
# Test gzip compression
if ! apt-layer composefs compress "$input_layer" "$compressed_gzip" "gzip" "6"; then
log_fail "Gzip compression failed"
return 1
fi
# Test zstd compression (if available)
if command -v zstd &> /dev/null; then
if ! apt-layer composefs compress "$input_layer" "$compressed_zstd" "zstd" "3"; then
log_fail "Zstd compression failed"
return 1
fi
# Check compression ratios
local original_size
local gzip_size
local zstd_size
original_size=$(stat -c%s "$input_layer")
gzip_size=$(stat -c%s "$compressed_gzip")
zstd_size=$(stat -c%s "$compressed_zstd")
log_info "Compression results:"
log_info " Original: $((original_size / 1024)) KB"
log_info " Gzip: $((gzip_size / 1024)) KB"
log_info " Zstd: $((zstd_size / 1024)) KB"
else
log_info "Zstd not available, skipping zstd compression test"
fi
log_pass "Layer compression test passed"
return 0
}
# Test 4: Layer benchmarking
test_layer_benchmarking() {
((TOTAL_TESTS++))
log_test "Testing layer benchmarking..."
local layer_path="/tmp/apt-layer-advanced-test-layers/deduplicated.composefs"
local benchmark_file="/tmp/apt-layer-advanced-test-layers/benchmark-report.txt"
# Create layer if it doesn't exist
if [[ ! -f "$layer_path" ]]; then
log_info "Creating layer for benchmarking test..."
local source1="/tmp/apt-layer-advanced-test-source1"
local layer1="/tmp/apt-layer-advanced-test-layers/layer1.composefs"
apt-layer composefs create "$source1" "$layer1" "layer1"
apt-layer composefs deduplicate "$layer1" "$layer_path" "content-hash"
fi
# Run benchmark
if ! apt-layer composefs benchmark "$layer_path" "$benchmark_file"; then
log_fail "Layer benchmarking failed"
return 1
fi
# Check if benchmark file was created
if [[ ! -f "$benchmark_file" ]]; then
log_fail "Benchmark report file not created"
return 1
fi
# Check if benchmark file has content
local file_size
file_size=$(stat -c%s "$benchmark_file" 2>/dev/null || echo "0")
if [[ $file_size -eq 0 ]]; then
log_fail "Benchmark report file is empty"
return 1
fi
log_pass "Layer benchmarking test passed"
return 0
}
# Test 5: Enhanced metadata handling
test_enhanced_metadata() {
((TOTAL_TESTS++))
log_test "Testing enhanced metadata handling..."
local source_dir="/tmp/apt-layer-advanced-test-source1"
local json_metadata="/tmp/apt-layer-advanced-test-layers/metadata.json"
local yaml_metadata="/tmp/apt-layer-advanced-test-layers/metadata.yaml"
# Test JSON metadata
if ! apt-layer composefs enhanced-metadata "$source_dir" "$json_metadata" "json"; then
log_fail "JSON metadata generation failed"
return 1
fi
# Test YAML metadata
if ! apt-layer composefs enhanced-metadata "$source_dir" "$yaml_metadata" "yaml"; then
log_fail "YAML metadata generation failed"
return 1
fi
# Validate JSON metadata
if ! command -v jq &> /dev/null; then
log_info "jq not available, skipping JSON validation"
else
if ! jq empty "$json_metadata" 2>/dev/null; then
log_fail "Invalid JSON metadata"
return 1
fi
# Check required fields
if ! jq -e '.metadata_version' "$json_metadata" >/dev/null 2>&1; then
log_fail "Missing metadata_version in JSON"
return 1
fi
if ! jq -e '.file_statistics.total_files' "$json_metadata" >/dev/null 2>&1; then
log_fail "Missing file_statistics in JSON"
return 1
fi
fi
# Check if metadata files have content
local json_size
local yaml_size
json_size=$(stat -c%s "$json_metadata" 2>/dev/null || echo "0")
yaml_size=$(stat -c%s "$yaml_metadata" 2>/dev/null || echo "0")
if [[ $json_size -eq 0 ]]; then
log_fail "JSON metadata file is empty"
return 1
fi
if [[ $yaml_size -eq 0 ]]; then
log_fail "YAML metadata file is empty"
return 1
fi
log_pass "Enhanced metadata handling test passed"
return 0
}
# Test 6: Layer relationship tracking
test_layer_relationships() {
((TOTAL_TESTS++))
log_test "Testing layer relationship tracking..."
local layer1="/tmp/apt-layer-advanced-test-layers/layer1.composefs"
local layer2="/tmp/apt-layer-advanced-test-layers/layer2.composefs"
local composed="/tmp/apt-layer-advanced-test-layers/multi-composed.composefs"
local relationship_file="/tmp/apt-layer-advanced-test-layers/relationships.json"
# Create layers if they don't exist
if [[ ! -f "$layer1" ]]; then
local source1="/tmp/apt-layer-advanced-test-source1"
apt-layer composefs create "$source1" "$layer1" "layer1"
fi
if [[ ! -f "$layer2" ]]; then
local source2="/tmp/apt-layer-advanced-test-source2"
apt-layer composefs create "$source2" "$layer2" "layer2"
fi
if [[ ! -f "$composed" ]]; then
apt-layer composefs multi-compose "$layer1" "$layer2" "$composed"
fi
# Track relationships
if ! apt-layer composefs track-relationships "$composed" "$relationship_file" "$layer1" "$layer2"; then
log_fail "Layer relationship tracking failed"
return 1
fi
# Check if relationship file was created
if [[ ! -f "$relationship_file" ]]; then
log_fail "Relationship file not created"
return 1
fi
# Validate relationship file (basic check)
if ! command -v jq &> /dev/null; then
log_info "jq not available, skipping relationship validation"
else
if ! jq empty "$relationship_file" 2>/dev/null; then
log_fail "Invalid relationship JSON"
return 1
fi
# Check required fields
if ! jq -e '.layer_id' "$relationship_file" >/dev/null 2>&1; then
log_fail "Missing layer_id in relationship file"
return 1
fi
if ! jq -e '.parent_layers' "$relationship_file" >/dev/null 2>&1; then
log_fail "Missing parent_layers in relationship file"
return 1
fi
fi
log_pass "Layer relationship tracking test passed"
return 0
}
# Test 7: Advanced conflict resolution
test_advanced_conflict_resolution() {
((TOTAL_TESTS++))
log_test "Testing advanced conflict resolution..."
# Create conflicting layers
local conflict_source1="/tmp/apt-layer-advanced-test-conflict1"
local conflict_source2="/tmp/apt-layer-advanced-test-conflict2"
local conflict_layer1="/tmp/apt-layer-advanced-test-layers/conflict1.composefs"
local conflict_layer2="/tmp/apt-layer-advanced-test-layers/conflict2.composefs"
local resolved_layer="/tmp/apt-layer-advanced-test-layers/resolved.composefs"
local conflict_report="/tmp/apt-layer-advanced-test-layers/conflict-report.txt"
# Create conflicting sources
mkdir -p "$conflict_source1" "$conflict_source2"
echo "Version 1 of conflicting file" > "$conflict_source1/conflict.txt"
echo "Version 2 of conflicting file" > "$conflict_source2/conflict.txt"
echo "Unique to source 1" > "$conflict_source1/unique1.txt"
echo "Unique to source 2" > "$conflict_source2/unique2.txt"
# Create layers
if ! apt-layer composefs create "$conflict_source1" "$conflict_layer1" "conflict1"; then
log_fail "Failed to create conflict layer 1"
return 1
fi
if ! apt-layer composefs create "$conflict_source2" "$conflict_layer2" "conflict2"; then
log_fail "Failed to create conflict layer 2"
return 1
fi
# Resolve conflicts
if ! apt-layer composefs resolve-conflicts "$conflict_layer1" "$conflict_layer2" "$resolved_layer" "$conflict_report"; then
log_fail "Conflict resolution failed"
return 1
fi
# Check if conflict report was created
if [[ ! -f "$conflict_report" ]]; then
log_fail "Conflict report not created"
return 1
fi
# Validate resolved layer
if ! apt-layer composefs validate "$resolved_layer"; then
log_fail "Resolved layer validation failed"
return 1
fi
# Test mounting resolved layer
local mount_point="/tmp/apt-layer-advanced-test-mounts/resolved"
if ! apt-layer composefs mount "$resolved_layer" "$mount_point"; then
log_fail "Failed to mount resolved layer"
return 1
fi
# Check if files are present
if [[ ! -f "$mount_point/conflict.txt" ]]; then
log_fail "Conflicting file not found in resolved layer"
return 1
fi
if [[ ! -f "$mount_point/unique1.txt" ]]; then
log_fail "Unique1 file not found in resolved layer"
return 1
fi
if [[ ! -f "$mount_point/unique2.txt" ]]; then
log_fail "Unique2 file not found in resolved layer"
return 1
fi
# Unmount
apt-layer composefs unmount "$mount_point"
log_pass "Advanced conflict resolution test passed"
return 0
}
# Test 8: Performance testing
test_performance_features() {
((TOTAL_TESTS++))
log_test "Testing performance features..."
local layer_path="/tmp/apt-layer-advanced-test-layers/deduplicated.composefs"
local benchmark_file="/tmp/apt-layer-advanced-test-layers/performance-benchmark.txt"
# Create layer if it doesn't exist
if [[ ! -f "$layer_path" ]]; then
log_info "Creating layer for performance test..."
local source1="/tmp/apt-layer-advanced-test-source1"
local layer1="/tmp/apt-layer-advanced-test-layers/layer1.composefs"
apt-layer composefs create "$source1" "$layer1" "layer1"
apt-layer composefs deduplicate "$layer1" "$layer_path" "content-hash"
fi
# Run performance benchmark
local start_time
local end_time
local benchmark_time
start_time=$(date +%s.%N)
if ! apt-layer composefs benchmark "$layer_path" "$benchmark_file"; then
log_fail "Performance benchmarking failed"
return 1
fi
end_time=$(date +%s.%N)
benchmark_time=$(echo "$end_time - $start_time" | bc -l 2>/dev/null || echo "0")
log_info "Benchmark execution time: ${benchmark_time}s"
# Check if benchmark completed within reasonable time (30 seconds)
if [[ $(echo "$benchmark_time > 30" | bc -l 2>/dev/null || echo "0") -eq 1 ]]; then
log_fail "Benchmark took too long: ${benchmark_time}s"
return 1
fi
log_pass "Performance features test passed"
return 0
}
# Main test execution
main() {
echo "=========================================="
echo "apt-layer ADVANCED COMPOSEFS FEATURES TEST"
echo "=========================================="
echo "Testing Phase 2.3: Advanced ComposeFS Features"
echo "=========================================="
echo ""
# Setup test environment
setup_test_env
# Run tests
test_multi_layer_composition
test_layer_deduplication
test_layer_compression
test_layer_benchmarking
test_enhanced_metadata
test_layer_relationships
test_advanced_conflict_resolution
test_performance_features
# Print summary
print_summary
}
# Cleanup on exit
trap cleanup EXIT
# Run main function
main "$@"