blob: ac1d0df8dfcdd1a5abf48d6ab507eacae0f1b21c [file] [log] [blame]
/*
* Copyright 2019-present Open Networking Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package db
import (
"context"
"os"
"testing"
"time"
"github.com/opencord/voltha-lib-go/v3/pkg/mocks"
"github.com/phayes/freeport"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
const (
embedEtcdServerHost = "localhost"
defaultTimeout = 1
defaultPathPrefix = "Prefix"
)
var (
embedEtcdServerPort int
dummyEtcdServerPort int
)
func TestMain(m *testing.M) {
var err error
embedEtcdServerPort, err = freeport.GetFreePort()
if err != nil {
logger.Fatal(err)
}
dummyEtcdServerPort, err = freeport.GetFreePort()
if err != nil {
logger.Fatal(err)
}
peerPort, err := freeport.GetFreePort()
if err != nil {
logger.Fatal(err)
}
etcdServer := mocks.StartEtcdServer(mocks.MKConfig("voltha.db.test", embedEtcdServerPort, peerPort, "voltha.lib.db", "error"))
res := m.Run()
etcdServer.Stop()
os.Exit(res)
}
func provisionBackendWithEmbeddedEtcdServer(t *testing.T) *Backend {
backend := NewBackend("etcd", embedEtcdServerHost, embedEtcdServerPort, defaultTimeout, defaultPathPrefix)
assert.NotNil(t, backend)
assert.NotNil(t, backend.Client)
return backend
}
func provisionBackendWithDummyEtcdServer(t *testing.T) *Backend {
backend := NewBackend("etcd", embedEtcdServerHost, dummyEtcdServerPort, defaultTimeout, defaultPathPrefix)
assert.NotNil(t, backend)
assert.NotNil(t, backend.Client)
return backend
}
// Create instance using Etcd Kvstore
func TestNewBackend_EtcdKvStore(t *testing.T) {
backend := NewBackend("etcd", embedEtcdServerHost, embedEtcdServerPort, defaultTimeout, defaultPathPrefix)
// Verify all attributes of backend have got set correctly
assert.NotNil(t, backend)
assert.NotNil(t, backend.Client)
assert.Equal(t, backend.StoreType, "etcd")
assert.Equal(t, backend.Host, embedEtcdServerHost)
assert.Equal(t, backend.Port, embedEtcdServerPort)
assert.Equal(t, backend.Timeout, defaultTimeout)
assert.Equal(t, backend.PathPrefix, defaultPathPrefix)
assert.Equal(t, backend.alive, false) // backend is not alive at start
assert.Nil(t, backend.liveness) // no liveness channel is created at start
assert.Equal(t, backend.LivenessChannelInterval, DefaultLivenessChannelInterval)
}
// Create instance using Consul Kvstore
func TestNewBackend_ConsulKvStore(t *testing.T) {
backend := NewBackend("consul", embedEtcdServerHost, embedEtcdServerPort, defaultTimeout, defaultPathPrefix)
// Verify kvstore type attribute of backend has got set correctly
assert.NotNil(t, backend)
assert.NotNil(t, backend.Client)
assert.Equal(t, backend.StoreType, "consul")
}
// Create instance using Invalid Kvstore; instance creation should fail
func TestNewBackend_InvalidKvstore(t *testing.T) {
backend := NewBackend("unknown", embedEtcdServerHost, embedEtcdServerPort, defaultTimeout, defaultPathPrefix)
assert.NotNil(t, backend)
assert.Nil(t, backend.Client)
}
func TestMakePath(t *testing.T) {
backend := provisionBackendWithEmbeddedEtcdServer(t)
path := backend.makePath("Suffix")
assert.Equal(t, defaultPathPrefix+"/Suffix", path)
}
// Liveness Check against Embedded Etcd Server should return alive state
func TestPerformLivenessCheck_EmbeddedEtcdServer(t *testing.T) {
backend := provisionBackendWithEmbeddedEtcdServer(t)
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout*time.Second)
defer cancel()
alive := backend.PerformLivenessCheck(ctx)
assert.True(t, alive)
}
// Liveness Check against Dummy Etcd Server should return not-alive state
func TestPerformLivenessCheck_DummyEtcdServer(t *testing.T) {
backend := provisionBackendWithDummyEtcdServer(t)
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout*time.Second)
defer cancel()
alive := backend.PerformLivenessCheck(ctx)
assert.False(t, alive)
}
// Enabling Liveness Channel before First Liveness Check
func TestEnableLivenessChannel_EmbeddedEtcdServer_BeforeLivenessCheck(t *testing.T) {
backend := provisionBackendWithEmbeddedEtcdServer(t)
alive := backend.EnableLivenessChannel()
assert.NotNil(t, alive)
assert.Equal(t, 1, len(alive))
assert.Equal(t, false, <-alive)
assert.NotNil(t, backend.liveness)
}
// Enabling Liveness Channel after First Liveness Check
func TestEnableLivenessChannel_EmbeddedEtcdServer_AfterLivenessCheck(t *testing.T) {
backend := provisionBackendWithEmbeddedEtcdServer(t)
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout*time.Second)
defer cancel()
backend.PerformLivenessCheck(ctx)
alive := backend.EnableLivenessChannel()
assert.NotNil(t, alive)
assert.Equal(t, 1, len(alive))
assert.Equal(t, true, <-alive)
assert.NotNil(t, backend.liveness)
}
// Update Liveness with alive status change
func TestUpdateLiveness_AliveStatusChange(t *testing.T) {
backend := provisionBackendWithEmbeddedEtcdServer(t)
// Enable Liveness Channel and verify initial state is not-alive
aliveState := backend.EnableLivenessChannel()
assert.NotNil(t, aliveState)
assert.Equal(t, 1, len(backend.liveness))
assert.Equal(t, false, <-backend.liveness)
lastUpdateTime := backend.lastLivenessTime
// Update with changed alive state. Verify alive state push & liveness time update
backend.updateLiveness(true)
assert.Equal(t, 1, len(backend.liveness))
assert.Equal(t, true, <-backend.liveness)
assert.NotEqual(t, lastUpdateTime, backend.lastLivenessTime)
}
// Update Liveness with same alive status reporting
func TestUpdateLiveness_AliveStatusUnchanged(t *testing.T) {
backend := provisionBackendWithEmbeddedEtcdServer(t)
// Enable Liveness Channel and verify initial state is not-alive
aliveState := backend.EnableLivenessChannel()
assert.NotNil(t, aliveState)
assert.Equal(t, false, <-backend.liveness)
lastUpdateTime := backend.lastLivenessTime
// Update with same alive state. Verify no further alive state push
backend.updateLiveness(false)
assert.Equal(t, 0, len(backend.liveness))
assert.Equal(t, lastUpdateTime, backend.lastLivenessTime)
// Now set lastUpdateTime 10 min back and push again
tenMinDuration, _ := time.ParseDuration("10m")
backend.lastLivenessTime = time.Now().Add(-tenMinDuration)
lastUpdateTime = backend.lastLivenessTime
backend.updateLiveness(false)
assert.Equal(t, 1, len(backend.liveness))
assert.Equal(t, false, <-backend.liveness)
assert.NotEqual(t, lastUpdateTime, backend.lastLivenessTime)
}
func TestIsErrorIndicatingAliveKvstore(t *testing.T) {
tests := []struct {
name string
arg error
want bool
}{
{"No Error", nil, true},
{"Request Canceled", context.Canceled, true},
{"Request Timeout", context.DeadlineExceeded, false},
{"Etcd Error - InvalidArgument", status.New(codes.InvalidArgument, "").Err(), true},
{"Etcd Error - DeadlineExceeded", status.New(codes.DeadlineExceeded, "").Err(), false},
{"Etcd Error - Unavailable", status.New(codes.Unavailable, "").Err(), false},
{"Etcd Error - DataLoss", status.New(codes.DataLoss, "").Err(), false},
{"Etcd Error - NotFound", status.New(codes.NotFound, "").Err(), true},
{"Etcd Error - PermissionDenied ", status.New(codes.PermissionDenied, "").Err(), true},
{"Etcd Error - FailedPrecondition ", status.New(codes.FailedPrecondition, "").Err(), true},
}
backend := provisionBackendWithEmbeddedEtcdServer(t)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if backend.isErrorIndicatingAliveKvstore(tt.arg) != tt.want {
t.Errorf("isErrorIndicatingAliveKvstore failed for %s: expected %t but got %t", tt.name, tt.want, !tt.want)
}
})
}
}
func TestPut_EmbeddedEtcdServer(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
backend := provisionBackendWithEmbeddedEtcdServer(t)
err := backend.Put(ctx, "key1", []uint8("value1"))
assert.Nil(t, err)
// Assert alive state has become true
assert.True(t, backend.alive)
// Assert that kvstore has this value stored
kvpair, err := backend.Get(ctx, "key1")
assert.Nil(t, err)
assert.NotNil(t, kvpair)
assert.Equal(t, defaultPathPrefix+"/key1", kvpair.Key)
assert.Equal(t, []uint8("value1"), kvpair.Value)
// Assert that Put overrides the Value
err = backend.Put(ctx, "key1", []uint8("value11"))
assert.Nil(t, err)
kvpair, err = backend.Get(ctx, "key1")
assert.Nil(t, err)
assert.NotNil(t, kvpair)
assert.Equal(t, defaultPathPrefix+"/key1", kvpair.Key)
assert.Equal(t, []uint8("value11"), kvpair.Value)
}
// Put operation should fail against Dummy Non-existent Etcd Server
func TestPut_DummyEtcdServer(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
backend := provisionBackendWithDummyEtcdServer(t)
err := backend.Put(ctx, "key1", []uint8("value1"))
assert.NotNil(t, err)
// Assert alive state is still false
assert.False(t, backend.alive)
}
// Test Get for existing and non-existing key
func TestGet_EmbeddedEtcdServer(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
backend := provisionBackendWithEmbeddedEtcdServer(t)
err := backend.Put(ctx, "key2", []uint8("value2"))
assert.Nil(t, err)
// Assert alive state has become true
assert.True(t, backend.alive)
// Assert that kvstore has this key stored
kvpair, err := backend.Get(ctx, "key2")
assert.NotNil(t, kvpair)
assert.Nil(t, err)
assert.Equal(t, defaultPathPrefix+"/key2", kvpair.Key)
assert.Equal(t, []uint8("value2"), kvpair.Value)
// Assert that Get works fine for absent key3
kvpair, err = backend.Get(ctx, "key3")
assert.Nil(t, err) // no error as lookup is successful
assert.Nil(t, kvpair)
}
// Get operation should fail against Dummy Non-existent Etcd Server
func TestGet_DummyEtcdServer(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
backend := provisionBackendWithDummyEtcdServer(t)
kvpair, err := backend.Get(ctx, "key2")
assert.NotNil(t, err)
assert.Nil(t, kvpair)
// Assert alive state is still false
assert.False(t, backend.alive)
}
// Test Delete for existing and non-existing key
func TestDelete_EmbeddedEtcdServer(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
backend := provisionBackendWithEmbeddedEtcdServer(t)
err := backend.Put(ctx, "key3", []uint8("value3"))
assert.Nil(t, err)
// Assert alive state has become true
assert.True(t, backend.alive)
// Assert that kvstore has this key stored
kvpair, err := backend.Get(ctx, "key3")
assert.Nil(t, err)
assert.NotNil(t, kvpair)
// Delete and Assert that key has been removed
err = backend.Delete(ctx, "key3")
assert.Nil(t, err)
kvpair, err = backend.Get(ctx, "key3")
assert.Nil(t, err)
assert.Nil(t, kvpair)
// Assert that Delete silently ignores absent key3
err = backend.Delete(ctx, "key3")
assert.Nil(t, err)
}
// Delete operation should fail against Dummy Non-existent Etcd Server
func TestDelete_DummyEtcdServer(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
backend := provisionBackendWithDummyEtcdServer(t)
err := backend.Delete(ctx, "key3")
assert.NotNil(t, err)
// Assert alive state is still false
assert.False(t, backend.alive)
}
// Test List for series of values under a key path
func TestList_EmbeddedEtcdServer(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
key41 := "key4/subkey1"
key42 := "key4/subkey2"
backend := provisionBackendWithEmbeddedEtcdServer(t)
err := backend.Put(ctx, key41, []uint8("value4-1"))
assert.Nil(t, err)
err = backend.Put(ctx, key42, []uint8("value4-2"))
assert.Nil(t, err)
// Assert alive state has become true
assert.True(t, backend.alive)
// Assert that Get does not retrieve these Subkeys
kvpair, err := backend.Get(ctx, "key4")
assert.Nil(t, kvpair)
assert.Nil(t, err)
// Assert that List operation retrieves these Child Keys
kvmap, err := backend.List(ctx, "key4")
assert.NotNil(t, kvmap)
assert.Nil(t, err)
assert.Equal(t, 2, len(kvmap))
fullkey41 := defaultPathPrefix + "/" + key41
fullkey42 := defaultPathPrefix + "/" + key42
assert.Equal(t, fullkey41, kvmap[fullkey41].Key)
assert.Equal(t, []uint8("value4-1"), kvmap[fullkey41].Value)
assert.Equal(t, fullkey42, kvmap[fullkey42].Key)
assert.Equal(t, []uint8("value4-2"), kvmap[fullkey42].Value)
}
// List operation should fail against Dummy Non-existent Etcd Server
func TestList_DummyEtcdServer(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
backend := provisionBackendWithDummyEtcdServer(t)
kvmap, err := backend.List(ctx, "key4")
assert.Nil(t, kvmap)
assert.NotNil(t, err)
// Assert alive state is still false
assert.False(t, backend.alive)
}
// Test Create and Delete Watch for Embedded Etcd Server
func TestCreateWatch_EmbeddedEtcdServer(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
backend := provisionBackendWithEmbeddedEtcdServer(t)
eventChan := backend.CreateWatch(ctx, "key5")
assert.NotNil(t, eventChan)
assert.Equal(t, 0, len(eventChan))
// Assert this method does not change alive state
assert.False(t, backend.alive)
// Put a value for watched key and event should appear
err := backend.Put(ctx, "key5", []uint8("value5"))
assert.Nil(t, err)
time.Sleep(time.Millisecond * 100)
assert.Equal(t, 1, len(eventChan))
backend.DeleteWatch("key5", eventChan)
}