Compare commits
4 Commits
ebed73dc11
...
master
Author | SHA1 | Date | |
---|---|---|---|
5ab03331fc | |||
3775939a3b | |||
9ea19a3532 | |||
45d5c38c90 |
9
go.mod
9
go.mod
@ -4,9 +4,13 @@ go 1.21
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/dgraph-io/badger/v4 v4.2.0
|
github.com/dgraph-io/badger/v4 v4.2.0
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.2
|
||||||
github.com/google/uuid v1.4.0
|
github.com/google/uuid v1.4.0
|
||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
|
github.com/klauspost/compress v1.17.4
|
||||||
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,11 +24,10 @@ require (
|
|||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
github.com/golang/snappy v0.0.3 // indirect
|
github.com/golang/snappy v0.0.3 // indirect
|
||||||
github.com/google/flatbuffers v1.12.1 // indirect
|
github.com/google/flatbuffers v1.12.1 // indirect
|
||||||
github.com/klauspost/compress v1.12.3 // indirect
|
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
go.opencensus.io v0.22.5 // indirect
|
go.opencensus.io v0.22.5 // indirect
|
||||||
golang.org/x/net v0.7.0 // indirect
|
golang.org/x/net v0.10.0 // indirect
|
||||||
golang.org/x/sys v0.5.0 // indirect
|
golang.org/x/sys v0.14.0 // indirect
|
||||||
google.golang.org/protobuf v1.28.1 // indirect
|
google.golang.org/protobuf v1.28.1 // indirect
|
||||||
)
|
)
|
||||||
|
17
go.sum
17
go.sum
@ -18,6 +18,8 @@ github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4
|
|||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
|
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
|
||||||
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
||||||
@ -42,8 +44,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
|||||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU=
|
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||||
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
@ -52,6 +54,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
@ -64,6 +68,7 @@ go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0=
|
|||||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
@ -79,8 +84,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@ -95,8 +100,8 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# KVS Integration Test Suite - Working Version
|
# KVS Integration Test Suite - Adapted for Merkle Tree Sync
|
||||||
# Tests all critical features of the distributed key-value store
|
# Tests all critical features of the distributed key-value store with Merkle Tree replication
|
||||||
|
|
||||||
# Colors for output
|
# Colors for output
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
@ -43,7 +43,7 @@ test_start() {
|
|||||||
# Cleanup function
|
# Cleanup function
|
||||||
cleanup() {
|
cleanup() {
|
||||||
log_info "Cleaning up test environment..."
|
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
|
rm -rf "$TEST_DIR" 2>/dev/null || true
|
||||||
sleep 2 # Allow processes to fully terminate
|
sleep 2 # Allow processes to fully terminate
|
||||||
}
|
}
|
||||||
@ -75,6 +75,7 @@ test_build() {
|
|||||||
log_error "Binary build failed"
|
log_error "Binary build failed"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
# Ensure we are back in TEST_DIR for subsequent tests
|
||||||
cd "$TEST_DIR"
|
cd "$TEST_DIR"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,12 +104,12 @@ EOF
|
|||||||
-d '{"message":"hello world"}')
|
-d '{"message":"hello world"}')
|
||||||
|
|
||||||
local get_result=$(curl -s http://localhost:8090/kv/test/basic)
|
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
|
if [ "$message" = "hello world" ]; then
|
||||||
log_success "Basic CRUD operations work"
|
log_success "Basic CRUD operations work"
|
||||||
else
|
else
|
||||||
log_error "Basic CRUD failed: $get_result"
|
log_error "Basic CRUD failed: Expected 'hello world', got '$message' from $get_result"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log_error "Basic test node failed to start"
|
log_error "Basic test node failed to start"
|
||||||
@ -120,7 +121,7 @@ EOF
|
|||||||
|
|
||||||
# Test 3: Cluster formation
|
# Test 3: Cluster formation
|
||||||
test_cluster_formation() {
|
test_cluster_formation() {
|
||||||
test_start "2-node cluster formation"
|
test_start "2-node cluster formation and Merkle Tree replication"
|
||||||
|
|
||||||
# Node 1 config
|
# Node 1 config
|
||||||
cat > cluster1.yaml <<EOF
|
cat > cluster1.yaml <<EOF
|
||||||
@ -158,7 +159,7 @@ EOF
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sleep 2
|
sleep 2 # Give node 1 a moment to fully initialize
|
||||||
$BINARY cluster2.yaml >/dev/null 2>&1 &
|
$BINARY cluster2.yaml >/dev/null 2>&1 &
|
||||||
local pid2=$!
|
local pid2=$!
|
||||||
|
|
||||||
@ -168,28 +169,46 @@ EOF
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Wait for cluster formation
|
# Wait for cluster formation and initial Merkle sync
|
||||||
sleep 8
|
sleep 15
|
||||||
|
|
||||||
# Check if nodes see each other
|
# Check if nodes see each other
|
||||||
local node1_members=$(curl -s http://localhost:8101/members/ | jq length 2>/dev/null || echo 0)
|
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)
|
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
|
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
|
# Test data replication
|
||||||
|
log_info "Putting data on Node 1, waiting for Merkle sync..."
|
||||||
curl -s -X PUT http://localhost:8101/kv/cluster/test \
|
curl -s -X PUT http://localhost:8101/kv/cluster/test \
|
||||||
-H "Content-Type: application/json" \
|
-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)
|
local node2_data_full=$(curl -s http://localhost:8102/kv/cluster/test)
|
||||||
if [ "$node2_data" = "node1" ]; then
|
local node2_data_source=$(echo "$node2_data_full" | jq -r '.data.source' 2>/dev/null)
|
||||||
log_success "Data replication works correctly"
|
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
|
else
|
||||||
log_error "Data replication failed: $node2_data"
|
log_error "Data replication failed: Node 2 data: $node2_data_full"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log_error "Cluster formation failed (N1 members: $node1_members, N2 members: $node2_members)"
|
log_error "Cluster formation failed (N1 members: $node1_members, N2 members: $node2_members)"
|
||||||
@ -199,9 +218,12 @@ EOF
|
|||||||
sleep 2
|
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_conflict_resolution() {
|
||||||
test_start "Conflict resolution test"
|
test_start "Conflict resolution test (Merkle Tree based)"
|
||||||
|
|
||||||
# Create conflicting data using our utility
|
# Create conflicting data using our utility
|
||||||
rm -rf conflict1_data conflict2_data 2>/dev/null || true
|
rm -rf conflict1_data conflict2_data 2>/dev/null || true
|
||||||
@ -233,7 +255,8 @@ sync_interval: 8
|
|||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Start nodes
|
# 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=$!
|
local pid1=$!
|
||||||
|
|
||||||
if wait_for_service 8111; then
|
if wait_for_service 8111; then
|
||||||
@ -242,26 +265,50 @@ EOF
|
|||||||
local pid2=$!
|
local pid2=$!
|
||||||
|
|
||||||
if wait_for_service 8112; then
|
if wait_for_service 8112; then
|
||||||
# Get initial data
|
# Get initial data (full StoredValue)
|
||||||
local node1_initial=$(curl -s http://localhost:8111/kv/test/conflict/data | jq -r '.message' 2>/dev/null)
|
local node1_initial_full=$(curl -s http://localhost:8111/kv/test/conflict/data)
|
||||||
local node2_initial=$(curl -s http://localhost:8112/kv/test/conflict/data | jq -r '.message' 2>/dev/null)
|
local node2_initial_full=$(curl -s http://localhost:8112/kv/test/conflict/data)
|
||||||
|
|
||||||
# Wait for conflict resolution
|
local node1_initial_msg=$(echo "$node1_initial_full" | jq -r '.data.message' 2>/dev/null)
|
||||||
sleep 12
|
local node2_initial_msg=$(echo "$node2_initial_full" | jq -r '.data.message' 2>/dev/null)
|
||||||
|
|
||||||
# Get final data
|
log_info "Initial conflict state: Node1='$node1_initial_msg', Node2='$node2_initial_msg'"
|
||||||
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)
|
# 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
|
# Check if they converged
|
||||||
if [ "$node1_final" = "$node2_final" ] && [ -n "$node1_final" ]; then
|
if [ "$node1_final_msg" = "$node2_final_msg" ] && [ -n "$node1_final_msg" ]; then
|
||||||
if grep -q "conflict resolution" conflict1.log conflict2.log 2>/dev/null; then
|
log_success "Conflict resolution converged to: '$node1_final_msg'"
|
||||||
log_success "Conflict resolution detected and resolved ($node1_initial vs $node2_initial → $node1_final)"
|
|
||||||
|
# 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
|
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
|
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
|
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
|
fi
|
||||||
else
|
else
|
||||||
log_error "Conflict node 2 failed to start"
|
log_error "Conflict node 2 failed to start"
|
||||||
@ -276,14 +323,14 @@ EOF
|
|||||||
sleep 2
|
sleep 2
|
||||||
else
|
else
|
||||||
cd "$TEST_DIR"
|
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
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Main test execution
|
# Main test execution
|
||||||
main() {
|
main() {
|
||||||
echo "=================================================="
|
echo "=================================================="
|
||||||
echo " KVS Integration Test Suite"
|
echo " KVS Integration Test Suite (Merkle Tree)"
|
||||||
echo "=================================================="
|
echo "=================================================="
|
||||||
|
|
||||||
# Setup
|
# Setup
|
||||||
@ -308,7 +355,7 @@ main() {
|
|||||||
echo "=================================================="
|
echo "=================================================="
|
||||||
|
|
||||||
if [ $TESTS_FAILED -eq 0 ]; then
|
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
|
cleanup
|
||||||
exit 0
|
exit 0
|
||||||
else
|
else
|
||||||
|
291
next_steps.md
Normal file
291
next_steps.md
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
# KVS Development Phase 2: Implementation Specification
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
This document specifies the next development phase for the KVS (Key-Value Store) distributed database. Phase 2 adds authentication, authorization, data management improvements, and basic security features while maintaining backward compatibility with the existing Merkle tree-based replication system.
|
||||||
|
|
||||||
|
## 1. Authentication & Authorization System
|
||||||
|
|
||||||
|
### 1.1 Core Components
|
||||||
|
|
||||||
|
**Users**
|
||||||
|
- Identified by UUID (generated server-side)
|
||||||
|
- Nickname stored as SHA3-512 hash
|
||||||
|
- Can belong to multiple groups
|
||||||
|
- Storage key: `user:<uuid>`
|
||||||
|
|
||||||
|
**Groups**
|
||||||
|
- Identified by UUID (generated server-side)
|
||||||
|
- Group name stored as SHA3-512 hash
|
||||||
|
- Contains list of member user UUIDs
|
||||||
|
- Storage key: `group:<uuid>`
|
||||||
|
|
||||||
|
**API Tokens**
|
||||||
|
- JWT tokens with SHA3-512 hashed storage
|
||||||
|
- 1-hour default expiration (configurable)
|
||||||
|
- Storage key: `token:<sha3-512-hash>`
|
||||||
|
|
||||||
|
### 1.2 Permission Model
|
||||||
|
|
||||||
|
**POSIX-inspired ACL framework** with 12-bit permissions:
|
||||||
|
- 4 bits each for Owner/Group/Others
|
||||||
|
- Operations: Create(C), Delete(D), Write(W), Read(R)
|
||||||
|
- Default permissions: Owner(1111), Group(0110), Others(0010)
|
||||||
|
- Stored as integer bitmask in resource metadata
|
||||||
|
|
||||||
|
**Resource Metadata Schema**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"owner_uuid": "string",
|
||||||
|
"group_uuid": "string",
|
||||||
|
"permissions": 3826, // 12-bit integer
|
||||||
|
"ttl": "24h"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 API Endpoints
|
||||||
|
|
||||||
|
**User Management**
|
||||||
|
```
|
||||||
|
POST /api/users
|
||||||
|
Body: {"nickname": "string"}
|
||||||
|
Returns: {"uuid": "string"}
|
||||||
|
|
||||||
|
GET /api/users/{uuid}
|
||||||
|
PUT /api/users/{uuid}
|
||||||
|
Body: {"nickname": "string", "groups": ["uuid1", "uuid2"]}
|
||||||
|
DELETE /api/users/{uuid}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Group Management**
|
||||||
|
```
|
||||||
|
POST /api/groups
|
||||||
|
Body: {"groupname": "string", "members": ["uuid1", "uuid2"]}
|
||||||
|
Returns: {"uuid": "string"}
|
||||||
|
|
||||||
|
GET /api/groups/{uuid}
|
||||||
|
PUT /api/groups/{uuid}
|
||||||
|
Body: {"members": ["uuid1", "uuid2"]}
|
||||||
|
DELETE /api/groups/{uuid}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Token Management**
|
||||||
|
```
|
||||||
|
POST /api/tokens
|
||||||
|
Body: {"user_uuid": "string", "scopes": ["read", "write"]}
|
||||||
|
Returns: {"token": "jwt-string", "expires_at": "timestamp"}
|
||||||
|
```
|
||||||
|
|
||||||
|
All endpoints require `Authorization: Bearer <token>` header.
|
||||||
|
|
||||||
|
### 1.4 Implementation Requirements
|
||||||
|
|
||||||
|
- Use `golang.org/x/crypto/sha3` for all hashing
|
||||||
|
- Store token SHA3-512 hash in BadgerDB with TTL
|
||||||
|
- Implement `CheckPermission(userUUID, resourceKey, operation) bool` function
|
||||||
|
- Include user/group data in existing Merkle tree replication
|
||||||
|
- Create migration script for existing data (add default metadata)
|
||||||
|
|
||||||
|
## 2. Database Enhancements
|
||||||
|
|
||||||
|
### 2.1 ZSTD Compression
|
||||||
|
|
||||||
|
**Configuration**:
|
||||||
|
```yaml
|
||||||
|
database:
|
||||||
|
compression_enabled: true
|
||||||
|
compression_level: 3 # 1-19, balance performance/ratio
|
||||||
|
```
|
||||||
|
|
||||||
|
**Implementation**:
|
||||||
|
- Use `github.com/klauspost/compress/zstd`
|
||||||
|
- Compress all JSON values before BadgerDB storage
|
||||||
|
- Decompress on read operations
|
||||||
|
- Optional: Batch recompression of existing data on startup
|
||||||
|
|
||||||
|
### 2.2 TTL (Time-To-Live)
|
||||||
|
|
||||||
|
**Features**:
|
||||||
|
- Per-key TTL support via resource metadata
|
||||||
|
- Global default TTL configuration (optional)
|
||||||
|
- Automatic expiration via BadgerDB's native TTL
|
||||||
|
- TTL applied to main data and revision keys
|
||||||
|
|
||||||
|
**API Integration**:
|
||||||
|
```json
|
||||||
|
// In PUT/POST requests
|
||||||
|
{
|
||||||
|
"data": {...},
|
||||||
|
"ttl": "24h" // Go duration format
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Revision History
|
||||||
|
|
||||||
|
**Storage Pattern**:
|
||||||
|
- Main data: `data:<key>`
|
||||||
|
- Revisions: `data:<key>:rev:1`, `data:<key>:rev:2`, `data:<key>:rev:3`
|
||||||
|
- Metadata: `data:<key>:metadata` includes `"revisions": [1,2,3]`
|
||||||
|
|
||||||
|
**Rotation Logic**:
|
||||||
|
- On write: rev:1→rev:2, rev:2→rev:3, new→rev:1, delete rev:3
|
||||||
|
- Store up to 3 revisions per key
|
||||||
|
|
||||||
|
**API Endpoints**:
|
||||||
|
```
|
||||||
|
GET /api/data/{key}/history
|
||||||
|
Returns: {"revisions": [{"number": 1, "timestamp": "..."}]}
|
||||||
|
|
||||||
|
GET /api/data/{key}/history/{revision}
|
||||||
|
Returns: StoredValue for specific revision
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 Backup System
|
||||||
|
|
||||||
|
**Configuration**:
|
||||||
|
```yaml
|
||||||
|
backups:
|
||||||
|
enabled: true
|
||||||
|
schedule: "0 0 * * *" # Daily midnight
|
||||||
|
path: "/backups"
|
||||||
|
retention: 7 # days
|
||||||
|
```
|
||||||
|
|
||||||
|
**Implementation**:
|
||||||
|
- Use `github.com/robfig/cron/v3` for scheduling
|
||||||
|
- Create ZSTD-compressed BadgerDB snapshots
|
||||||
|
- Filename format: `kvs-backup-YYYY-MM-DD.zstd`
|
||||||
|
- Automatic cleanup of old backups
|
||||||
|
- Status API: `GET /api/backup/status`
|
||||||
|
|
||||||
|
### 2.5 JSON Size Limits
|
||||||
|
|
||||||
|
**Configuration**:
|
||||||
|
```yaml
|
||||||
|
database:
|
||||||
|
max_json_size: 1048576 # 1MB default
|
||||||
|
```
|
||||||
|
|
||||||
|
**Implementation**:
|
||||||
|
- Check size before compression/storage
|
||||||
|
- Return HTTP 413 if exceeded
|
||||||
|
- Apply to main data and revisions
|
||||||
|
- Log oversized attempts
|
||||||
|
|
||||||
|
## 3. Security Features
|
||||||
|
|
||||||
|
### 3.1 Rate Limiting
|
||||||
|
|
||||||
|
**Configuration**:
|
||||||
|
```yaml
|
||||||
|
rate_limit:
|
||||||
|
requests: 100
|
||||||
|
window: "1m"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Implementation**:
|
||||||
|
- Per-user rate limiting using BadgerDB counters
|
||||||
|
- Key pattern: `ratelimit:<user_uuid>:<window_start>`
|
||||||
|
- Return HTTP 429 when limit exceeded
|
||||||
|
- Counters have TTL equal to window duration
|
||||||
|
|
||||||
|
### 3.2 Tamper-Evident Logs
|
||||||
|
|
||||||
|
**Log Entry Schema**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"timestamp": "2025-09-11T17:29:00Z",
|
||||||
|
"action": "data_write", // Configurable actions
|
||||||
|
"user_uuid": "string",
|
||||||
|
"resource": "string",
|
||||||
|
"signature": "sha3-512 hash" // Hash of all fields
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Storage**:
|
||||||
|
- Key: `log:<timestamp>:<uuid>`
|
||||||
|
- Compressed with ZSTD
|
||||||
|
- Hourly Merkle tree roots: `log:merkle:<timestamp>`
|
||||||
|
- Include in cluster replication
|
||||||
|
|
||||||
|
**Configurable Actions**:
|
||||||
|
```yaml
|
||||||
|
tamper_logs:
|
||||||
|
actions: ["data_write", "user_create", "auth_failure"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Implementation Phases
|
||||||
|
|
||||||
|
### Phase 2.1: Core Authentication
|
||||||
|
1. Implement user/group storage schema
|
||||||
|
2. Add SHA3-512 hashing utilities
|
||||||
|
3. Create basic CRUD APIs for users/groups
|
||||||
|
4. Implement JWT token generation/validation
|
||||||
|
5. Add authorization middleware
|
||||||
|
|
||||||
|
### Phase 2.2: Data Features
|
||||||
|
1. Add ZSTD compression to BadgerDB operations
|
||||||
|
2. Implement TTL support in resource metadata
|
||||||
|
3. Build revision history system
|
||||||
|
4. Add JSON size validation
|
||||||
|
|
||||||
|
### Phase 2.3: Security & Operations
|
||||||
|
1. Implement rate limiting middleware
|
||||||
|
2. Add tamper-evident logging system
|
||||||
|
3. Build backup scheduling system
|
||||||
|
4. Create migration scripts for existing data
|
||||||
|
|
||||||
|
### Phase 2.4: Integration & Testing
|
||||||
|
1. Integrate auth with existing replication
|
||||||
|
2. End-to-end testing of all features
|
||||||
|
3. Performance benchmarking
|
||||||
|
4. Documentation updates
|
||||||
|
|
||||||
|
## 5. Configuration Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
node_id: "node1"
|
||||||
|
bind_address: "127.0.0.1"
|
||||||
|
port: 8080
|
||||||
|
data_dir: "./data"
|
||||||
|
|
||||||
|
database:
|
||||||
|
compression_enabled: true
|
||||||
|
compression_level: 3
|
||||||
|
max_json_size: 1048576
|
||||||
|
default_ttl: "0" # No default TTL
|
||||||
|
|
||||||
|
backups:
|
||||||
|
enabled: true
|
||||||
|
schedule: "0 0 * * *"
|
||||||
|
path: "/backups"
|
||||||
|
retention: 7
|
||||||
|
|
||||||
|
rate_limit:
|
||||||
|
requests: 100
|
||||||
|
window: "1m"
|
||||||
|
|
||||||
|
tamper_logs:
|
||||||
|
actions: ["data_write", "user_create", "auth_failure"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Migration Strategy
|
||||||
|
|
||||||
|
1. **Backward Compatibility**: All existing APIs remain functional
|
||||||
|
2. **Optional Features**: New features can be disabled via configuration
|
||||||
|
|
||||||
|
|
||||||
|
## 7. Dependencies
|
||||||
|
|
||||||
|
**New Libraries**:
|
||||||
|
- `golang.org/x/crypto/sha3` - SHA3-512 hashing
|
||||||
|
- `github.com/klauspost/compress/zstd` - Compression
|
||||||
|
- `github.com/robfig/cron/v3` - Backup scheduling
|
||||||
|
- `github.com/golang-jwt/jwt/v4` - JWT tokens (recommended)
|
||||||
|
|
||||||
|
**Existing Libraries** (no changes):
|
||||||
|
- `github.com/dgraph-io/badger/v4`
|
||||||
|
- `github.com/google/uuid`
|
||||||
|
- `github.com/gorilla/mux`
|
||||||
|
- `github.com/sirupsen/logrus`
|
||||||
|
|
Reference in New Issue
Block a user