Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add test coverage to EchoVault #45

Merged
merged 27 commits into from
Jun 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ae5a675
Added test for TLS connection
kelvinmwinuka May 29, 2024
3202120
Added client openssl files in order to faciliate MTLS testing. Implem…
kelvinmwinuka May 29, 2024
e8840cb
Added test for forward command
kelvinmwinuka May 29, 2024
e1d5e82
Disabled cluster test until a more efficient method is found.
kelvinmwinuka May 29, 2024
502e804
Removed logic to get unexported methods from the echovault package in…
kelvinmwinuka May 30, 2024
6f85116
Close client connection on quit command
kelvinmwinuka May 30, 2024
c7560ce
Updated all test suites to include connection and server shutdown on …
kelvinmwinuka May 30, 2024
b48ee54
Updated test suites to retry connection to tcp server. Use concurrenc…
kelvinmwinuka May 31, 2024
5de2cc8
Admin tests are no longer parallel as module loading interferes with …
kelvinmwinuka May 31, 2024
3bd09ab
Added test coverage for empty command and write command to non-leader…
kelvinmwinuka May 31, 2024
393700b
Updated TLS/mTLS listener log
kelvinmwinuka May 31, 2024
fdb3b87
Implemented ACL tests using test suites and server cleanup functions.
kelvinmwinuka May 31, 2024
4d56ee9
Use log.Println instead of log.Fatal upon failing to load ACl config
kelvinmwinuka Jun 1, 2024
bdfaf54
Updated logic for loading acl config. If the config file does not exi…
kelvinmwinuka Jun 1, 2024
d4506ce
Added tests for ACL LOAD and ACL SAVE commands.
kelvinmwinuka Jun 1, 2024
dd323f6
Renamed config variable name in echovault_test.go
kelvinmwinuka Jun 1, 2024
60f5d03
Only fail ForwardCommand test if there's an error present.
kelvinmwinuka Jun 1, 2024
66b6c4b
Disable TCP/UDP offloading in GH Action
kelvinmwinuka Jun 1, 2024
99be0fd
Fixed data race issue when reading and writing ACL user data. Now, a …
kelvinmwinuka Jun 2, 2024
bbc53ff
Use time.After instead of custom clock wrappet for TTL key eviction w…
kelvinmwinuka Jun 2, 2024
166e9a8
Shutdown raft nodes from the last one to the first one in echovault t…
kelvinmwinuka Jun 2, 2024
7855726
Removed TCP/UDP offload disabling in go.yml workflow
kelvinmwinuka Jun 2, 2024
0a4a3c0
Added TCP/UDP offload disabling in go.yml workflow
kelvinmwinuka Jun 2, 2024
b7e691b
Changed default memberlist config to DefaultWANConfig
kelvinmwinuka Jun 2, 2024
bc6537a
Replaces use of time.Sleep with time.Ticker in echovault and echovaul…
kelvinmwinuka Jun 2, 2024
5553874
Replaced all time.After instances to time.Ticker
kelvinmwinuka Jun 2, 2024
66b2842
Implemented tests for ACL permissions
kelvinmwinuka Jun 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Tune GitHub-hosted runner network
uses: smorimoto/tune-github-hosted-runner-network@v1

