Certification tests for MemCached State Store component.
* Add network instability tests. * Fixed gofumpt issues. * Addresses TTL translation from Dapr and Memcache domains. Memcached uses `0` as the non-expiring marker TTL. [src](https://github.com/memcached/memcached/wiki/Commands#set). On the other hand, Dapr uses `-1` for that. [src](https://docs.dapr.io/developing-applications/building-blocks/state-management/state-store-ttl/) This PR updates certification and memcached code and tests so Dapr -1 (and negative) values are translated to Memcached's `0`. Closes #1929 Signed-off-by: Tiago Alves Macambira <tmacam@burocrata.org>
This commit is contained in:
parent
ac87ed4e9d
commit
8e4af0a3a4
|
@ -46,6 +46,7 @@ jobs:
|
|||
- state.redis
|
||||
- state.postgresql
|
||||
- state.cassandra
|
||||
- state.memcached
|
||||
- bindings.alicloud.dubbo
|
||||
- bindings.kafka
|
||||
- bindings.redis
|
||||
|
|
|
@ -125,6 +125,14 @@ func (m *Memcached) parseTTL(req *state.SetRequest) (*int32, error) {
|
|||
}
|
||||
parsedInt := int32(parsedVal)
|
||||
|
||||
// Notice that for Dapr, -1 means "persist with no TTL".
|
||||
// Memcached uses "0" as the non-expiring marker TTL.
|
||||
// https://github.com/memcached/memcached/wiki/Commands#set
|
||||
// So let's translate Dapr's -1 and beyound to Memcache's 0
|
||||
if parsedInt < 0 {
|
||||
parsedInt = 0
|
||||
}
|
||||
|
||||
return &parsedInt, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -96,6 +96,16 @@ func TestParseTTL(t *testing.T) {
|
|||
assert.NotNil(t, err, "tll is not an integer")
|
||||
assert.Nil(t, ttl)
|
||||
})
|
||||
t.Run("TTL is a negative integer ends up translated to 0", func(t *testing.T) {
|
||||
ttlInSeconds := -1
|
||||
ttl, err := store.parseTTL(&state.SetRequest{
|
||||
Metadata: map[string]string{
|
||||
"ttlInSeconds": strconv.Itoa(ttlInSeconds),
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int(*ttl), 0)
|
||||
})
|
||||
t.Run("TTL specified with wrong key", func(t *testing.T) {
|
||||
ttlInSeconds := 12345
|
||||
ttl, err := store.parseTTL(&state.SetRequest{
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
# Memcached State Store certification testing
|
||||
|
||||
This project aims to test the [Memcached State Store] component under various conditions.
|
||||
|
||||
This state store [supports the following features][features]:
|
||||
* CRUD
|
||||
* TTL
|
||||
|
||||
# Test plan
|
||||
|
||||
## Basic Test for CRUD operations:
|
||||
1. Able to create and test connection.
|
||||
2. Able to do set, fetch, update and delete.
|
||||
3. Negative test to fetch record with key, that is not present.
|
||||
|
||||
## Test save or update data with different TTL settings:
|
||||
1. TTL not expiring ([`0` for memcached](https://github.com/memcached/memcached/wiki/Commands#set))
|
||||
2. TTL not a valid number
|
||||
3. Provide a TTL of 1 second:
|
||||
1. Fetch this record just after saving
|
||||
2. Sleep for 2 seconds
|
||||
3. Try to fetch again after a gap of 2 seconds, record shouldn't be found
|
||||
|
||||
|
||||
## Test network instability
|
||||
1. Configure memcache with a known (non-default) timeout of 20 seconds.
|
||||
2. Set a key to show the connection is fine. Make the TTL is way bigger than the timeout. Say 4x its value.
|
||||
3. Interrupt the network (the memcache ports) for longer than the established timeout value.
|
||||
4. Wait a few seconds seconds (less than the timeout value).
|
||||
5. Try to read the key written on step 2 and assert its.
|
||||
|
||||
## Out of scope
|
||||
|
||||
1. Tests verifying content persistence on Memcached reloads are out of scope as Memcached data is ephemeral.
|
||||
2. Tests for [features not implemented by Memcached][features] are out of scope. This includes
|
||||
* Transactional
|
||||
* ETag
|
||||
+ Notice that memcached has the concept of [64-bit CheckAndSet (CAS) values][cas] but that doesn't translate cleanly to ETags.
|
||||
* Actors
|
||||
* Query
|
||||
|
||||
|
||||
# References:
|
||||
|
||||
* [Memcache State Component reference page][Memcached State Store]
|
||||
* [List of state stores and their features][features]
|
||||
* [Memcached API reference](https://github.com/memcached/memcached)
|
||||
* [gomemcache - our client documentation](https://pkg.go.dev/github.com/bradfitz/gomemcache/memcache)
|
||||
|
||||
[Memcached State Store]: https://docs.dapr.io/reference/components-reference/supported-state-stores/setup-memcached/
|
||||
[features]: https://docs.dapr.io/reference/components-reference/supported-state-stores/
|
||||
[cas]: https://github.com/memcached/memcached/wiki/Commands#cas
|
|
@ -0,0 +1,12 @@
|
|||
apiVersion: dapr.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: statestore
|
||||
spec:
|
||||
type: state.memcached
|
||||
version: v1
|
||||
metadata:
|
||||
- name: hosts
|
||||
value: "localhost:11211"
|
||||
- timeout:
|
||||
value: 20000 # Unit is ms
|
|
@ -0,0 +1,10 @@
|
|||
apiVersion: dapr.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: statestore
|
||||
spec:
|
||||
type: state.memcached
|
||||
version: v1
|
||||
metadata:
|
||||
- name: hosts
|
||||
value: "localhost:11211"
|
|
@ -0,0 +1,4 @@
|
|||
apiVersion: dapr.io/v1alpha1
|
||||
kind: Configuration
|
||||
metadata:
|
||||
name: memcachedstateconfig
|
|
@ -0,0 +1,7 @@
|
|||
version: '2'
|
||||
|
||||
services:
|
||||
memcached:
|
||||
image: docker.io/memcached:1.6
|
||||
ports:
|
||||
- '11211:11211'
|
|
@ -0,0 +1,132 @@
|
|||
module github.com/dapr/components-contrib/tests/certification/state/memcached
|
||||
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/dapr/components-contrib v1.8.0-rc.6
|
||||
github.com/dapr/components-contrib/tests/certification v0.0.0-20220526162429-d03aeba3e0d6
|
||||
github.com/dapr/dapr v1.8.4-0.20220922033213-ca2b9a109f5e
|
||||
github.com/dapr/go-sdk v1.4.0
|
||||
github.com/dapr/kit v0.0.2
|
||||
github.com/stretchr/testify v1.8.0
|
||||
)
|
||||
|
||||
require (
|
||||
contrib.go.opencensus.io/exporter/prometheus v0.4.1 // indirect
|
||||
github.com/AdhityaRamadhanus/fasthttpcors v0.0.0-20170121111917-d4c07198763a // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e // indirect
|
||||
github.com/armon/go-metrics v0.3.10 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||
github.com/fasthttp/router v1.3.8 // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-kit/log v0.2.0 // indirect
|
||||
github.com/go-logfmt/logfmt v0.5.1 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/cel-go v0.9.0 // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/googleapis/gnostic v0.5.5 // indirect
|
||||
github.com/grandcat/zeroconf v0.0.0-20190424104450-85eadb44205c // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
|
||||
github.com/hashicorp/consul/api v1.11.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-hclog v1.2.1 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/hashicorp/serf v0.9.6 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.15.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/miekg/dns v1.1.50 // indirect
|
||||
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/openzipkin/zipkin-go v0.4.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.12.2 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.35.0 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/prometheus/statsd_exporter v0.22.3 // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20210217112953-d4a072536008 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/sony/gobreaker v0.4.2-0.20210216022020-dd874f9dd33b // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stoewer/go-strcase v1.2.0 // indirect
|
||||
github.com/stretchr/objx v0.4.0 // indirect
|
||||
github.com/tylertreat/comcast v1.0.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.34.0 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.opentelemetry.io/otel v1.7.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.7.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.7.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.7.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.7.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/zipkin v1.7.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.7.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.7.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.16.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/net v0.0.0-20220630215102-69896b714898 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a // indirect
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect
|
||||
golang.org/x/tools v0.1.11 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220622171453-ea41d75dfa0f // indirect
|
||||
google.golang.org/grpc v1.48.0 // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/api v0.23.0 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.23.0 // indirect
|
||||
k8s.io/apimachinery v0.23.0 // indirect
|
||||
k8s.io/client-go v0.23.0 // indirect
|
||||
k8s.io/component-base v0.23.0 // indirect
|
||||
k8s.io/klog/v2 v2.30.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
|
||||
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b // indirect
|
||||
sigs.k8s.io/controller-runtime v0.11.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.0 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/dapr/components-contrib/tests/certification => ../../
|
||||
|
||||
replace github.com/dapr/components-contrib => ../../../../
|
||||
|
||||
replace github.com/dapr/go-sdk => github.com/hunter007/dapr-go-sdk v1.3.1-0.20220709114046-2f2dc4f9a684
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,321 @@
|
|||
/*
|
||||
Copyright 2021 The Dapr Authors
|
||||
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 memcached_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/dapr/components-contrib/state"
|
||||
"github.com/dapr/go-sdk/client"
|
||||
|
||||
state_memcached "github.com/dapr/components-contrib/state/memcached"
|
||||
"github.com/dapr/components-contrib/tests/certification/embedded"
|
||||
"github.com/dapr/components-contrib/tests/certification/flow"
|
||||
"github.com/dapr/components-contrib/tests/certification/flow/dockercompose"
|
||||
"github.com/dapr/components-contrib/tests/certification/flow/network"
|
||||
"github.com/dapr/components-contrib/tests/certification/flow/sidecar"
|
||||
state_loader "github.com/dapr/dapr/pkg/components/state"
|
||||
"github.com/dapr/dapr/pkg/runtime"
|
||||
dapr_testing "github.com/dapr/dapr/pkg/testing"
|
||||
"github.com/dapr/kit/logger"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
sidecarNamePrefix = "memcached-sidecar-"
|
||||
dockerComposeClusterYAML = "docker-compose.yml"
|
||||
stateStoreName = "statestore"
|
||||
certificationTestPrefix = "stable-certification-"
|
||||
testKey1 = certificationTestPrefix + "key1"
|
||||
testKey2 = certificationTestPrefix + "key2"
|
||||
testKey1Value = "memcachedCert"
|
||||
testKey2Value = "memcachedCert2"
|
||||
testUpdateValue = "memcachedCertUpdate"
|
||||
testNonexistentKey = "ThisKeyDoesNotExistInTheStateStore"
|
||||
servicePortToInterrupt = "11211"
|
||||
)
|
||||
|
||||
func TestMemcached(t *testing.T) {
|
||||
log := logger.NewLogger("dapr.components")
|
||||
stateStore := state_memcached.NewMemCacheStateStore(log)
|
||||
|
||||
ports, err := dapr_testing.GetFreePorts(2)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// var rdb redis.Client
|
||||
currentGrpcPort := ports[0]
|
||||
currentHTTPPort := ports[1]
|
||||
|
||||
// Basic CRUD tests
|
||||
basicTest := func(ctx flow.Context) error {
|
||||
client, err := client.NewClientWithPort(fmt.Sprint(currentGrpcPort))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
err = client.SaveState(ctx, stateStoreName, testKey1, []byte(testKey1Value), nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = client.SaveState(ctx, stateStoreName, testKey2, []byte(testKey2Value), nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// get state
|
||||
item, err := client.GetState(ctx, stateStoreName, testKey1, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, testKey1Value, string(item.Value))
|
||||
|
||||
errUpdate := client.SaveState(ctx, stateStoreName, testKey1, []byte(testUpdateValue), nil)
|
||||
assert.NoError(t, errUpdate)
|
||||
item, errUpdatedGet := client.GetState(ctx, stateStoreName, testKey1, nil)
|
||||
assert.NoError(t, errUpdatedGet)
|
||||
assert.Equal(t, testUpdateValue, string(item.Value))
|
||||
|
||||
// delete state
|
||||
err = client.DeleteState(ctx, stateStoreName, testKey1, nil)
|
||||
assert.NoError(t, err)
|
||||
item, err = client.GetState(ctx, stateStoreName, testKey1, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, nil, item)
|
||||
|
||||
// nonexistent key
|
||||
item, err = client.GetState(ctx, stateStoreName, testNonexistentKey, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, nil, item)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Time-To-Live Tests
|
||||
timeToLiveTestWithInvalidTTLValue := func(ctx flow.Context) error {
|
||||
client, err := client.NewClientWithPort(fmt.Sprint(currentGrpcPort))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
// Invalid TTL value
|
||||
|
||||
key := certificationTestPrefix + "_InvalidTTLValueKey"
|
||||
value := "with an invalid TTL this key should not be persisted."
|
||||
|
||||
// TTL has to be a number
|
||||
ttlInSecondsNotNumeric := "mock value"
|
||||
mapOptionsNotNumeric := map[string]string{
|
||||
"ttlInSeconds": ttlInSecondsNotNumeric,
|
||||
}
|
||||
errNotNumeric := client.SaveState(ctx, stateStoreName, key, []byte(value), mapOptionsNotNumeric)
|
||||
assert.Error(t, errNotNumeric)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
timeToLiveTestWithNonExpiringTTL := func(ctx flow.Context) error {
|
||||
client, err := client.NewClientWithPort(fmt.Sprint(currentGrpcPort))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
key := certificationTestPrefix + "_timeToLiveTestWithNonExpiringTTLKey"
|
||||
value := "This value does not expire and should be retrieved just fine"
|
||||
|
||||
// Notice: we are actively setting a TTL value here: an non-expiring one.
|
||||
// This is different than the basic tests where no TTL is assigned.
|
||||
//
|
||||
// Notice that Memcached uses "0" as the non-expiring marker TTL.
|
||||
// https://github.com/memcached/memcached/wiki/Commands#set
|
||||
// OTOH Dapr uses -1 for that.
|
||||
// https://docs.dapr.io/developing-applications/building-blocks/state-management/state-store-ttl/
|
||||
// So we are using -1 here and expect the state store to translate this accordingly.
|
||||
ttlInSecondsNonExpiring := -1
|
||||
mapOptionsNonExpiring := map[string]string{
|
||||
"ttlInSeconds": strconv.Itoa(ttlInSecondsNonExpiring),
|
||||
}
|
||||
|
||||
// We can successfully save...
|
||||
errSave := client.SaveState(ctx, stateStoreName, key, []byte(value), mapOptionsNonExpiring)
|
||||
assert.NoError(t, errSave)
|
||||
// and retrieve this key.
|
||||
item, errGet := client.GetState(ctx, stateStoreName, key, nil)
|
||||
assert.NoError(t, errGet)
|
||||
assert.Equal(t, value, string(item.Value))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
timeToLiveWithAOneSecondTTL := func(ctx flow.Context) error {
|
||||
client, err := client.NewClientWithPort(fmt.Sprint(currentGrpcPort))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
key := certificationTestPrefix + "_expiresInOneSecondKey"
|
||||
value := "This key will self-destroy in 1 second"
|
||||
|
||||
ttlExpirationTime := 1 * time.Second
|
||||
ttlInSeconds := int(ttlExpirationTime.Seconds())
|
||||
mapOptionsExpiringKey := map[string]string{
|
||||
"ttlInSeconds": strconv.Itoa(ttlInSeconds),
|
||||
}
|
||||
|
||||
errSave := client.SaveState(ctx, stateStoreName, key, []byte(value), mapOptionsExpiringKey)
|
||||
assert.NoError(t, errSave)
|
||||
|
||||
// get state
|
||||
item, errGetBeforeTTLExpiration := client.GetState(ctx, stateStoreName, key, nil)
|
||||
assert.NoError(t, errGetBeforeTTLExpiration)
|
||||
assert.Equal(t, value, string(item.Value))
|
||||
// Let the key expire
|
||||
time.Sleep(2 * ttlExpirationTime) // It should be safe to check in double TTL
|
||||
itemAfterTTL, errGetAfterTTL := client.GetState(ctx, stateStoreName, key, nil)
|
||||
assert.NoError(t, errGetAfterTTL)
|
||||
assert.Nil(t, nil, itemAfterTTL)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
flow.New(t, "Connecting Memcached And Test for CRUD operations").
|
||||
Step(dockercompose.Run("memcached", dockerComposeClusterYAML)).
|
||||
Step("Waiting for component to start...", flow.Sleep(5*time.Second)).
|
||||
Step(sidecar.Run(sidecarNamePrefix+"dockerClusterDefault",
|
||||
embedded.WithoutApp(),
|
||||
embedded.WithDaprGRPCPort(currentGrpcPort),
|
||||
embedded.WithDaprHTTPPort(currentHTTPPort),
|
||||
embedded.WithComponentsPath("components/docker/default"),
|
||||
componentRuntimeOptions(stateStore, log, "memcached"),
|
||||
)).
|
||||
Step("Waiting for component to load...", flow.Sleep(5*time.Second)).
|
||||
Step("Run basic test", basicTest).
|
||||
Step("Stop Memcached server", dockercompose.Stop("memcached", dockerComposeClusterYAML)).
|
||||
Run()
|
||||
|
||||
flow.New(t, "Connecting Memcached And verifying TTL tests").
|
||||
Step(dockercompose.Run("memcached", dockerComposeClusterYAML)).
|
||||
Step("Waiting for component to start...", flow.Sleep(5*time.Second)).
|
||||
Step(sidecar.Run(sidecarNamePrefix+"dockerClusterDefault",
|
||||
embedded.WithoutApp(),
|
||||
embedded.WithDaprGRPCPort(currentGrpcPort),
|
||||
embedded.WithDaprHTTPPort(currentHTTPPort),
|
||||
embedded.WithComponentsPath("components/docker/default"),
|
||||
componentRuntimeOptions(stateStore, log, "memcached"),
|
||||
)).
|
||||
Step("Waiting for component to load...", flow.Sleep(5*time.Second)).
|
||||
Step("Run basic test", basicTest).
|
||||
Step("Run TTL related test: TTL not a valid number.", timeToLiveTestWithInvalidTTLValue).
|
||||
Step("Run TTL related test: TTL not expiring.", timeToLiveTestWithNonExpiringTTL).
|
||||
Step("Run TTL related test: TTL of 1 second.", timeToLiveWithAOneSecondTTL).
|
||||
Step("Stop Memcached server", dockercompose.Stop("memcached", dockerComposeClusterYAML)).
|
||||
Run()
|
||||
}
|
||||
|
||||
func TestMemcachedNetworkInstability(t *testing.T) {
|
||||
log := logger.NewLogger("dapr.components")
|
||||
stateStore := state_memcached.NewMemCacheStateStore(log)
|
||||
|
||||
ports, err := dapr_testing.GetFreePorts(2)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// var rdb redis.Client
|
||||
currentGrpcPort := ports[0]
|
||||
currentHTTPPort := ports[1]
|
||||
|
||||
const (
|
||||
targetKey = certificationTestPrefix + "_TestMemcachedNetworkInstabilityKey"
|
||||
targetValue = "This key should still be there after the network returns"
|
||||
componentsPathFor20sTimeout = "components/docker/20secondsTimeout"
|
||||
memcachedTimeout = 20 * time.Second
|
||||
keyTTL = memcachedTimeout * 4
|
||||
networkInstabilityTime = memcachedTimeout * 2
|
||||
waitAfterInstabilityTime = networkInstabilityTime / 2
|
||||
)
|
||||
|
||||
assertKey := func(key string, value string) flow.Runnable {
|
||||
return func(ctx flow.Context) error {
|
||||
client, err := client.NewClientWithPort(fmt.Sprint(currentGrpcPort))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
item, err := client.GetState(ctx, stateStoreName, key, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, value, string(item.Value))
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
setKeyWithTTL := func(ttlExpirationTime time.Duration, key string, value string) flow.Runnable {
|
||||
return func(ctx flow.Context) error {
|
||||
client, err := client.NewClientWithPort(fmt.Sprint(currentGrpcPort))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
ttlInSeconds := int(ttlExpirationTime.Seconds())
|
||||
mapOptionsExpiringKey := map[string]string{
|
||||
"ttlInSeconds": strconv.Itoa(ttlInSeconds),
|
||||
}
|
||||
|
||||
errSave := client.SaveState(ctx, stateStoreName, key, []byte(value), mapOptionsExpiringKey)
|
||||
assert.NoError(t, errSave)
|
||||
// assert the key is there
|
||||
item, errGetBeforeTTLExpiration := client.GetState(ctx, stateStoreName, key, nil)
|
||||
assert.NoError(t, errGetBeforeTTLExpiration)
|
||||
assert.Equal(t, value, string(item.Value))
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
flow.New(t, "Connecting Memcached And Handling network instability").
|
||||
Step(dockercompose.Run("memcached", dockerComposeClusterYAML)).
|
||||
Step("Waiting for component to start...", flow.Sleep(5*time.Second)).
|
||||
Step(sidecar.Run(sidecarNamePrefix+"dockerClusterDefault",
|
||||
embedded.WithoutApp(),
|
||||
embedded.WithDaprGRPCPort(currentGrpcPort),
|
||||
embedded.WithDaprHTTPPort(currentHTTPPort),
|
||||
embedded.WithComponentsPath(componentsPathFor20sTimeout),
|
||||
componentRuntimeOptions(stateStore, log, "memcached"),
|
||||
)).
|
||||
Step("Waiting for component to load...", flow.Sleep(5*time.Second)).
|
||||
Step("Setup a key with a TTL of 4x memcached timeout ", setKeyWithTTL(keyTTL, targetKey, targetValue)).
|
||||
Step("Wait 1s", flow.Sleep(1*time.Second)).
|
||||
// Heads up, future developer friend: this will fail if running from WSL. :(
|
||||
Step("Interrupt network for 2x memcached timeout",
|
||||
network.InterruptNetwork(networkInstabilityTime, nil, nil, servicePortToInterrupt)).
|
||||
// Component should recover at this point.
|
||||
Step("Wait for component to recover", flow.Sleep(waitAfterInstabilityTime)).
|
||||
Step("Run basic test again to verify reconnection occurred", assertKey(targetKey, targetValue)).
|
||||
Step("Stop Memcached server", dockercompose.Stop("memcached", dockerComposeClusterYAML)).
|
||||
Run()
|
||||
}
|
||||
|
||||
func componentRuntimeOptions(stateStore state.Store, log logger.Logger, stateStoreName string) []runtime.Option {
|
||||
stateRegistry := state_loader.NewRegistry()
|
||||
stateRegistry.Logger = log
|
||||
componentFactory := func(l logger.Logger) state.Store { return stateStore }
|
||||
|
||||
stateRegistry.RegisterComponent(componentFactory, stateStoreName)
|
||||
|
||||
return []runtime.Option{
|
||||
runtime.WithStates(stateRegistry),
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue