From 3086450ced271e96bb1cadfcdca57357bf43709b Mon Sep 17 00:00:00 2001 From: Nghia Tran Date: Wed, 20 Jan 2021 14:38:09 -0800 Subject: [PATCH] Add CosmosDB state store conformance tests (#622) * Add CosmosDB conformance tests * Fix workflow & lint * Add more disclaimers to README.md --- .github/workflows/conformance.yml | 25 +++++++++++--- go.mod | 1 + tests/config/state/cosmosdb/statestore.yaml | 16 +++++++++ tests/config/state/tests.yml | 11 ++++++- tests/conformance/README.md | 12 +++---- tests/conformance/common.go | 31 +++++++++++++++++- tests/conformance/common_test.go | 36 +++++++++++++++++++++ 7 files changed, 120 insertions(+), 12 deletions(-) create mode 100644 tests/config/state/cosmosdb/statestore.yaml diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml index d35dceef9..cb096ce68 100644 --- a/.github/workflows/conformance.yml +++ b/.github/workflows/conformance.yml @@ -23,16 +23,33 @@ jobs: # # The KeyVault policy must be granted to your Service Principal using # az keyvault set-policy -n $AZURE_KEYVAULT --secret-permissions get list --spn $SPN_CLIENT_ID - AZURE_KEYVAULT: conformance-tests-secret + AZURE_KEYVAULT: dapr-conf-tests strategy: fail-fast: false # Keep running even if one component fails matrix: # List here all the components that needs to be invoked in this workflow. component: - - state.redis - pubsub.redis - secretstores.localenv + - state.cosmosdb + - state.redis + include: + - component: state.cosmosdb + # Unfortunately, Azure secrets can't have underscores in + # names, while environment variables with hyphens ('-') are + # troublesome. + # + # We work around here by leveraging the fact that + # environment variable names are case sensitive, so + # CamelCase would still work. + # + # That is slightly better than something like + # AZURECOSMOSDBMASTERKEY, which is extremely hard to read + # and errorprone. + # + # Only list the secrets you need for this component. + required-secrets: AzureCosmosDBMasterKey,AzureCosmosDBUrl,AzureCosmosDB,AzureCosmosDBCollection steps: - name: Check out code onto GOPATH uses: actions/checkout@v2 @@ -65,11 +82,11 @@ jobs: NAME=$(echo ${{ matrix.component }} | cut -d. -f2) KIND_UPPER="$(tr '[:lower:]' '[:upper:]' <<< ${KIND:0:1})${KIND:1}" - if [ ${KIND}=="secretstores" ]; then + if [ "${KIND}" = "secretstores" ]; then KIND_UPPER=SecretStore fi - if [ ${KIND}=="output-binding" ]; then + if [ "${KIND}" = "output-binding" ]; then KIND_UPPER=OutputBinding fi diff --git a/go.mod b/go.mod index 873b32b43..8bc190999 100644 --- a/go.mod +++ b/go.mod @@ -71,6 +71,7 @@ require ( gopkg.in/couchbase/gocb.v1 v1.6.4 gopkg.in/yaml.v2 v2.3.0 k8s.io/api v0.20.0 + k8s.io/apiextensions-apiserver v0.17.2 k8s.io/apimachinery v0.20.0 k8s.io/client-go v0.20.0 ) diff --git a/tests/config/state/cosmosdb/statestore.yaml b/tests/config/state/cosmosdb/statestore.yaml new file mode 100644 index 000000000..d67582ca4 --- /dev/null +++ b/tests/config/state/cosmosdb/statestore.yaml @@ -0,0 +1,16 @@ +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: statestore +spec: + type: state.azure.cosmosdb + version: v1 + metadata: + - name: url + value: ${{AzureCosmosDBUrl}} + - name: masterKey + value: ${{AzureCosmosDBMasterKey}} + - name: database + value: ${{AzureCosmosDB}} + - name: collection + value: ${{AzureCosmosDBCollection}} diff --git a/tests/config/state/tests.yml b/tests/config/state/tests.yml index 0df9a6671..5ca4ac636 100644 --- a/tests/config/state/tests.yml +++ b/tests/config/state/tests.yml @@ -4,4 +4,13 @@ components: allOperations: true config: maxInitDuration: 30 - maxSetDuration: 20 \ No newline at end of file + maxSetDuration: 20 + - component: cosmosdb + allOperations: true + config: + maxInitDuration: 900 + maxSetDuration: 300 + maxGetDuration: 300 + maxBulkSetDuration: 300 + maxBulkDeleteDuration: 300 + maxDeleteDuration: 300 diff --git a/tests/conformance/README.md b/tests/conformance/README.md index 9c827fc75..7bf54164e 100644 --- a/tests/conformance/README.md +++ b/tests/conformance/README.md @@ -6,13 +6,13 @@ 2. All the conformance tests are within the `tests/conformance` directory. 3. All the configurations are in the `tests/config` directory. 4. Each of the component specific `component` definition are in their specific `component type` folder in the `tests/config` folder. For eg: `redis` statestore component definition within `state` directory. And the other component types are `state`, `secretstores`, `pubsub` and `bindings`. -5. Similar to the component definitions, each component type has its own set of the conformance tests definitions. -6. Each component type contains a `tests.yml` definition that defines the component to be tested along with component specific test configuration. -7. All the tests configurations are defined in `common.go` file. -8. Each component type has its own `_test` file to trigger the conformance tests. +5. Similar to the component definitions, each component type has its own set of the conformance tests definitions. +6. Each component type contains a `tests.yml` definition that defines the component to be tested along with component specific test configuration. +7. All the tests configurations are defined in `common.go` file. +8. Each component type has its own `_test` file to trigger the conformance tests. ## Running conformance tests -1. Test test setup is independent of the test run. +1. Test test setup is independent of the test run. 2. Run Redis with 6379 exposed locally. 3. Run `make conf-tests` to run the conformance tests locally. -> Note: Any component that cannot run as dockerized containers are not tested locally. \ No newline at end of file +> Note Some conformance tests require credentials in the form of environment variables. For examples Azure CosmosDB conformance tests will need to have Azure CosmosDB credentials. You will need to supply them to make these tests pass. diff --git a/tests/conformance/common.go b/tests/conformance/common.go index cb223b91c..93f1c42e5 100644 --- a/tests/conformance/common.go +++ b/tests/conformance/common.go @@ -1,3 +1,8 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// ------------------------------------------------------------ + package conformance import ( @@ -5,6 +10,7 @@ import ( "fmt" "io/ioutil" "math/rand" + "os" "path/filepath" "strings" "testing" @@ -12,11 +18,13 @@ import ( "fortio.org/fortio/log" "github.com/dapr/components-contrib/pubsub" + p_servicebus "github.com/dapr/components-contrib/pubsub/azure/servicebus" p_redis "github.com/dapr/components-contrib/pubsub/redis" "github.com/dapr/components-contrib/secretstores" ss_local_env "github.com/dapr/components-contrib/secretstores/local/env" ss_local_file "github.com/dapr/components-contrib/secretstores/local/file" "github.com/dapr/components-contrib/state" + s_cosmosdb "github.com/dapr/components-contrib/state/azure/cosmosdb" s_redis "github.com/dapr/components-contrib/state/redis" conf_pubsub "github.com/dapr/components-contrib/tests/conformance/pubsub" conf_secret "github.com/dapr/components-contrib/tests/conformance/secretstores" @@ -74,10 +82,27 @@ func LoadComponents(componentPath string) ([]v1alpha1.Component, error) { return components, nil } +func LookUpEnv(key string) string { + if val, ok := os.LookupEnv(key); ok { + return val + } + + return "" +} + func ConvertMetadataToProperties(items []v1alpha1.MetadataItem) map[string]string { properties := map[string]string{} for _, c := range items { - properties[c.Name] = c.Value.String() + val := c.Value.String() + if strings.HasPrefix(c.Value.String(), "${{") { + // look up env var with that name. remove ${{}} and space + k := strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(val, "${{"), "}}")) + v := LookUpEnv(k) + if v != "" { + val = v + } + } + properties[c.Name] = val } return properties @@ -172,6 +197,8 @@ func loadPubSub(tc TestComponent) pubsub.PubSub { switch tc.Component { case "redis": pubsub = p_redis.NewRedisStreams(testLogger) + case "azure-servicebus": + pubsub = p_servicebus.NewAzureServiceBus(testLogger) default: return nil } @@ -198,6 +225,8 @@ func loadStateStore(tc TestComponent) state.Store { switch tc.Component { case "redis": store = s_redis.NewRedisStateStore(testLogger) + case "cosmosdb": + store = s_cosmosdb.NewCosmosDBStateStore(testLogger) default: return nil } diff --git a/tests/conformance/common_test.go b/tests/conformance/common_test.go index a57084972..1206d462b 100644 --- a/tests/conformance/common_test.go +++ b/tests/conformance/common_test.go @@ -1,9 +1,12 @@ package conformance import ( + "os" "testing" + "github.com/dapr/dapr/pkg/apis/components/v1alpha1" "github.com/stretchr/testify/assert" + v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" ) func TestDecodeYaml(t *testing.T) { @@ -40,3 +43,36 @@ func TestIsYaml(t *testing.T) { resp = isYaml("test.exe") assert.False(t, resp) } + +func TestLookUpEnv(t *testing.T) { + os.Setenv("CONF_TEST_KEY", "testval") + defer os.Unsetenv("CONF_TEST_KEY") + r := LookUpEnv("CONF_TEST_KEY") + assert.Equal(t, "testval", r) + r = LookUpEnv("CONF_TEST_NOT_THERE") + assert.Equal(t, "", r) +} + +func TestConvertMetadataToProperties(t *testing.T) { + items := []v1alpha1.MetadataItem{ + { + Name: "test_key", + Value: v1alpha1.DynamicValue{ + JSON: v1.JSON{Raw: []byte("test")}, + }, + }, + { + Name: "env_var_sub", + Value: v1alpha1.DynamicValue{ + JSON: v1.JSON{Raw: []byte("${{CONF_TEST_KEY}}")}, + }, + }, + } + os.Setenv("CONF_TEST_KEY", "testval") + defer os.Unsetenv("CONF_TEST_KEY") + resp := ConvertMetadataToProperties(items) + assert.NotNil(t, resp) + assert.Equal(t, 2, len(resp)) + assert.Equal(t, "test", resp["test_key"]) + assert.Equal(t, "testval", resp["env_var_sub"]) +}