- name: Checkout tree
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v4
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
.idea
.DS_Store
bin
volumes/nodes
dist/
pkg/modules/*/aof
pkg/echovault/aof
dump.rdb
**/*/testdata
**/*/testdata
2 changes: 0 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,11 @@ run:
test:
env RACE=false OUT=internal/modules/admin/testdata make build-modules-test && \
env RACE=false OUT=echovault/testdata make build-modules-test && \
go clean -testcache && \
CGO_ENABLED=1 go test ./... -coverprofile coverage/coverage.out

test-race:
env RACE=true OUT=internal/modules/admin/testdata make build-modules-test && \
env RACE=true OUT=echovault/testdata make build-modules-test && \
go clean -testcache && \
CGO_ENABLED=1 go test ./... --race

cover:
Expand Down
2,795 changes: 1,407 additions & 1,388 deletions coverage/coverage.out

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions echovault/api_admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,7 @@ func TestEchoVault_CommandCount(t *testing.T) {
func TestEchoVault_Save(t *testing.T) {
conf := DefaultConfig()
conf.DataDir = path.Join(".", "testdata", "data")
conf.EvictionPolicy = constants.NoEviction
server := createEchoVaultWithConfig(conf)

tests := []struct {
Expand Down
116 changes: 60 additions & 56 deletions echovault/echovault.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@

type EchoVault struct {
// clock is an implementation of a time interface that allows mocking of time functions during testing.
clock clock.Clock
getClock func() clock.Clock
clock clock.Clock

// config holds the echovault configuration variables.
config config.Config
Expand All @@ -69,12 +68,12 @@
rwMutex sync.RWMutex // Mutex as only one process should be able to update this list at a time.
keys []string // string slice of the volatile keys
}
// LFU cache used when eviction policy is allkeys-lfu or volatile-lfu
// LFU cache used when eviction policy is allkeys-lfu or volatile-lfu.
lfuCache struct {
mutex sync.Mutex // Mutex as only one goroutine can edit the LFU cache at a time.
cache eviction.CacheLFU // LFU cache represented by a min head.
}
// LRU cache used when eviction policy is allkeys-lru or volatile-lru
// LRU cache used when eviction policy is allkeys-lru or volatile-lru.
lruCache struct {
mutex sync.Mutex // Mutex as only one goroutine can edit the LRU at a time.
cache eviction.CacheLRU // LRU cache represented by a max head.
Expand All @@ -83,26 +82,25 @@
// Holds the list of all commands supported by the echovault.
commandsRWMut sync.RWMutex
commands []internal.Command
getCommands func() []internal.Command

raft *raft.Raft // The raft replication layer for the echovault.
memberList *memberlist.MemberList // The memberlist layer for the echovault.

context context.Context

acl *acl.ACL
getACL func() interface{}

pubSub *pubsub.PubSub
getPubSub func() interface{}
pubSub *pubsub.PubSub

snapshotInProgress atomic.Bool // Atomic boolean that's true when actively taking a snapshot.
rewriteAOFInProgress atomic.Bool // Atomic boolean that's true when actively rewriting AOF file is in progress.
stateCopyInProgress atomic.Bool // Atomic boolean that's true when actively copying state for snapshotting or preamble generation.
stateMutationInProgress atomic.Bool // Atomic boolean that is set to true when state mutation is in progress.
latestSnapshotMilliseconds atomic.Int64 // Unix epoch in milliseconds
snapshotEngine *snapshot.Engine // Snapshot engine for standalone mode
aofEngine *aof.Engine // AOF engine for standalone mode
latestSnapshotMilliseconds atomic.Int64 // Unix epoch in milliseconds.
snapshotEngine *snapshot.Engine // Snapshot engine for standalone mode.
aofEngine *aof.Engine // AOF engine for standalone mode.

listener atomic.Value // Holds the TCP listener.
quit chan struct{} // Channel that signals the closing of all client connections.
}

// WithContext is an options that for the NewEchoVault function that allows you to
Expand Down Expand Up @@ -147,6 +145,7 @@
commands = append(commands, str.Commands()...)
return commands
}(),
quit: make(chan struct{}),
}

for _, option := range options {
Expand All @@ -167,27 +166,11 @@
log.Printf("loaded plugin %s\n", path)
}

// Function for server commands retrieval
echovault.getCommands = func() []internal.Command {
return echovault.commands
}

// Function for clock retrieval
echovault.getClock = func() clock.Clock {
return echovault.clock
}

// Set up ACL module
echovault.acl = acl.NewACL(echovault.config)
echovault.getACL = func() interface{} {
return echovault.acl
}

// Set up Pub/Sub module
echovault.pubSub = pubsub.NewPubSub()
echovault.getPubSub = func() interface{} {
return echovault.pubSub
}

if echovault.isInCluster() {
echovault.raft = raft.NewRaft(raft.Opts{
Expand Down Expand Up @@ -290,10 +273,10 @@
// If eviction policy is not noeviction, start a goroutine to evict keys every 100 milliseconds.
if echovault.config.EvictionPolicy != constants.NoEviction {
go func() {
for {
<-echovault.clock.After(echovault.config.EvictionInterval)
ticker := time.NewTicker(echovault.config.EvictionInterval)
for _ = range ticker.C {

Check warning on line 277 in echovault/echovault.go

View check run for this annotation

Codecov / codecov/patch

echovault/echovault.go#L276-L277

Added lines #L276 - L277 were not covered by tests
if err := echovault.evictKeysWithExpiredTTL(context.Background()); err != nil {
log.Println(err)
log.Printf("evict with ttl: %v\n", err)

Check warning on line 279 in echovault/echovault.go

View check run for this annotation

Codecov / codecov/patch

echovault/echovault.go#L279

Added line #L279 was not covered by tests
}
}
}()
Expand Down Expand Up @@ -341,30 +324,35 @@
KeepAlive: 200 * time.Millisecond,
}

listener, err := listenConfig.Listen(server.context, "tcp", fmt.Sprintf("%s:%d", conf.BindAddr, conf.Port))

listener, err := listenConfig.Listen(
server.context,
"tcp",
fmt.Sprintf("%s:%d", conf.BindAddr, conf.Port),
)
if err != nil {
log.Fatal(err)
log.Printf("listener error: %v", err)
return

Check warning on line 334 in echovault/echovault.go

View check run for this annotation

Codecov / codecov/patch

echovault/echovault.go#L333-L334

Added lines #L333 - L334 were not covered by tests
}

if !conf.TLS {
// TCP
log.Printf("Starting TCP echovault at Address %s, Port %d...\n", conf.BindAddr, conf.Port)
log.Printf("Starting TCP server at Address %s, Port %d...\n", conf.BindAddr, conf.Port)
}

if conf.TLS || conf.MTLS {
// TLS
if conf.TLS {
log.Printf("Starting mTLS echovault at Address %s, Port %d...\n", conf.BindAddr, conf.Port)
if conf.MTLS {
log.Printf("Starting mTLS server at Address %s, Port %d...\n", conf.BindAddr, conf.Port)
} else {
log.Printf("Starting TLS echovault at Address %s, Port %d...\n", conf.BindAddr, conf.Port)
log.Printf("Starting TLS server at Address %s, Port %d...\n", conf.BindAddr, conf.Port)
}

var certificates []tls.Certificate
for _, certKeyPair := range conf.CertKeyPairs {
c, err := tls.LoadX509KeyPair(certKeyPair[0], certKeyPair[1])
if err != nil {
log.Fatal(err)
log.Printf("load cert key pair: %v\n", err)
return

Check warning on line 355 in echovault/echovault.go

View check run for this annotation

Codecov / codecov/patch

echovault/echovault.go#L354-L355

Added lines #L354 - L355 were not covered by tests
}
certificates = append(certificates, c)
}
Expand All @@ -377,14 +365,15 @@
for _, c := range conf.ClientCAs {
ca, err := os.Open(c)
if err != nil {
log.Fatal(err)
log.Printf("client cert open: %v\n", err)
return

Check warning on line 369 in echovault/echovault.go

View check run for this annotation

Codecov / codecov/patch

echovault/echovault.go#L368-L369

Added lines #L368 - L369 were not covered by tests
}
certBytes, err := io.ReadAll(ca)
if err != nil {
log.Fatal(err)
log.Printf("client cert read: %v\n", err)

Check warning on line 373 in echovault/echovault.go

View check run for this annotation

Codecov / codecov/patch

echovault/echovault.go#L373

Added line #L373 was not covered by tests
}
if ok := clientCerts.AppendCertsFromPEM(certBytes); !ok {
log.Fatal(err)
log.Printf("client cert append: %v\n", err)

Check warning on line 376 in echovault/echovault.go

View check run for this annotation

Codecov / codecov/patch

echovault/echovault.go#L376

Added line #L376 was not covered by tests
}
}
}
Expand All @@ -396,15 +385,22 @@
})
}

// Listen to connection
server.listener.Store(listener)

// Listen to connection.
for {
conn, err := listener.Accept()
if err != nil {
log.Println("Could not establish connection")
continue
select {
case <-server.quit:
return

Check warning on line 394 in echovault/echovault.go

View check run for this annotation

Codecov / codecov/patch

echovault/echovault.go#L393-L394

Added lines #L393 - L394 were not covered by tests
default:
conn, err := listener.Accept()
if err != nil {
log.Printf("listener error: %v\n", err)
return
}
// Read loop for connection
go server.handleConnection(conn)
}
// Read loop for connection
go server.handleConnection(conn)
}
}

Expand All @@ -420,6 +416,13 @@
ctx := context.WithValue(server.context, internal.ContextConnID("ConnectionID"),
fmt.Sprintf("%s-%d", server.context.Value(internal.ContextServerID("ServerID")), cid))

defer func() {
log.Printf("closing connection %d...", cid)
if err := conn.Close(); err != nil {
log.Println(err)

Check warning on line 422 in echovault/echovault.go

View check run for this annotation

Codecov / codecov/patch

echovault/echovault.go#L422

Added line #L422 was not covered by tests
}
}()

for {
message, err := internal.ReadMessage(r)

Expand All @@ -435,11 +438,9 @@
}

res, err := server.handleCommand(ctx, message, &conn, false, false)

if err != nil && errors.Is(err, io.EOF) {
break
}

if err != nil {
if _, err = w.Write([]byte(fmt.Sprintf("-Error %s\r\n", err.Error()))); err != nil {
log.Println(err)
Expand All @@ -449,7 +450,7 @@

chunkSize := 1024

// If the length of the response is 0, return nothing to the client
// If the length of the response is 0, return nothing to the client.
if len(res) == 0 {
continue
}
Expand Down Expand Up @@ -477,10 +478,6 @@
startIndex += chunkSize
}
}

if err := conn.Close(); err != nil {
log.Println(err)
}
}

// Start starts the EchoVault instance's TCP listener.
Expand Down Expand Up @@ -556,6 +553,13 @@
// ShutDown gracefully shuts down the EchoVault instance.
// This function shuts down the memberlist and raft layers.
func (server *EchoVault) ShutDown() {
if server.listener.Load() != nil {
go func() { server.quit <- struct{}{} }()
log.Println("closing tcp listener...")
if err := server.listener.Load().(net.Listener).Close(); err != nil {
log.Printf("listener close: %v\n", err)

Check warning on line 560 in echovault/echovault.go

View check run for this annotation

Codecov / codecov/patch

echovault/echovault.go#L560

Added line #L560 was not covered by tests
}
}
if server.isInCluster() {
server.raft.RaftShutdown()
server.memberList.MemberListShutdown()
Expand Down
Loading
Loading