Added Merkel Trees for better replication state tracking. #1
@@ -1,7 +1,7 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
 | 
			
		||||
# KVS Integration Test Suite - Working Version
 | 
			
		||||
# Tests all critical features of the distributed key-value store
 | 
			
		||||
# KVS Integration Test Suite - Adapted for Merkle Tree Sync
 | 
			
		||||
# Tests all critical features of the distributed key-value store with Merkle Tree replication
 | 
			
		||||
 | 
			
		||||
# Colors for output
 | 
			
		||||
RED='\033[0;31m'
 | 
			
		||||
@@ -43,7 +43,7 @@ test_start() {
 | 
			
		||||
# Cleanup function
 | 
			
		||||
cleanup() {
 | 
			
		||||
    log_info "Cleaning up test environment..."
 | 
			
		||||
    pkill -f "./kvs" 2>/dev/null || true
 | 
			
		||||
    pkill -f "$BINARY" 2>/dev/null || true
 | 
			
		||||
    rm -rf "$TEST_DIR" 2>/dev/null || true
 | 
			
		||||
    sleep 2  # Allow processes to fully terminate
 | 
			
		||||
}
 | 
			
		||||
@@ -75,6 +75,7 @@ test_build() {
 | 
			
		||||
        log_error "Binary build failed"
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
    # Ensure we are back in TEST_DIR for subsequent tests
 | 
			
		||||
    cd "$TEST_DIR"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -103,12 +104,12 @@ EOF
 | 
			
		||||
            -d '{"message":"hello world"}')
 | 
			
		||||
        
 | 
			
		||||
        local get_result=$(curl -s http://localhost:8090/kv/test/basic)
 | 
			
		||||
        local message=$(echo "$get_result" | jq -r '.message' 2>/dev/null)
 | 
			
		||||
        local message=$(echo "$get_result" | jq -r '.data.message' 2>/dev/null) # Adjusted jq path
 | 
			
		||||
        
 | 
			
		||||
        if [ "$message" = "hello world" ]; then
 | 
			
		||||
            log_success "Basic CRUD operations work"
 | 
			
		||||
        else
 | 
			
		||||
            log_error "Basic CRUD failed: $get_result"
 | 
			
		||||
            log_error "Basic CRUD failed: Expected 'hello world', got '$message' from $get_result"
 | 
			
		||||
        fi
 | 
			
		||||
    else
 | 
			
		||||
        log_error "Basic test node failed to start"
 | 
			
		||||
@@ -120,7 +121,7 @@ EOF
 | 
			
		||||
 | 
			
		||||
# Test 3: Cluster formation
 | 
			
		||||
test_cluster_formation() {
 | 
			
		||||
    test_start "2-node cluster formation"
 | 
			
		||||
    test_start "2-node cluster formation and Merkle Tree replication"
 | 
			
		||||
    
 | 
			
		||||
    # Node 1 config
 | 
			
		||||
    cat > cluster1.yaml <<EOF
 | 
			
		||||
@@ -158,7 +159,7 @@ EOF
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
    
 | 
			
		||||
    sleep 2
 | 
			
		||||
    sleep 2 # Give node 1 a moment to fully initialize
 | 
			
		||||
    $BINARY cluster2.yaml >/dev/null 2>&1 &
 | 
			
		||||
    local pid2=$!
 | 
			
		||||
    
 | 
			
		||||
@@ -168,28 +169,46 @@ EOF
 | 
			
		||||
        return 1
 | 
			
		||||
    fi
 | 
			
		||||
    
 | 
			
		||||
    # Wait for cluster formation
 | 
			
		||||
    sleep 8
 | 
			
		||||
    # Wait for cluster formation and initial Merkle sync
 | 
			
		||||
    sleep 15
 | 
			
		||||
    
 | 
			
		||||
    # Check if nodes see each other
 | 
			
		||||
    local node1_members=$(curl -s http://localhost:8101/members/ | jq length 2>/dev/null || echo 0)
 | 
			
		||||
    local node2_members=$(curl -s http://localhost:8102/members/ | jq length 2>/dev/null || echo 0)
 | 
			
		||||
    
 | 
			
		||||
    if [ "$node1_members" -ge 1 ] && [ "$node2_members" -ge 1 ]; then
 | 
			
		||||
        log_success "2-node cluster formed successfully"
 | 
			
		||||
        log_success "2-node cluster formed successfully (N1 members: $node1_members, N2 members: $node2_members)"
 | 
			
		||||
        
 | 
			
		||||
        # Test data replication
 | 
			
		||||
        log_info "Putting data on Node 1, waiting for Merkle sync..."
 | 
			
		||||
        curl -s -X PUT http://localhost:8101/kv/cluster/test \
 | 
			
		||||
            -H "Content-Type: application/json" \
 | 
			
		||||
            -d '{"source":"node1"}' >/dev/null
 | 
			
		||||
            -d '{"source":"node1", "value": 1}' >/dev/null
 | 
			
		||||
        
 | 
			
		||||
        sleep 12  # Wait for sync cycle
 | 
			
		||||
        # Wait for Merkle sync cycle to complete
 | 
			
		||||
        sleep 12
 | 
			
		||||
        
 | 
			
		||||
        local node2_data=$(curl -s http://localhost:8102/kv/cluster/test | jq -r '.source' 2>/dev/null)
 | 
			
		||||
        if [ "$node2_data" = "node1" ]; then
 | 
			
		||||
            log_success "Data replication works correctly"
 | 
			
		||||
        local node2_data_full=$(curl -s http://localhost:8102/kv/cluster/test)
 | 
			
		||||
        local node2_data_source=$(echo "$node2_data_full" | jq -r '.data.source' 2>/dev/null)
 | 
			
		||||
        local node2_data_value=$(echo "$node2_data_full" | jq -r '.data.value' 2>/dev/null)
 | 
			
		||||
        local node1_data_full=$(curl -s http://localhost:8101/kv/cluster/test)
 | 
			
		||||
        
 | 
			
		||||
        if [ "$node2_data_source" = "node1" ] && [ "$node2_data_value" = "1" ]; then
 | 
			
		||||
            log_success "Data replication works correctly (Node 2 has data from Node 1)"
 | 
			
		||||
 | 
			
		||||
            # Verify UUIDs and Timestamps are identical (crucial for Merkle sync correctness)
 | 
			
		||||
            local node1_uuid=$(echo "$node1_data_full" | jq -r '.uuid' 2>/dev/null)
 | 
			
		||||
            local node1_timestamp=$(echo "$node1_data_full" | jq -r '.timestamp' 2>/dev/null)
 | 
			
		||||
            local node2_uuid=$(echo "$node2_data_full" | jq -r '.uuid' 2>/dev/null)
 | 
			
		||||
            local node2_timestamp=$(echo "$node2_data_full" | jq -r '.timestamp' 2>/dev/null)
 | 
			
		||||
 | 
			
		||||
            if [ "$node1_uuid" = "$node2_uuid" ] && [ "$node1_timestamp" = "$node2_timestamp" ]; then
 | 
			
		||||
                log_success "Replicated data retains original UUID and Timestamp"
 | 
			
		||||
            else
 | 
			
		||||
                log_error "Replicated data changed UUID/Timestamp: N1_UUID=$node1_uuid, N1_TS=$node1_timestamp, N2_UUID=$node2_uuid, N2_TS=$node2_timestamp"
 | 
			
		||||
            fi
 | 
			
		||||
        else
 | 
			
		||||
            log_error "Data replication failed: $node2_data"
 | 
			
		||||
            log_error "Data replication failed: Node 2 data: $node2_data_full"
 | 
			
		||||
        fi
 | 
			
		||||
    else
 | 
			
		||||
        log_error "Cluster formation failed (N1 members: $node1_members, N2 members: $node2_members)"
 | 
			
		||||
@@ -199,9 +218,12 @@ EOF
 | 
			
		||||
    sleep 2
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Test 4: Conflict resolution (simplified)
 | 
			
		||||
# Test 4: Conflict resolution (Merkle Tree based)
 | 
			
		||||
# This test assumes 'test_conflict.go' creates two BadgerDBs with a key
 | 
			
		||||
# that has the same path and timestamp but different UUIDs, or different timestamps
 | 
			
		||||
# but same path. The Merkle tree sync should then trigger conflict resolution.
 | 
			
		||||
test_conflict_resolution() {
 | 
			
		||||
    test_start "Conflict resolution test"
 | 
			
		||||
    test_start "Conflict resolution test (Merkle Tree based)"
 | 
			
		||||
    
 | 
			
		||||
    # Create conflicting data using our utility
 | 
			
		||||
    rm -rf conflict1_data conflict2_data 2>/dev/null || true
 | 
			
		||||
@@ -233,7 +255,8 @@ sync_interval: 8
 | 
			
		||||
EOF
 | 
			
		||||
        
 | 
			
		||||
        # Start nodes
 | 
			
		||||
        $BINARY conflict1.yaml >conflict1.log 2>&1 &
 | 
			
		||||
        # Node 1 started first, making it "older" for tie-breaker if timestamps are equal
 | 
			
		||||
        "$BINARY" conflict1.yaml >conflict1.log 2>&1 &
 | 
			
		||||
        local pid1=$!
 | 
			
		||||
        
 | 
			
		||||
        if wait_for_service 8111; then
 | 
			
		||||
@@ -242,26 +265,50 @@ EOF
 | 
			
		||||
            local pid2=$!
 | 
			
		||||
            
 | 
			
		||||
            if wait_for_service 8112; then
 | 
			
		||||
                # Get initial data
 | 
			
		||||
                local node1_initial=$(curl -s http://localhost:8111/kv/test/conflict/data | jq -r '.message' 2>/dev/null)
 | 
			
		||||
                local node2_initial=$(curl -s http://localhost:8112/kv/test/conflict/data | jq -r '.message' 2>/dev/null)
 | 
			
		||||
                # Get initial data (full StoredValue)
 | 
			
		||||
                local node1_initial_full=$(curl -s http://localhost:8111/kv/test/conflict/data)
 | 
			
		||||
                local node2_initial_full=$(curl -s http://localhost:8112/kv/test/conflict/data)
 | 
			
		||||
                
 | 
			
		||||
                # Wait for conflict resolution
 | 
			
		||||
                sleep 12
 | 
			
		||||
                local node1_initial_msg=$(echo "$node1_initial_full" | jq -r '.data.message' 2>/dev/null)
 | 
			
		||||
                local node2_initial_msg=$(echo "$node2_initial_full" | jq -r '.data.message' 2>/dev/null)
 | 
			
		||||
                
 | 
			
		||||
                # Get final data
 | 
			
		||||
                local node1_final=$(curl -s http://localhost:8111/kv/test/conflict/data | jq -r '.message' 2>/dev/null)
 | 
			
		||||
                local node2_final=$(curl -s http://localhost:8112/kv/test/conflict/data | jq -r '.message' 2>/dev/null)
 | 
			
		||||
                log_info "Initial conflict state: Node1='$node1_initial_msg', Node2='$node2_initial_msg'"
 | 
			
		||||
 | 
			
		||||
                # Wait for conflict resolution (multiple sync cycles might be needed)
 | 
			
		||||
                sleep 20
 | 
			
		||||
                
 | 
			
		||||
                # Get final data (full StoredValue)
 | 
			
		||||
                local node1_final_full=$(curl -s http://localhost:8111/kv/test/conflict/data)
 | 
			
		||||
                local node2_final_full=$(curl -s http://localhost:8112/kv/test/conflict/data)
 | 
			
		||||
 | 
			
		||||
                local node1_final_msg=$(echo "$node1_final_full" | jq -r '.data.message' 2>/dev/null)
 | 
			
		||||
                local node2_final_msg=$(echo "$node2_final_full" | jq -r '.data.message' 2>/dev/null)
 | 
			
		||||
                
 | 
			
		||||
                # Check if they converged
 | 
			
		||||
                if [ "$node1_final" = "$node2_final" ] && [ -n "$node1_final" ]; then
 | 
			
		||||
                    if grep -q "conflict resolution" conflict1.log conflict2.log 2>/dev/null; then
 | 
			
		||||
                        log_success "Conflict resolution detected and resolved ($node1_initial vs $node2_initial → $node1_final)"
 | 
			
		||||
                if [ "$node1_final_msg" = "$node2_final_msg" ] && [ -n "$node1_final_msg" ]; then
 | 
			
		||||
                    log_success "Conflict resolution converged to: '$node1_final_msg'"
 | 
			
		||||
                    
 | 
			
		||||
                    # Verify UUIDs and Timestamps are identical after resolution
 | 
			
		||||
                    local node1_final_uuid=$(echo "$node1_final_full" | jq -r '.uuid' 2>/dev/null)
 | 
			
		||||
                    local node1_final_timestamp=$(echo "$node1_final_full" | jq -r '.timestamp' 2>/dev/null)
 | 
			
		||||
                    local node2_final_uuid=$(echo "$node2_final_full" | jq -r '.uuid' 2>/dev/null)
 | 
			
		||||
                    local node2_final_timestamp=$(echo "$node2_final_full" | jq -r '.timestamp' 2>/dev/null)
 | 
			
		||||
 | 
			
		||||
                    if [ "$node1_final_uuid" = "$node2_final_uuid" ] && [ "$node1_final_timestamp" = "$node2_final_timestamp" ]; then
 | 
			
		||||
                        log_success "Resolved data retains consistent UUID and Timestamp across nodes"
 | 
			
		||||
                    else
 | 
			
		||||
                        log_success "Nodes converged without conflicts ($node1_final)"
 | 
			
		||||
                        log_error "Resolved data has inconsistent UUID/Timestamp: N1_UUID=$node1_final_uuid, N1_TS=$node1_final_timestamp, N2_UUID=$node2_final_uuid, N2_TS=$node2_final_timestamp"
 | 
			
		||||
                    fi
 | 
			
		||||
 | 
			
		||||
                    # Optionally, check logs for conflict resolution messages
 | 
			
		||||
                    if grep -q "Conflict resolved" conflict1.log conflict2.log 2>/dev/null; then
 | 
			
		||||
                        log_success "Conflict resolution messages found in logs"
 | 
			
		||||
                    else
 | 
			
		||||
                        log_error "No 'Conflict resolved' messages found in logs, but data converged."
 | 
			
		||||
                    fi
 | 
			
		||||
 | 
			
		||||
                else
 | 
			
		||||
                    log_error "Conflict resolution failed: N1='$node1_final', N2='$node2_final'"
 | 
			
		||||
                    log_error "Conflict resolution failed: N1_final='$node1_final_msg', N2_final='$node2_final_msg'"
 | 
			
		||||
                fi
 | 
			
		||||
            else
 | 
			
		||||
                log_error "Conflict node 2 failed to start"
 | 
			
		||||
@@ -276,14 +323,14 @@ EOF
 | 
			
		||||
        sleep 2
 | 
			
		||||
    else
 | 
			
		||||
        cd "$TEST_DIR"
 | 
			
		||||
        log_error "Failed to create conflict test data"
 | 
			
		||||
        log_error "Failed to create conflict test data. Ensure test_conflict.go is correct."
 | 
			
		||||
    fi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Main test execution
 | 
			
		||||
main() {
 | 
			
		||||
    echo "=================================================="
 | 
			
		||||
    echo "         KVS Integration Test Suite"
 | 
			
		||||
    echo "         KVS Integration Test Suite (Merkle Tree)"
 | 
			
		||||
    echo "=================================================="
 | 
			
		||||
    
 | 
			
		||||
    # Setup
 | 
			
		||||
@@ -308,7 +355,7 @@ main() {
 | 
			
		||||
    echo "=================================================="
 | 
			
		||||
    
 | 
			
		||||
    if [ $TESTS_FAILED -eq 0 ]; then
 | 
			
		||||
        echo -e "${GREEN}🎉 All tests passed! KVS is working correctly.${NC}"
 | 
			
		||||
        echo -e "${GREEN}🎉 All tests passed! KVS with Merkle Tree sync is working correctly.${NC}"
 | 
			
		||||
        cleanup
 | 
			
		||||
        exit 0
 | 
			
		||||
    else
 | 
			
		||||
@@ -322,4 +369,4 @@ main() {
 | 
			
		||||
trap cleanup INT TERM
 | 
			
		||||
 | 
			
		||||
# Run tests
 | 
			
		||||
main "$@"
 | 
			
		||||
main "$@"
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user